diff --git a/src/bin/uudoc.rs b/src/bin/uudoc.rs index 5a713e040..064637be1 100644 --- a/src/bin/uudoc.rs +++ b/src/bin/uudoc.rs @@ -84,6 +84,8 @@ fn gen_manpage( } else { validation::setup_localization_or_exit(utility); let mut cmd = util_map.get(utility).unwrap().1(); + cmd.set_bin_name(utility.clone()); + let mut cmd = cmd.display_name(utility); if let Some(zip) = tldr { if let Ok(examples) = write_zip_examples(zip, utility, false) { cmd = cmd.after_help(examples); @@ -292,13 +294,21 @@ fn main() -> io::Result<()> { } println!("Writing to utils"); + let hashsum_cmd = utils.iter().find(|n| *n.0 == "hashsum").unwrap().1.1; for (&name, (_, command)) in utils { - if name == "[" { - continue; - } - let p = format!("docs/src/utils/{name}.md"); + let (utils_name, usage_name, command) = match name { + "[" => { + continue; + } + name if is_hashsum_family(name) => { + // These use the hashsum + ("hashsum", name, &hashsum_cmd) + } + n => (n, n, command), + }; + let p = format!("docs/src/utils/{usage_name}.md"); - let fluent = File::open(format!("src/uu/{name}/locales/en-US.ftl")) + let fluent = File::open(format!("src/uu/{utils_name}/locales/en-US.ftl")) .and_then(|mut f: File| { let mut s = String::new(); f.read_to_string(&mut s)?; @@ -310,21 +320,68 @@ fn main() -> io::Result<()> { MDWriter { w: Box::new(f), command: command(), - name, + name: usage_name, tldr_zip: &mut tldr_zip, utils_per_platform: &utils_per_platform, fluent, + fluent_key: utils_name.to_string(), } .markdown()?; println!("Wrote to '{p}'"); } else { println!("Error writing to {p}"); } - writeln!(summary, "* [{name}](utils/{name}.md)")?; + writeln!(summary, "* [{usage_name}](utils/{usage_name}.md)")?; } Ok(()) } +fn fix_usage(name: &str, usage: String) -> String { + match name { + "test" => { + // replace to [ but not the first two line + usage + .lines() + .enumerate() + .map(|(i, l)| { + if i > 1 { + l.replace("test", "[") + } else { + l.to_string() + } + }) + .collect::>() + .join("\n") + } + "hashsum" => usage, + name if is_hashsum_family(name) => { + usage.replace("-- ", "").replace("hashsum", name) + } + _ => usage, + } +} + +fn is_hashsum_family(name: &str) -> bool { + matches!( + name, + "md5sum" + | "sha1sum" + | "sha224sum" + | "sha256sum" + | "sha384sum" + | "sha512sum" + | "sha3sum" + | "sha3-224sum" + | "sha3-256sum" + | "sha3-384sum" + | "sha3-512sum" + | "shake128sum" + | "shake256sum" + | "b2sum" + | "b3sum" + ) +} + struct MDWriter<'a, 'b> { w: Box, command: Command, @@ -332,6 +389,7 @@ struct MDWriter<'a, 'b> { tldr_zip: &'b mut Option>, utils_per_platform: &'b HashMap<&'b str, Vec>, fluent: Option, + fluent_key: String, } impl MDWriter<'_, '_> { @@ -351,7 +409,6 @@ impl MDWriter<'_, '_> { fn extract_fluent_value(&self, key: &str) -> Option { let content = self.fluent.as_ref()?; let resource = parser::parse(content.clone()).ok()?; - for entry in resource.body { if let Entry::Message(Message { id, @@ -362,9 +419,20 @@ impl MDWriter<'_, '_> { if id.name == key { // Simple text extraction - just concatenate text elements let mut result = String::new(); + use fluent_syntax::ast::{ + Expression, InlineExpression, + PatternElement::{Placeable, TextElement}, + }; for element in elements { - if let fluent_syntax::ast::PatternElement::TextElement { value } = element { - result.push_str(&value); + if let TextElement { ref value } = element { + result.push_str(value); + } + if let Placeable { + expression: + Expression::Inline(InlineExpression::StringLiteral { ref value }), + } = element + { + result.push_str(value); } } return Some(result); @@ -422,7 +490,8 @@ impl MDWriter<'_, '_> { /// # Errors /// Returns an error if the writer fails. fn usage(&mut self) -> io::Result<()> { - if let Some(usage) = self.extract_fluent_value(&format!("{}-usage", self.name)) { + if let Some(usage) = self.extract_fluent_value(&format!("{}-usage", self.fluent_key)) { + let usage = fix_usage(self.name, usage); writeln!(self.w, "\n```")?; writeln!(self.w, "{usage}")?; writeln!(self.w, "```") @@ -434,7 +503,7 @@ impl MDWriter<'_, '_> { /// # Errors /// Returns an error if the writer fails. fn about(&mut self) -> io::Result<()> { - if let Some(about) = self.extract_fluent_value(&format!("{}-about", self.name)) { + if let Some(about) = self.extract_fluent_value(&format!("{}-about", self.fluent_key)) { writeln!(self.w, "{about}") } else { Ok(()) @@ -444,7 +513,9 @@ impl MDWriter<'_, '_> { /// # Errors /// Returns an error if the writer fails. fn after_help(&mut self) -> io::Result<()> { - if let Some(after_help) = self.extract_fluent_value(&format!("{}-after-help", self.name)) { + if let Some(after_help) = + self.extract_fluent_value(&format!("{}-after-help", self.fluent_key)) + { writeln!(self.w, "\n\n{after_help}") } else { Ok(()) @@ -515,7 +586,7 @@ impl MDWriter<'_, '_> { writeln!(self.w, "")?; let help_text = arg.get_help().unwrap_or_default().to_string(); // Try to resolve Fluent key if it looks like one, otherwise use as-is - let resolved_help = if help_text.starts_with(&format!("{}-help-", self.name)) { + let resolved_help = if help_text.starts_with(&format!("{}-help-", self.fluent_key)) { self.extract_fluent_value(&help_text).unwrap_or(help_text) } else { help_text diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 047d6889c..4091a663d 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -439,7 +439,7 @@ fn uu_app(binary_name: &str) -> (Command, bool) { let usage = translate!("hashsum-usage-specific", "utility_name" => binary_name); command .help_template(uucore::localized_help_template(binary_name)) - .override_usage(format_usage(&usage)) + .override_usage(format_usage(&usage).replace("-- ", "")) }; (command, is_hashsum_bin) diff --git a/src/uu/test/locales/en-US.ftl b/src/uu/test/locales/en-US.ftl index 86871637f..66d7c0a83 100644 --- a/src/uu/test/locales/en-US.ftl +++ b/src/uu/test/locales/en-US.ftl @@ -3,7 +3,7 @@ test-usage = test EXPRESSION test {"[ EXPRESSION ]"} {"[ ]"} - {"[ OPTION ]"} + {"[ OPTION"} test-after-help = Exit with the status determined by EXPRESSION. An omitted EXPRESSION defaults to false. diff --git a/src/uu/test/locales/fr-FR.ftl b/src/uu/test/locales/fr-FR.ftl index 78cae9b44..36729d3f7 100644 --- a/src/uu/test/locales/fr-FR.ftl +++ b/src/uu/test/locales/fr-FR.ftl @@ -3,7 +3,7 @@ test-usage = test EXPRESSION test {"[ EXPRESSION ]"} {"[ ]"} - {"[ OPTION ]"} + {"[ OPTION"} test-after-help = Quitter avec le statut déterminé par EXPRESSION. Une EXPRESSION omise vaut false par défaut. diff --git a/tests/test_uudoc.rs b/tests/test_uudoc.rs new file mode 100644 index 000000000..d8e80f7a1 --- /dev/null +++ b/tests/test_uudoc.rs @@ -0,0 +1,122 @@ +//! Tests on the `uudoc` binary. +//! +//! To run the uudoc +//! ``` +//! cargo run --bin uudoc --features uudoc +//! ``` +//! +//! To run the tests +//! ``` +//! cargo test --features uudoc +//! ``` +#![cfg(feature = "uudoc")] + +use std::env; +pub const TESTS_BINARY: &str = env!("CARGO_BIN_EXE_uudoc"); + +// Set the environment variable for any tests + +// Use the ctor attribute to run this function before any tests +#[ctor::ctor] +fn init() { + // No need for unsafe here + unsafe { + std::env::set_var("UUTESTS_BINARY_PATH", TESTS_BINARY); + } + // Print for debugging + eprintln!("Setting UUTESTS_BINARY_PATH={TESTS_BINARY}"); +} + +/// Run the `uudoc` command and return the output as a vector of strings. +/// # Errors +/// If the command fails to execute or if the output cannot be converted to UTF-8, an `io::Error` is returned. +fn run_write_doc() -> Vec { + use std::process::Command; + use uutests::util::TestScenario; + + let scenario = TestScenario::new(""); + let output = Command::new(&scenario.bin_path).output().unwrap(); + assert!( + output.status.success(), + "uudoc command failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + String::from_utf8(output.stdout) + .unwrap() + .lines() + .map(String::from) + .filter(|line| line.starts_with("Wrote")) + .collect::>() +} + +fn get_doc_file_from_output(output: &str) -> (String, String) { + let correct_path_test = output + .strip_prefix("Wrote to '") + .unwrap() + .strip_suffix("'") + .unwrap() + .to_string(); + let content = std::fs::read_to_string(&correct_path_test); + let content = match content { + Ok(content) => content, + Err(e) => { + panic!( + "Failed to read file {}: {} from {:?}", + correct_path_test, + e, + env::current_dir() + ); + } + }; + (correct_path_test, content) +} + +#[test] +fn uudoc_check_test() { + let pages = run_write_doc(); + // assert wrote to the correct file + let path_test = pages.iter().find(|line| line.contains("test.md")).unwrap(); + let (correct_path, content) = get_doc_file_from_output(path_test); + + // open the file + assert!( + content.contains( + "``` +test EXPRESSION +test +[ EXPRESSION ] +[ ] +[ OPTION +``` +", + ), + "{correct_path} does not contains the required text" + ); +} + +#[test] +fn uudoc_check_sums() { + let pages = run_write_doc(); + let sums = [ + "md5sum", + "sha1sum", + "sha224sum", + "sha256sum", + "sha384sum", + "sha512sum", + "b2sum", + ]; + for one_sum in sums { + let output_path = pages + .iter() + .find(|one_line| one_line.contains(one_sum)) + .unwrap(); + let (correct_path, content) = get_doc_file_from_output(output_path); + let formatted = format!("```\n{one_sum} [OPTIONS]... [FILE]...\n```"); + assert!( + content.contains(&formatted), + "Content of {correct_path} does not contain the expected format: {formatted}", + ); + } +}