diff --git a/docs/theme/head.hbs b/docs/theme/head.hbs index 31cc2dad5..f4c021480 100644 --- a/docs/theme/head.hbs +++ b/docs/theme/head.hbs @@ -5,10 +5,17 @@ main { position: relative; } - .version { + .additional { position: absolute; - top: 1em; + top: 0em; right: 0; + display: flex; + gap: 5px; + align-items: center; + font-size: 1.3em; + } + .platforms { + font-size: 1.5em; } dd > p { margin-top: 0.2em; diff --git a/src/bin/uudoc.rs b/src/bin/uudoc.rs index 24c347fe7..53e31519f 100644 --- a/src/bin/uudoc.rs +++ b/src/bin/uudoc.rs @@ -5,6 +5,7 @@ // spell-checker:ignore tldr use clap::Command; +use std::collections::HashMap; use std::ffi::OsString; use std::fs::File; use std::io::Cursor; @@ -29,6 +30,7 @@ fn main() -> io::Result<()> { x => x, }?; + println!("Writing initial info to SUMMARY.md"); let mut summary = File::create("docs/src/SUMMARY.md")?; let _ = write!( @@ -44,6 +46,40 @@ fn main() -> io::Result<()> { * [Multi-call binary](multicall.md)\n", ); + println!("Gathering utils per platform"); + let utils_per_platform = { + let mut map = HashMap::new(); + for platform in ["unix", "macos", "windows"] { + let platform_utils: Vec = String::from_utf8( + std::process::Command::new("./util/show-utils.sh") + .arg(format!("--features=feat_os_{}", platform)) + .output()? + .stdout, + ) + .unwrap() + .split(' ') + .map(ToString::to_string) + .collect(); + map.insert(platform, platform_utils); + } + + // Linux is a special case because it can support selinux + let platform_utils: Vec = String::from_utf8( + std::process::Command::new("./util/show-utils.sh") + .arg("--features=feat_os_unix feat_selinux") + .output()? + .stdout, + ) + .unwrap() + .split(' ') + .map(ToString::to_string) + .collect(); + map.insert("linux", platform_utils); + + map + }; + + println!("Writing to utils"); let mut utils = utils.entries().collect::>(); utils.sort(); for (&name, (_, command)) in utils { @@ -52,7 +88,14 @@ fn main() -> io::Result<()> { } let p = format!("docs/src/utils/{}.md", name); if let Ok(f) = File::create(&p) { - write_markdown(f, &mut command(), name, &mut tldr_zip)?; + MDWriter { + w: Box::new(f), + command: command(), + name, + tldr_zip: &mut tldr_zip, + utils_per_platform: &utils_per_platform, + } + .markdown()?; println!("Wrote to '{}'", p); } else { println!("Error writing to {}", p); @@ -62,88 +105,185 @@ fn main() -> io::Result<()> { Ok(()) } -fn write_markdown( - mut w: impl Write, - command: &mut Command, - name: &str, - tldr_zip: &mut zip::ZipArchive, -) -> io::Result<()> { - write!(w, "# {}\n\n", name)?; - write_version(&mut w, command)?; - write_usage(&mut w, command, name)?; - write_description(&mut w, command)?; - write_options(&mut w, command)?; - write_examples(&mut w, name, tldr_zip) +struct MDWriter<'a, 'b> { + w: Box, + command: Command<'a>, + name: &'a str, + tldr_zip: &'b mut ZipArchive>>, + utils_per_platform: &'b HashMap<&'b str, Vec>, } -fn write_version(w: &mut impl Write, command: &Command) -> io::Result<()> { - writeln!( - w, - "
version: {}
", - command.render_version().split_once(' ').unwrap().1 - ) -} +impl<'a, 'b> MDWriter<'a, 'b> { + fn markdown(&mut self) -> io::Result<()> { + write!(self.w, "# {}\n\n", self.name)?; + self.additional()?; + self.usage()?; + self.description()?; + self.options()?; + self.examples() + } -fn write_usage(w: &mut impl Write, command: &mut Command, name: &str) -> io::Result<()> { - writeln!(w, "\n```")?; - let mut usage: String = command - .render_usage() - .lines() - .skip(1) - .map(|l| l.trim()) - .filter(|l| !l.is_empty()) - .collect::>() - .join("\n"); - usage = usage.replace(uucore::execution_phrase(), name); - writeln!(w, "{}", usage)?; - writeln!(w, "```") -} + fn additional(&mut self) -> io::Result<()> { + writeln!(self.w, "
")?; + self.platforms()?; + self.version()?; + writeln!(self.w, "
") + } + + fn platforms(&mut self) -> io::Result<()> { + writeln!(self.w, "
")?; + for (feature, icon) in [ + ("linux", "linux"), + // freebsd is disabled for now because mdbook does not use font-awesome 5 yet. + // ("unix", "freebsd"), + ("macos", "apple"), + ("windows", "windows"), + ] { + if self.name.contains("sum") + || self.utils_per_platform[feature] + .iter() + .any(|u| u == self.name) + { + writeln!(self.w, "", icon)?; + } + } + writeln!(self.w, "
")?; -fn write_description(w: &mut impl Write, command: &Command) -> io::Result<()> { - if let Some(about) = command.get_long_about().or_else(|| command.get_about()) { - writeln!(w, "{}", about) - } else { Ok(()) } -} -fn write_examples( - w: &mut impl Write, - name: &str, - tldr_zip: &mut zip::ZipArchive, -) -> io::Result<()> { - let content = if let Some(f) = get_zip_content(tldr_zip, &format!("pages/common/{}.md", name)) { - f - } else if let Some(f) = get_zip_content(tldr_zip, &format!("pages/linux/{}.md", name)) { - f - } else { - return Ok(()); - }; + fn version(&mut self) -> io::Result<()> { + writeln!( + self.w, + "
v{}
", + self.command.render_version().split_once(' ').unwrap().1 + ) + } - writeln!(w, "## Examples")?; - writeln!(w)?; - for line in content.lines().skip_while(|l| !l.starts_with('-')) { - if let Some(l) = line.strip_prefix("- ") { - writeln!(w, "{}", l)?; - } else if line.starts_with('`') { - writeln!(w, "```shell\n{}\n```", line.trim_matches('`'))?; - } else if line.is_empty() { - writeln!(w)?; + fn usage(&mut self) -> io::Result<()> { + writeln!(self.w, "\n```")?; + let mut usage: String = self + .command + .render_usage() + .lines() + .skip(1) + .map(|l| l.trim()) + .filter(|l| !l.is_empty()) + .collect::>() + .join("\n"); + usage = usage.replace(uucore::execution_phrase(), self.name); + writeln!(self.w, "{}", usage)?; + writeln!(self.w, "```") + } + + fn description(&mut self) -> io::Result<()> { + if let Some(about) = self + .command + .get_long_about() + .or_else(|| self.command.get_about()) + { + writeln!(self.w, "{}", about) } else { - println!("Not sure what to do with this line:"); - println!("{}", line); + Ok(()) } } - writeln!(w)?; - writeln!( - w, - "> The examples are provided by the [tldr-pages project](https://tldr.sh) under the [CC BY 4.0 License](https://github.com/tldr-pages/tldr/blob/main/LICENSE.md)." - )?; - writeln!(w, ">")?; - writeln!( - w, - "> Please note that, as uutils is a work in progress, some examples might fail." - ) + + fn examples(&mut self) -> io::Result<()> { + let content = if let Some(f) = + get_zip_content(self.tldr_zip, &format!("pages/common/{}.md", self.name)) + { + f + } else if let Some(f) = + get_zip_content(self.tldr_zip, &format!("pages/linux/{}.md", self.name)) + { + f + } else { + return Ok(()); + }; + + writeln!(self.w, "## Examples")?; + writeln!(self.w)?; + for line in content.lines().skip_while(|l| !l.starts_with('-')) { + if let Some(l) = line.strip_prefix("- ") { + writeln!(self.w, "{}", l)?; + } else if line.starts_with('`') { + writeln!(self.w, "```shell\n{}\n```", line.trim_matches('`'))?; + } else if line.is_empty() { + writeln!(self.w)?; + } else { + println!("Not sure what to do with this line:"); + println!("{}", line); + } + } + writeln!(self.w)?; + writeln!( + self.w, + "> The examples are provided by the [tldr-pages project](https://tldr.sh) under the [CC BY 4.0 License](https://github.com/tldr-pages/tldr/blob/main/LICENSE.md)." + )?; + writeln!(self.w, ">")?; + writeln!( + self.w, + "> Please note that, as uutils is a work in progress, some examples might fail." + ) + } + + fn options(&mut self) -> io::Result<()> { + writeln!(self.w, "

Options

")?; + write!(self.w, "
")?; + for arg in self.command.get_arguments() { + write!(self.w, "
")?; + let mut first = true; + for l in arg.get_long_and_visible_aliases().unwrap_or_default() { + if !first { + write!(self.w, ", ")?; + } else { + first = false; + } + write!(self.w, "")?; + write!(self.w, "--{}", l)?; + if let Some(names) = arg.get_value_names() { + write!( + self.w, + "={}", + names + .iter() + .map(|x| format!("<{}>", x)) + .collect::>() + .join(" ") + )?; + } + write!(self.w, "")?; + } + for s in arg.get_short_and_visible_aliases().unwrap_or_default() { + if !first { + write!(self.w, ", ")?; + } else { + first = false; + } + write!(self.w, "")?; + write!(self.w, "-{}", s)?; + if let Some(names) = arg.get_value_names() { + write!( + self.w, + " {}", + names + .iter() + .map(|x| format!("<{}>", x)) + .collect::>() + .join(" ") + )?; + } + write!(self.w, "")?; + } + writeln!(self.w, "
")?; + writeln!( + self.w, + "
\n\n{}\n\n
", + arg.get_help().unwrap_or_default().replace('\n', "
") + )?; + } + writeln!(self.w, "
\n") + } } fn get_zip_content(archive: &mut ZipArchive, name: &str) -> Option { @@ -151,61 +291,3 @@ fn get_zip_content(archive: &mut ZipArchive, name: &str) -> Op archive.by_name(name).ok()?.read_to_string(&mut s).unwrap(); Some(s) } - -fn write_options(w: &mut impl Write, command: &Command) -> io::Result<()> { - writeln!(w, "

Options

")?; - write!(w, "
")?; - for arg in command.get_arguments() { - write!(w, "
")?; - let mut first = true; - for l in arg.get_long_and_visible_aliases().unwrap_or_default() { - if !first { - write!(w, ", ")?; - } else { - first = false; - } - write!(w, "")?; - write!(w, "--{}", l)?; - if let Some(names) = arg.get_value_names() { - write!( - w, - "={}", - names - .iter() - .map(|x| format!("<{}>", x)) - .collect::>() - .join(" ") - )?; - } - write!(w, "")?; - } - for s in arg.get_short_and_visible_aliases().unwrap_or_default() { - if !first { - write!(w, ", ")?; - } else { - first = false; - } - write!(w, "")?; - write!(w, "-{}", s)?; - if let Some(names) = arg.get_value_names() { - write!( - w, - " {}", - names - .iter() - .map(|x| format!("<{}>", x)) - .collect::>() - .join(" ") - )?; - } - write!(w, "")?; - } - writeln!(w, "
")?; - writeln!( - w, - "
\n\n{}\n\n
", - arg.get_help().unwrap_or_default().replace('\n', "
") - )?; - } - writeln!(w, "
\n") -}