diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index cf12ee2d7..9e4218a3f 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -408,6 +408,16 @@ jobs: make test env: RUST_BACKTRACE: "1" + - name: "`make install`" + shell: bash + run: | + DESTDIR=/tmp/ make PROFILE=release install + # Check that the manpage is present + test -f /tmp/usr/local/share/man/man1/whoami.1 + # Check that the completion is present + test -f /tmp/usr/local/share/zsh/site-functions/_install + env: + RUST_BACKTRACE: "1" build_rust_stable: diff --git a/Cargo.lock b/Cargo.lock index 89d67bf81..e4f489eae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -267,6 +267,16 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "clap_mangen" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0f09a0ca8f0dd8ac92c546b426f466ef19828185c6d504c80c48c9c2768ed9" +dependencies = [ + "clap", + "roff", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -324,6 +334,7 @@ dependencies = [ "chrono", "clap", "clap_complete", + "clap_mangen", "conv", "filetime", "glob", @@ -1874,6 +1885,12 @@ dependencies = [ "libc", ] +[[package]] +name = "roff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" + [[package]] name = "rstest" version = "0.16.0" diff --git a/Cargo.toml b/Cargo.toml index 61a461deb..d8d43b252 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ # coreutils (uutils) # * see the repository LICENSE, README, and CONTRIBUTING files for more information -# spell-checker:ignore (libs) libselinux gethostid procfs bigdecimal kqueue fundu +# spell-checker:ignore (libs) libselinux gethostid procfs bigdecimal kqueue fundu mangen [package] name = "coreutils" @@ -271,6 +271,7 @@ byteorder = "1.3.2" chrono = { version="^0.4.23", default-features=false, features=["std", "alloc", "clock"]} clap = { version = "4.0", features = ["wrap_help", "cargo"] } clap_complete = "4.0" +clap_mangen = "0.2" compare = "0.1.0" coz = { version = "0.1.3" } crossterm = ">=0.19" @@ -352,6 +353,7 @@ clap = { workspace=true } once_cell = { workspace=true } uucore = { workspace=true } clap_complete = { workspace=true } +clap_mangen = { workspace=true } phf = { workspace=true } selinux = { workspace=true, optional = true } textwrap = { workspace=true } diff --git a/GNUmakefile b/GNUmakefile index b242bc8ce..81b90d32f 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -349,10 +349,12 @@ endif mkdir -p $(DESTDIR)$(DATAROOTDIR)/zsh/site-functions mkdir -p $(DESTDIR)$(DATAROOTDIR)/bash-completion/completions mkdir -p $(DESTDIR)$(DATAROOTDIR)/fish/vendor_completions.d + mkdir -p $(DESTDIR)$(DATAROOTDIR)/man/man1 $(foreach prog, $(INSTALLEES), \ $(BUILDDIR)/coreutils completion $(prog) zsh > $(DESTDIR)$(DATAROOTDIR)/zsh/site-functions/_$(PROG_PREFIX)$(prog); \ $(BUILDDIR)/coreutils completion $(prog) bash > $(DESTDIR)$(DATAROOTDIR)/bash-completion/completions/$(PROG_PREFIX)$(prog); \ $(BUILDDIR)/coreutils completion $(prog) fish > $(DESTDIR)$(DATAROOTDIR)/fish/vendor_completions.d/$(PROG_PREFIX)$(prog).fish; \ + $(BUILDDIR)/coreutils manpage $(prog) > $(DESTDIR)$(DATAROOTDIR)/man/man1/$(PROG_PREFIX)$(prog).1; \ ) uninstall: diff --git a/README.md b/README.md index 66a0395e9..d9da80a81 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ ----------------------------------------------- - + uutils is an attempt at writing universal (as in cross-platform) CLI utilities in [Rust](http://www.rust-lang.org). @@ -145,8 +145,8 @@ $ cargo install --path . This command will install uutils into Cargo's *bin* folder (*e.g.* `$HOME/.cargo/bin`). -This does not install files necessary for shell completion. For shell completion to work, -use `GNU Make` or see `Manually install shell completions`. +This does not install files necessary for shell completion or manpages. +For manpages or shell completion to work, use `GNU Make` or see `Manually install shell completions`/`Manually install manpages`. ### GNU Make @@ -214,6 +214,20 @@ run: cargo run completion ls bash > /usr/local/share/bash-completion/completions/ls ``` +### Manually install manpages + +To generate manpages, the syntax is: +```bash +cargo run manpage +``` + +So, to install the manpage for `ls` to `/usr/local/share/man/man1/ls.1` +run: + +```bash +cargo run manpage ls > /usr/local/share/man/man1/ls.1 +``` + ## Un-installation Un-installation differs depending on how you have installed uutils. If you used diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 08c0774ba..ac9750f93 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -5,6 +5,8 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +// spell-checker:ignore manpages mangen + use clap::{Arg, Command}; use clap_complete::Shell; use std::cmp; @@ -90,6 +92,10 @@ fn main() { gen_completions(args, &utils); } + if util == "manpage" { + gen_manpage(args, &utils); + } + match utils.get(util) { Some(&(uumain, _)) => { process::exit(uumain((vec![util_os].into_iter()).chain(args))); @@ -167,6 +173,39 @@ fn gen_completions( process::exit(0); } +/// Generate the manpage for the utility in the first parameter +fn gen_manpage( + args: impl Iterator, + util_map: &UtilityMap, +) -> ! { + let all_utilities: Vec<_> = std::iter::once("coreutils") + .chain(util_map.keys().copied()) + .collect(); + + let matches = Command::new("manpage") + .about("Prints manpage to stdout") + .arg( + Arg::new("utility") + .value_parser(clap::builder::PossibleValuesParser::new(all_utilities)) + .required(true), + ) + .get_matches_from(std::iter::once(OsString::from("manpage")).chain(args)); + + let utility = matches.get_one::("utility").unwrap(); + + let command = if utility == "coreutils" { + gen_coreutils_app(util_map) + } else { + util_map.get(utility).unwrap().1() + }; + + let man = clap_mangen::Man::new(command); + man.render(&mut io::stdout()) + .expect("Man page generation failed"); + io::stdout().flush().unwrap(); + process::exit(0); +} + fn gen_coreutils_app(util_map: &UtilityMap) -> Command { let mut command = Command::new("coreutils"); for (_, (_, sub_app)) in util_map {