Skip to content

Commit

Permalink
Support all versions of Node js
Browse files Browse the repository at this point in the history
  • Loading branch information
mathiasrw committed Mar 18, 2017
1 parent 5d76bf6 commit 615ee77
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 20 deletions.
1 change: 1 addition & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "presets": ["es2015"] }
9 changes: 7 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,10 @@ language: node_js
node_js:
- "node"
- "6"


- "5"
- "4"
- "0.12"
- "0.11"
- "0.10"
- "0.8"
- "0.6"
24 changes: 15 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
[![Build Status](https://travis-ci.org/mathiasrw/rexreplace.svg?branch=master)](https://travis-ci.org/mathiasrw/rexreplace)
[![npm](https://img.shields.io/npm/v/rexreplace.svg)](https://www.npmjs.com/package/rexreplace)
[![npm version](https://badge.fury.io/js/rexreplace.svg)](https://www.npmjs.com/package/rexreplace)

# RexReplace

_Commandline search and replace as it should be._

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


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.

### Install
```bash
> npm install -g rexreplace
```


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

# 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.
> rexreplace '(f?(o))o(.*)' '€3€1€2' myfile.md
# 'foobar' in myfile.md will become 'barfoo'
# As '€' is treated as an alias for '$' this also transforms 'foobar' into 'barfoo'

```

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

### Quirks
- 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.

### Limitations
- Reads each file into memory, so using RexReplace on your 3Gb log file will probably not be ideal.

### Test
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:
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:

```bash
> npm test
```

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


### inspiration
### Inspiration

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

188 changes: 188 additions & 0 deletions bin/rexreplace.cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
#!/usr/bin/env node
'use strict';

var version = '2.0.3';
var fs = require('fs');
var path = require('path');
var font = require('chalk');
var globs = require('globs');

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')
//.conflicts('o', 'd')

.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')

/* // Ideas
.boolean('n')
.describe('n', "Do replacement on file names instead of file content (rename the files)")
.alias('n', 'name')
.boolean('v')
.describe('v', "More chatty output")
.alias('v', 'verbose')
.boolean('p')
.describe('p', "Pattern will be piped in. Note that replacement must then be first argument. Other elements like -P and -€ will be applyed afterwards.")
.alias('p', 'pattern-pipe')
.boolean('r')
.describe('r', "Replacement will be piped in. Note that filename/globs must then be second argument")
.alias('r', 'replacement-pipe')
.boolean('P')
.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 -€).)")
.alias('P', 'pattern-file')
.boolean('R')
.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 -€).")
.alias('R', 'replacement-file')
.boolean('G')
.describe('G', "filename/globas are filename(s) for files containing one filename/globs on each line to be search/replaced")
.alias('G', 'globs-file')
.boolean('g')
.describe('g', "filename/globs will be piped in. If any filename/globs are given in command the piped data will be prepened")
.alias('g', 'glob-pipe')
.boolean('j')
.describe('j', "Pattern is javascript source that will return a string giving the pattern to use")
.alias('j', 'pattern-js')
.boolean('J')
.describe('J', "Replacement is javascript source that will return a string giving the replacement to use to use")
.alias('j', 'replacement-js')
.boolean('glob-js')
.describe('glob-js', "filename/globs are javascript source that will return a string with newline seperating each glob to work on")
*/

.help('h').alias('h', 'help').epilog('What "sed" should have been...');

var args = yargs.argv;

debug(args);

if (args._.length < 3) {
die('Need more than 2 arguments', args._.length + ' was found', true);
}

if (!args['€']) {
args._[0] = args._[0].replace('€', '$');
args._[1] = args._[1].replace('€', '$');
}

var flags = 'g';
if (!args['void-ignore-case']) {
flags += 'i';
}
if (!args['void-multiline']) {
flags += 'm';
}
if (args.unicode) {
flags += 'u';
}

debug(flags);

// Get regex pattern
var regex = args._.shift();
try {
regex = new RegExp(regex, flags);
} catch (err) {
die('Wrong formatted regexp', regex);
}

// Get replacement
var replace = args._.shift();

// The rest are files
var files = globs.sync(args._);

files
// Correct filepath
.map(function (filepath) {
return path.normalize(process.cwd() + '/' + filepath);
})

// Find out if any filepaths are invalid
.filter(function (filepath) {
return fs.existsSync(filepath) ? true : error('File not found:', filepath);
})

// Do the replacement
.forEach(function (filepath) {
return replaceInFile(filepath, regex, replace, args.encoding);
});

function replaceInFile(file, regex, replace, encoding) {
fs.readFile(file, encoding, function (err, data) {
if (err) {
return error(err);
}
debug('About to replace in: ' + file);
var result = data.replace(regex, replace);

if (args.output) {
debug('Outputting result from: ' + file);
return process.stdout.write(result);
}

// Nothing replaced = no need for writing file again
if (result === data) {
debug('Nothing changed in: ' + file);
return;
}

debug('About to write to: ' + file);
fs.writeFile(file, result, encoding, function (err) {
if (err) {
return error(err);
}
info(file);
});
});
}

function info(msg) {
var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';

if (args.quiet || args['quiet-total']) {
return;
}
console.log(font.green(msg), data);
}

function die(msg) {
var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
var displayHelp = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;

if (displayHelp) {
yargs.showHelp();
}
error(msg, data);
kill();
}

function error(msg) {
var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';

if (!args.quiet && !args['quiet-total']) {
console.error(font.red(msg), data);
}
if (args.halt) {
kill();
}
return false;
}

function debug(data) {
if (args.debug) {
console.log(font.gray(JSON.stringify(data, null, 4)));
}
}

function kill() {
var error = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;

setTimeout(function () {
return process.exit(error);
}, 10); // give stdout a bit of time to finish
}
20 changes: 12 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
{
"name": "rexreplace",
"version": "2.0.2",
"description": "CLI regexp replacement for files with lookahead and backreference to matching groups in the replacement.",
"version": "2.0.3",
"description": "Friendly CLI regexp search and replace in files.",
"author": "Mathias Rangel Wulff",
"license": "MIT",
"main": "rexreplace-cli.js",
"main": "bin/rexreplace-cli.js",
"repository": {
"type": "git",
"url": "https://github.com/mathiasrw/rexreplace/"
},
"bin": {
"rexreplace": "./rexreplace-cli.js",
"rr": "./rexreplace-cli.js"
"rexreplace": "bin/rexreplace.cli.js",
"rr": "bin/rexreplace.cli.js"
},
"scripts": {
"test": "npm -g install ./ && bash test.sh",
"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));'"
"test": "npm run build && npm -g install ./ && cd test && bash test.sh && cd ..",
"build":"babel src -d bin",
"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))\""
},
"keywords": [
"CLI",
Expand All @@ -26,7 +27,10 @@
"expression",
"sed"
],
"devDependencies": {},
"devDependencies": {
"babel-cli": "^6.24.0",
"babel-preset-es2015": "^6.24.0"
},
"dependencies": {
"chalk": "^1.1.3",
"globs": "^0.1.3",
Expand Down
2 changes: 1 addition & 1 deletion rexreplace-cli.js → src/rexreplace.cli.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env node

const version = `2.0.1`;
const version = '@VERSION_NUMBER';
const fs = require('fs');
const path = require('path');
const font = require('chalk');
Expand Down
File renamed without changes.
File renamed without changes.

0 comments on commit 615ee77

Please sign in to comment.