Repository navigation
Print the names of affected files #703
Description
This is a breaking change.
Today
When RexReplace changes files in place, it prints the name of each changed file to stderr:
> rexreplace foo bar '*.md'
a.md
b.mdThe names are the result of the run, but they are treated as a side note. You can only pipe them with 2>&1, and that also includes the errors.
With --output the content of all files is printed right after each other, like cat. A file with no line break at the end is glued onto the next one, and a program can not tell where one file ends.
Change
Print the names of the changed files to stdout, one name on each line. This is how grep -l, gofmt -l and prettier --write do it.
> rexreplace foo bar '**/*.md' | xargs git add
> rexreplace foo bar '**/*.md' | wc -l- stderr is then only for errors,
--verboseand--debug. --quietand--quiet-totalstop the names from being printed.- With
--outputor--output-matchstdout has the result, so the names are printed to stderr. They are the same names as without the flag. - Piped data is never named, as there is no file.
Delimiters
One set of delimiter flags for both the names and the content. Content is the content of each file with --output and each match with --output-match.
| Flag | Default | What it does |
|---|---|---|
--name-delimiter |
a line break | Put after the name of each file. The default is a line break, unless the name already ends with one |
--name-delimiter-tail |
on | Also put the name delimiter after the last name. Turn it off with --no-name-delimiter-tail or --name-delimiter-tail=false |
--content-delimiter |
a line break | Put after the content of each file with --output, or after each match with --output-match. The default is a line break, unless the content already ends with one |
--content-delimiter-tail |
on, off for piped data | Also put the content delimiter after the last content |
--delimiter |
Sets both --name-delimiter and --content-delimiter |
|
--null |
The same as --delimiter '\0' |
|
--tail |
Sets both --name-delimiter-tail and --content-delimiter-tail. --no-tail turns both off |
|
--nl (-l) |
off | What is printed to stdout and stderr ends with a line break. If it already does, nothing is added |
--name-all |
off | Name every file that is checked for a match, also the ones where nothing is changed |
> rexreplace foo bar '*.md' --name-delimiter ', ' --no-name-delimiter-tail
a.md, b.md
> rexreplace foo bar '**/*.md' --null | xargs -0 git add
> rexreplace foo bar '*.md' --output --content-delimiter='---\n' 2>/dev/null
bar
---
bar
---
nothing
---
> rexreplace o _ my.file -m --content-delimiter ', ' --no-tail --nl
o, o
> printf foo | rexreplace o x --nl
fxxRules:
- The most specific flag wins: the specific flag, then
--delimiter, then--null, then the default. No flags conflict.--delimiter ', ' --nullgives,for both.--null --name-delimiter ', 'gives,after the names and NUL between the content.
- Escapes: the delimiters are treated like the replacement. Escape sequences like
\tare understood, and the€and§aliases are used unless--void-euroor--void-sectionis set. - The default line break: without a given delimiter, each name and each content ends with a line break, unless it already ends with one. So
--outputon files that end with a line break gives the same ascat, and a file without one is never glued onto the next. A given delimiter is used as it is:--content-delimiter '\n'always puts a line break. - Tail: the delimiter also comes after the last item, so a
while readloop orxargs -0gets the last name too. The specific tail flags win over--tail. - Content is content:
--outputand--output-matchfollow the same rule. Use--content-delimiter ''to join the content. - Piped data is left as it is: its content tail is off by default.
--nl, or a delimiter and--content-delimiter-tail, can add something after it. --nldeals with stdout and stderr. That is the names, the content from--output,--output-matchor piped data, and the messages. The messages always end with a line break, so only names or content can need one. It does not change the tail: the tail is printed if it is asked for, and after that a line break is added if stdout does not end with one. Output that ends with more line breaks is kept as it is. If nothing is printed, nothing is added, so empty output stays empty.- Short flags: only
-lfor--nl.-0is left out for now; only--nullis added.
The path of a name
-
By default a name is printed in the way the path was given, like today: what we get is what we print. A relative path is printed as a relative path, and a full path as a full path.
-
--absolute-path(-a): Always print the full path from/.> rexreplace foo bar 'docs/*.md' docs/a.md docs/b.md > rexreplace foo bar 'docs/*.md' --absolute-path /Users/me/project/docs/a.md /Users/me/project/docs/b.md > rexreplace foo bar '/tmp/notes/*.md' /tmp/notes/a.md
-
-ais not in use.fdhas the same flag for the same thing. -
grep -l,rg -landfindalso print the path in the way it was given. -
The two forms are already in the code of
--replacement-jsasfile_relandfile. -
A file given with a full path was not changed. That was a bug of its own: A file given with an absolute path is not changed #726.
What breaks
--outputadds a line break after a file that does not end with one. Use--no-content-delimiter-tailand--content-delimiter ''to get the content exactly as it is.- Scripts that expect stdout to be empty when files are changed in place.
- Scripts that read the names of the changed files from stderr.
- Scripts that merge the two streams with
--output ... 2>&1now also get the names.
Order of the names
Files are handled asynchronously by default, so the names come in the order the files are done. This is accepted. --void-async gives the order of the file list.
Good to know
- A value that starts with
-must be given with=, like--content-delimiter='---\n'. yargs reads--content-delimiter '---\n'as an unknown flag. This is the same for all string flags. - We will move from yargs to argmate.
--no-name-delimiter-tailand--name-delimiter-tail=falsework in both.--name-delimiter-tail false(with a space) only works in yargs. - argmate reads
-A0as-Awith the value0. This is one reason to leave out-0. - For
--nl,write()keeps the char code of the last character written to each stream (text.charCodeAt(text.length - 1)), and it is compared with 10 (a line break). Everything is printed withwrite(), also the messages, so how each stream ends is always known. It is checked once at the end of the run. Reading the last character costs nothing next to writing the text: a benchmark in Bun and Node gave no difference betweenslice(-1),text[text.length - 1],charCodeAtandendsWith, for 3 bytes up to 10 MB. It is the last character written, not the last content: with-oon the filesa\nand an empty file, the last content is empty, but the outputa\n\nalready ends with a line break. - The JS tests capture stdout and stderr in
rr()intest/js/helper.js. - With Bun as the runtime everything on stderr is red (Bun does this for
console.error). The names will no longer be red when they are on stdout.
Reference
This was made before, on a branch that is not merged. Look at it to see how it was done. It can not be copied as it is, as it is written for older code.
src/output.tslines 20-28 onv8.0:info()prints to stdout, and to stderr when the result is printed with--outputor--output-match.test/cli/run.shlines 130-149 onv8/autofix: CLI tests that check stdout and stderr apart.- Use the
€and§aliases for all parameters #704 the€and§aliases for all parameters
Activity
[-]Print the names of changed files to stdout[/-][+]Print the names of affected files[/+]