feat(cli): print completion scripts and the man page to stdout
`completions install` writes into a home directory, which a package build does not have. `completions print <shell>` and `man` give it the files to put under /usr/share instead.
This commit is contained in:
Generated
+24
-7
@@ -476,9 +476,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.2"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011"
|
||||
checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -486,9 +486,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.6.2"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b"
|
||||
checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -508,14 +508,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.6.1"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
|
||||
checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
"syn 3.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -524,6 +524,16 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "clap_mangen"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "211d617eaa4b735c96c9e0228fcbdb5120ef623f2b8cb67ffb84c3e02dbc28a4"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"roff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color-eyre"
|
||||
version = "0.6.5"
|
||||
@@ -2251,6 +2261,12 @@ version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||
|
||||
[[package]]
|
||||
name = "roff"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "323c417e1d9665a65b263ec744ba09030cfb277e9daa0b018a4ab62e57bc8189"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.28"
|
||||
@@ -2550,6 +2566,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"clap_mangen",
|
||||
"color-eyre",
|
||||
"directories",
|
||||
"dotenv",
|
||||
|
||||
@@ -30,6 +30,7 @@ soulseek-rs-lib = { version = "19.0.0", path = "../soulseek-rs-lib" }
|
||||
|
||||
clap = { version = "4.6.2", features = ["derive", "color", "wrap_help", "env"] }
|
||||
clap_complete = "4.6.7"
|
||||
clap_mangen = "0.3.3"
|
||||
ratatui = "0.30.2"
|
||||
unicode-width = "0.2"
|
||||
color-eyre = "0.6.5"
|
||||
|
||||
@@ -236,6 +236,9 @@ this skill file into the local agent directories, or remove it: `agent`,
|
||||
completion script where bash, zsh, or fish looks for it, or take it back
|
||||
again: `shell`, `path`, `action`. For a human at a prompt, not for you.
|
||||
|
||||
**`completions print <shell>`** and **`man`** — the completion script or the
|
||||
man page on stdout, raw rather than records. For whoever builds a package.
|
||||
|
||||
## Idioms
|
||||
|
||||
```bash
|
||||
|
||||
@@ -249,6 +249,9 @@ pub enum Commands {
|
||||
/// Tab completion for bash, zsh, and fish
|
||||
#[command(subcommand)]
|
||||
Completions(CompletionsCommand),
|
||||
|
||||
/// Print the man page to stdout, for packaging
|
||||
Man,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Default)]
|
||||
@@ -315,6 +318,12 @@ pub enum CompletionsCommand {
|
||||
|
||||
/// Remove the completion script again
|
||||
Uninstall(CompletionsArgs),
|
||||
|
||||
/// Print the completion script to stdout, for packaging
|
||||
Print {
|
||||
#[arg(value_enum)]
|
||||
shell: Shell,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
|
||||
use super::{unwritable, xdg};
|
||||
use crate::cli::{Cli, CompletionsArgs, CompletionsCommand, Shell};
|
||||
use crate::output::{CliError, CliResult, CompletionRecord, Out};
|
||||
use crate::output::{CliError, CliResult, CompletionRecord, Exit, Out};
|
||||
use clap::CommandFactory;
|
||||
use directories::UserDirs;
|
||||
use std::io::Write;
|
||||
use std::io::{ErrorKind, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// The command being completed, and the file name the scripts are written as.
|
||||
@@ -65,6 +65,27 @@ pub fn run(out: &Out, command: &CompletionsCommand) -> CliResult {
|
||||
match command {
|
||||
CompletionsCommand::Install(args) => install(out, &targets(args)?),
|
||||
CompletionsCommand::Uninstall(args) => uninstall(out, &targets(args)?),
|
||||
CompletionsCommand::Print { shell } => print(&script(*shell)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn man() -> CliResult {
|
||||
let mut page = Vec::new();
|
||||
clap_mangen::Man::new(Cli::command().about("Soulseek client in Rust"))
|
||||
.render(&mut page)
|
||||
.expect("a Vec accepts every write");
|
||||
print(&page)
|
||||
}
|
||||
|
||||
fn print(bytes: &[u8]) -> CliResult {
|
||||
match std::io::stdout().write_all(bytes) {
|
||||
Err(error) if error.kind() != ErrorKind::BrokenPipe => {
|
||||
Err(CliError::new(
|
||||
Exit::Failure,
|
||||
format!("cannot write stdout: {error}"),
|
||||
))
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -330,7 +330,8 @@ fn dispatch(ctx: &Ctx, command: Commands) -> CliResult {
|
||||
| Commands::Config(_)
|
||||
| Commands::Shares(_)
|
||||
| Commands::Skills(_)
|
||||
| Commands::Completions(_) => {
|
||||
| Commands::Completions(_)
|
||||
| Commands::Man => {
|
||||
Err(CliError::usage("handled without a session; see main::run"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,14 @@ pub fn run(mut cli: Cli, out: &Out) -> CliResult {
|
||||
}
|
||||
}
|
||||
|
||||
match &cli.command {
|
||||
Some(Commands::Completions(command)) => {
|
||||
return crate::commands::completions::run(out, command);
|
||||
}
|
||||
Some(Commands::Man) => return crate::commands::completions::man(),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let config_path = config_path(&cli);
|
||||
let file_config = match &config_path {
|
||||
Some(path) => crate::persist::config::FileConfig::load(path)
|
||||
@@ -84,9 +92,6 @@ pub fn run(mut cli: Cli, out: &Out) -> CliResult {
|
||||
resolved.username.as_deref(),
|
||||
);
|
||||
}
|
||||
Commands::Completions(ref command) => {
|
||||
return crate::commands::completions::run(out, command);
|
||||
}
|
||||
Commands::Config(ConfigCommand::Path) => {
|
||||
return crate::commands::settings::config_path(out, &store);
|
||||
}
|
||||
|
||||
@@ -412,6 +412,28 @@ fn installing_completions_is_repeatable_and_reversible() {
|
||||
assert_eq!(action(&again), "absent");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_package_build_reads_completions_and_the_man_page_from_stdout() {
|
||||
let dir = Scratch::new("package-build");
|
||||
let config = dir.path().join("config.toml");
|
||||
std::fs::write(&config, "not = [toml").expect("a config file");
|
||||
let broken = config.display().to_string();
|
||||
|
||||
for (shell, marker) in [
|
||||
("bash", "complete -F"),
|
||||
("zsh", "#compdef soulseek-rs"),
|
||||
("fish", "complete -c soulseek-rs"),
|
||||
] {
|
||||
let output = run(&["--config", &broken, "completions", "print", shell]);
|
||||
assert_eq!(code(&output), EXIT_OK, "stderr: {}", stderr(&output));
|
||||
assert!(stdout(&output).contains(marker), "{shell} script");
|
||||
}
|
||||
|
||||
let man = run(&["--config", &broken, "man"]);
|
||||
assert_eq!(code(&man), EXIT_OK, "stderr: {}", stderr(&man));
|
||||
assert!(stdout(&man).contains(".TH soulseek-rs 1"));
|
||||
}
|
||||
|
||||
// --- wishlist, the half that only touches the config file ------------------
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user