├── .gitignore ├── test ├── test_files │ ├── test_case.txt │ ├── test_basic.txt │ ├── test_preview.txt │ ├── test_paths │ │ ├── sample1.txt │ │ ├── test.png │ │ ├── test1.txt │ │ └── test2.txt │ └── test_multiline.txt ├── README.md ├── sanity.js └── paths.js ├── testfunc.js ├── bin ├── search.js ├── replace.js └── shared-options.js ├── defaultignore ├── package.json ├── LICENSE ├── README.md └── replace.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /test/test_files/test_case.txt: -------------------------------------------------------------------------------- 1 | AAAA -------------------------------------------------------------------------------- /test/test_files/test_basic.txt: -------------------------------------------------------------------------------- 1 | aaaccc -------------------------------------------------------------------------------- /test/test_files/test_preview.txt: -------------------------------------------------------------------------------- 1 | aaaa -------------------------------------------------------------------------------- /test/test_files/test_paths/sample1.txt: -------------------------------------------------------------------------------- 1 | aaaa -------------------------------------------------------------------------------- /test/test_files/test_paths/test.png: -------------------------------------------------------------------------------- 1 | aaaa -------------------------------------------------------------------------------- /test/test_files/test_paths/test1.txt: -------------------------------------------------------------------------------- 1 | aaaa -------------------------------------------------------------------------------- /test/test_files/test_paths/test2.txt: -------------------------------------------------------------------------------- 1 | aaaa -------------------------------------------------------------------------------- /test/test_files/test_multiline.txt: -------------------------------------------------------------------------------- 1 | abc 2 | def -------------------------------------------------------------------------------- /testfunc.js: -------------------------------------------------------------------------------- 1 | function(match) { 2 | return match.toLowerCase(); 3 | } -------------------------------------------------------------------------------- /bin/search.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var nomnom = require("nomnom"), 4 | replace = require("../replace"), 5 | sharedOptions = require("./shared-options"); 6 | 7 | var options = nomnom.options(sharedOptions) 8 | .script("search") 9 | .parse(); 10 | 11 | replace(options); 12 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # testing 2 | 3 | To run the tests in this directory, first install the dev dependencies with this command from the top-level directory: 4 | 5 | ``` 6 | npm install --dev 7 | ``` 8 | 9 | You'll also have to globally install [tap](https://github.com/isaacs/node-tap). `npm install tap -g`. 10 | 11 | Run the unit tests with: 12 | 13 | ``` 14 | tap . 15 | ``` -------------------------------------------------------------------------------- /defaultignore: -------------------------------------------------------------------------------- 1 | # Repositories # 2 | .git 3 | .hg 4 | .svn 5 | 6 | # Font files # 7 | *.ttf 8 | *.eot 9 | *.woff 10 | 11 | # Data # 12 | *.bson 13 | 14 | # Image files # 15 | *.xcf 16 | *.psd 17 | *.jpg 18 | *.png 19 | *.gif 20 | *.jpeg 21 | *.ico 22 | *.bmp 23 | *.tiff 24 | *.swp 25 | *.mpg 26 | *.swf 27 | *.flv 28 | *.avi 29 | *.mov 30 | *.wav 31 | *.wmv 32 | *.mp3 33 | *.flac 34 | *.ogg 35 | 36 | # Compiled source # 37 | *.com 38 | *.class 39 | *.dll 40 | *.exe 41 | *.a 42 | *.o 43 | *.so 44 | *.pyc 45 | *.pyo 46 | 47 | # Packages # 48 | *.7z 49 | *.dmg 50 | *.gz 51 | *.iso 52 | *.jar 53 | *.rar 54 | *.tar 55 | *.zip 56 | 57 | # Logs and databases # 58 | *.sql 59 | *.sqlite 60 | 61 | # OS generated files # 62 | .DS_Store 63 | ehthumbs.db 64 | Thumbs.db 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "replace", 3 | "description": "Command line search and replace utility", 4 | "license": "MIT", 5 | "version": "0.3.0", 6 | "author": "Heather Arthur ", 7 | "main": "replace.js", 8 | "repository": { 9 | "type": "git", 10 | "url": "http://github.com/harthur/replace.git" 11 | }, 12 | "scripts": { 13 | "test": "tap test" 14 | }, 15 | "dependencies": { 16 | "nomnom": "1.6.x", 17 | "colors": "0.5.x", 18 | "minimatch": "~0.2.9" 19 | }, 20 | "devDependencies": { 21 | "tape": "~0.2.2" 22 | }, 23 | "bin": { 24 | "replace": "./bin/replace.js", 25 | "search": "./bin/search.js" 26 | }, 27 | "keywords": [ 28 | "sed", 29 | "grep", 30 | "search", 31 | "replace" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2014 Heather Arthur and contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /bin/replace.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var nomnom = require("nomnom"), 4 | replace = require("../replace"), 5 | sharedOptions = require("./shared-options"); 6 | 7 | /* Additional options that apply to `replace`, but not `search` */ 8 | var addlOptions = { 9 | replacement: { 10 | position: 1, 11 | help: "Replacement string for matches", 12 | type: "string", 13 | required: true 14 | }, 15 | paths: { 16 | position: 2, 17 | help: "File or directory to search (default is '*')", 18 | type: "string", 19 | list: true, 20 | default: ["*"] 21 | }, 22 | funcFile: { 23 | abbr: 'f', 24 | full: 'function-file', 25 | metavar: 'PATH', 26 | help: 'file containing JS replacement function', 27 | hidden: true 28 | }, 29 | maxLines: { 30 | string: '-n NUMLINES', 31 | help: 'limit the number of lines to preview' 32 | }, 33 | silent: { 34 | abbr: 's', 35 | flag: true, 36 | help: "Don't print out anything" 37 | }, 38 | preview: { 39 | abbr: 'p', 40 | flag: true, 41 | help: "Preview the replacements, but don't modify files" 42 | } 43 | } 44 | 45 | var opts = {}; 46 | for (var opt in sharedOptions) { 47 | opts[opt] = sharedOptions[opt]; 48 | } 49 | for (var opt in addlOptions) { 50 | opts[opt] = addlOptions[opt]; 51 | } 52 | 53 | var options = nomnom.options(opts) 54 | .script("replace") 55 | .parse(); 56 | 57 | replace(options); 58 | -------------------------------------------------------------------------------- /bin/shared-options.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | 3 | module.exports = { 4 | regex: { 5 | position: 0, 6 | help: "JavaScript regex for searching file e.g. '\\d+'", 7 | required: true 8 | }, 9 | paths: { 10 | position: 1, 11 | help: "File or directory to search (default is '*')", 12 | list: true, 13 | type: "string", 14 | default: ["*"] 15 | }, 16 | recursive: { 17 | abbr: 'r', 18 | flag: true, 19 | help: "Recursively search directories" 20 | }, 21 | ignoreCase: { 22 | abbr: 'i', 23 | flag: true, 24 | help: "Ignore case when searching" 25 | }, 26 | multiline: { 27 | abbr: 'm', 28 | flag: true, 29 | help: "Match line by line, default is true", 30 | default: true 31 | }, 32 | include: { 33 | string: '--include=PATHS', 34 | help: "Only search in these files, e.g. '*.js,*.foo'" 35 | }, 36 | exclude: { 37 | string: '--exclude=PATHS', 38 | help: "Don't search in these files, e.g. '*.min.js'" 39 | }, 40 | excludeList: { 41 | full: 'exclude-list', 42 | metavar: 'FILE', 43 | help: "File containing a new-line separated list of files to ignore", 44 | default: path.join(__dirname, "..", "defaultignore"), 45 | hidden: true 46 | }, 47 | maxLines: { 48 | string: '-n NUMLINES', 49 | help: 'limit the number of lines to preview' 50 | }, 51 | count: { 52 | abbr: 'c', 53 | flag: true, 54 | help: 'display count of occurances in each file' 55 | }, 56 | quiet: { 57 | abbr: 'q', 58 | flag: true, 59 | help: "Just print the names of the files matches occured in (faster)" 60 | }, 61 | color: { 62 | metavar: 'COLOR', 63 | help: "highlight color, e.g. 'green' or 'blue'", 64 | choices: ['red', 'green', 'blue', 'cyan', 'yellow', 'magenta', 'bold', 'italic'], 65 | default: 'cyan' 66 | }, 67 | fileColor: { 68 | help: "highlight matching file's name in color, e.g. 'green' or 'blue'", 69 | choices: ['red', 'green', 'blue', 'cyan', 'yellow', 'magenta', 'bold', 'italic'], 70 | default: 'yellow' 71 | }, 72 | async: { 73 | abbr: 'a', 74 | flag: true, 75 | help: "asynchronously read/write files in directory (faster)", 76 | hidden: true 77 | }, 78 | noColor: { 79 | help: 'Disable color output.', 80 | flag: true 81 | } 82 | }; 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # replace 2 | `replace` is a command line utility for performing search-and-replace on files. It's similar to sed but there are a few differences: 3 | 4 | * Modifies files when matches are found 5 | * Recursive search on directories with -r 6 | * Uses [JavaScript syntax](https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions#Using_Simple_Patterns) for regular expressions and [replacement strings](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_string_as_a_parameter). 7 | 8 | # Install 9 | With [node.js](http://nodejs.org/) and [npm](http://github.com/isaacs/npm): 10 | 11 | npm install replace -g 12 | 13 | You can now use `replace` and `search` from the command line. 14 | 15 | 16 | ## Examples 17 | 18 | Replace all occurrences of "foo" with "bar" in files in the current directory: 19 | 20 | ``` 21 | replace 'foo' 'bar' * 22 | ``` 23 | 24 | Replace in all files in a recursive search of the current directory: 25 | 26 | ``` 27 | replace 'foo' 'bar' . -r 28 | ``` 29 | 30 | Replace only in test/file1.js and test/file2.js: 31 | 32 | ``` 33 | replace 'foo' 'bar' test/file1.js test/file2.js 34 | ``` 35 | 36 | Replace all word pairs with "_" in middle with a "-": 37 | 38 | ``` 39 | replace '(\w+)_(\w+)' '$1-$2' * 40 | ``` 41 | 42 | Replace only in files with names matching *.js: 43 | 44 | ``` 45 | replace 'foo' 'bar' . -r --include="*.js" 46 | ``` 47 | 48 | Don't replace in files with names matching *.min.js and *.py: 49 | 50 | ``` 51 | replace 'foo' 'bar' . -r --exclude="*.min.js,*.py" 52 | ``` 53 | 54 | Preview the replacements without modifying any files: 55 | 56 | ``` 57 | replace 'foo' 'bar' . -r --preview 58 | ``` 59 | 60 | See all the options: 61 | 62 | ``` 63 | replace -h 64 | ``` 65 | 66 | ## Search 67 | There's also a `search` command. It's like `grep`, but with `replace`'s syntax. 68 | 69 | ``` 70 | search "setTimeout" . -r 71 | ``` 72 | 73 | ## Programmatic Usage 74 | You can use replace from your JS program: 75 | 76 | ```javascript 77 | var replace = require("replace"); 78 | 79 | replace({ 80 | regex: "foo", 81 | replacement: "bar", 82 | paths: ['.'], 83 | recursive: true, 84 | silent: true, 85 | }); 86 | ``` 87 | 88 | ## More Details 89 | 90 | ### Excludes 91 | By default, `replace` and `search` will exclude files (binaries, images, etc) that match patterns in the `"defaultignore"` located in this directory. 92 | 93 | ### On huge directories 94 | If `replace` is taking too long on a large directory, try turning on the quiet flag with `-q`, only including the necessary file types with `--include` or limiting the lines shown in a preview with `-n`. 95 | 96 | 97 | ## What it looks like 98 | ![replace](http://i.imgur.com/qmJjS.png) 99 | 100 | -------------------------------------------------------------------------------- /test/sanity.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"), 2 | test = require('tape'), 3 | replace = require('../replace'); 4 | 5 | function getText(file) { 6 | var content = fs.readFileSync(file, "utf-8"); 7 | return content; 8 | } 9 | 10 | test('basic', function (t) { 11 | t.plan(2); 12 | 13 | var file = "./test_files/test_basic.txt"; 14 | 15 | replace({ 16 | regex: "a", 17 | replacement: "b", 18 | paths:[file] 19 | }); 20 | 21 | var expected = "bbbccc"; 22 | t.equal(getText(file), expected, "single letter replace works"); 23 | 24 | replace({ 25 | regex: "b", 26 | replacement: "a", 27 | paths:[file] 28 | }); 29 | 30 | var expected = "aaaccc"; 31 | t.equal(getText(file), expected, "reverting worked"); 32 | }); 33 | 34 | test('numbers', function(t) { 35 | t.plan(2); 36 | 37 | var file = "./test_files/test_numbers.txt"; 38 | 39 | replace({ 40 | regex: "123", 41 | replacement: "456", 42 | paths:[file] 43 | }); 44 | 45 | var expected = "a456b"; 46 | t.equal(getText(file), expected, "number replace works"); 47 | 48 | replace({ 49 | regex: "456", 50 | replacement: "123", 51 | paths:[file] 52 | }); 53 | 54 | var expected = "a123b"; 55 | t.equal(getText(file), expected, "reverting worked"); 56 | }) 57 | 58 | 59 | test('multiline', function(t) { 60 | t.plan(3); 61 | 62 | var file = "./test_files/test_multiline.txt"; 63 | 64 | replace({ 65 | regex: "c$", 66 | replacement: "t", 67 | paths:[file], 68 | multiline: false 69 | }); 70 | 71 | var expected = "abc\ndef"; 72 | t.equal(getText(file), expected, "$ shouldn't match without multiline"); 73 | 74 | replace({ 75 | regex: "c$", 76 | replacement: "t", 77 | paths:[file], 78 | multiline: true 79 | }); 80 | 81 | var expected = "abt\ndef"; 82 | t.equal(getText(file), expected, "with multiline, $ should match eol"); 83 | 84 | replace({ 85 | regex: "t$", 86 | replacement: "c", 87 | paths:[file], 88 | multiline: true 89 | }); 90 | 91 | var expected = "abc\ndef"; 92 | t.equal(getText(file), expected, "reverting worked"); 93 | }); 94 | 95 | test('case insensitive', function(t) { 96 | t.plan(2); 97 | 98 | var file = "./test_files/test_case.txt"; 99 | 100 | replace({ 101 | regex: "a", 102 | replacement: "c", 103 | paths:[file], 104 | ignoreCase: true 105 | }); 106 | 107 | var expected = "cccc"; 108 | t.equal(getText(file), expected, "case insensitive replace"); 109 | 110 | replace({ 111 | regex: "c", 112 | replacement: "A", 113 | paths:[file] 114 | }); 115 | 116 | var expected = "AAAA"; 117 | t.equal(getText(file), expected, "reverting worked"); 118 | }) 119 | 120 | test('preview', function(t) { 121 | t.plan(1); 122 | 123 | var file = "./test_files/test_preview.txt"; 124 | 125 | replace({ 126 | regex: "a", 127 | replacement: "c", 128 | paths:[file], 129 | preview: true 130 | }); 131 | 132 | var expected = "aaaa"; 133 | t.equal(getText(file), expected, "no replacement if 'preview' is true"); 134 | }) 135 | -------------------------------------------------------------------------------- /test/paths.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"), 2 | test = require('tape'), 3 | replace = require('../replace'); 4 | 5 | function getText(file) { 6 | var content = fs.readFileSync(file, "utf-8"); 7 | return content; 8 | } 9 | 10 | test('recursive', function (t) { 11 | t.plan(7); 12 | 13 | replace({ 14 | regex: "a", 15 | replacement: "b", 16 | paths: ["test_files/test_paths"], 17 | recursive: true 18 | }); 19 | 20 | var changedFiles = [ 21 | "./test_files/test_paths/test1.txt", 22 | "./test_files/test_paths/test2.txt", 23 | "./test_files/test_paths/sample1.txt"]; 24 | var expected = "bbbb"; 25 | changedFiles.forEach(function(file) { 26 | t.equal(getText(file), expected, "recursive replace on directory " + file); 27 | }) 28 | 29 | var expected = "aaaa"; 30 | var ignored = "./test_files/test_paths/test.png" 31 | t.equal(getText(ignored), expected, "skip file with match in defaultignore") 32 | 33 | replace({ 34 | regex: "b", 35 | replacement: "a", 36 | paths: ["test_files/test_paths"], 37 | recursive: true 38 | }); 39 | 40 | changedFiles.forEach(function(file) { 41 | t.equal(getText(file), expected, "reverting worked"); 42 | }); 43 | }); 44 | 45 | test('include', function(t) { 46 | t.plan(5); 47 | 48 | replace({ 49 | regex: "a", 50 | replacement: "b", 51 | paths: ["test_files/test_paths"], 52 | recursive: true, 53 | include: "sample*.txt" 54 | }); 55 | 56 | var changedFiles = [ 57 | "./test_files/test_paths/sample1.txt", 58 | ]; 59 | var expected = "bbbb"; 60 | changedFiles.forEach(function(file) { 61 | t.equal(getText(file), expected, "replace in included file " + file); 62 | }); 63 | 64 | var ignoredFiles = [ 65 | "./test_files/test_paths/test1.txt", 66 | "./test_files/test_paths/test2.txt", 67 | "./test_files/test_paths/test.png"]; 68 | var expected = "aaaa"; 69 | ignoredFiles.forEach(function(file) { 70 | t.equal(getText(file), expected, "don't replace in not-included file " + file); 71 | }); 72 | 73 | replace({ 74 | regex: "b", 75 | replacement: "a", 76 | paths: ["test_files/test_paths"], 77 | recursive: true 78 | }); 79 | 80 | var expected = "aaaa"; 81 | changedFiles.forEach(function(file) { 82 | t.equal(getText(file), expected, "reverting worked"); 83 | }); 84 | }) 85 | 86 | test('exclude', function(t) { 87 | t.plan(6); 88 | 89 | replace({ 90 | regex: "a", 91 | replacement: "b", 92 | paths: ["test_files/test_paths"], 93 | recursive: true, 94 | exclude: "*sample*.txt" 95 | }); 96 | 97 | var changedFiles = [ 98 | "./test_files/test_paths/test1.txt", 99 | "./test_files/test_paths/test2.txt"]; 100 | var expected = "bbbb"; 101 | changedFiles.forEach(function(file) { 102 | t.equal(getText(file), expected, "replace in non-excluded file " + file); 103 | }); 104 | 105 | var ignoredFiles = [ 106 | "./test_files/test_paths/sample1.txt", 107 | "./test_files/test_paths/test.png"]; 108 | var expected = "aaaa"; 109 | ignoredFiles.forEach(function(file) { 110 | t.equal(getText(file), expected, "don't replace in excluded file " + file); 111 | }); 112 | 113 | replace({ 114 | regex: "b", 115 | replacement: "a", 116 | paths: ["test_files/test_paths"], 117 | recursive: true 118 | }); 119 | 120 | var expected = "aaaa"; 121 | changedFiles.forEach(function(file) { 122 | t.equal(getText(file), expected, "reverting worked"); 123 | }); 124 | }) -------------------------------------------------------------------------------- /replace.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"), 2 | path = require("path"), 3 | colors = require("colors"), 4 | minimatch = require("minimatch"), 5 | sharedOptions = require("./bin/shared-options"); 6 | 7 | module.exports = function(options) { 8 | // If the path is the same as the default and the recursive option was not 9 | // specified, search recursively under the current directory as a 10 | // convenience. 11 | if (options.paths.length === 1 && 12 | options.paths[0] === sharedOptions.paths.default[0] && 13 | !options.hasOwnProperty('recursive')) { 14 | options.paths = ['.']; 15 | options.recursive = true; 16 | } 17 | 18 | var lineCount = 0, 19 | limit = 400; // chars per line 20 | 21 | if (!options.color) { 22 | options.color = "cyan"; 23 | } 24 | 25 | var flags = "g"; // global multiline 26 | if (options.ignoreCase) { 27 | flags += "i"; 28 | } 29 | if (options.multiline) { 30 | flags += "m"; 31 | } 32 | 33 | var regex; 34 | if (options.regex instanceof RegExp) { 35 | regex = options.regex; 36 | } 37 | else { 38 | regex = new RegExp(options.regex, flags); 39 | } 40 | var canReplace = !options.preview && options.replacement !== undefined; 41 | 42 | var includes; 43 | if (options.include) { 44 | includes = options.include.split(","); 45 | } 46 | var excludes = []; 47 | if (options.exclude) { 48 | excludes = options.exclude.split(","); 49 | } 50 | var ignoreFile = options.excludeList || path.join(__dirname, '/defaultignore'); 51 | var ignores = fs.readFileSync(ignoreFile, "utf-8").split("\n"); 52 | excludes = excludes.concat(ignores); 53 | 54 | var replaceFunc; 55 | if (options.funcFile) { 56 | eval('replaceFunc = ' + fs.readFileSync(options.funcFile, "utf-8")); 57 | } 58 | 59 | for (var i = 0; i < options.paths.length; i++) { 60 | if (options.async) { 61 | replacizeFile(options.paths[i]); 62 | } 63 | else { 64 | replacizeFileSync(options.paths[i]); 65 | } 66 | } 67 | 68 | function canSearch(file, isFile) { 69 | var inIncludes = includes && includes.some(function(include) { 70 | return minimatch(file, include, { matchBase: true }); 71 | }) 72 | var inExcludes = excludes.some(function(exclude) { 73 | return minimatch(file, exclude, { matchBase: true }); 74 | }) 75 | 76 | return ((!includes || !isFile || inIncludes) && (!excludes || !inExcludes)); 77 | } 78 | 79 | function replacizeFile(file) { 80 | fs.lstat(file, function(err, stats) { 81 | if (err) throw err; 82 | 83 | if (stats.isSymbolicLink()) { 84 | // don't follow symbolic links for now 85 | return; 86 | } 87 | var isFile = stats.isFile(); 88 | if (!canSearch(file, isFile)) { 89 | return; 90 | } 91 | if (isFile) { 92 | fs.readFile(file, "utf-8", function(err, text) { 93 | if (err) { 94 | if (err.code == 'EMFILE') { 95 | console.log('Too many files, try running `replace` without --async'); 96 | process.exit(1); 97 | } 98 | throw err; 99 | } 100 | 101 | text = replacizeText(text, file); 102 | if (canReplace && text !== null) { 103 | fs.writeFile(file, text, function(err) { 104 | if (err) throw err; 105 | }); 106 | } 107 | }); 108 | } 109 | else if (stats.isDirectory() && options.recursive) { 110 | fs.readdir(file, function(err, files) { 111 | if (err) throw err; 112 | for (var i = 0; i < files.length; i++) { 113 | replacizeFile(path.join(file, files[i])); 114 | } 115 | }); 116 | } 117 | }); 118 | } 119 | 120 | function replacizeFileSync(file) { 121 | var stats = fs.lstatSync(file); 122 | if (stats.isSymbolicLink()) { 123 | // don't follow symbolic links for now 124 | return; 125 | } 126 | var isFile = stats.isFile(); 127 | if (!canSearch(file, isFile)) { 128 | return; 129 | } 130 | if (isFile) { 131 | var text = fs.readFileSync(file, "utf-8"); 132 | 133 | text = replacizeText(text, file); 134 | if (canReplace && text !== null) { 135 | fs.writeFileSync(file, text); 136 | } 137 | } 138 | else if (stats.isDirectory() && options.recursive) { 139 | var files = fs.readdirSync(file); 140 | for (var i = 0; i < files.length; i++) { 141 | replacizeFileSync(path.join(file, files[i])); 142 | } 143 | } 144 | } 145 | 146 | function replacizeText(text, file) { 147 | var match = text.match(regex); 148 | if (!match) { 149 | return null; 150 | } 151 | 152 | if (!options.silent) { 153 | var printout = options.noColor ? file : file[options.fileColor] || file; 154 | if (options.count) { 155 | var count = " (" + match.length + ")"; 156 | count = options.noColor ? count : count.grey; 157 | printout += count; 158 | } 159 | console.log(printout); 160 | } 161 | if (!options.silent && !options.quiet 162 | && !(lineCount > options.maxLines) 163 | && options.multiline) { 164 | var lines = text.split("\n"); 165 | for (var i = 0; i < lines.length; i++) { 166 | var line = lines[i]; 167 | if (line.match(regex)) { 168 | if (++lineCount > options.maxLines) { 169 | break; 170 | } 171 | var replacement = options.replacement || "$&"; 172 | if (!options.noColor) { 173 | replacement = replacement[options.color]; 174 | } 175 | line = line.replace(regex, replaceFunc || replacement); 176 | console.log(" " + (i + 1) + ": " + line.slice(0, limit)); 177 | } 178 | } 179 | } 180 | if (canReplace) { 181 | return text.replace(regex, replaceFunc || options.replacement); 182 | } 183 | } 184 | } 185 | --------------------------------------------------------------------------------