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
14 changes: 4 additions & 10 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"test-ci": "yarn test-format && yarn build && yarn test-cli && yarn test-js && yarn test-minify",
"test-speed": "bash test/speed/run.sh",
"rebase": "yarn is-git-clean && git fetch && git rebase origin/main",
"load-options": "# rr -h | rr 'Options:(.+)Examples:' _ -ms | rr '\\n {26,}|\\n\\n *' ' ' | rr \"'\" '`' | rr '^ (-.+?), (--[^ ]+) *' '`€1` | **`€2`** ' | rr '(^---- . ----).+?(## Good to know)' '€1 + nl + pipe + nl + nl + €2' readme.md -jsT",
"load-options": "# rr -h | rr 'Options:(.+)Examples:' _ -ms | rr '\\n {26,}|\\n\\n *' ' ' | rr \"'\" '`' | rr '^ (-.+?), (--[^ ]+) *' '`€1` | **`€2`** ' | rr '(^---- . ----).+?(## Good to know)' '€1 + nl + pipe + nl + nl + €2' readme.md -jspT",
"test-format": "prettier . --list-different || (echo 'Please correct file formatting using `yarn format` and try again.' && exit 1)",
"format": "prettier . --write",
"is-git-clean": "(git diff --quiet --exit-code --cached && git diff --quiet --exit-code) || (echo Please commit or stash changes && exit 1)",
Expand Down
75 changes: 36 additions & 39 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// CLI interface for rexreplace

import * as rexreplace from './engine.ts';
import {outputConfig, die, warn} from './output.ts';
import {outputConfig, die} from './output.ts';

// Make it visible when Bun is the runtime (like with rexreplace-bun) as the regex engine is then not the one from Node.
// Added as semver build metadata (`+bun`) as a `-bun` would make it a prerelease.
Expand Down Expand Up @@ -189,15 +189,12 @@ const yargs = require('yargs')
''
)

.boolean('P')
.alias('P', 'void-pipe')
.describe(
'P',
`Ignore any pipe data - even if stdin is open. Can not be combined with -R or -T. ` +
`Can also be set via the environment variable RR_VOID_PIPE.`
)
.conflicts('P', 'R')
.conflicts('P', 'T')
.boolean('p')
.alias('p', 'pipe')
.describe('p', `Read the piped data and make it available to --replacement-js as \`pipe\`.`)

.boolean('js-pipe')
.describe('js-pipe', `Shorthand for --replacement-js --pipe (-jp).`)

