diff --git a/README.md b/README.md index 4cde7bb9..9e16866f 100644 --- a/README.md +++ b/README.md @@ -239,7 +239,7 @@ Use --js-pipe to access the pipe data in your javascript code. | | **`--tail`** Shorthand for setting both --name-delimiter-tail and --content-delimiter-tail. Use --no-tail to not print the delimiter after the last name and the last content. The specific tail flags win over --tail. [boolean] | | `-l` | **`--nl`** Let what is printed to stdout and stderr (the names, or the content from -o, -m or piped data) always end with a new line. No extra new line if it already ends with a new line, or if nothing was printed. [boolean] | | `-H` | **`--halt`**, **`--bail`** Halt on first error [boolean] [default: false] | -| `-S` | **`--status-compact`** As the last thing before ending any execution a status report will be printed as one line of JSON. Will print even in combination with `--quiet-total`. Will incude extra info about the inner workings of the engine in combination with the `--verbose` flag. [boolean] | +| `-c` | **`--status-compact`** As the last thing before ending any execution a status report will be printed as one line of JSON. Will print even in combination with `--quiet-total`. Will incude extra info about the inner workings of the engine in combination with the `--verbose` flag. [boolean] | | | **`--status`** Same as `--status-compact`, but always starting with a `\n` followed by pretty printed JSON. [boolean] | | `-d` | **`--debug`** Only print a status report, with what went into the engine and its steps. The result of --output and --output-match will still printed. [boolean] | @@ -249,6 +249,7 @@ Use --js-pipe to access the pipe data in your javascript code. | `-A` | **`--void-async`** Handle files in a synchronous flow. Good to limit memory usage when handling large files. [boolean] | | `-B` | **`--void-backup`** Avoid temporary backing up file. Works async (independent of -A flag) and will speed up things but at one point data lives only in memory, and you will lose the content if the process is interrupted. [boolean] | | `-b` | **`--keep-backup`** Keep a backup file of the original content. [boolean] | +| `-S` | **`--simulate`** Do everything but write the files. The names of the files that would be changed are printed as usual, and no backup is made. [boolean] | | `-m` | **`--output-match`** Output each match on a new line. Will not replace any content but you still need to provide a dummy value (like `_`) as replacement parameter. If search pattern does not contain matching groups the full match will be outputted. If search pattern does contain matching groups only matching groups will be outputted (same line with no delimiter). [boolean] | | `-T` | **`--trim-pipe`** Trim piped data before processing. If piped data only consists of chars that can be trimmed (new line, space, tabs...) it will become an empty string. [boolean] | | `-p` | **`--pipe`** Read the piped data and make it available to --replacement-js as `pipe`. [boolean] | diff --git a/src/cli.ts b/src/cli.ts index a1f6a3ea..ae2081b6 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -233,6 +233,13 @@ const yargs = require('yargs') .describe('b', 'Keep a backup file of the original content.') .alias('b', 'keep-backup') + .boolean('S') + .describe( + 'S', + 'Do everything but write the files. The names of the files that would be changed are printed as usual, and no backup is made.' + ) + .alias('S', 'simulate') + .boolean('m') .describe( 'm', @@ -384,7 +391,7 @@ All variables, except from module, date objects, \`nl\` and \`_\`, has a corresp Upper Case Free Lower Case Free a -C c +C D F g @@ -403,10 +410,10 @@ Z z */ - .boolean('S') - .alias('S', 'status-compact') + .boolean('c') + .alias('c', 'status-compact') .describe( - 'S', + 'c', 'As the last thing before ending any execution a status report will be printed as one line of JSON. Will print even in combination with `--quiet-total`. Will incude extra info about the inner workings of the engine in combination with the `--verbose` flag.' ) diff --git a/src/engine.ts b/src/engine.ts index 7623fc22..fe63131c 100644 --- a/src/engine.ts +++ b/src/engine.ts @@ -212,6 +212,11 @@ function doReplacement(_file_rr: string, _config_rr: any, _data_rr: string) { return; } + // --simulate does everything but write, so the file is named as if it was changed + if (_config_rr.simulate) { + return updated(_file_rr, _config_rr); + } + // Release the memory while storing files _data_rr = ''; diff --git a/test/cli/run.sh b/test/cli/run.sh index 346823f6..5b2e01f2 100644 --- a/test/cli/run.sh +++ b/test/cli/run.sh @@ -451,6 +451,13 @@ assert "rexreplace 'b' 'x' my.file --content-delimiter '\x4' 2>&1 | grep -c assert "rexreplace 'b' 'x' my.file --delimiter '\x4' 2>&1 | grep -c 'Invalid escape sequence in --delimiter'" '1' assert "cat my.file" 'foobar' +# --simulate: the files that would be changed are named, but nothing is written (#710) +reset +assert "rexreplace 'oo' 'x' my.file your.file --simulate" 'my.file' +assert "cat my.file" 'foobar' +assert "rexreplace 'b' 'x' my.file -S" 'my.file' +assert "cat my.file" 'foobar' + # --full-path reset assert "rexreplace 'b' 'x' my.file -f" "$(pwd -P)/my.file" diff --git a/test/js/fix/710.test.js b/test/js/fix/710.test.js new file mode 100644 index 00000000..1e6a4fa0 --- /dev/null +++ b/test/js/fix/710.test.js @@ -0,0 +1,55 @@ +// https://github.com/mathiasrw/rexreplace/issues/710 +// There was no way to see which files a command would change without changing them. +import {describe, test, expect} from 'bun:test'; +import fs from 'fs'; +import {rr, rrStatus} from '../helper.js'; + +// Note that rr() from the helper sets voidAsync per default, so the async flow is `voidAsync: false` +const flows = [ + ['the async flow', {voidAsync: false}], + ['--void-async', {voidAsync: true}], +]; + +// Make a.file and c.file with `foo`, and b.file with `bar` (nothing to replace) +function makeFiles() { + fs.writeFileSync('a.file', 'foo'); + fs.writeFileSync('b.file', 'bar'); + fs.writeFileSync('c.file', 'foo'); + return {includeGlob: ['*.file'], simulate: true}; +} + +const contents = () => ['a.file', 'b.file', 'c.file'].map(name => fs.readFileSync(name, 'utf8')); + +describe('#710 --simulate', () => { + test.each(flows)( + 'names the files that would be changed, and changes nothing, with %s', + async (name, flags) => { + const result = await rr('o', 'x', {...makeFiles(), ...flags}); + expect(result.out.split('\n').sort()).toEqual(['', 'a.file', 'c.file']); + expect(result.err).toBe(''); + expect(result.exit).toBe(null); + expect(contents()).toEqual(['foo', 'bar', 'foo']); + expect(fs.readdirSync('.').sort()).toEqual(['a.file', 'b.file', 'c.file']); + } + ); + + test('makes no backup, also with --keep-backup', async () => { + await rr('o', 'x', {...makeFiles(), keepBackup: true}); + expect(fs.readdirSync('.').sort()).toEqual(['a.file', 'b.file', 'c.file']); + }); + + test('has the files that would be changed in the status report', async () => { + const status = await rrStatus('o', 'x', makeFiles()); + expect(status.files).toEqual({ + searched: ['a.file', 'b.file', 'c.file'], + updated: ['a.file', 'c.file'], + }); + }); + + test('with --name-all names all files, but only the changed ones are updated', async () => { + const config = {...makeFiles(), nameAll: true}; + expect((await rr('o', 'x', config)).out).toBe('a.file\nb.file\nc.file\n'); + expect((await rrStatus('o', 'x', config)).files.updated).toEqual(['a.file', 'c.file']); + expect(contents()).toEqual(['foo', 'bar', 'foo']); + }); +}); diff --git a/test/js/fix/742.test.js b/test/js/fix/742.test.js index 9e37236c..82bff536 100644 --- a/test/js/fix/742.test.js +++ b/test/js/fix/742.test.js @@ -210,7 +210,7 @@ describe('#742 the CLI', () => { }); }); - test.each(['--status-compact', '-S'])( + test.each(['--status-compact', '-c'])( '%s prints the status report on one line, with no line break in front', flag => { fs.writeFileSync('a.file', 'foo');