Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion argh/examples/completion_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ enum Subcommands {
/// Generate shell completions.
#[argh(subcommand, name = "completion")]
struct CompletionCmd {
/// the shell to generate for (bash, zsh, fish)
/// the shell to generate for (bash, zsh, fish, nushell)
#[argh(positional)]
shell: String,
}
Expand Down Expand Up @@ -102,6 +102,12 @@ fn main() {
"fish" => {
println!("{}", argh_complete::fish::Fish::generate(&command_name, &cmd_info))
}
"nushell" => {
println!(
"{}",
argh_complete::nushell::Nushell::generate(&command_name, &cmd_info)
)
}
_ => eprintln!("Unsupported shell: {}", cmd.shell),
}
}
Expand Down
1 change: 1 addition & 0 deletions argh_complete/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

pub mod bash;
pub mod fish;
pub mod nushell;
pub mod zsh;

use argh_shared::CommandInfoWithArgs;
Expand Down
84 changes: 84 additions & 0 deletions argh_complete/src/nushell.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (c) 2026 Google LLC All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//! Generation of completions for Nushell.

use crate::Generator;
use argh_shared::{CommandInfoWithArgs, FlagInfoKind, Optionality};
use std::fmt::Write;

/// A generator for Nushell shell completions.
pub struct Nushell;

impl Generator for Nushell {
fn generate(cmd_name: &str, cmd: &CommandInfoWithArgs<'_>) -> String {
let mut out = String::new();
generate_nushell_cmd(&mut out, cmd_name, cmd);
out
}
}

fn generate_nushell_cmd(out: &mut String, cmd_name: &str, cmd: &CommandInfoWithArgs<'_>) {
// Generate the extern block for the current command
writeln!(out, "export extern \"{}\" [", cmd_name).unwrap();

// Generate flags
for flag in cmd.flags {
let mut flag_def = String::new();

if !flag.long.is_empty() {
flag_def.push_str(flag.long);
}

if let Some(short) = flag.short {
if flag.long.is_empty() {
// If there is only a short flag, we must output it as a short option.
// Nushell requires some long name for just `-s`, but standard is `-s`.
// If it's only short, typically `-$short`
flag_def.push_str(&format!("-{}", short));
} else {
flag_def.push_str(&format!("(-{})", short));
}
}

if let FlagInfoKind::Option { .. } = flag.kind {
flag_def.push_str(": string");
}

if !flag.description.is_empty() {
flag_def.push_str(&format!(" # {}", flag.description));
}

writeln!(out, " {}", flag_def).unwrap();
}

// Generate positional arguments
for pos in cmd.positionals {
let name = if pos.name.is_empty() { "arg" } else { pos.name };

let mut pos_def = String::new();
match pos.optionality {
Optionality::Required => pos_def.push_str(&format!("{}: string", name)),
Optionality::Optional => pos_def.push_str(&format!("{}?: string", name)),
Optionality::Repeating | Optionality::Greedy => {
pos_def.push_str(&format!("...{}: string", name))
}
}

if !pos.description.is_empty() {
pos_def.push_str(&format!(" # {}", pos.description));
}

writeln!(out, " {}", pos_def).unwrap();
}

writeln!(out, "]").unwrap();
writeln!(out).unwrap();

// Recurse for subcommands
for subcmd in &cmd.commands {
let next_cmd_name = format!("{} {}", cmd_name, subcmd.name);
generate_nushell_cmd(out, &next_cmd_name, &subcmd.command);
}
}
11 changes: 11 additions & 0 deletions argh_complete/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,14 @@ fn test_fish_generator() {
assert!(fish_out.contains("complete -c mycmd -n 'not __fish_seen_subcommand_from subcmd' -f -l verbose -s v -d 'verbose output'"));
assert!(fish_out.contains("complete -c mycmd -n 'not __fish_seen_subcommand_from subcmd' -f -a 'subcmd' -d 'a sub command'"));
}

#[cfg(test)]
#[test]
fn test_nushell_generator() {
let cmd = make_mock_command();
let nushell_out = crate::nushell::Nushell::generate("mycmd", &cmd);

assert!(nushell_out.contains("export extern \"mycmd\" ["));
assert!(nushell_out.contains("--verbose(-v) # verbose output"));
assert!(nushell_out.contains("export extern \"mycmd subcmd\" ["));
}
Loading