実践structopt: Rustのコマンドラインパーサを使う

# 2019/10/23追記
structopt v0.3にてbreaking changeが入ったため、それに合わせてソースコードを更新しました。

# はじめに
[structopt](https://github.com/TeXitoi/structopt)はRustのコマンドラインパーサです。コマンドラインパーサとしては[clap](https://clap.rs)が有名ですが、structoptはstruct定義からclapのコードを自動生成するものです。
本家のドキュメントを含めてあまり細かい例が書かれた記事がなかったので、ここではそれなりに実践的な例を示します。

ちなみに現在計画中の[clap v3](https://clap.rs/2019/03/08/clap-v3-update-structopt/)ではstructopt相当の機能が取り込まれるようです。記法が変わる可能性はありますが、似たような書き方がclapでも使えるようになりそうです。

# ドキュメント
まず最初に参照すべきドキュメントについて書きます。先に述べた通り、structoptはclapのコードを生成します。そのため細かい設定をしようとするとstructoptよりclapのドキュメントを読むことになります。structoptのドキュメントもあまり詳細な具体例が載っていないのですが、詳細を書いていくとclapの説明になってしまうためやむを得ない感じもします。

- https://docs.rs/structopt
- https://docs.rs/clap


# サンプル
以下のソースを使って解説していきます。

https://github.com/dalance/structopt-sample

```rust
use lazy_static::lazy_static;
use regex::Regex;
use std::path::PathBuf;
use structopt::clap::arg_enum;
use structopt::{clap, StructOpt};

#[derive(Debug, StructOpt)]
#[structopt(name = "App name")]
#[structopt(long_version(option_env!("LONG_VERSION").unwrap_or(env!("CARGO_PKG_VERSION"))))]
#[structopt(setting(clap::AppSettings::ColoredHelp))]
pub struct Opt {
    #[structopt(name = "WORDS")]
    pub words: Vec<String>,

    #[structopt(short = "n")]
    pub count: Option<usize>,

    // before Rust 1.32
    //#[structopt(short = "p", long = "path", parse(from_os_str))]
    //pub path: PathBuf,
    #[structopt(short = "p", long = "path")]
    pub path: PathBuf,

    #[structopt(short = "r", long = "regex")]
    pub regex: Regex,

    #[structopt(
        short = "t",
        long = "threads",
        default_value(&THREADS),
        value_name = "NUM"
    )]
    pub threads: usize,

    #[structopt(
        short = "m",
        long = "method",
        possible_values(&Method::variants())
    )]
    pub method: Option<Method>,

    #[structopt(short = "a", conflicts_with_all(&["b", "c"]))]
    pub a: bool,

    #[structopt(short = "b", conflicts_with_all(&["a", "c"]))]
    pub b: bool,

    #[structopt(short = "c", conflicts_with_all(&["a", "b"]))]
    pub c: bool,

    #[structopt(short = "v", long = "verbose")]
    pub verbose: bool,

    #[structopt(subcommand)]
    pub sub: Sub,
}

#[derive(Debug, StructOpt)]
pub enum Sub {
    #[structopt(name = "sub1", about = "sub command1")]
    #[structopt(setting(clap::AppSettings::ColoredHelp))]
    Sub1,
}

arg_enum! {
    #[derive(Debug)]
    pub enum Method {
        A,
        B,
        C,
    }
}

lazy_static! {
    static ref THREADS: String = format!("{}", num_cpus::get());
}

fn main() {
    let _ = Opt::from_args();
}
```

これは以下のようなコマンドラインオプションを生成します。
実践といいつつ名前が適当な感じですいません…。

```console
$ structopt-sample --help
App name 0.1.0
dalance@gmail.com

USAGE:
    structopt-sample [FLAGS] [OPTIONS] --path <path> --regex <regex> [WORDS]... <SUBCOMMAND>

FLAGS:
    -a
    -b
    -c
    -h, --help       Prints help information
    -V, --version    Prints version information
    -v, --verbose

OPTIONS:
    -n <count>
    -m, --method <method>     [possible values: A, B, C]
    -p, --path <path>
    -r, --regex <regex>
    -t, --threads <NUM>       [default: 32]

ARGS:
    <WORDS>...

SUBCOMMANDS:
    help    Prints this message or the help of the given subcommand(s)
    sub1    sub command1
```

この表示は白黒ですが、実際にはclapがいい感じに色付けして表示してくれます。

# clapのインポート

structoptはclapをエクスポートしています。clap以下を参照することはよくあるのでuseしておくと便利です。

```rust
use structopt::{clap, StructOpt};
```

# パーサ全体の設定

まず`#[derive(StructOpt)]`でこのstructにstructoptを適用することを示します。

```rust
#[derive(Debug, StructOpt)]
pub struct Opt {
}
```

structへのderiveでパーサの設定を渡せます。これは[clap::App](https://docs.rs/clap/latest/clap/struct.App.html)の呼び出しに相当するのでclap側のドキュメントを見ながら必要な関数を呼んでいきましょう。

```rust
#[structopt(name = "App name")]
```

`関数名 = 引数`でその引数を使った関数呼び出しができるので、この例だと`name("App name")`を呼ぶコードが生成されます。

~~ただ、このように書くと関数の引数にリテラルしか渡すことができません。`&str`や`usize`を要求する関数はいいのですが、例えばenumを渡したい場合や何らかのコードの実行結果を渡したい場合はrawで囲みます。こうすると引数部分の文字列リテラルをRustのコードとして評価した結果を渡すことができます。~~
structopt v0.3でリテラルでない任意のコードを渡すことができるようになり、rawは不要になりました。

```rust
#[structopt(setting(clap::AppSettings::ColoredHelp))]
```

この例ではhelpを色付けして表示する設定をしています。

## long_versionにgitリビジョンを入れる
開発中のバイナリを部分的に配布したりするときなど、gitのリビジョン情報を埋め込んでおきたいことがあります。

```rust
#[structopt(long_version(option_env!("LONG_VERSION").unwrap_or(env!("CARGO_PKG_VERSION"))))]
```

このようにすることでコンパイル時に環境変数`LONG_VERSION`が設定されていれば`long_version`がその値に、なければ`Cargo.toml`のバージョン（`CARGO_PKG_VERSION`）が使われます。
すると以下のようにgitリビジョンを埋め込むことができます。

```console
$ LONG_VERSION=`git rev-parse HEAD` cargo build
```

# 各オプションの設定
structのメンバへのderiveでオプション毎の設定を書くことができます。こちらは[clap::Arg](https://docs.rs/clap/latest/clap/struct.Arg.html)の呼び出しになるのでそちらのドキュメントから必要なものを探します。

## オプションの型
オプションの型によってそのオプションが必須かどうかや複数取れるかどうかを示します。
取れる型は`bool`と`FromStr`を実装した型及びその`Vec`と`Option`です。

```rust
    #[structopt(name = "WORDS")]
    pub words: Vec<String>,

    #[structopt(short = "n")]
    pub count: Option<usize>,

    #[structopt(short = "v", long = "verbose")]
    pub verbose: bool,
```

## デフォルト値を動的に変える
`raw`を使えば`default_value`の値を動的に変えることができます。ただ、deriveにすべて書くのは可読性が悪いので、`lazy_static!`と組み合わせるといいでしょう。

```rust
    #[structopt(
        short = "t",
        long = "threads",
        default_value(&THREADS),
        value_name = "NUM"
    )]
    pub threads: usize,
```

ここでは`lazy_static!`を使ってCPU数を取得して`threads`オプションのデフォルト値としています。

```rust
lazy_static! {
    static ref THREADS: String = format!("{}", num_cpus::get());
}
```

## 列挙型

オプション引数に特定のパターンしか許可しない場合は、直接列挙型にすることができます。

列挙型定義を`arg_enum!`で囲んで

```rust
arg_enum! {
    #[derive(Debug)]
    pub enum Method {
        A,
        B,
        C,
    }
}
```
`possible_values`に渡します。

```rust
    #[structopt(
        short = "m",
        long = "method",
        possible_values(&Method::variants())
    )]
    pub method: Option<Method>,
```


## 配列を渡す
`conflicts_with`など文字列リテラルの配列を渡すケースがありますが、これも素のderiveでは書けないので`raw`にします。
この例では`-a`、`-b`、`-c`はどれか一つしか指定できません。

```rust
    #[structopt(short = "a", conflicts_with_all(&["b", "c"]))]
    pub a: bool,

    #[structopt(short = "b", conflicts_with_all(&["a", "c"]))]
    pub b: bool,

    #[structopt(short = "c", conflicts_with_all(&["a", "b"]))]
    pub c: bool,
```

## 型変換
`FromStr`が実装されている型はそのまま受け取れますがそうでない場合何らかの変換が必要です。
例えば`PathBuf`を受けとりたい場合は以下でした。

```rust
    #[structopt(short = "p", long = "path", parse(from_os_str))]
    pub path: PathBuf,
```

この変換は頻出なのでstructoptで`from_os_str`が定義されていて`OsStr`経由で変換できるとドキュメントにも書いてありますが、実はRust1.32から`PathBuf`にも`FromStr`が実装されたので、今はこれでも通ります。

```rust
    #[structopt(short = "p", long = "path")]
    pub path: PathBuf,
```

例えば`Regex`も`FromStr`を実装しているのでそのまま通ります。

```rust
    #[structopt(short = "r", long = "regex")]
    pub regex: Regex,
```

# サブコマンド
サブコマンドはメインのstructにenumを入れて`#[structopt(subcommand)]`で指定します。

```rust
    #[structopt(subcommand)]
    pub sub: Sub,
```

```rust
#[derive(Debug, StructOpt)]
pub enum Sub {
    #[structopt(name = "sub1", about = "sub command1")]
    #[structopt(setting(clap::AppSettings::ColoredHelp))]
    Sub1,
}
```

clap::Appの設定はサブコマンド毎に独立なのでそれぞれ設定する必要があります。
ここでの`clap::AppSettings::ColoredHelp`は`sub1 --help`したときの色付け設定になります。
メインのstructなしにいきなりenumから書いても大丈夫です。

# パース実行

最後に`from_args`を呼ぶことで、パース結果のstructが得られます。

```rust
    let _ = Opt::from_args();  
```

パースエラーはclap側で（エラーメッセージの表示からアプリの終了まで）ハンドリングされるので、ここでは正常にパースされたstructそのものが返ってきます。