mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
uudoc: move manpage generation away from the binary
This commit is contained in:
Generated
+1
@@ -4334,6 +4334,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"number_prefix",
|
||||
"os_display",
|
||||
"phf",
|
||||
"procfs",
|
||||
"selinux",
|
||||
"sha1",
|
||||
|
||||
+5
-5
@@ -31,7 +31,7 @@ expensive_tests = []
|
||||
# "test_risky_names" == enable tests that create problematic file names (would make a network share inaccessible to Windows, breaks SVN on Mac OS, etc.)
|
||||
test_risky_names = []
|
||||
# * only build `uudoc` when `--feature uudoc` is activated
|
||||
uudoc = ["zip", "dep:fluent-syntax"]
|
||||
uudoc = ["clap_complete", "clap_mangen", "fluent-syntax", "zip", "feat_Tier1"]
|
||||
## features
|
||||
## Optional feature for stdbuf
|
||||
# "feat_external_libstdbuf" == use an external libstdbuf.so for stdbuf instead of embedding it
|
||||
@@ -408,14 +408,14 @@ uutests = { version = "0.3.0", package = "uutests", path = "tests/uutests" }
|
||||
|
||||
[dependencies]
|
||||
clap.workspace = true
|
||||
uucore.workspace = true
|
||||
clap_complete.workspace = true
|
||||
clap_mangen.workspace = true
|
||||
clap_complete = { workspace = true, optional = true }
|
||||
clap_mangen = { workspace = true, optional = true }
|
||||
fluent-syntax = { workspace = true, optional = true }
|
||||
phf.workspace = true
|
||||
selinux = { workspace = true, optional = true }
|
||||
textwrap.workspace = true
|
||||
uucore.workspace = true
|
||||
zip = { workspace = true, optional = true }
|
||||
fluent-syntax = { workspace = true, optional = true }
|
||||
|
||||
|
||||
# * uutils
|
||||
|
||||
+11
-7
@@ -302,6 +302,7 @@ TEST_PROGS := \
|
||||
unexpand \
|
||||
uniq \
|
||||
unlink \
|
||||
uudoc \
|
||||
wc \
|
||||
who
|
||||
|
||||
@@ -406,10 +407,13 @@ distclean: clean
|
||||
$(CARGO) clean $(CARGOFLAGS) && $(CARGO) update $(CARGOFLAGS)
|
||||
|
||||
ifeq ($(MANPAGES),y)
|
||||
manpages: build-coreutils
|
||||
build-uudoc:
|
||||
${CARGO} build ${CARGOFLAGS} --bin uudoc --features "uudoc ${EXES}" ${PROFILE_CMD} --no-default-features
|
||||
|
||||
manpages: build-coreutils build-uudoc
|
||||
mkdir -p $(BUILDDIR)/man/
|
||||
$(foreach prog, $(INSTALLEES) $(HASHSUM_PROGS), \
|
||||
$(BUILDDIR)/coreutils manpage $(prog) > $(BUILDDIR)/man/$(PROG_PREFIX)$(prog).1 $(newline) \
|
||||
$(BUILDDIR)/uudoc manpage $(prog) > $(BUILDDIR)/man/$(PROG_PREFIX)$(prog).1 $(newline) \
|
||||
)
|
||||
|
||||
install-manpages: manpages
|
||||
@@ -422,12 +426,12 @@ install-manpages:
|
||||
endif
|
||||
|
||||
ifeq ($(COMPLETIONS),y)
|
||||
completions: build-coreutils
|
||||
completions: build-coreutils build-uudoc
|
||||
mkdir -p $(BUILDDIR)/completions/zsh $(BUILDDIR)/completions/bash $(BUILDDIR)/completions/fish
|
||||
$(foreach prog, $(INSTALLEES) $(HASHSUM_PROGS) , \
|
||||
$(BUILDDIR)/coreutils completion $(prog) zsh > $(BUILDDIR)/completions/zsh/_$(PROG_PREFIX)$(prog) $(newline) \
|
||||
$(BUILDDIR)/coreutils completion $(prog) bash > $(BUILDDIR)/completions/bash/$(PROG_PREFIX)$(prog) $(newline) \
|
||||
$(BUILDDIR)/coreutils completion $(prog) fish > $(BUILDDIR)/completions/fish/$(PROG_PREFIX)$(prog).fish $(newline) \
|
||||
$(BUILDDIR)/uudoc completion $(prog) zsh > $(BUILDDIR)/completions/zsh/_$(PROG_PREFIX)$(prog) $(newline) \
|
||||
$(BUILDDIR)/uudoc completion $(prog) bash > $(BUILDDIR)/completions/bash/$(PROG_PREFIX)$(prog) $(newline) \
|
||||
$(BUILDDIR)/uudoc completion $(prog) fish > $(BUILDDIR)/completions/fish/$(PROG_PREFIX)$(prog).fish $(newline) \
|
||||
)
|
||||
|
||||
install-completions: completions
|
||||
@@ -520,4 +524,4 @@ endif
|
||||
rm -f $(addprefix $(DESTDIR)$(DATAROOTDIR)/fish/vendor_completions.d/$(PROG_PREFIX),$(addsuffix .fish,$(PROGS)))
|
||||
rm -f $(addprefix $(DESTDIR)$(DATAROOTDIR)/man/man1/$(PROG_PREFIX),$(addsuffix .1,$(PROGS)))
|
||||
|
||||
.PHONY: all build build-coreutils build-pkgs test distclean clean busytest install uninstall
|
||||
.PHONY: all build build-coreutils build-pkgs build-uudoc test distclean clean busytest install uninstall
|
||||
|
||||
@@ -252,19 +252,23 @@ The `coreutils` binary can generate completions for the `bash`, `elvish`,
|
||||
The syntax is:
|
||||
|
||||
```shell
|
||||
cargo run completion <utility> <shell>
|
||||
# Install uudoc first
|
||||
cargo install --bin uudoc --features uudoc --path .
|
||||
|
||||
# Then use the installed binary
|
||||
uudoc completion <utility> <shell>
|
||||
```
|
||||
|
||||
So, to install completions for `ls` on `bash` to
|
||||
`/usr/local/share/bash-completion/completions/ls`, run:
|
||||
|
||||
```shell
|
||||
cargo run completion ls bash > /usr/local/share/bash-completion/completions/ls
|
||||
uudoc completion ls bash > /usr/local/share/bash-completion/completions/ls
|
||||
```
|
||||
|
||||
Completion for prefixed `cp` with `uu-` on `zsh` is generated by
|
||||
```shell
|
||||
env PROG_PREFIX=uu- cargo run completion cp zsh
|
||||
env PROG_PREFIX=uu- uudoc completion cp zsh
|
||||
```
|
||||
|
||||
### Manually install manpages
|
||||
@@ -272,13 +276,17 @@ env PROG_PREFIX=uu- cargo run completion cp zsh
|
||||
To generate manpages, the syntax is:
|
||||
|
||||
```bash
|
||||
cargo run manpage <utility>
|
||||
# Install uudoc first
|
||||
cargo install --bin uudoc --features uudoc --path .
|
||||
|
||||
# Then use the installed binary
|
||||
uudoc manpage <utility>
|
||||
```
|
||||
|
||||
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
|
||||
uudoc manpage ls > /usr/local/share/man/man1/ls.1
|
||||
```
|
||||
|
||||
## Un-installation
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (vars) krate
|
||||
// spell-checker:ignore (vars) krate mangen
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
@@ -32,7 +32,8 @@ pub fn main() {
|
||||
// Allow this as we have a bunch of info in the comments
|
||||
#[allow(clippy::match_same_arms)]
|
||||
match krate.as_ref() {
|
||||
"default" | "macos" | "unix" | "windows" | "selinux" | "zip" => continue, // common/standard feature names
|
||||
"default" | "macos" | "unix" | "windows" | "selinux" | "zip" | "clap_complete"
|
||||
| "clap_mangen" | "fluent_syntax" => continue, // common/standard feature names
|
||||
"nightly" | "test_unimplemented" | "expensive_tests" | "test_risky_names" => {
|
||||
continue;
|
||||
} // crate-local custom features
|
||||
|
||||
Generated
+26
@@ -1081,6 +1081,25 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.32"
|
||||
@@ -1367,6 +1386,12 @@ version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
||||
|
||||
[[package]]
|
||||
name = "sm3"
|
||||
version = "0.4.2"
|
||||
@@ -1733,6 +1758,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"number_prefix",
|
||||
"os_display",
|
||||
"phf",
|
||||
"procfs",
|
||||
"sha1",
|
||||
"sha2",
|
||||
|
||||
+21
-246
@@ -3,18 +3,14 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore manpages mangen prefixcat testcat
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use clap_complete::Shell;
|
||||
use std::cmp;
|
||||
use std::ffi::OsStr;
|
||||
use std::ffi::OsString;
|
||||
use std::io::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::locale;
|
||||
|
||||
use clap::Command;
|
||||
|
||||
use coreutils::validation;
|
||||
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
@@ -38,61 +34,6 @@ fn usage<T>(utils: &UtilityMap<T>, name: &str) {
|
||||
);
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
/// Panics if the binary path cannot be determined
|
||||
fn binary_path(args: &mut impl Iterator<Item = OsString>) -> PathBuf {
|
||||
match args.next() {
|
||||
Some(ref s) if !s.is_empty() => PathBuf::from(s),
|
||||
_ => std::env::current_exe().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn name(binary_path: &Path) -> Option<&str> {
|
||||
binary_path.file_stem()?.to_str()
|
||||
}
|
||||
|
||||
fn get_canonical_util_name(util_name: &str) -> &str {
|
||||
match util_name {
|
||||
// uu_test aliases - '[' is an alias for test
|
||||
"[" => "test",
|
||||
|
||||
// hashsum aliases - all these hash commands are aliases for hashsum
|
||||
"md5sum" | "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum"
|
||||
| "sha3sum" | "sha3-224sum" | "sha3-256sum" | "sha3-384sum" | "sha3-512sum"
|
||||
| "shake128sum" | "shake256sum" | "b2sum" | "b3sum" => "hashsum",
|
||||
|
||||
"dir" => "ls", // dir is an alias for ls
|
||||
|
||||
// Default case - return the util name as is
|
||||
_ => util_name,
|
||||
}
|
||||
}
|
||||
|
||||
fn find_prefixed_util<'a>(
|
||||
binary_name: &str,
|
||||
mut util_keys: impl Iterator<Item = &'a str>,
|
||||
) -> Option<&'a str> {
|
||||
util_keys.find(|util| {
|
||||
binary_name.ends_with(*util)
|
||||
&& binary_name.len() > util.len() // Ensure there's actually a prefix
|
||||
&& !binary_name[..binary_name.len() - (*util).len()]
|
||||
.ends_with(char::is_alphanumeric)
|
||||
})
|
||||
}
|
||||
|
||||
fn setup_localization_or_exit(util_name: &str) {
|
||||
locale::setup_localization(get_canonical_util_name(util_name)).unwrap_or_else(|err| {
|
||||
match err {
|
||||
uucore::locale::LocalizationError::ParseResource {
|
||||
error: err_msg,
|
||||
snippet,
|
||||
} => eprintln!("Localization parse error at {snippet}: {err_msg}"),
|
||||
other => eprintln!("Could not init the localization system: {other}"),
|
||||
}
|
||||
process::exit(99)
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn main() {
|
||||
uucore::panic::mute_sigpipe_panic();
|
||||
@@ -100,44 +41,38 @@ fn main() {
|
||||
let utils = util_map();
|
||||
let mut args = uucore::args_os();
|
||||
|
||||
let binary = binary_path(&mut args);
|
||||
let binary_as_util = name(&binary).unwrap_or_else(|| {
|
||||
let binary = validation::binary_path(&mut args);
|
||||
let binary_as_util = validation::name(&binary).unwrap_or_else(|| {
|
||||
usage(&utils, "<unknown binary name>");
|
||||
process::exit(0);
|
||||
});
|
||||
|
||||
// binary name equals util name?
|
||||
if let Some(&(uumain, _)) = utils.get(binary_as_util) {
|
||||
setup_localization_or_exit(binary_as_util);
|
||||
validation::setup_localization_or_exit(binary_as_util);
|
||||
process::exit(uumain(vec![binary.into()].into_iter().chain(args)));
|
||||
}
|
||||
|
||||
// binary name equals prefixed util name?
|
||||
// * prefix/stem may be any string ending in a non-alphanumeric character
|
||||
// For example, if the binary is named `uu_test`, it will match `test` as a utility.
|
||||
let util_name = if let Some(util) = find_prefixed_util(binary_as_util, utils.keys().copied()) {
|
||||
// prefixed util => replace 0th (aka, executable name) argument
|
||||
Some(OsString::from(util))
|
||||
} else {
|
||||
// unmatched binary name => regard as multi-binary container and advance argument list
|
||||
uucore::set_utility_is_second_arg();
|
||||
args.next()
|
||||
};
|
||||
let util_name =
|
||||
if let Some(util) = validation::find_prefixed_util(binary_as_util, utils.keys().copied()) {
|
||||
// prefixed util => replace 0th (aka, executable name) argument
|
||||
Some(OsString::from(util))
|
||||
} else {
|
||||
// unmatched binary name => regard as multi-binary container and advance argument list
|
||||
uucore::set_utility_is_second_arg();
|
||||
args.next()
|
||||
};
|
||||
|
||||
// 0th argument equals util name?
|
||||
if let Some(util_os) = util_name {
|
||||
fn not_found(util: &OsStr) -> ! {
|
||||
println!("{}: function/utility not found", util.maybe_quote());
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
let Some(util) = util_os.to_str() else {
|
||||
not_found(&util_os)
|
||||
validation::not_found(&util_os)
|
||||
};
|
||||
|
||||
match util {
|
||||
"completion" => gen_completions(args, &utils),
|
||||
"manpage" => gen_manpage(args, &utils),
|
||||
"--list" => {
|
||||
let mut utils: Vec<_> = utils.keys().collect();
|
||||
utils.sort();
|
||||
@@ -161,7 +96,7 @@ fn main() {
|
||||
// binary to avoid the load of the flt
|
||||
// Could be something like:
|
||||
// #[cfg(not(feature = "only_english"))]
|
||||
setup_localization_or_exit(util);
|
||||
validation::setup_localization_or_exit(util);
|
||||
process::exit(uumain(vec![util_os].into_iter().chain(args)));
|
||||
}
|
||||
None => {
|
||||
@@ -169,7 +104,7 @@ fn main() {
|
||||
// see if they want help on a specific util
|
||||
if let Some(util_os) = args.next() {
|
||||
let Some(util) = util_os.to_str() else {
|
||||
not_found(&util_os)
|
||||
validation::not_found(&util_os)
|
||||
};
|
||||
|
||||
match utils.get(util) {
|
||||
@@ -182,13 +117,13 @@ fn main() {
|
||||
io::stdout().flush().expect("could not flush stdout");
|
||||
process::exit(code);
|
||||
}
|
||||
None => not_found(&util_os),
|
||||
None => validation::not_found(&util_os),
|
||||
}
|
||||
}
|
||||
usage(&utils, binary_as_util);
|
||||
process::exit(0);
|
||||
} else {
|
||||
not_found(&util_os);
|
||||
validation::not_found(&util_os);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,163 +133,3 @@ fn main() {
|
||||
process::exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints completions for the utility in the first parameter for the shell in the second parameter to stdout
|
||||
/// # Panics
|
||||
/// Panics if the utility map is empty
|
||||
fn gen_completions<T: uucore::Args>(
|
||||
args: impl Iterator<Item = OsString>,
|
||||
util_map: &UtilityMap<T>,
|
||||
) -> ! {
|
||||
let all_utilities: Vec<_> = std::iter::once("coreutils")
|
||||
.chain(util_map.keys().copied())
|
||||
.collect();
|
||||
|
||||
let matches = Command::new("completion")
|
||||
.about("Prints completions to stdout")
|
||||
.arg(
|
||||
Arg::new("utility")
|
||||
.value_parser(clap::builder::PossibleValuesParser::new(all_utilities))
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("shell")
|
||||
.value_parser(clap::builder::EnumValueParser::<Shell>::new())
|
||||
.required(true),
|
||||
)
|
||||
.get_matches_from(std::iter::once(OsString::from("completion")).chain(args));
|
||||
|
||||
let utility = matches.get_one::<String>("utility").unwrap();
|
||||
let shell = *matches.get_one::<Shell>("shell").unwrap();
|
||||
|
||||
let mut command = if utility == "coreutils" {
|
||||
gen_coreutils_app(util_map)
|
||||
} else {
|
||||
util_map.get(utility).unwrap().1()
|
||||
};
|
||||
let bin_name = std::env::var("PROG_PREFIX").unwrap_or_default() + utility;
|
||||
|
||||
clap_complete::generate(shell, &mut command, bin_name, &mut io::stdout());
|
||||
io::stdout().flush().unwrap();
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
/// Generate the manpage for the utility in the first parameter
|
||||
/// # Panics
|
||||
/// Panics if the utility map is empty
|
||||
fn gen_manpage<T: uucore::Args>(
|
||||
args: impl Iterator<Item = OsString>,
|
||||
util_map: &UtilityMap<T>,
|
||||
) -> ! {
|
||||
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::<String>("utility").unwrap();
|
||||
|
||||
let command = if utility == "coreutils" {
|
||||
gen_coreutils_app(util_map)
|
||||
} else {
|
||||
setup_localization_or_exit(utility);
|
||||
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);
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
/// Panics if the utility map is empty
|
||||
fn gen_coreutils_app<T: uucore::Args>(util_map: &UtilityMap<T>) -> Command {
|
||||
let mut command = Command::new("coreutils");
|
||||
for (name, (_, sub_app)) in util_map {
|
||||
// Recreate a small subcommand with only the relevant info
|
||||
// (name & short description)
|
||||
let about = sub_app()
|
||||
.get_about()
|
||||
.expect("Could not get the 'about'")
|
||||
.to_string();
|
||||
let sub_app = Command::new(name).about(about);
|
||||
command = command.subcommand(sub_app);
|
||||
}
|
||||
command
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn test_get_canonical_util_name() {
|
||||
// Test a few key aliases
|
||||
assert_eq!(get_canonical_util_name("["), "test");
|
||||
assert_eq!(get_canonical_util_name("md5sum"), "hashsum");
|
||||
assert_eq!(get_canonical_util_name("dir"), "ls");
|
||||
|
||||
// Test passthrough case
|
||||
assert_eq!(get_canonical_util_name("cat"), "cat");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_name() {
|
||||
// Test normal executable name
|
||||
assert_eq!(name(Path::new("/usr/bin/ls")), Some("ls"));
|
||||
assert_eq!(name(Path::new("cat")), Some("cat"));
|
||||
assert_eq!(
|
||||
name(Path::new("./target/debug/coreutils")),
|
||||
Some("coreutils")
|
||||
);
|
||||
|
||||
// Test with extensions
|
||||
assert_eq!(name(Path::new("program.exe")), Some("program"));
|
||||
assert_eq!(name(Path::new("/path/to/utility.bin")), Some("utility"));
|
||||
|
||||
// Test edge cases
|
||||
assert_eq!(name(Path::new("")), None);
|
||||
assert_eq!(name(Path::new("/")), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_prefixed_util() {
|
||||
let utils = ["test", "cat", "ls", "cp"];
|
||||
|
||||
// Test exact prefixed matches
|
||||
assert_eq!(
|
||||
find_prefixed_util("uu_test", utils.iter().copied()),
|
||||
Some("test")
|
||||
);
|
||||
assert_eq!(
|
||||
find_prefixed_util("my-cat", utils.iter().copied()),
|
||||
Some("cat")
|
||||
);
|
||||
assert_eq!(
|
||||
find_prefixed_util("prefix_ls", utils.iter().copied()),
|
||||
Some("ls")
|
||||
);
|
||||
|
||||
// Test non-alphanumeric separator requirement
|
||||
assert_eq!(find_prefixed_util("prefixcat", utils.iter().copied()), None); // no separator
|
||||
assert_eq!(find_prefixed_util("testcat", utils.iter().copied()), None); // no separator
|
||||
|
||||
// Test no match
|
||||
assert_eq!(find_prefixed_util("unknown", utils.iter().copied()), None);
|
||||
assert_eq!(find_prefixed_util("", utils.iter().copied()), None);
|
||||
|
||||
// Test exact util name (should not match as prefixed)
|
||||
assert_eq!(find_prefixed_util("test", utils.iter().copied()), None);
|
||||
assert_eq!(find_prefixed_util("cat", utils.iter().copied()), None);
|
||||
}
|
||||
}
|
||||
|
||||
+138
-6
@@ -2,23 +2,155 @@
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
// spell-checker:ignore tldr uuhelp
|
||||
|
||||
use clap::Command;
|
||||
// spell-checker:ignore mangen tldr
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::OsString,
|
||||
fs::File,
|
||||
io::{self, Read, Seek, Write},
|
||||
process,
|
||||
};
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use clap_complete::Shell;
|
||||
use clap_mangen::Man;
|
||||
use fluent_syntax::ast::{Entry, Message, Pattern};
|
||||
use fluent_syntax::parser;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Seek, Write};
|
||||
use textwrap::{fill, indent, termwidth};
|
||||
use zip::ZipArchive;
|
||||
|
||||
use coreutils::validation;
|
||||
use uucore::Args;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/uutils_map.rs"));
|
||||
|
||||
/// Print usage information for uudoc
|
||||
fn usage<T: Args>(utils: &UtilityMap<T>) {
|
||||
println!("uudoc - Documentation generator for uutils coreutils");
|
||||
println!();
|
||||
println!("Usage: uudoc [command] [args]");
|
||||
println!();
|
||||
println!("Commands:");
|
||||
println!(" (no command) Generate mdbook documentation (default)");
|
||||
println!(" manpage <utility> Generate manpage for a utility");
|
||||
println!(" completion <utility> <shell> Generate shell completions for a utility");
|
||||
println!();
|
||||
println!("Available utilities:");
|
||||
let all_utilities = validation::get_all_utilities(utils);
|
||||
let display_list = all_utilities.join(", ");
|
||||
let width = std::cmp::min(termwidth(), 100) - 4 * 2;
|
||||
println!("{}", indent(&fill(&display_list, width), " "));
|
||||
}
|
||||
|
||||
/// Generates the coreutils app for the utility map
|
||||
fn gen_coreutils_app<T: Args>(util_map: &UtilityMap<T>) -> clap::Command {
|
||||
let mut command = clap::Command::new("coreutils");
|
||||
for (name, (_, sub_app)) in util_map {
|
||||
// Recreate a small subcommand with only the relevant info
|
||||
// (name & short description)
|
||||
let about = sub_app()
|
||||
.get_about()
|
||||
.expect("Could not get the 'about'")
|
||||
.to_string();
|
||||
let sub_app = clap::Command::new(name).about(about);
|
||||
command = command.subcommand(sub_app);
|
||||
}
|
||||
command
|
||||
}
|
||||
|
||||
/// Generate the manpage for the utility in the first parameter
|
||||
fn gen_manpage<T: Args>(args: impl Iterator<Item = OsString>, util_map: &UtilityMap<T>) -> ! {
|
||||
let all_utilities = validation::get_all_utilities(util_map);
|
||||
|
||||
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::<String>("utility").unwrap();
|
||||
let command = if utility == "coreutils" {
|
||||
gen_coreutils_app(util_map)
|
||||
} else {
|
||||
validation::setup_localization_or_exit(utility);
|
||||
util_map.get(utility).unwrap().1()
|
||||
};
|
||||
|
||||
let man = Man::new(command);
|
||||
man.render(&mut io::stdout())
|
||||
.expect("Man page generation failed");
|
||||
io::stdout().flush().unwrap();
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
/// Generate shell completions for the utility in the first parameter
|
||||
fn gen_completions<T: Args>(args: impl Iterator<Item = OsString>, util_map: &UtilityMap<T>) -> ! {
|
||||
let all_utilities = validation::get_all_utilities(util_map);
|
||||
|
||||
let matches = Command::new("completion")
|
||||
.about("Prints completions to stdout")
|
||||
.arg(
|
||||
Arg::new("utility")
|
||||
.value_parser(clap::builder::PossibleValuesParser::new(&all_utilities))
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("shell")
|
||||
.value_parser(clap::builder::EnumValueParser::<Shell>::new())
|
||||
.required(true),
|
||||
)
|
||||
.get_matches_from(std::iter::once(OsString::from("completion")).chain(args));
|
||||
|
||||
let utility = matches.get_one::<String>("utility").unwrap();
|
||||
let shell = *matches.get_one::<Shell>("shell").unwrap();
|
||||
|
||||
let mut command = if utility == "coreutils" {
|
||||
gen_coreutils_app(util_map)
|
||||
} else {
|
||||
validation::setup_localization_or_exit(utility);
|
||||
util_map.get(utility).unwrap().1()
|
||||
};
|
||||
let bin_name = std::env::var("PROG_PREFIX").unwrap_or_default() + utility;
|
||||
|
||||
clap_complete::generate(shell, &mut command, bin_name, &mut io::stdout());
|
||||
io::stdout().flush().unwrap();
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
/// # Errors
|
||||
/// Returns an error if the writer fails.
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn main() -> io::Result<()> {
|
||||
let args: Vec<OsString> = uucore::args_os_filtered().collect();
|
||||
|
||||
// Check for manpage/completion commands first
|
||||
if args.len() > 1 {
|
||||
let command = args.get(1).and_then(|s| s.to_str()).unwrap_or_default();
|
||||
match command {
|
||||
"manpage" => {
|
||||
let args_iter = args.into_iter().skip(2);
|
||||
gen_manpage(args_iter, &util_map::<Box<dyn Iterator<Item = OsString>>>());
|
||||
}
|
||||
"completion" => {
|
||||
let args_iter = args.into_iter().skip(2);
|
||||
gen_completions(args_iter, &util_map::<Box<dyn Iterator<Item = OsString>>>());
|
||||
}
|
||||
"--help" | "-h" => {
|
||||
usage(&util_map::<Box<dyn Iterator<Item = OsString>>>());
|
||||
process::exit(0);
|
||||
}
|
||||
_ => {
|
||||
eprintln!("Unknown command: {command}");
|
||||
eprintln!("Use 'uudoc --help' for usage information.");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut tldr_zip = File::open("docs/tldr.zip")
|
||||
.ok()
|
||||
.and_then(|f| ZipArchive::new(f).ok());
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
pub mod validation;
|
||||
@@ -0,0 +1,157 @@
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore prefixcat testcat
|
||||
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
|
||||
use uucore::Args;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::locale;
|
||||
|
||||
/// Gets all available utilities including "coreutils"
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn get_all_utilities<T: Args>(
|
||||
util_map: &phf::OrderedMap<&'static str, (fn(T) -> i32, fn() -> clap::Command)>,
|
||||
) -> Vec<&'static str> {
|
||||
std::iter::once("coreutils")
|
||||
.chain(util_map.keys().copied())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Prints a "utility not found" error and exits
|
||||
pub fn not_found(util: &OsStr) -> ! {
|
||||
println!("{}: function/utility not found", util.maybe_quote());
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
/// Sets up localization for a utility with proper error handling
|
||||
pub fn setup_localization_or_exit(util_name: &str) {
|
||||
let util_name = get_canonical_util_name(util_name);
|
||||
locale::setup_localization(util_name).unwrap_or_else(|err| {
|
||||
match err {
|
||||
locale::LocalizationError::ParseResource {
|
||||
error: err_msg,
|
||||
snippet,
|
||||
} => eprintln!("Localization parse error at {snippet}: {err_msg}"),
|
||||
other => eprintln!("Could not init the localization system: {other}"),
|
||||
}
|
||||
process::exit(99)
|
||||
});
|
||||
}
|
||||
|
||||
/// Gets the canonical utility name, resolving aliases
|
||||
fn get_canonical_util_name(util_name: &str) -> &str {
|
||||
match util_name {
|
||||
// uu_test aliases - '[' is an alias for test
|
||||
"[" => "test",
|
||||
|
||||
// hashsum aliases - all these hash commands are aliases for hashsum
|
||||
"md5sum" | "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum"
|
||||
| "sha3sum" | "sha3-224sum" | "sha3-256sum" | "sha3-384sum" | "sha3-512sum"
|
||||
| "shake128sum" | "shake256sum" | "b2sum" | "b3sum" => "hashsum",
|
||||
|
||||
"dir" => "ls", // dir is an alias for ls
|
||||
|
||||
// Default case - return the util name as is
|
||||
_ => util_name,
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds a utility with a prefix (e.g., "uu_test" -> "test")
|
||||
pub fn find_prefixed_util<'a>(
|
||||
binary_name: &str,
|
||||
mut util_keys: impl Iterator<Item = &'a str>,
|
||||
) -> Option<&'a str> {
|
||||
util_keys.find(|util| {
|
||||
binary_name.ends_with(*util)
|
||||
&& binary_name.len() > util.len() // Ensure there's actually a prefix
|
||||
&& !binary_name[..binary_name.len() - (*util).len()]
|
||||
.ends_with(char::is_alphanumeric)
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the binary path from command line arguments
|
||||
/// # Panics
|
||||
/// Panics if the binary path cannot be determined
|
||||
pub fn binary_path(args: &mut impl Iterator<Item = OsString>) -> PathBuf {
|
||||
match args.next() {
|
||||
Some(ref s) if !s.is_empty() => PathBuf::from(s),
|
||||
_ => std::env::current_exe().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts the binary name from a path
|
||||
pub fn name(binary_path: &Path) -> Option<&str> {
|
||||
binary_path.file_stem()?.to_str()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_get_canonical_util_name() {
|
||||
// Test a few key aliases
|
||||
assert_eq!(get_canonical_util_name("["), "test");
|
||||
assert_eq!(get_canonical_util_name("md5sum"), "hashsum");
|
||||
assert_eq!(get_canonical_util_name("dir"), "ls");
|
||||
|
||||
// Test passthrough case
|
||||
assert_eq!(get_canonical_util_name("cat"), "cat");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_name() {
|
||||
// Test normal executable name
|
||||
assert_eq!(name(Path::new("/usr/bin/ls")), Some("ls"));
|
||||
assert_eq!(name(Path::new("cat")), Some("cat"));
|
||||
assert_eq!(
|
||||
name(Path::new("./target/debug/coreutils")),
|
||||
Some("coreutils")
|
||||
);
|
||||
|
||||
// Test with extensions
|
||||
assert_eq!(name(Path::new("program.exe")), Some("program"));
|
||||
assert_eq!(name(Path::new("/path/to/utility.bin")), Some("utility"));
|
||||
|
||||
// Test edge cases
|
||||
assert_eq!(name(Path::new("")), None);
|
||||
assert_eq!(name(Path::new("/")), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_prefixed_util() {
|
||||
let utils = ["test", "cat", "ls", "cp"];
|
||||
|
||||
// Test exact prefixed matches
|
||||
assert_eq!(
|
||||
find_prefixed_util("uu_test", utils.iter().copied()),
|
||||
Some("test")
|
||||
);
|
||||
assert_eq!(
|
||||
find_prefixed_util("my-cat", utils.iter().copied()),
|
||||
Some("cat")
|
||||
);
|
||||
assert_eq!(
|
||||
find_prefixed_util("prefix_ls", utils.iter().copied()),
|
||||
Some("ls")
|
||||
);
|
||||
|
||||
// Test non-alphanumeric separator requirement
|
||||
assert_eq!(find_prefixed_util("prefixcat", utils.iter().copied()), None); // no separator
|
||||
assert_eq!(find_prefixed_util("testcat", utils.iter().copied()), None); // no separator
|
||||
|
||||
// Test no match
|
||||
assert_eq!(find_prefixed_util("unknown", utils.iter().copied()), None);
|
||||
assert_eq!(find_prefixed_util("", utils.iter().copied()), None);
|
||||
|
||||
// Test exact util name (should not match as prefixed)
|
||||
assert_eq!(find_prefixed_util("test", utils.iter().copied()), None);
|
||||
assert_eq!(find_prefixed_util("cat", utils.iter().copied()), None);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
pub mod common;
|
||||
|
||||
pub use common::validation;
|
||||
@@ -27,6 +27,7 @@ chrono = { workspace = true, optional = true }
|
||||
clap = { workspace = true }
|
||||
uucore_procs = { workspace = true }
|
||||
number_prefix = { workspace = true }
|
||||
phf = { workspace = true }
|
||||
dns-lookup = { workspace = true, optional = true }
|
||||
dunce = { version = "1.0.4", optional = true }
|
||||
wild = "2.2.1"
|
||||
|
||||
@@ -377,6 +377,13 @@ pub fn args_os() -> impl Iterator<Item = OsString> {
|
||||
ARGV.iter().cloned()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the command line arguments as `OsString`s, filtering out empty arguments.
|
||||
/// This is useful for handling cases where extra whitespace or empty arguments are present.
|
||||
/// args_os_filtered() can be expensive to call
|
||||
pub fn args_os_filtered() -> impl Iterator<Item = OsString> {
|
||||
ARGV.iter().filter(|arg| !arg.is_empty()).cloned()
|
||||
}
|
||||
|
||||
/// Read a line from stdin and check whether the first character is `'y'` or `'Y'`
|
||||
pub fn read_yes() -> bool {
|
||||
let mut s = String::new();
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#[cfg(target_os = "linux")]
|
||||
use uutests::at_and_ucmd;
|
||||
use uutests::new_ucmd;
|
||||
use uutests::util::TestScenario;
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -236,42 +235,3 @@ cyBvdmVyIHRoZSBsYXp5IGRvZy4=
|
||||
// cSpell:enable
|
||||
);
|
||||
}
|
||||
|
||||
// Prevent regression to:
|
||||
//
|
||||
// ❯ coreutils manpage base64 | rg --fixed-strings -- 'base32'
|
||||
// The data are encoded as described for the base32 alphabet in RFC 4648.
|
||||
// to the bytes of the formal base32 alphabet. Use \-\-ignore\-garbage
|
||||
// The data are encoded as described for the base32 alphabet in RFC 4648.
|
||||
// to the bytes of the formal base32 alphabet. Use \-\-ignore\-garbage
|
||||
#[test]
|
||||
fn test_manpage() {
|
||||
use std::process::{Command, Stdio};
|
||||
unsafe {
|
||||
// force locale to english to avoid issues with manpage output
|
||||
std::env::set_var("LANG", "C");
|
||||
}
|
||||
|
||||
let test_scenario = TestScenario::new("");
|
||||
|
||||
let child = Command::new(&test_scenario.bin_path)
|
||||
.arg("manpage")
|
||||
.arg("base64")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
|
||||
let output = child.wait_with_output().unwrap();
|
||||
|
||||
assert_eq!(output.status.code().unwrap(), 0);
|
||||
|
||||
assert!(output.stderr.is_empty());
|
||||
|
||||
let stdout_str = std::str::from_utf8(&output.stdout).unwrap();
|
||||
|
||||
assert!(stdout_str.contains("base64 alphabet"));
|
||||
|
||||
assert!(!stdout_str.to_ascii_lowercase().contains("base32"));
|
||||
}
|
||||
|
||||
@@ -202,62 +202,6 @@ fn util_invalid_name_invalid_command() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "true")]
|
||||
fn util_completion() {
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
let scenario = TestScenario::new("completion");
|
||||
if !scenario.bin_path.exists() {
|
||||
println!("Skipping test: Binary not found at {:?}", scenario.bin_path);
|
||||
return;
|
||||
}
|
||||
|
||||
let child = Command::new(&scenario.bin_path)
|
||||
.arg("completion")
|
||||
.arg("true")
|
||||
.arg("powershell")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
let output = child.wait_with_output().unwrap();
|
||||
assert_eq!(output.status.code(), Some(0));
|
||||
assert_eq!(output.stderr, b"");
|
||||
let output_str = String::from_utf8(output.stdout).unwrap();
|
||||
assert!(
|
||||
output_str.contains("using namespace System.Management.Automation"),
|
||||
"{output_str:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "true")]
|
||||
fn util_manpage() {
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
let scenario = TestScenario::new("completion");
|
||||
if !scenario.bin_path.exists() {
|
||||
println!("Skipping test: Binary not found at {:?}", scenario.bin_path);
|
||||
return;
|
||||
}
|
||||
|
||||
let child = Command::new(&scenario.bin_path)
|
||||
.arg("manpage")
|
||||
.arg("true")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
let output = child.wait_with_output().unwrap();
|
||||
assert_eq!(output.status.code(), Some(0));
|
||||
assert_eq!(output.stderr, b"");
|
||||
let output_str = String::from_utf8(output.stdout).unwrap();
|
||||
assert!(output_str.contains("\n.TH true 1 "), "{output_str:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn util_version() {
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
@@ -400,6 +400,10 @@ mod test_unlink;
|
||||
#[path = "by-util/test_uptime.rs"]
|
||||
mod test_uptime;
|
||||
|
||||
#[cfg(feature = "uudoc")]
|
||||
#[path = "uudoc/mod.rs"]
|
||||
mod test_uudoc;
|
||||
|
||||
#[cfg(feature = "users")]
|
||||
#[path = "by-util/test_users.rs"]
|
||||
mod test_users;
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use std::{env, path::PathBuf, process::Command, sync::OnceLock};
|
||||
|
||||
static UUDOC_BINARY_PATH: OnceLock<PathBuf> = OnceLock::new();
|
||||
|
||||
fn get_uudoc_command() -> Command {
|
||||
let uudoc_binary = UUDOC_BINARY_PATH.get_or_init(|| {
|
||||
let coreutils_binary = PathBuf::from(env!("CARGO_BIN_EXE_coreutils"));
|
||||
coreutils_binary.parent().unwrap().join("uudoc")
|
||||
});
|
||||
Command::new(uudoc_binary)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_manpage_generation() {
|
||||
let output = get_uudoc_command()
|
||||
.arg("manpage")
|
||||
.arg("ls")
|
||||
.output()
|
||||
.expect("Failed to execute command");
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"Command failed with status: {}",
|
||||
output.status
|
||||
);
|
||||
assert!(
|
||||
output.stderr.is_empty(),
|
||||
"stderr should be empty but got: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||
assert!(output_str.contains("\n.TH"), "{output_str}");
|
||||
assert!(output_str.contains('1'), "{output_str}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_manpage_coreutils() {
|
||||
let output = get_uudoc_command()
|
||||
.arg("manpage")
|
||||
.arg("coreutils")
|
||||
.output()
|
||||
.expect("Failed to execute command");
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"Command failed with status: {}",
|
||||
output.status
|
||||
);
|
||||
assert!(
|
||||
output.stderr.is_empty(),
|
||||
"stderr should be empty but got: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||
assert!(output_str.contains("\n.TH"), "{output_str}");
|
||||
assert!(output_str.contains("coreutils"), "{output_str}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_manpage_invalid_utility() {
|
||||
let output = get_uudoc_command()
|
||||
.arg("manpage")
|
||||
.arg("nonexistent_utility")
|
||||
.output()
|
||||
.expect("Failed to execute command");
|
||||
|
||||
// Should fail for invalid utility
|
||||
assert!(!output.status.success(), "Command should have failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_generation() {
|
||||
let output = get_uudoc_command()
|
||||
.arg("completion")
|
||||
.arg("ls")
|
||||
.arg("powershell")
|
||||
.output()
|
||||
.expect("Failed to execute command");
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"Command failed with status: {}",
|
||||
output.status
|
||||
);
|
||||
assert!(
|
||||
output.stderr.is_empty(),
|
||||
"stderr should be empty but got: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||
assert!(
|
||||
output_str.contains("using namespace System.Management.Automation"),
|
||||
"{output_str}"
|
||||
);
|
||||
}
|
||||
|
||||
// Prevent regression to:
|
||||
//
|
||||
// ❯ uudoc manpage base64 | rg --fixed-strings -- 'base32'
|
||||
// The data are encoded as described for the base32 alphabet in RFC 4648.
|
||||
// to the bytes of the formal base32 alphabet. Use \-\-ignore\-garbage
|
||||
// The data are encoded as described for the base32 alphabet in RFC 4648.
|
||||
// to the bytes of the formal base32 alphabet. Use \-\-ignore\-garbage
|
||||
#[test]
|
||||
fn test_manpage_base64() {
|
||||
let output = get_uudoc_command()
|
||||
.arg("manpage")
|
||||
.arg("base64")
|
||||
.env("LANG", "C") // force locale to english to avoid issues with manpage output
|
||||
.output()
|
||||
.expect("Failed to execute command");
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"Command failed with status: {}",
|
||||
output.status
|
||||
);
|
||||
assert!(
|
||||
output.stderr.is_empty(),
|
||||
"stderr should be empty but got: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||
assert!(output_str.contains("base64 alphabet"));
|
||||
assert!(!output_str.to_ascii_lowercase().contains("base32"));
|
||||
}
|
||||
Reference in New Issue
Block a user