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]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.6.2"
|
version = "4.6.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011"
|
checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@@ -486,9 +486,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.6.2"
|
version = "4.6.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b"
|
checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@@ -508,14 +508,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.6.1"
|
version = "4.6.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
|
checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.119",
|
"syn 3.0.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -524,6 +524,16 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
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]]
|
[[package]]
|
||||||
name = "color-eyre"
|
name = "color-eyre"
|
||||||
version = "0.6.5"
|
version = "0.6.5"
|
||||||
@@ -2251,6 +2261,12 @@ version = "0.8.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "roff"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "323c417e1d9665a65b263ec744ba09030cfb277e9daa0b018a4ab62e57bc8189"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.28"
|
version = "0.1.28"
|
||||||
@@ -2550,6 +2566,7 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
|
"clap_mangen",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"directories",
|
"directories",
|
||||||
"dotenv",
|
"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 = { version = "4.6.2", features = ["derive", "color", "wrap_help", "env"] }
|
||||||
clap_complete = "4.6.7"
|
clap_complete = "4.6.7"
|
||||||
|
clap_mangen = "0.3.3"
|
||||||
ratatui = "0.30.2"
|
ratatui = "0.30.2"
|
||||||
unicode-width = "0.2"
|
unicode-width = "0.2"
|
||||||
color-eyre = "0.6.5"
|
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
|
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.
|
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
|
## Idioms
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -249,6 +249,9 @@ pub enum Commands {
|
|||||||
/// Tab completion for bash, zsh, and fish
|
/// Tab completion for bash, zsh, and fish
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
Completions(CompletionsCommand),
|
Completions(CompletionsCommand),
|
||||||
|
|
||||||
|
/// Print the man page to stdout, for packaging
|
||||||
|
Man,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args, Debug, Default)]
|
#[derive(Args, Debug, Default)]
|
||||||
@@ -315,6 +318,12 @@ pub enum CompletionsCommand {
|
|||||||
|
|
||||||
/// Remove the completion script again
|
/// Remove the completion script again
|
||||||
Uninstall(CompletionsArgs),
|
Uninstall(CompletionsArgs),
|
||||||
|
|
||||||
|
/// Print the completion script to stdout, for packaging
|
||||||
|
Print {
|
||||||
|
#[arg(value_enum)]
|
||||||
|
shell: Shell,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
|
|
||||||
use super::{unwritable, xdg};
|
use super::{unwritable, xdg};
|
||||||
use crate::cli::{Cli, CompletionsArgs, CompletionsCommand, Shell};
|
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 clap::CommandFactory;
|
||||||
use directories::UserDirs;
|
use directories::UserDirs;
|
||||||
use std::io::Write;
|
use std::io::{ErrorKind, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
/// The command being completed, and the file name the scripts are written as.
|
/// 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 {
|
match command {
|
||||||
CompletionsCommand::Install(args) => install(out, &targets(args)?),
|
CompletionsCommand::Install(args) => install(out, &targets(args)?),
|
||||||
CompletionsCommand::Uninstall(args) => uninstall(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::Config(_)
|
||||||
| Commands::Shares(_)
|
| Commands::Shares(_)
|
||||||
| Commands::Skills(_)
|
| Commands::Skills(_)
|
||||||
| Commands::Completions(_) => {
|
| Commands::Completions(_)
|
||||||
|
| Commands::Man => {
|
||||||
Err(CliError::usage("handled without a session; see main::run"))
|
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 config_path = config_path(&cli);
|
||||||
let file_config = match &config_path {
|
let file_config = match &config_path {
|
||||||
Some(path) => crate::persist::config::FileConfig::load(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(),
|
resolved.username.as_deref(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Commands::Completions(ref command) => {
|
|
||||||
return crate::commands::completions::run(out, command);
|
|
||||||
}
|
|
||||||
Commands::Config(ConfigCommand::Path) => {
|
Commands::Config(ConfigCommand::Path) => {
|
||||||
return crate::commands::settings::config_path(out, &store);
|
return crate::commands::settings::config_path(out, &store);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -412,6 +412,28 @@ fn installing_completions_is_repeatable_and_reversible() {
|
|||||||
assert_eq!(action(&again), "absent");
|
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 ------------------
|
// --- wishlist, the half that only touches the config file ------------------
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user