Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md

Large diffs are not rendered by default.

12 changes: 9 additions & 3 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ const yargs = require('yargs')
.describe('L', 'Literal string search (no regex used when searching)')
.alias('L', 'literal')

.boolean('l')
.describe(
'l',
'Use the replacement as it is. Without it, `$1`, `$&` and the other `$` signs in the replacement mean the groups and the match, as in javascript.'
)
.alias('l', 'literal-replacement')

.boolean('I')
.describe('I', 'Void case insensitive search. Applies to the pattern and to all globs.')
.alias('I', 'void-ignore-case')
Expand Down Expand Up @@ -186,12 +193,11 @@ const yargs = require('yargs')
'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')
.boolean('nl')
.describe(
'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.'
)
.alias('l', 'nl')

.boolean('H')
.describe('H', 'Halt on first error')
Expand Down
11 changes: 11 additions & 0 deletions src/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,17 @@ async function run(config) {
};
}

// --literal-replacement: the value of a function is used as it is, so `$1` and `$&` are just text.
// The code of --js is a string here too, but its value is already used as it is.
if (
config.literalReplacement &&
!config.replacementJs &&
'string' === typeof config.replacement
) {
const text = config.replacement;
config.replacement = () => text;
}

const piped = handlePipedData(config);

if (null !== config.pipedData) {
Expand Down
7 changes: 2 additions & 5 deletions test/cli/nl.test.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
// --nl (-l) ends stdout and stderr with a line break, unless they already end with one
// --nl ends stdout and stderr with a line break, unless they already end with one
import {describe, test, expect} from 'bun:test';
import fs from 'fs';
import {$} from 'bun';
import {cli, sh} from '../helper.js';

describe('--nl', () => {
test('adds a line break after the last match, also as -l', async () => {
test('adds a line break after the last match', async () => {
fs.writeFileSync('my.file', 'foobar\n');
fs.writeFileSync('your.file', 'abc123\n');
expect(
(await sh($`bun ${cli} '[bc]' _ my.file your.file -m --delimiter ',' --no-tail --nl`)).out
).toBe('b,b,c\n');
expect(
(await sh($`bun ${cli} '[bc]' _ my.file your.file -m --delimiter ',' --no-tail -l`)).out
).toBe('b,b,c\n');
});

test('comes after the tail', async () => {
Expand Down
4 changes: 4 additions & 0 deletions test/cli/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ reset
assert "rexreplace '*+*' 'b' my.file -o --literal" 'foobar'


# --literal-replacement (-l) uses the replacement as it is, so `$1` is just text
reset
assert "rexreplace '(foo)' '€1' my.file -o --literal-replacement" '$1bar'

# --replacement-js: the code gives the replacement
reset
assert "rexreplace 'foo' '2+2' my.file -o --replacement-js" '4bar'
Expand Down
41 changes: 41 additions & 0 deletions test/engine/literal-replacement.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {describe, test, expect} from 'bun:test';
import fs from 'fs';
import {rr} from '../helper.js';

describe('--literal-replacement', () => {
test('uses the replacement as it is, with no meaning for $', async () => {
const config = {pipedData: 'PRICE', literalReplacement: true};
expect((await rr('(PRICE)', 'only $1.99', config)).out).toBe('only $1.99');
expect((await rr('(PRICE)', "$& $$ $` $'", config)).out).toBe("$& $$ $` $'");
});

test('without it $1 is the group', async () => {
expect((await rr('(PRICE)', 'only $1.99', {pipedData: 'PRICE'})).out).toBe('only PRICE.99');
});

test('works the same for a replacement from --replacement-pipe', async () => {
fs.writeFileSync('my.file', 'PRICE');
const config = {
includeGlob: ['my.file'],
pipedData: '$1.99',
replacementPipe: true,
literalReplacement: true,
};
await rr('(PRICE)', '_', config);
expect(fs.readFileSync('my.file', 'utf8')).toBe('$1.99');
});

test('changes nothing for --js, as the value of the code is already used as it is', async () => {
const config = {pipedData: 'foo', replacementJs: true};
expect((await rr('(o)', '"[$1]" + $1', config)).out).toBe('f[$1]o[$1]o');
expect((await rr('(o)', '"[$1]" + $1', {...config, literalReplacement: true})).out).toBe(
'f[$1]o[$1]o'
);
});

test('works with --rename', async () => {
fs.writeFileSync('a.md', 'foo');
const config = {includeGlob: ['a.md'], rename: true, literalReplacement: true};
expect((await rr('^(a)', '$1-$$', config)).out).toBe('$1-$$.md\n');
});
});
Loading