From b8a2cfa9be78d0cdd2e08687346dfe3bcf82f86f Mon Sep 17 00:00:00 2001 From: SFG545 Date: Sat, 28 Feb 2026 01:01:33 -0600 Subject: [PATCH] feat: add CLI asset generation for shell completions and man page --- Cargo.lock | 27 +++++++++++ Cargo.toml | 2 + README.md | 4 ++ meson.build | 10 +++++ src/cli.rs | 7 +++ src/cli_assets.rs | 74 +++++++++++++++++++++++++++++++ src/commands.rs | 8 +++- src/main.rs | 1 + tools/meson/install-cli-assets.sh | 46 +++++++++++++++++++ 9 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 src/cli_assets.rs create mode 100755 tools/meson/install-cli-assets.sh diff --git a/Cargo.lock b/Cargo.lock index fa1a7ac..d6d4c86 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -226,6 +226,15 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_complete" +version = "4.5.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c757a3b7e39161a4e56f9365141ada2a6c915a8622c408ab6bb4b5d047371031" +dependencies = [ + "clap", +] + [[package]] name = "clap_derive" version = "4.5.55" @@ -244,6 +253,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +[[package]] +name = "clap_mangen" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ea63a92086df93893164221ad4f24142086d535b3a0957b9b9bea2dc86301" +dependencies = [ + "clap", + "roff", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -369,6 +388,8 @@ dependencies = [ "ar", "bzip2", "clap", + "clap_complete", + "clap_mangen", "fd-lock", "filetime", "flate2", @@ -1608,6 +1629,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "roff" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" + [[package]] name = "rpassword" version = "7.4.0" diff --git a/Cargo.toml b/Cargo.toml index 87eee7e..c525ad6 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,8 @@ petgraph = "0.8.3" fd-lock = "4.0.4" reqwest = { version = "0.13.2", default-features = false, features = ["blocking", "native-tls"] } filetime = "0.2.27" +clap_complete = "4.5.66" +clap_mangen = "0.2.31" [dev-dependencies] tempfile = "=3.25.0" diff --git a/README.md b/README.md index ad89432..4ed5106 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,10 @@ Depot is a source-based package manager designed for Linux. It focuses on reprod cargo build --release ``` +When installing via Meson (`meson install`), Depot now generates and installs: +- Shell completions for Bash, Zsh, and Fish +- A `depot(1)` man page + ### Installing a Package To install a package from a spec file: diff --git a/meson.build b/meson.build index 6cf65f1..d677554 100644 --- a/meson.build +++ b/meson.build @@ -125,3 +125,13 @@ install_data( 'contrib/README.md', install_dir: join_paths(get_option('datadir'), 'doc', meson.project_name()), ) + +meson.add_install_script( + sh, + join_paths(src_root, 'tools', 'meson', 'install-cli-assets.sh'), + join_paths(build_root, 'depot'), + join_paths(get_option('datadir'), 'bash-completion', 'completions'), + join_paths(get_option('datadir'), 'zsh', 'site-functions'), + join_paths(get_option('datadir'), 'fish', 'vendor_completions.d'), + join_paths(get_option('mandir'), 'man1'), +) diff --git a/src/cli.rs b/src/cli.rs index b8a039d..27bb514 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -112,6 +112,13 @@ pub enum Commands { }, /// Show current configuration Config, + /// Generate shell completion scripts and a man page into an output directory. + #[command(hide = true)] + GenerateArtifacts { + /// Output directory for generated files + #[arg(long, value_name = "DIR")] + out_dir: PathBuf, + }, /// Create a new package specification interactively MakeSpec { /// Output file path (defaults to .toml) diff --git a/src/cli_assets.rs b/src/cli_assets.rs new file mode 100644 index 0000000..e4f9222 --- /dev/null +++ b/src/cli_assets.rs @@ -0,0 +1,74 @@ +//! Generation helpers for CLI man pages and shell completions. + +use crate::cli::Cli; +use anyhow::{Context, Result}; +use clap::CommandFactory; +use clap_complete::Shell; +use std::fs; +use std::path::Path; + +const BIN_NAME: &str = "depot"; + +/// Generate all supported shell completion scripts and a man page into `out_dir`. +pub fn generate_cli_assets(out_dir: &Path) -> Result<()> { + fs::create_dir_all(out_dir) + .with_context(|| format!("Failed to create output directory {}", out_dir.display()))?; + + generate_completion(out_dir, Shell::Bash, "depot.bash")?; + generate_completion(out_dir, Shell::Zsh, "_depot")?; + generate_completion(out_dir, Shell::Fish, "depot.fish")?; + generate_man_page(out_dir, "depot.1")?; + Ok(()) +} + +fn command_for_generation() -> clap::Command { + Cli::command().name(BIN_NAME) +} + +fn generate_completion(out_dir: &Path, shell: Shell, filename: &str) -> Result<()> { + let mut command = command_for_generation(); + let output_path = out_dir.join(filename); + let mut buffer = Vec::new(); + clap_complete::generate(shell, &mut command, BIN_NAME, &mut buffer); + fs::write(&output_path, buffer).with_context(|| { + format!( + "Failed to write {} completion to {}", + shell, + output_path.display() + ) + })?; + Ok(()) +} + +fn generate_man_page(out_dir: &Path, filename: &str) -> Result<()> { + let output_path = out_dir.join(filename); + let mut buffer = Vec::new(); + clap_mangen::Man::new(command_for_generation()) + .render(&mut buffer) + .context("Failed to render clap man page")?; + fs::write(&output_path, buffer) + .with_context(|| format!("Failed to write man page {}", output_path.display()))?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn generate_cli_assets_writes_expected_files() { + let temp = tempfile::tempdir().unwrap(); + generate_cli_assets(temp.path()).unwrap(); + + let bash = temp.path().join("depot.bash"); + let zsh = temp.path().join("_depot"); + let fish = temp.path().join("depot.fish"); + let man = temp.path().join("depot.1"); + + assert!(bash.exists()); + assert!(zsh.exists()); + assert!(fish.exists()); + assert!(man.exists()); + assert!(!std::fs::read_to_string(&man).unwrap().is_empty()); + } +} diff --git a/src/commands.rs b/src/commands.rs index 6948988..e4946d5 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,7 +1,7 @@ use crate::cli::{Cli, Commands, RepoCommands, RepoKindArg}; use crate::{ - builder, config, cross, db, deps, index, install, locking, package, planner, signing, source, - staging, ui, + builder, cli_assets, config, cross, db, deps, index, install, locking, package, planner, + signing, source, staging, ui, }; use anyhow::{Context, Result}; use std::fs; @@ -1949,6 +1949,10 @@ pub fn run(cli: Cli) -> Result<()> { } } }, + Commands::GenerateArtifacts { out_dir } => { + cli_assets::generate_cli_assets(&out_dir)?; + ui::success(format!("Generated CLI assets in {}", out_dir.display())); + } Commands::Config => { let config = config::Config::for_rootfs(&cli.rootfs); let config_lock = locking::open_lock(&config)?; diff --git a/src/main.rs b/src/main.rs index fadeffc..c30a242 100755 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod builder; mod cli; +mod cli_assets; mod commands; mod config; mod cross; diff --git a/tools/meson/install-cli-assets.sh b/tools/meson/install-cli-assets.sh new file mode 100755 index 0000000..463f067 --- /dev/null +++ b/tools/meson/install-cli-assets.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [ "$#" -ne 5 ]; then + echo "usage: $0 " >&2 + exit 2 +fi + +depot_bin="$1" +bash_dir="$2" +zsh_dir="$3" +fish_dir="$4" +man1_dir="$5" + +if [ ! -x "$depot_bin" ]; then + echo "error: depot binary is not executable: $depot_bin" >&2 + exit 1 +fi + +if [ -z "${MESON_INSTALL_DESTDIR_PREFIX:-}" ]; then + echo "error: MESON_INSTALL_DESTDIR_PREFIX is not set" >&2 + exit 1 +fi + +resolve_dest() { + case "$1" in + /*) printf '%s%s\n' "${DESTDIR:-}" "$1" ;; + *) printf '%s/%s\n' "$MESON_INSTALL_DESTDIR_PREFIX" "$1" ;; + esac +} + +tmpdir="$(mktemp -d)" +trap 'rm -rf "$tmpdir"' EXIT + +"$depot_bin" generate-artifacts --out-dir "$tmpdir" + +bash_dest="$(resolve_dest "$bash_dir")" +zsh_dest="$(resolve_dest "$zsh_dir")" +fish_dest="$(resolve_dest "$fish_dir")" +man_dest="$(resolve_dest "$man1_dir")" + +mkdir -p "$bash_dest" "$zsh_dest" "$fish_dest" "$man_dest" +cp "$tmpdir/depot.bash" "$bash_dest/depot" +cp "$tmpdir/_depot" "$zsh_dest/_depot" +cp "$tmpdir/depot.fish" "$fish_dest/depot.fish" +cp "$tmpdir/depot.1" "$man_dest/depot.1"