にっき

技術的な話題はないです

Rust のコマンドラインオプション解析色々

本エントリは Rust その2 Advent Calendar 2016 - Qiita の 10 日目の記事です。

本記事では、Rust を使って CLI アプリケーションを作成してきた過程で得られた知見のひとつとして、コマンドラインオプション解析に用いる crate を簡単にまとめたいと思います。

基本事項

コマンドライン引数は std::env モジュールの args() (または args_os() )を用いて取得します。 最初の要素には通常実行ファイルのパスが格納され、引数自体は2番目以降に格納されます。

use std::env;

fn main() {
  let program: String = env::args().next().unwrap();
  let args: Vec<String> = env::args().skip(1).collect();
}

getopts

getopts は公式の提供しているコマンドライン解析用の crate です。 使い勝手としては Python の argparse が近いと思います。

github.com

getopts の特長として、必要最小限の機能しか提供していないためシンプルであり、導入における敷居が低いことが挙げられます。 そのため、単純なオプションを解析を行う場合にはこれで十分事足りると思います。

使い方は以下のようになります(公式ドキュメントの例を一部修正)。

extern crate getopts;
use std::{env, process};
use getopts::Options;

#[derive(Debug)]
struct Args {
  input: Vec<String>,
  output: Option<String>,
  // ...
}

fn print_usage(program: &str, opts: &Options) {
  let brief = format!("Usage: {} FILE [options]", program);
  print!("{}", opts.usage(&brief));
  process::exit(0);
}

fn parse_args() -> Args {
  let args: Vec<String> = env::args().collect();
  let program = args[0].clone();

  let mut opts = Options::new();
  opts.optopt("o", "", "set output file name", "NAME");
  opts.optflag("h", "help", "print this help menu");
  // ...

  let matches = opts.parse(&args[1..])
    .unwrap_or_else(|f| panic!(f.to_string()));

  if matches.opt_present("h") {
    print_usage(&program, &opts);
  }

  if matches.free.is_empty() {
    print_usage(&program, &opts);
  }

  Args {
    input: matches.free.clone(),
    output: matches.opt_str("o"),
    // ...
  }
}

fn main() {
  let args = parse_args();
  println!("{:?}", args);
}

docopt.rs

docopt.rs はドキュメント文字列を元にオプション解析器を生成する crate です。 多言語にも同名のパッケージがあると思いますが、それらと同じ感覚で使用することが出来ます。

github.com

docopt.rs 特有の機能として、RustcDecodable を実装した構造体を用いてオプションの解析結果を 受け取ることができます。 また、nightly 限定ですがドキュメントを解析して解析結果を受け取る構造体を自動生成する docopt! という マクロを使用することでオプション解析を簡略化することが出来ます(僕は stable 派なので使ったことないですが…)。

使用例は以下の通りです。

extern crate docopt;
extern crate rustc_serialize;

use docopt::Docopt;

const USAGE: &'static str = r"
JSON version of xargs
Usage:
  jsonargs [--parallel] <name> [<args>...]
  jsonargs (-h | --help)
Options:
  -h --help     Show this message.
  --parallel    Run each command parallel
";

#[derive(Debug, RustcDecodable)]
struct Args {
  flag_parallel: bool,
  arg_name: String,
  arg_args: Vec<String>,
}

fn main() {
  let args = Docopt::new(USAGE)
    .and_then(|opt| opt.decode())
    .unwrap_or_else(|e| e.exit());
  let Args { arg_name: name, arg_args: args, flag_parallel: parallel } = args;

  // ...
}

docopt.rs の詳細な使用方法ははすでに良質な解説記事がありますのでそちらもご参照ください。

qiita.com

clap.rs

getoptsdocopt.rs はどちらかと言うと小規模なアプリケーション向けのパーサであり、 複数のサブコマンドを用いるなど複雑なオプションが必要なアプリケーション向きでは無いです。

複雑なコマンドラインオプションの解析には clap.rs を用いることが出来ます。

github.com

claprustup でも用いられているコマンドラインパーサであり、 公式リポジトリで紹介されている機能一覧を見てもわかるように とにかく高機能です(語彙力)。

とりあえず気になるものだけ列挙してみると、次のような機能があります。

  • 色付きのヘルプメッセージの出力に対応
  • YAML によるオプション定義への対応
  • 各種シェル用の補完スクリプトの自動生成 (Bash, fish shell, Zsh)

使用例は以下の通りです。

extern crate clap;
use clap::{App, AppSettings, Arg};

fn build_app() -> clap::App<'static, 'static> {
  let program = std::env::args()
    .nth(0)
    .and_then(|s| {
      std::path::PathBuf::from(s)
        .file_stem()
        .map(|s| s.to_string_lossy().into_owned())
    })
    .unwrap();

  App::new(program)
    .about("find files")
    .version("0.0.1")
    .author("Author name <author@example.com>")
    .setting(AppSettings::VersionlessSubcommands)
    .arg(Arg::from_usage("-i --ignore=[IGNORE]   'Ignored pattern'"))
    .arg(Arg::from_usage("-m --matches=[MATCHES] 'Pattern to match'"))
    .arg(Arg::from_usage("-a --absolute          'Show absolute path'"))
    .arg(Arg::from_usage("-d --directory         'Show only directories'"))
    .arg(Arg::from_usage("-A --async             'Search asynchronously'"))
    .arg(Arg::from_usage("-M --max-items=[N]     'Limit of displayed items'"))
    // ...
}

fn main() {
  let matches = build_app().get_matches();
  let matchre : Option<&str> = matches.value_of("matches");
  let ignore: Option<&str> = matches.value_of("ignore");
  // ...、
}

個人的に便利だと思ったのが、各種シェル用の補完関数を自動生成する機能です。 例えば Zsh 用の補完スクリプトを生成する場合は次のようにすることで可能です。

use clap::Shell;

let mut file = std::fs::OpenOptions::new()
  .write(true)
  .create(true)
  .open(concat!("_", env!("CARGO_PKG_NAME")))
  .unwrap();

build_app().gen_completions_to(env!("CARGO_PKG_NAME"), Shell::Zsh, &mut file);

おわりに

以上、Rust でコマンドラインオプションの解析に使える crate の紹介でした。