Screaming Loud

日々是精進

Rustのstructoptで作ったCLIにシェル補完をつける

引き続きCLIシリーズの紹介です

cliを作ったはいいものの、やはりコマンドのシェル補完はないと厳しいですよね。

structoptで作ったCLIでも簡単に補完のスクリプトを生成できるようになっています。 structoptのベースであるclap側にその機能があり、それをstructoptで呼び出すという感じです。

structopt

早速コードを見ていきましょう。 以下はec2-searchのコードを一部省いて載せています。

use std::io;
use structopt::clap::Shell;
use structopt::StructOpt;

#[derive(Debug, StructOpt)]
struct Cli {
    #[structopt(subcommand)]
    cmd: Command,
}

#[derive(Debug, StructOpt)]
enum Command {
    #[structopt(about = "Prints version information")]
    Version,
    #[structopt(about = "Prints Completion")]
    Completion(CompletionOpt),
}
#[derive(Debug, StructOpt)]
enum CompletionOpt {
    Zsh,
    Bash,
    Fish,
}

#[tokio::main]
async fn main() {
    match Cli::from_args().cmd {
        Command::Version => version(),
        Command::Completion(opt) => match opt {
            CompletionOpt::Bash => completion(Shell::Bash),
            CompletionOpt::Zsh => completion(Shell::Zsh),
            CompletionOpt::Fish => completion(Shell::Fish),
        },
    }
}

fn version() {
    println!("ec2-search {}", env!("CARGO_PKG_VERSION"))
}

fn completion(s: Shell) {
    Cli::clap().gen_completions_to(env!("CARGO_BIN_NAME"), s, &mut io::stdout())
}

Cli::clap() でclapの関数を呼べるようになっているので、これで補完スクリプトを生成する gen_completions_to を呼び出します。 生成したものを標準出力に出したいので、stdoutに吐いています。

実際のコードは以下リンク先です。

github.com

homebrewに対応

brewでインストールさせている場合、tapのスクリプトに以下を設定することでインストール時にシェルのcompletionの設定ができます

 def install
    bin.install 'ec2s'

    # bash completion
    output = Utils.safe_popen_read("#{bin}/ec2s", 'completion', 'bash')
    (bash_completion / 'ec2s').write output
    # zsh completion
    output = Utils.safe_popen_read("#{bin}/ec2s", 'completion', 'zsh')
    (zsh_completion / '_ec2s').write output
  end

この記述は何をしているかというとbashzshの補完スクリプトを各シェルの補完用ディレクトリに保存しています。 実際には以下のようなコマンドを実行しているのと等価です。

bashの場合

$ ec2s completion bash >  /usr/local/etc/bash_completion.d/ec2s

zshの場合

$ ec2s completion zsh >  /usr/local/share/zsh/site-functions/_ec2s

実際のコードは以下リンクです

homebrew-tap/ec2-search.rb at cd8f5e4dc0201a0a670d737a3dde5007ba7ba726 · mocyuto/homebrew-tap · GitHub

ハマったところ

clapには conflicts_with というオプションを複数指定した場合に使っていないオプションを指定していると以下のような実行時エラーが発生します。

$ cargo run completion zsh
   Compiling ec2-search v0.9.1 (/Users/yuto/GitHub/ec2-search)
    Finished dev [unoptimized + debuginfo] target(s) in 26.67s
     Running `target/debug/ec2s completion zsh`
thread 'main' panicked at 'Fatal internal error. Please consider filing a bug report at https://github.com/clap-rs/clap/issues', /Users/yuto/.cargo/registry/src/github.com-1ecc6299db9ec823/clap-2.33.3/src/completions/zsh.rs:346:29
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

denoでも同じようなバグが発生しているので、 conflicts_with を使う場合気をつけましょう。

github.com

以下のように生成部分をテストに含めて壊れないかチェックしておくとよいでしょう。 https://github.com/mocyuto/ec2-search/blob/v0.9.1/src/main.rs#L56-L61