diff options
| -rw-r--r-- | var/shell-complete.rs | 109 | 
1 files changed, 107 insertions, 2 deletions
| diff --git a/var/shell-complete.rs b/var/shell-complete.rs index 0a525d8..c69a426 100644 --- a/var/shell-complete.rs +++ b/var/shell-complete.rs @@ -52,8 +52,113 @@ pub struct Args {    pub command: String,  } +fn generate_bash<W>(command: &str, output: &mut W) +where +  W: io::Write, +{ +  let mut app = nitrocli::Args::clap(); +  app.gen_completions_to(command, clap::Shell::Bash, output); +} +  fn main() {    let args = Args::from_args(); -  let mut app = nitrocli::Args::clap(); -  app.gen_completions_to(&args.command, clap::Shell::Bash, &mut io::stdout()); +  generate_bash(&args.command, &mut io::stdout()) +} + +#[cfg(test)] +mod tests { +  use super::*; + +  use std::io; +  use std::ops::Add as _; +  use std::process; + +  /// Separate the given words by newlines. +  fn lines<'w, W>(mut words: W) -> String +  where +    W: Iterator<Item = &'w str>, +  { +    let first = words.next().unwrap_or(""); +    words +      .fold(first.to_string(), |words, word| { +        format!("{}\n{}", words, word) +      }) +      .add("\n") +  } + +  /// Check if `bash` is present on the system. +  fn has_bash() -> bool { +    match process::Command::new("bash").arg("-c").arg("exit").spawn() { +      // We deliberately only indicate that bash does not exist if we +      // get a file-not-found error. We don't expect any other error but +      // should there be one things will blow up later. +      Err(ref err) if err.kind() == io::ErrorKind::NotFound => false, +      _ => true, +    } +  } + +  /// Perform a bash completion of the given arguments to nitrocli. +  fn complete_bash<'w, W>(words: W) -> Vec<u8> +  where +    W: ExactSizeIterator<Item = &'w str>, +  { +    let mut buffer = Vec::new(); +    generate_bash("nitrocli", &mut buffer); + +    let script = String::from_utf8(buffer).unwrap(); +    let command = format!( +      " +set -e; +eval '{script}'; +export COMP_WORDS=({words}); +export COMP_CWORD={index}; +_nitrocli; +echo -n ${{COMPREPLY}} +      ", +      index = words.len(), +      words = lines(Some("nitrocli").into_iter().chain(words)), +      script = script +    ); + +    let output = process::Command::new("bash") +      .arg("-c") +      .arg(command) +      .output() +      .unwrap(); + +    output.stdout +  } + +  #[test] +  fn array_lines() { +    assert_eq!(&lines(vec![].into_iter()), "\n"); +    assert_eq!(&lines(vec!["first"].into_iter()), "first\n"); +    assert_eq!( +      &lines(vec!["first", "second"].into_iter()), +      "first\nsecond\n" +    ); +    assert_eq!( +      &lines(vec!["first", "second", "third"].into_iter()), +      "first\nsecond\nthird\n" +    ); +  } + +  #[test] +  fn complete_all_the_things() { +    if !has_bash() { +      return; +    } + +    assert_eq!(complete_bash(vec!["stat"].into_iter()), b"status"); +    assert_eq!( +      complete_bash(vec!["status", "--ver"].into_iter()), +      b"--version" +    ); +    assert_eq!(complete_bash(vec!["--version"].into_iter()), b"--version"); +    assert_eq!(complete_bash(vec!["--model", "s"].into_iter()), b"storage"); +    assert_eq!( +      complete_bash(vec!["otp", "get", "--model", "p"].into_iter()), +      b"pro" +    ); +  }  } | 
