mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-25 14:53:34 +00:00
* fix: guard zsh compdef call to prevent error before compinit The generated ironclaw.zsh completions file calls compdef without checking if it exists. Users who source this file before compinit runs in their .zshrc get "compdef: command not found" on every terminal open. Wrap the call with the standard (( $+functions[compdef] )) guard. Closes #420 Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(completions): apply compdef guard during zsh generation Instead of hand-patching the generated ironclaw.zsh file (which is fragile and lost on regeneration), patch the compdef call in the generation code itself. The Zsh output is post-processed to wrap `compdef _ironclaw ironclaw` with a `$+functions[compdef]` guard. Regenerated ironclaw.zsh from the patched code to stay in sync. Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]>
79 lines
2.6 KiB
Rust
79 lines
2.6 KiB
Rust
use clap::{CommandFactory, Parser};
|
|
use clap_complete::{Shell, generate};
|
|
use std::io::{self, Write};
|
|
|
|
/// Generate shell completion scripts for ironclaw
|
|
#[derive(Parser, Debug)]
|
|
pub struct Completion {
|
|
/// The shell to generate completions for
|
|
#[arg(value_enum, long)]
|
|
pub shell: Shell,
|
|
}
|
|
|
|
impl Completion {
|
|
pub fn run(&self) -> anyhow::Result<()> {
|
|
let mut cmd = crate::cli::Cli::command();
|
|
let bin_name = cmd.get_name().to_string();
|
|
|
|
if self.shell == Shell::Zsh {
|
|
// Generate to buffer so we can patch the compdef call.
|
|
// clap_complete emits bare `compdef _ironclaw ironclaw` which
|
|
// errors if sourced before compinit. Guard it so the script
|
|
// works in all sourcing contexts.
|
|
let mut buf = Vec::new();
|
|
generate(self.shell, &mut cmd, bin_name.clone(), &mut buf);
|
|
let script = String::from_utf8(buf)?;
|
|
|
|
let bare = format!("compdef _{0} {0}", bin_name);
|
|
let guarded = format!("(( $+functions[compdef] )) && compdef _{0} {0}", bin_name);
|
|
let patched = script.replace(&bare, &guarded);
|
|
|
|
io::stdout().write_all(patched.as_bytes())?;
|
|
} else {
|
|
generate(self.shell, &mut cmd, bin_name, &mut io::stdout());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use clap::CommandFactory;
|
|
|
|
#[test]
|
|
fn test_run_generates_output() {
|
|
let completion = Completion { shell: Shell::Zsh };
|
|
let mut cmd = crate::cli::Cli::command();
|
|
let bin_name = cmd.get_name().to_string();
|
|
let mut buf = Vec::new();
|
|
generate(completion.shell, &mut cmd, bin_name, &mut buf);
|
|
assert!(!buf.is_empty(), "generate() should produce output");
|
|
}
|
|
|
|
#[test]
|
|
fn test_zsh_compdef_guard_applied() {
|
|
let mut cmd = crate::cli::Cli::command();
|
|
let bin_name = cmd.get_name().to_string();
|
|
let mut buf = Vec::new();
|
|
generate(Shell::Zsh, &mut cmd, bin_name.clone(), &mut buf);
|
|
let raw = String::from_utf8(buf).unwrap();
|
|
|
|
// Apply the same patching logic as run()
|
|
let bare = format!("compdef _{0} {0}", bin_name);
|
|
let guarded = format!("(( $+functions[compdef] )) && compdef _{0} {0}", bin_name);
|
|
let patched = raw.replace(&bare, &guarded);
|
|
|
|
let bare_compdef = format!(" compdef _{0} {0}\n", bin_name);
|
|
assert!(
|
|
!patched.contains(&bare_compdef),
|
|
"bare compdef should not appear after patching"
|
|
);
|
|
assert!(
|
|
patched.contains("$+functions[compdef]"),
|
|
"patched output should contain compdef guard"
|
|
);
|
|
}
|
|
}
|