Rustのコマンドラインパーサ『clap』でマクロが使えるようになりそう。

# はじめに
みなさま、コマンドラインツールは何で作っているでしょうか。Rustでは[clap](https://github.com/clap-rs/clap)や[structopt](https://github.com/TeXitoi/structopt)が有名ですよね。今回は`clap`がマクロでの記述に対応しそうなので、それについて解説したいと思います。

# clapとは
clapはRustでコマンドラインツールを作るためのクレートです。コマンドラインツールを作るときに、オプションの解析やヘルプメッセージの準備など面倒ですよね。clapはそんな面倒事を引き受けてくれます。百聞は一見に如かずということでサンプルを見てみましょう。

```rust
    let matches = App::new("My Super Program")
        .version("1.0")
        .author("Kevin K. <kbknapp@gmail.com>")
        .about("Does awesome things")
        .arg(Arg::new("config")
            .short('c')
            .long("config")
            .value_name("FILE")
            .about("Sets a custom config file")
            .takes_value(true))
        .arg(Arg::new("INPUT")
            .about("Sets the input file to use")
            .required(true)
            .index(1))
        .arg(Arg::new("v")
            .short('v')
            .multiple_occurrences(true)
            .takes_value(true)
            .about("Sets the level of verbosity"))
        .subcommand(App::new("test")
            .about("controls testing features")
            .version("1.3")
            .author("Someone E. <someone_else@other.com>")
            .arg(Arg::new("debug")
                .short('d')
                .about("print debug information verbosely")))
        .get_matches();
```
(https://github.com/clap-rs/clap のREADMEから引用)

なんとなく雰囲気はわかるのではないでしょうか。[ビルダーパターン](https://ja.wikipedia.org/wiki/Builder_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3)を用いて、引数を解析した構造体`match`を生成しています。けどなんか長ったらしいですね。

# structoptとは
structoptも同様なコマンドラインパーサーなのですが、マクロを使って引数を解析した構造体を生成できます。

```rust
#[derive(StructOpt, Debug)]
#[structopt(name = "basic")]
struct Opt {
    // A flag, true if used in the command line. Note doc comment will
    // be used for the help message of the flag. The name of the
    // argument will be, by default, based on the name of the field.
    /// Activate debug mode
    #[structopt(short, long)]
    debug: bool,

    // The number of occurrences of the `v/verbose` flag
    /// Verbose mode (-v, -vv, -vvv, etc.)
    #[structopt(short, long, parse(from_occurrences))]
    verbose: u8,

    /// Set speed
    #[structopt(short, long, default_value = "42")]
    speed: f64,

    /// Output file
    #[structopt(short, long, parse(from_os_str))]
    output: PathBuf,

    // the long option will be translated by default to kebab case,
    // i.e. `--nb-cars`.
    /// Number of cars
    #[structopt(short = "c", long)]
    nb_cars: Option<i32>,

    /// admin_level to consider
    #[structopt(short, long)]
    level: Vec<String>,

    /// Files to process
    #[structopt(name = "FILE", parse(from_os_str))]
    files: Vec<PathBuf>,
}
```

(https://github.com/TeXitoi/structopt のREADMEから引用)

こっちの方がスッキリしています。

# clapでもマクロでの定義に対応しそう
さて本題です。clapではv2.x.xまでは(多分)マクロによる定義はできなかったのですが、clap v3からはマクロによる定義ができるようになりそうです。内部ではstrcutoptと同じコードが動いているそうです。そこでclapのマクロ定義を使ってlinuxのheadコマンドの簡易的な実装を記述してみました。

:::note warn
このプログラムはclap = "3.0.0-beta.4"を使って実装しています。beta版なのでAPIはまだ安定していないことにご注意ください。使うのは3.0がreleaseされてからの方が良いでしょう。
:::

```rust:myhead.rs
use clap::{AppSettings, Clap};
use std::path::PathBuf;
use std::fs::File;
use std::path::Path;
use std::io::{self, BufReader};
use std::io::prelude::*;

#[derive(Clap)]
#[clap(version="1.0", author= "yukichi hukuzawa")]
#[clap(setting = AppSettings::ColoredHelp)]
struct Opts {
    // nオプションにのみ対応
    #[clap(short = 'n', long)]
    lines: Option<u64>,
    #[clap(name="FILE", parse(from_os_str))]
    files: Vec<PathBuf>,
}

fn main() {
    let opts = Opts::parse();
    // headはデフォルトで頭10行を表示　
    let mut n = 10;
    if let Some(_n) = opts.lines  {
        // nオプションが指定されていれば上書き
        n = _n;
    }

    // ファイルが一個なら普通にプリント
    if opts.files.len() == 1 {
        print(&opts.files[0], n).expect("Can't print!");
        return;
    }

    // ファイルが複数なら、それぞれのヘッダーのファイル名もプリントする
    for file in &opts.files {
        println!("==> {:?} <==", file);
        print(file, n).expect("Can't print!");
    }


}

fn print(filepath: impl AsRef<Path>,n: u64) -> io::Result<()> 
{

     let file = File::open(filepath)?;
     let file = BufReader::new(file);
     let mut count = 0;
     for line in file.lines() {
        if count >= n {
            break;
        }
        count += 1;
        println!("{}", line?);
     }
     Ok(())
}
```

## 実行例

### helpオプション
```
PS C:\Users\yukichi> myhead -h
```

```:実行結果

yukichi hukuzawa

    myhead.exe [OPTIONS] [FILE]...
ARGS:
    <FILE>...

FLAGS:
    -h, --help       Print help information
    -V, --version    Print version information

OPTIONS:
    -n, --lines <LINES>
```

### 単ファイルを実行

```
PS C:\Users\yukichi> myhead .\myhead.rs
```

```:実行結果
use clap::{AppSettings, Clap};
use std::path::PathBuf;
use std::fs::File;
use std::path::Path;
use std::io::{self, BufReader};
use std::io::prelude::*;

#[derive(Clap)]
#[clap(version="1.0", author= "yukichi hukuzawa")]
#[clap(setting = AppSettings::ColoredHelp)]
```

### nオプションつきで、複数ファイルに実行

```
PS C:\Users\yukichi> myhead -n 3 .\myhead.rs .\myuniq.rs
```

```:実行結果
==> ".\\myhead.rs" <==
use clap::{AppSettings, Clap};
use std::path::PathBuf;
use std::fs::File;
==> ".\\myuniq.rs" <==
use std::env;
use std::fs::{File, OpenOptions};
use std::collections::HashSet;
```

# 終わりに
この記事ではclapがマクロによる定義に対応しそうだよ！という紹介をしました。マクロって、使い手側からすると便利ですけど、謎めいてますよね。。

## 参考文献
- https://github.com/clap-rs/clap
- https://github.com/TeXitoi/structopt