Skip to content

Commit 615ee77

Browse files
committed
Support all versions of Node js
1 parent 5d76bf6 commit 615ee77

File tree

8 files changed

+224
-20
lines changed

8 files changed

+224
-20
lines changed

.babelrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "presets": ["es2015"] }

.travis.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,10 @@ language: node_js
22
node_js:
33
- "node"
44
- "6"
5-
6-
5+
- "5"
6+
- "4"
7+
- "0.12"
8+
- "0.11"
9+
- "0.10"
10+
- "0.8"
11+
- "0.6"

README.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
[![Build Status](https://travis-ci.org/mathiasrw/rexreplace.svg?branch=master)](https://travis-ci.org/mathiasrw/rexreplace)
2-
[![npm](https://img.shields.io/npm/v/rexreplace.svg)](https://www.npmjs.com/package/rexreplace)
2+
[![npm version](https://badge.fury.io/js/rexreplace.svg)](https://www.npmjs.com/package/rexreplace)
33

44
# RexReplace
55

66
_Commandline search and replace as it should be._
77

8-
Regexp search and replace in files using lookahead and
8+
Friendly regexp search and replace in files using lookahead and
99
backreference to matching groups in the replacement.
10-
Defaults to global multiline case-insensitive search.
11-
Needs node v6+.
10+
Defaults to global multiline case-insensitive search. Will run with any version of Node JS (yes, even v0.6)
11+
1212

1313
Files can be given as [glob notation](https://www.tcl.tk/man/tcl8.5/tutorial/Tcl16a.html), for example the glob `docs/*.md` represents each markdown file in the `docs/` dir.
1414

1515
### Install
1616
```bash
1717
> npm install -g rexreplace
1818
```
19+
1920

2021
### Usages
2122
```bash
@@ -40,9 +41,8 @@ Hard on your fingers to write on your keyboard? We got you covered with `rr` as
4041
> rexreplace '(f?(o))o(.*)' '$3$1$2' myfile.md
4142
# 'foobar' in myfile.md will become 'barfoo'
4243

43-
# Per default '€' is treated as an alias for '$' because some commandline tools have a special relationship with with the `$` char. Your can escape your way out of this old love story but it often popup in unexpected ways.
4444
> rexreplace '(f?(o))o(.*)' '€3€1€2' myfile.md
45-
# 'foobar' in myfile.md will become 'barfoo'
45+
# As '€' is treated as an alias for '$' this also transforms 'foobar' into 'barfoo'
4646

4747
```
4848

@@ -69,15 +69,20 @@ Hard on your fingers to write on your keyboard? We got you covered with `rr` as
6969
-h, --help Show help [boolean]
7070
```
7171

72+
### Quirks
73+
- Per default `` is treated as an alias for `$`. The main reason is for you not to worry about some commandline tools having a special relationship with with the `$` char. Your can escape your way out of this old love story but it often popup in unexpected ways. Use the `-€` flag if you need to search or replace the actual euro char.
74+
75+
### Limitations
76+
- Reads each file into memory, so using RexReplace on your 3Gb log file will probably not be ideal.
77+
7278
### Test
73-
All tests are defined in [test.sh](https://github.com/mathiasrw/rexreplace/blob/master/test.sh) and after `git clone`ing the repo you can invoke them with:
79+
All tests are defined in [test.sh](https://github.com/mathiasrw/rexreplace/blob/master/test.sh) and after `git clone`'ing the repo you can invoke them with:
7480

7581
```bash
7682
> npm test
7783
```
7884

7985
### Future ideas
80-
- Support node 0.12+
8186
- Test-run a with info outputted about what will happen (sets -v and does not change anything)
8287
- Let search and replace be withing the names of the files (ask for overwrite. -Y = no questions)
8388
- Let search and replace be within the path of the files (ask for overwrite. -Y = no questions)
@@ -89,9 +94,10 @@ All tests are defined in [test.sh](https://github.com/mathiasrw/rexreplace/blob/
8994
- Let pattern, replacement, glob be javascript code returning string as result
9095
- Error != warning
9196
- Debug != all debug
97+
- Flag for sync or async read file handeling. Test if async or sync is best.
9298

9399

94-
### inspiration
100+
### Inspiration
95101

96102
.oO(_What should "sed" have looked like by now?_)
97103

bin/rexreplace.cli.js

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
#!/usr/bin/env node
2+
'use strict';
3+
4+
var version = '2.0.3';
5+
var fs = require('fs');
6+
var path = require('path');
7+
var font = require('chalk');
8+
var globs = require('globs');
9+
10+
var yargs = require('yargs').strict().usage('RexReplace ' + version + ': Regexp search and replace for files using lookahead and backreference to matching groups in the replacement. Defaults to global multiline case-insensitive search.\n\n> rexreplace searchFor replaceWith filename').example("> rexreplace '(f?(o))o(.*)' '$3$1$2' myfile.md", "'foobar' in myfile.md will become 'barfoo'").example('').example("> rexreplace -I 'Foo' 'xxx' myfile.md", "'foobar' in myfile.md will remain 'foobar'").example('').example('> rexreplace \'^#\' \'##\' *.md', 'All markdown files in this dir got all headlines moved one level deeper').version('v', 'Echo rexreplace version', version).alias('v', 'version').boolean('I').describe('I', 'Void case insensitive search pattern.').alias('I', 'void-ignore-case').boolean('M').describe('M', 'Void multiline search pattern. Makes ^ and $ match start/end of whole content rather than each line.').alias('M', 'void-multiline').boolean('u').describe('u', 'Treat pattern as a sequence of unicode code points.').alias('u', 'unicode').describe('e', 'Encoding of files.').alias('e', 'encoding').default('e', 'utf8').boolean('o').describe('o', 'Output the result instead of saving to file. Will also output content even if no replacement have taken place.').alias('o', 'output')
11+
//.conflicts('o', 'd')
12+
13+
.boolean('q').describe('q', "Only display erros (no other info)").alias('q', 'quiet').boolean('Q').describe('Q', "Never display erros or info").alias('Q', 'quiet-total').boolean('H').describe('H', "Halt on first error").alias('H', 'halt').default('H', false).boolean('d').describe('d', "Print debug info").alias('d', 'debug').boolean('€').describe('€', "Void having '€' as alias for '$' in pattern and replacement").alias('€', 'void-euro')
14+
15+
/* // Ideas
16+
.boolean('n')
17+
.describe('n', "Do replacement on file names instead of file content (rename the files)")
18+
.alias('n', 'name')
19+
20+
.boolean('v')
21+
.describe('v', "More chatty output")
22+
.alias('v', 'verbose')
23+
.boolean('p')
24+
.describe('p', "Pattern will be piped in. Note that replacement must then be first argument. Other elements like -P and -€ will be applyed afterwards.")
25+
.alias('p', 'pattern-pipe')
26+
.boolean('r')
27+
.describe('r', "Replacement will be piped in. Note that filename/globs must then be second argument")
28+
.alias('r', 'replacement-pipe')
29+
30+
.boolean('P')
31+
.describe('P', "Pattern is a filename from where the pattern will be generated. Pattern will be defined by each line trimmed and having newlines removed followed by other other rules (like -€).)")
32+
.alias('P', 'pattern-file')
33+
.boolean('R')
34+
.describe('R', "Replacement is a filename from where the replacement will be generated. Replacement will be defined by each line trimmed and having newlines removed followed by other other rules (like -€).")
35+
.alias('R', 'replacement-file')
36+
37+
.boolean('G')
38+
.describe('G', "filename/globas are filename(s) for files containing one filename/globs on each line to be search/replaced")
39+
.alias('G', 'globs-file')
40+
.boolean('g')
41+
.describe('g', "filename/globs will be piped in. If any filename/globs are given in command the piped data will be prepened")
42+
.alias('g', 'glob-pipe')
43+
44+
.boolean('j')
45+
.describe('j', "Pattern is javascript source that will return a string giving the pattern to use")
46+
.alias('j', 'pattern-js')
47+
48+
.boolean('J')
49+
.describe('J', "Replacement is javascript source that will return a string giving the replacement to use to use")
50+
.alias('j', 'replacement-js')
51+
52+
.boolean('glob-js')
53+
.describe('glob-js', "filename/globs are javascript source that will return a string with newline seperating each glob to work on")
54+
55+
*/
56+
57+
.help('h').alias('h', 'help').epilog('What "sed" should have been...');
58+
59+
var args = yargs.argv;
60+
61+
debug(args);
62+
63+
if (args._.length < 3) {
64+
die('Need more than 2 arguments', args._.length + ' was found', true);
65+
}
66+
67+
if (!args['€']) {
68+
args._[0] = args._[0].replace('€', '$');
69+
args._[1] = args._[1].replace('€', '$');
70+
}
71+
72+
var flags = 'g';
73+
if (!args['void-ignore-case']) {
74+
flags += 'i';
75+
}
76+
if (!args['void-multiline']) {
77+
flags += 'm';
78+
}
79+
if (args.unicode) {
80+
flags += 'u';
81+
}
82+
83+
debug(flags);
84+
85+
// Get regex pattern
86+
var regex = args._.shift();
87+
try {
88+
regex = new RegExp(regex, flags);
89+
} catch (err) {
90+
die('Wrong formatted regexp', regex);
91+
}
92+
93+
// Get replacement
94+
var replace = args._.shift();
95+
96+
// The rest are files
97+
var files = globs.sync(args._);
98+
99+
files
100+
// Correct filepath
101+
.map(function (filepath) {
102+
return path.normalize(process.cwd() + '/' + filepath);
103+
})
104+
105+
// Find out if any filepaths are invalid
106+
.filter(function (filepath) {
107+
return fs.existsSync(filepath) ? true : error('File not found:', filepath);
108+
})
109+
110+
// Do the replacement
111+
.forEach(function (filepath) {
112+
return replaceInFile(filepath, regex, replace, args.encoding);
113+
});
114+
115+
function replaceInFile(file, regex, replace, encoding) {
116+
fs.readFile(file, encoding, function (err, data) {
117+
if (err) {
118+
return error(err);
119+
}
120+
debug('About to replace in: ' + file);
121+
var result = data.replace(regex, replace);
122+
123+
if (args.output) {
124+
debug('Outputting result from: ' + file);
125+
return process.stdout.write(result);
126+
}
127+
128+
// Nothing replaced = no need for writing file again
129+
if (result === data) {
130+
debug('Nothing changed in: ' + file);
131+
return;
132+
}
133+
134+
debug('About to write to: ' + file);
135+
fs.writeFile(file, result, encoding, function (err) {
136+
if (err) {
137+
return error(err);
138+
}
139+
info(file);
140+
});
141+
});
142+
}
143+
144+
function info(msg) {
145+
var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
146+
147+
if (args.quiet || args['quiet-total']) {
148+
return;
149+
}
150+
console.log(font.green(msg), data);
151+
}
152+
153+
function die(msg) {
154+
var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
155+
var displayHelp = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
156+
157+
if (displayHelp) {
158+
yargs.showHelp();
159+
}
160+
error(msg, data);
161+
kill();
162+
}
163+
164+
function error(msg) {
165+
var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
166+
167+
if (!args.quiet && !args['quiet-total']) {
168+
console.error(font.red(msg), data);
169+
}
170+
if (args.halt) {
171+
kill();
172+
}
173+
return false;
174+
}
175+
176+
function debug(data) {
177+
if (args.debug) {
178+
console.log(font.gray(JSON.stringify(data, null, 4)));
179+
}
180+
}
181+
182+
function kill() {
183+
var error = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
184+
185+
setTimeout(function () {
186+
return process.exit(error);
187+
}, 10); // give stdout a bit of time to finish
188+
}

package.json

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
{
22
"name": "rexreplace",
3-
"version": "2.0.2",
4-
"description": "CLI regexp replacement for files with lookahead and backreference to matching groups in the replacement.",
3+
"version": "2.0.3",
4+
"description": "Friendly CLI regexp search and replace in files.",
55
"author": "Mathias Rangel Wulff",
66
"license": "MIT",
7-
"main": "rexreplace-cli.js",
7+
"main": "bin/rexreplace-cli.js",
88
"repository": {
99
"type": "git",
1010
"url": "https://github.com/mathiasrw/rexreplace/"
1111
},
1212
"bin": {
13-
"rexreplace": "./rexreplace-cli.js",
14-
"rr": "./rexreplace-cli.js"
13+
"rexreplace": "bin/rexreplace.cli.js",
14+
"rr": "bin/rexreplace.cli.js"
1515
},
1616
"scripts": {
17-
"test": "npm -g install ./ && bash test.sh",
18-
"pretest": "node -e 'file=`rexreplace-cli.js`;fs = require(`fs`);fs.writeFileSync(file,fs.readFileSync(file,`utf8`).replace(/^(const version = `).*?(?=`;)/mi,String.fromCharCode(36,49)+require(`./package.json`).version));'"
17+
"test": "npm run build && npm -g install ./ && cd test && bash test.sh && cd ..",
18+
"build":"babel src -d bin",
19+
"postbuild": "node -e \"file='bin/rexreplace.cli.js';fs = require('fs');fs.writeFileSync(file,fs.readFileSync(file,'utf8').replace('@VERSION_NUMBER',require('./package.json').version))\""
1920
},
2021
"keywords": [
2122
"CLI",
@@ -26,7 +27,10 @@
2627
"expression",
2728
"sed"
2829
],
29-
"devDependencies": {},
30+
"devDependencies": {
31+
"babel-cli": "^6.24.0",
32+
"babel-preset-es2015": "^6.24.0"
33+
},
3034
"dependencies": {
3135
"chalk": "^1.1.3",
3236
"globs": "^0.1.3",
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env node
22

3-
const version = `2.0.1`;
3+
const version = '@VERSION_NUMBER';
44
const fs = require('fs');
55
const path = require('path');
66
const font = require('chalk');
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)