.boolean('j')
.alias('j', 'js')
Expand All @@ -216,7 +213,7 @@ The code has access to the following variables:
\`fs\` from node,
\`path\` from node,
\`globs\` from npm,
\`pipe\`: the data piped into the command (null if no piped data),
\`pipe\`: the data piped into the command with --pipe (null if no piped data, and \`pipe_\` is \`❌ \`),
\`find\`: pattern searched for (the needle),
\`text\`: full text being searched i.e. file content or piped data (the haystack),
\`bytes\`: total size of the haystack in bytes,
Expand Down Expand Up @@ -335,7 +332,7 @@ K k
l
N n
O
P p
P
r
S
t
Expand Down Expand Up @@ -394,6 +391,10 @@ function unescapeString(str = '') {
}
});

if (config.jsPipe) {
config.replacementJs = config.pipe = true;
}

outputConfig(config);

let pipeInUse = false;
Expand Down Expand Up @@ -428,38 +429,34 @@ function unescapeString(str = '') {
config.output = true;
}*/

if (process.env.RR_VOID_PIPE) {
const conflict = ' conflicts with the env. var RR_VOID_PIPE as no data will be piped.';
config.replacementPipe && warn('The flag --replacement-pipe' + conflict);
config.trimPipe && warn('The flag --trim-pipe' + conflict);
config.voidPipe = true;
// Like grep, stdin is only read when piped data is expected. An open pipe that nobody writes to would make the run wait forever.
const pipeDataExpected = !config.includeGlob.length || config.replacementPipe || config.pipe;

if (!pipeDataExpected || Boolean(process.stdin.isTTY)) {
return rexreplace.engine(config);
}

if (config.voidPipe || Boolean(process.stdin.isTTY)) {
rexreplace.engine(config);
} else {
process.stdin.setEncoding(config.encoding);
process.stdin.setEncoding(config.encoding);

process.stdin.on('readable', () => {
let chunk = process.stdin.read();
process.stdin.on('readable', () => {
let chunk = process.stdin.read();

if (null !== chunk) {
pipeInUse = true;
if (null !== chunk) {
pipeInUse = true;
pipeData += chunk;
while ((chunk = process.stdin.read())) {
pipeData += chunk;
while ((chunk = process.stdin.read())) {
pipeData += chunk;
}
}
});

process.stdin.on('end', () => {
if (pipeInUse) {
if (yargs.argv.trimPipe) {
pipeData = pipeData.trim();
}
config.pipedData = pipeData;
}
});

process.stdin.on('end', () => {
if (pipeInUse) {
if (yargs.argv.trimPipe) {
pipeData = pipeData.trim();
}
rexreplace.engine(config);
});
}
config.pipedData = pipeData;
}
rexreplace.engine(config);
});
})();
5 changes: 3 additions & 2 deletions src/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,8 @@ function dynamicReplacement(_file_rr, _config_rr, _data_rr) {
globs,
path,
_pipe,
_pipe + _,
// Without piped data `pipe` is null, while `pipe_` is `❌ ` like the file variables
(_pipe ?? '❌') + _,
_find,
_find + _,
_text,
Expand Down Expand Up @@ -567,7 +568,7 @@ function dynamicReplacement(_file_rr, _config_rr, _data_rr) {
path,

__pipe,
__pipe + __,
(__pipe ?? '❌') + __,

__find,
__find + __,
Expand Down
44 changes: 13 additions & 31 deletions test/cli/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -285,44 +285,26 @@ reset
assert "printf x | rexreplace 'b' _ my.file -o --replacement-pipe" 'fooxar'


# -P
# --pipe
reset
assert "printf x | rexreplace 'foo' 'pipe' my.file -o --replacement-js" 'xbar'
assert "printf x | rexreplace 'foo' 'pipe' my.file -o --replacement-js -P" 'nullbar'
assert "printf x | rexreplace 'foo' 'pipe' my.file -o --replacement-js --void-pipe" 'nullbar'
assert_failure "printf x | rexreplace 'b' _ my.file -P -R"
assert_failure "printf x | rexreplace 'b' _ my.file -P -T"
assert "printf x | rexreplace 'foo' 'pipe' my.file -o --replacement-js" 'nullbar'
assert "printf x | rexreplace 'foo' 'pipe' my.file -o --replacement-js --pipe" 'xbar'
assert "printf x | rexreplace 'foo' 'pipe' my.file -o -jp" 'xbar'
assert "printf x | rexreplace 'foo' 'pipe' my.file -o --js-pipe" 'xbar'
assert "printf ' x ' | rexreplace 'foo' 'pipe' my.file -o --js-pipe -T" 'xbar'


# Warning when piped data is not used
reset
assert "printf x | rexreplace 'o' 'x' my.file 2>&1 | grep 'Data is piped in'" ' 🟡 Data is piped in, but no flag tells what to do with it. It is not used. '
assert "printf x | rexreplace 'o' 'x' my.file --pipe 2>&1 | grep 'Data is piped in'" ' 🟡 Data is piped in, but no flag tells what to do with it. It is not used. '
assert "cat my.file" 'fxxbar'
assert_failure "printf x | rexreplace 'o' 'x' my.file -q 2>&1 | grep 'Data is piped in'"
assert_failure "printf x | rexreplace 'o' 'x' my.file -P 2>&1 | grep 'Data is piped in'"
assert_failure "printf x | rexreplace 'o' 'x' my.file 2>&1 | grep 'Data is piped in'"
assert_failure "printf x | rexreplace 'o' 'x' my.file --pipe -q 2>&1 | grep 'Data is piped in'"
assert_failure "printf x | rexreplace 'b' _ my.file -R 2>&1 | grep 'Data is piped in'"
assert_failure "printf x | rexreplace 'b' 'pipe' my.file -j 2>&1 | grep 'Data is piped in'"
assert_failure "printf '' | rexreplace 'o' 'x' my.file 2>&1 | grep 'Data is piped in'"
assert_failure "printf '\n' | rexreplace 'o' 'x' my.file 2>&1 | grep 'Data is piped in'"
assert_failure "printf '\r\n' | rexreplace 'o' 'x' my.file 2>&1 | grep 'Data is piped in'"


# RR_VOID_PIPE
reset
assert "printf x | RR_VOID_PIPE=1 rexreplace 'foo' 'pipe' my.file -o --replacement-js 2>&1" 'nullbar'
assert "printf x | RR_VOID_PIPE= rexreplace 'foo' 'pipe' my.file -o --replacement-js" 'xbar'
assert "printf x | RR_VOID_PIPE=1 rexreplace 'foo' 'pipe' my.file -o --replacement-js -T" 'nullbar'
assert "printf x | RR_VOID_PIPE=1 rexreplace 'foo' 'pipe' my.file -o --replacement-js -T 2>&1 >/dev/null" ' 🟡 The flag --trim-pipe conflicts with the env. var RR_VOID_PIPE as no data will be piped. '
assert "printf x | RR_VOID_PIPE=1 rexreplace 'foo' 'pipe' my.file -o --replacement-js -T -q 2>&1" 'nullbar'
assert "printf x | RR_VOID_PIPE=1 rexreplace 'foo' 'pipe' my.file -o --replacement-js -T -Q 2>&1" 'nullbar'
assert_failure "printf x | RR_VOID_PIPE=1 rexreplace 'b' _ my.file -R"
assert "printf x | RR_VOID_PIPE=1 rexreplace 'b' _ my.file -R 2>&1 | grep -E 'RR_VOID_PIPE as|No data piped'" ' 🟡 The flag --replacement-pipe conflicts with the env. var RR_VOID_PIPE as no data will be piped. \n ❌ No data piped into replacement '
assert "printf x | RR_VOID_PIPE=1 rexreplace 'b' _ my.file -R -T 2>&1 | grep -E 'RR_VOID_PIPE as|No data piped'" ' 🟡 The flag --replacement-pipe conflicts with the env. var RR_VOID_PIPE as no data will be piped. \n 🟡 The flag --trim-pipe conflicts with the env. var RR_VOID_PIPE as no data will be piped. \n ❌ No data piped into replacement '
assert_failure "printf x | RR_VOID_PIPE=1 rexreplace 'b' _ my.file -R -q"
assert "printf x | RR_VOID_PIPE=1 rexreplace 'b' _ my.file -R -q 2>&1 | grep -v 'exited with code'" ' ❌ No data piped into replacement '
assert_failure "printf x | RR_VOID_PIPE=1 rexreplace 'b' _ my.file -R -Q"
assert "printf x | RR_VOID_PIPE=1 rexreplace 'b' _ my.file -R -Q 2>&1 | grep -v 'exited with code'" ''
assert "cat my.file" 'foobar'
assert_failure "printf x | rexreplace 'b' 'pipe' my.file --js-pipe 2>&1 | grep 'Data is piped in'"
assert_failure "printf '' | rexreplace 'o' 'x' my.file --pipe 2>&1 | grep 'Data is piped in'"
assert_failure "printf '\n' | rexreplace 'o' 'x' my.file --pipe 2>&1 | grep 'Data is piped in'"
assert_failure "printf '\r\n' | rexreplace 'o' 'x' my.file --pipe 2>&1 | grep 'Data is piped in'"



Expand Down
24 changes: 24 additions & 0 deletions test/js/fix/736.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// https://github.com/mathiasrw/rexreplace/issues/736
// stdin was read every time it was not a terminal, so a pipe that was open but never written to made the run wait forever.
// The flags that ask for the piped data are tested in test/cli/run.sh.
import {describe, test, expect} from 'bun:test';
import path from 'path';

const cli = path.resolve(import.meta.dir, '../../../src/cli.ts');

describe('#736 stdin is only read when the data is asked for', () => {
test('files are changed without waiting for a pipe that is never closed', async () => {
await Bun.write('my.file', 'foobar');
const proc = Bun.spawn([process.execPath, cli, 'o', 'x', 'my.file'], {
cwd: process.cwd(),
stdin: 'pipe',
stderr: 'pipe',
});

const exit = await Promise.race([proc.exited, Bun.sleep(2000).then(() => 'still waiting')]);
proc.kill();

expect(exit).toBe(0);
expect(await Bun.file('my.file').text()).toBe('fxxbar');
});
});
10 changes: 8 additions & 2 deletions test/js/replacement-js.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,17 @@ describe('--replacement-js', () => {

test('pipe is the piped data', async () => {
expect(await js('o', 'pipe', 'fo')).toBe('ffo');
expect(await js('o', 'pipe_', 'fo')).toBe('ffo ');
fs.writeFileSync('my.file', 'foobar');
const config = {includeGlob: ['my.file'], replacementJs: true, output: true};
expect((await rr('foo', 'pipe', {...config, pipedData: 'boo'})).out).toBe('boobar');
expect((await rr('foo', 'String(pipe)', config)).out).toBe('nullbar');
});

test('pipe is null and pipe_ is ❌ without piped data', async () => {
fs.writeFileSync('my.file', 'foobar');
const config = {includeGlob: ['my.file'], replacementJs: true, output: true};
expect((await rr('foo', 'String(pipe) + pipe_', config)).out).toBe('null❌ bar');
expect((await rr('(o)', 'pipe_ + €1', config)).out).toBe('f❌ o❌ obar');
});

test('file, file_rel, dirpath, dirpath_rel, dirname, filename, name and ext tell about the file', async () => {
Expand Down Expand Up @@ -218,7 +225,6 @@ describe('--replacement-js', () => {

test('most variables come in a version with a space after', async () => {
const names = [
'pipe',
'find',
'text',
'file',
Expand Down
5 changes: 0 additions & 5 deletions test/js/void-pipe.test.js

This file was deleted.

Loading