diff --git a/Cargo.lock b/Cargo.lock index d36021a..16628f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -294,6 +294,15 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_complete" +version = "4.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7a9bfdb35811f9e59832f0f05975114d2251b415fb534108e6f34060fd772" +dependencies = [ + "clap", +] + [[package]] name = "clap_derive" version = "4.6.1" @@ -399,6 +408,7 @@ name = "cph-cli" version = "0.0.1" dependencies = [ "clap", + "clap_complete", "cph-check", "cph-diag", "cph-typst", diff --git a/README.md b/README.md index 7bc7424..5da52ff 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,12 @@ cph check <工程目录> # 校验合法性(6 类诊断) cph build <工程目录> --target student -o build/student.pdf # 渲讲义 PDF ``` +Shell 补全(可选): + +```sh +cph completions zsh > ~/.zfunc/_cph # 或 bash/fish/powershell/elvish +``` + ## 仓库布局 ``` diff --git a/crates/cph-cli/Cargo.toml b/crates/cph-cli/Cargo.toml index ba6f955..b55feb9 100644 --- a/crates/cph-cli/Cargo.toml +++ b/crates/cph-cli/Cargo.toml @@ -13,3 +13,4 @@ cph-check = { path = "../cph-check" } cph-diag = { workspace = true } cph-typst = { path = "../cph-typst" } clap = { version = "4", features = ["derive"] } +clap_complete = "4" diff --git a/crates/cph-cli/src/main.rs b/crates/cph-cli/src/main.rs index 10a0936..7cf9be5 100644 --- a/crates/cph-cli/src/main.rs +++ b/crates/cph-cli/src/main.rs @@ -46,6 +46,22 @@ enum Command { #[arg(short = 'o', long, value_name = "OUT")] out: Option, }, + /// Print a shell-completion script to stdout (clap_complete; ADR-0013 opt-in + /// sibling: a local convenience, no lesson involved). Pipe to your shell's + /// completion file, e.g. `cph completions zsh > ~/.zfunc/_cph`. + Completions { + /// Which shell to generate completions for. + shell: Shell, + }, +} + +#[derive(Debug, Clone, Copy, clap::ValueEnum)] +enum Shell { + Bash, + Zsh, + Fish, + PowerShell, + Elvish, } fn main() -> ExitCode { @@ -59,9 +75,29 @@ fn main() -> ExitCode { match cli.command { Command::Check { path } => run_check(&path, &engine), Command::Build { path, target, out } => run_build(&path, &engine, &target, out), + Command::Completions { shell } => run_completions(shell), } } +/// Emit a shell-completion script for `shell` to stdout. The script is built +/// from the same `Cli` clap definition above, so it tracks subcommands/flags as +/// they evolve. +fn run_completions(shell: Shell) -> ExitCode { + use clap::CommandFactory; + use clap_complete::Shell as CompShell; + + let sh = match shell { + Shell::Bash => CompShell::Bash, + Shell::Zsh => CompShell::Zsh, + Shell::Fish => CompShell::Fish, + Shell::PowerShell => CompShell::PowerShell, + Shell::Elvish => CompShell::Elvish, + }; + let mut cmd = Cli::command(); + clap_complete::generate(sh, &mut cmd, "cph", &mut std::io::stdout()); + ExitCode::SUCCESS +} + /// Print every diagnostic in `report` to stderr, followed by a summary line. fn print_diagnostics(report: &CheckReport) { for d in &report.diagnostics {