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"}' +``` + +
+
+stdin: + +
+
+stdout: + +
+
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}") +}