From eeb098a20a1c64994e7ebdc86ff2ee125221fd1b Mon Sep 17 00:00:00 2001 From: HalFrgrd <4559349+HalFrgrd@users.noreply.github.com> Date: Thu, 21 May 2026 16:04:00 +0000 Subject: [PATCH] feat: Add JSON output support to flycomp and force static musl binary Changed the `--shell` flag to `--output` in `flycomp` and added `json` as a supported format. This prints the parsed `Command` structure directly as a JSON string. We also updated the `.cargo/config.toml` for `flycomp` to explicitly enable `target-feature=+crt-static` to ensure the `flycomp` musl binary is always statically linked. --- Cargo.lock | 2 ++ flycomp/.cargo/config.toml | 5 +++++ flycomp/Cargo.toml | 2 ++ flycomp/src/lib.rs | 6 +++--- flycomp/src/main.rs | 39 +++++++++++++++++++++++++++++++++----- 5 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 flycomp/.cargo/config.toml diff --git a/Cargo.lock b/Cargo.lock index c6f9373c..a72ae0f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -604,6 +604,8 @@ dependencies = [ "clap", "clap_complete", "log", + "serde", + "serde_json", ] [[package]] diff --git a/flycomp/.cargo/config.toml b/flycomp/.cargo/config.toml new file mode 100644 index 00000000..ef039b4f --- /dev/null +++ b/flycomp/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.x86_64-unknown-linux-musl] +rustflags = ["-C", "target-feature=+crt-static"] + +[target.aarch64-unknown-linux-musl] +rustflags = ["-C", "target-feature=+crt-static"] diff --git a/flycomp/Cargo.toml b/flycomp/Cargo.toml index 53567450..812193d5 100644 --- a/flycomp/Cargo.toml +++ b/flycomp/Cargo.toml @@ -16,3 +16,5 @@ anyhow = "1.0.102" clap = { version = "4.6.1", features = ["derive"] } clap_complete = { version = "4.6.3", features = ["unstable-dynamic"] } log = "0.4" +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" diff --git a/flycomp/src/lib.rs b/flycomp/src/lib.rs index cb1d5e1c..6ec511c1 100644 --- a/flycomp/src/lib.rs +++ b/flycomp/src/lib.rs @@ -11,7 +11,7 @@ // ────────────────────────────────────────────────────────────────────────────── /// A single command-line argument / flag. -#[derive(Debug, Clone, Default, PartialEq)] +#[derive(Debug, Clone, Default, PartialEq, serde::Serialize)] pub struct Arg { /// Long flag name, e.g. `--verbose`. pub long: Option, @@ -26,7 +26,7 @@ pub struct Arg { } /// A parsed command (or sub-command). -#[derive(Debug, Clone, Default, PartialEq)] +#[derive(Debug, Clone, Default, PartialEq, serde::Serialize)] pub struct Command { /// Name of the command, if known. pub name: Option, @@ -842,7 +842,7 @@ fn find_subcommand_mut<'a>(root: &'a mut Command, path: &[String]) -> Option<&'a /// /// Many tools print their help to *stderr* rather than *stdout*; this function /// returns whichever stream is non-empty (preferring stdout). -pub(crate) fn run_help(command_path: &str, extra_args: &[&str]) -> anyhow::Result { +pub fn run_help(command_path: &str, extra_args: &[&str]) -> anyhow::Result { let output = std::process::Command::new(command_path) .args(extra_args) .arg("--help") diff --git a/flycomp/src/main.rs b/flycomp/src/main.rs index 6decbbe1..a05d2ac9 100644 --- a/flycomp/src/main.rs +++ b/flycomp/src/main.rs @@ -1,19 +1,48 @@ use clap::Parser; +#[derive(Clone, Debug, clap::ValueEnum)] +#[value(rename_all = "lower")] +enum OutputFormat { + Bash, + Elvish, + Fish, + Powershell, + Zsh, + Json, +} + #[derive(Parser, Debug)] #[command(name = "flycomp")] #[command(about = "Generate shell completions from COMMAND --help output")] struct CliArgs { /// Command name or path to synthesize completions for. command: String, - /// Output shell type (defaults to bash). - #[arg(long, value_enum, default_value_t = clap_complete::Shell::Bash)] - shell: clap_complete::Shell, + /// Output format (defaults to bash). + #[arg(long, value_enum, default_value_t = OutputFormat::Bash)] + output: OutputFormat, } fn main() -> anyhow::Result<()> { let args = CliArgs::parse(); - let script = flycomp::generate_completion_script(&args.command, args.shell)?; - print!("{}", script); + + if matches!(args.output, OutputFormat::Json) { + let parsed_cmd = flycomp::synthesize_completion(&args.command, |extra_args| { + flycomp::run_help(&args.command, extra_args) + })?; + let json = serde_json::to_string_pretty(&parsed_cmd)?; + println!("{}", json); + } else { + let shell = match args.output { + OutputFormat::Bash => clap_complete::Shell::Bash, + OutputFormat::Elvish => clap_complete::Shell::Elvish, + OutputFormat::Fish => clap_complete::Shell::Fish, + OutputFormat::Powershell => clap_complete::Shell::PowerShell, + OutputFormat::Zsh => clap_complete::Shell::Zsh, + OutputFormat::Json => unreachable!(), + }; + let script = flycomp::generate_completion_script(&args.command, shell)?; + print!("{}", script); + } + Ok(()) }