diff --git a/TODO.txt b/TODO.txt
index ab88343..9a4418c 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -1,6 +1,5 @@
0.4
- Run integration tests using multiple shells.
-- `rew x --null` example with propagation.
- Better shell specific examples for `rew x`.
- Add `rew x --quote` with examples.
- Fix `--examples` order in generated docs.
@@ -14,4 +13,5 @@
- Add here more TODOs for command replacement of old rew filters.
1.0
+- Add `case` conversion command based on https://docs.rs/heck/latest/heck/
- Setup man generator.
diff --git a/docs/reference/rew-x.md b/docs/reference/rew-x.md
index 052b809..4b9025c 100644
--- a/docs/reference/rew-x.md
+++ b/docs/reference/rew-x.md
@@ -409,3 +409,24 @@ rew x '{seq}:\n\t{}'
+
+All global options `-0, --null`, `--buf-size` and `--buf-mode` are propagated to rew subcommands. Do not forget configure NUL separator manually for any external commands.
+
+```sh
+rew x --null '{upper | sed --null-data "s/^.//g"}'
+```
+
+
diff --git a/src/commands/x.rs b/src/commands/x.rs
index 8925a71..c36c4a5 100644
--- a/src/commands/x.rs
+++ b/src/commands/x.rs
@@ -127,6 +127,12 @@ pub const META: Meta = command_meta! {
input: &["first", "second", "third"],
output: &["1:", "\tfirst", "2:", "\tsecond", "3:", "\tthird"],
},
+ "All global options `-0, --null`, `--buf-size` and `--buf-mode` are propagated to rew subcommands. \
+ Do not forget configure NUL separator manually for any external commands.": {
+ args: &["--null", "{upper | sed --null-data 's/^.//g'}"],
+ input: &["aa", "bb", "cc"],
+ output: &["A", "B", "C"],
+ },
],
};
diff --git a/src/examples.rs b/src/examples.rs
index 8000740..7e880a3 100644
--- a/src/examples.rs
+++ b/src/examples.rs
@@ -29,6 +29,12 @@ pub struct Example {
pub output: &'static [&'static str],
}
+impl Example {
+ pub fn has_null_arg(&self) -> bool {
+ self.args.iter().any(|arg| *arg == "-0" || *arg == "--null")
+ }
+}
+
#[macro_export]
macro_rules! examples {
($($text:literal: { args: $args:expr, input: $input:expr, output: $output:expr, }),*,) => {
@@ -102,9 +108,10 @@ fn write(writer: &mut impl Write, command: &str, examples: &[Example]) -> Result
write_text(writer, example.text, term_width)?;
writeln!(writer)?;
write_command(writer, command, example.args)?;
- write_io(writer, example.input, example.output)?;
+ write_io(writer, example)?;
}
+ writeln!(writer)?;
Ok(())
}
@@ -163,13 +170,14 @@ fn write_command(writer: &mut impl Write, subcmd: &str, args: &[&str]) -> io::Re
writeln!(writer, " {GREEN}╰{}╯{RESET}", "─".repeat(width))
}
-fn write_io(writer: &mut impl Write, input: &[&str], output: &[&str]) -> io::Result<()> {
- if input.is_empty() && output.is_empty() {
+fn write_io(writer: &mut impl Write, example: &Example) -> io::Result<()> {
+ if example.input.is_empty() && example.output.is_empty() {
return Ok(());
}
- let input = normalize_lines(input);
- let output = normalize_lines(output);
+ let null_separator = example.has_null_arg();
+ let input = normalize_lines(example.input, null_separator);
+ let output = normalize_lines(example.output, null_separator);
let input_label = "stdin:";
let output_label = "stdout:";
@@ -211,11 +219,17 @@ fn write_io_line(writer: &mut impl Write, lines: &[String], index: usize) -> io:
write!(writer, " ")?;
if let Some(line) = lines.get(index) {
- write!(writer, "{GREEN}\"{RESET}{line}{GREEN}\"{RESET}{RESET}",)?;
- writer.write_all(" ".repeat(max_line_width(lines) - line.width()).as_bytes())
- } else {
- write!(writer, "{}", " ".repeat(max_line_width(lines) + 2))
+ let line = line.replace("\\0", &format!("{YELLOW}\\0{RESET}"));
+ write!(writer, "{GREEN}\"{RESET}{line}{GREEN}\"{RESET}{RESET}")?;
}
+
+ let padding = if let Some(line) = lines.get(index) {
+ max_line_width(lines) - line.width()
+ } else {
+ max_line_width(lines) + 2
+ };
+
+ write!(writer, "{}", " ".repeat(padding))
}
fn normalize_args(args: &[&str]) -> Vec {
@@ -232,11 +246,17 @@ fn normalize_args(args: &[&str]) -> Vec {
.collect::>()
}
-fn normalize_lines(lines: &[&str]) -> Vec {
- lines
+fn normalize_lines(lines: &[&str], null_separator: bool) -> Vec {
+ let lines = lines
.iter()
.map(|line| line.replace('\t', " "))
- .collect::>()
+ .collect::>();
+
+ if null_separator {
+ vec![format!("{}\\0", lines.join("\\0"))]
+ } else {
+ lines
+ }
}
fn max_line_width(lines: &[String]) -> usize {
diff --git a/tests/examples.rs b/tests/examples.rs
index cc3afa3..7692fc6 100644
--- a/tests/examples.rs
+++ b/tests/examples.rs
@@ -6,6 +6,12 @@ use std::env;
fn examples() {
for meta in rew::commands::METAS {
for example in meta.examples {
+ // Such examples require coreutils with NUL separator support
+ // which are not available on MacOS by default.
+ if cfg!(target_os = "macos") && example.has_null_arg() {
+ continue;
+ }
+
println!("[{}] {}", meta.name, first_line(example.text));
Command::cargo_bin(crate_name!())
@@ -13,10 +19,10 @@ fn examples() {
.env("SHELL", "sh") // Examples expect UNIX shell
.arg(meta.name)
.args(example.args)
- .write_stdin(join_lines(example.input))
+ .write_stdin(join_lines(example.input, example.has_null_arg()))
.assert()
.success()
- .stdout(join_lines(example.output))
+ .stdout(join_lines(example.output, example.has_null_arg()))
.stderr("");
}
}
@@ -26,6 +32,7 @@ fn first_line(text: &str) -> &str {
text.split('\n').next().unwrap_or_default()
}
-fn join_lines(lines: &[&str]) -> String {
- format!("{}\n", lines.join("\n"))
+fn join_lines(lines: &[&str], null_separator: bool) -> String {
+ let separator = if null_separator { "\0" } else { "\n" };
+ format!("{}{separator}", lines.join(separator))
}
diff --git a/xtask/src/docs.rs b/xtask/src/docs.rs
index a1ba708..f0437fc 100644
--- a/xtask/src/docs.rs
+++ b/xtask/src/docs.rs
@@ -290,11 +290,11 @@ fn write_example(writer: &mut impl Write, command: &Adapter<'_>, example: &Examp
writeln!(writer, "")?;
if !example.input.is_empty() {
- write_example_io(writer, "stdin", example.input)?;
+ write_example_io(writer, "stdin", example.input, example.has_null_arg())?;
}
if !example.output.is_empty() {
- write_example_io(writer, "stdout", example.output)?;
+ write_example_io(writer, "stdout", example.output, example.has_null_arg())?;
}
writeln!(writer, "
")?;
@@ -303,21 +303,34 @@ fn write_example(writer: &mut impl Write, command: &Adapter<'_>, example: &Examp
Ok(())
}
-fn write_example_io(writer: &mut impl Write, title: &str, lines: &[&str]) -> Result<()> {
+fn write_example_io(
+ writer: &mut impl Write,
+ title: &str,
+ lines: &[&str],
+ null_separator: bool,
+) -> Result<()> {
writeln!(writer, "")?;
writeln!(writer, "
{title}:")?;
writeln!(writer, "
")?;
- for line in lines {
- let line = line
- .replace(' ', "\u{a0}") // Unbreakable spaces to prevent trimming by mdbook html renderer.
- .replace('\t', "\u{a0}\u{a0}\u{a0}\u{a0}");
-
- // because mdbook html renderer cannot render empty `` (which we need for empty IO lines).
+ // We use because mdbook cannot render empty `` which we need for empty IO lines.
+ if null_separator {
+ let line = format!("{}\\0", lines.join("\\0"));
+ let line = normalize_line(&line);
writeln!(writer, "{line}
")?;
+ } else {
+ for line in lines {
+ let line = normalize_line(line);
+ writeln!(writer, "{line}
")?;
+ }
}
writeln!(writer, "
")?;
writeln!(writer, "
")?;
Ok(())
}
+
+fn normalize_line(line: &str) -> String {
+ line.replace(' ', "\u{a0}") // Unbreakable spaces to prevent trimming by mdbook html renderer.
+ .replace('\t', "\u{a0}\u{a0}\u{a0}\u{a0}")
+}