6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

NEAdvent Calendar 2023

Day 2

Rustのコマンドラインパーサーであるclapを使う

Posted at

こんにちは。この記事はNE Advent Calendar2日目の記事です。一年は早いですね。今年も色んなことがありました。ベースを再始動したり、ピアノを買ったり、比較的登山とかもできた気がしますが、何一つ身になっていない気もします。

とはいえ、食生活を変えたり、運動をし始めたりという良い習慣の流れはつくられ始めたので、また来年も続けられるといいなぁ、健康のトピックが家族親族で増えてきたのはやはり加齢のせいかもなぁと淡々と感じています。

さて、Rustのコマンドオプション解析のCrateである Clap の設定方法ががよく分からなくなるのでチップス的にアトリビュートの情報を書いておきます。

clapとは

Rustで作られたコマンドもよく使われるようになったかと思いますが、必ず必要なのに引数のオプション解析が面倒くさいです。その解析部分の面倒をよく見てくれるCrateです。デフォルトでは std::env::args 、同じようなCrateに getopts などがあります。

clapにはメソッドチェーン形式でオプションの定義を書いていく Builder形式リファレンス) と、マクロで宣言的に書いていく Derive形式リファレンス) が使えます。Derive形式の方が冗長にならなくていいのかな、という所感で、本記事でもそちらしか書いていません。

clapの最新は2023/12/02時点の最新、4.4.10を使っています。

準備

cargo new clap_test などで適当なプロジェクトを作り、 cargo add clap --features derive でderive形式も含めて使えるようにしておきます。さらにこの examplesrc/main.rc に書いて、色々変えていきながら表示を試します。

use clap::Parser;

/// Simple program to greet a person
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
    /// Name of the person to greet
    #[arg(short, long)]
    name: String,

    /// Number of times to greet
    #[arg(short, long, default_value_t = 1)]
    count: u8,
}

fn main() {
    let args = Args::parse();

    for _ in 0..args.count {
        println!("Hello {}!", args.name)
    }
}

これをcargo runするともう現時点でオプションのフォーマットを整えて出力してくれます。

$ cargo run -- --help
   Compiling clap_test v0.1.0 (/Users/masakuni/Desktop/work/tmp/clap_test)
    Finished dev [unoptimized + debuginfo] target(s) in 3.59s
     Running `target/debug/clap_test --help`
Simple program to greet a person

Usage: clap_test [OPTIONS] --name <NAME>

Options:
  -n, --name <NAME>    Name of the person to greet
  -c, --count <COUNT>  Number of times to greet [default: 1]
  -h, --help           Print help
  -V, --version        Print version

多幸感ある。

#[derive(Parser)]

Args構造体に clap の Parserトレイト を実装します。これによってこの構造体が引数の定義としておおむね扱えるようになります。

let args = Args::parse();

先の例では実装されたParserを実行し、ここで name オプションと count オプションを引数に取れる構造体を解析した、ということ。

#[command(author)]

このコマンドのauthorを設定します。

#[command(author = "Masakuni Ito")]

とも書けるし、書かなかった場合はCargo.tomlの authors を自動的に参照してくれるようです。ただ、このauthorは現状、基本的に出力されない模様で、テンプレート変更時などに利用できるようです。

#[command(version)]

このコマンドのバージョンを指定します。値を指定しなければCargo.tomlの値が参照されます。

#[command(version = "0.0.100")]

上記のように値を書くこともできて、この場合、Cargo.tomlより優先されますが、まぁ、あんまりここで指定することはないんじゃないかなぁ、という感想。

masakuni@mi-air % cargo run -- --version
    Blocking waiting for file lock on build directory
   Compiling clap_test v0.1.0 (/Users/masakuni/Desktop/work/tmp/clap_test)
    Finished dev [unoptimized + debuginfo] target(s) in 3.67s
     Running `target/debug/clap_test --version`
clap_test 0.0.100

#[command(about)]

このコマンドの概要を書きます。マクロ、Cargo.toml、ドキュメントコメントで指定することができて、それぞれ下記の優先度のようです。Cargo.tomlは about ではなく、 description の値が使われます。

# マクロで直接指定する
#[command(about = "一番優先されるぽい")]

# Cargo.tomlのdescription
description = "二番目に優先されるぽい"

# src/main.rc上部に書かれていたドキュメントコメントのサマリ
/// 三番目に優先されるぽい
$ cargo run -- --help
    Finished dev [unoptimized + debuginfo] target(s) in 0.10s
     Running `target/debug/clap_test --help`
一番優先されるぽい

Usage: clap_test [OPTIONS] --name <NAME>

Options:
  -n, --name <NAME>    Name of the person to greet
  -c, --count <COUNT>  Number of times to greet [default: 1]
  -h, --help           Print help
  -V, --version        Print version

じゃあ全部書かれてなかった場合はどうなるんだというと、何も記載されなくなります。ドキュメントコメントまで読んでくれるあたりがとても賢い。

#[command(long_about = None)]

コマンドのヘルプが長めに書けるようになります。 --help を指定したとき、オプションの説明がさらに丁寧なものになります。これもドキュメントコメントまで読んでくれます。

#[command(long_about = "ここに長めのコマンドの説明を書くことができます。\n様々な情熱に動かされながらこのコマンドを作ったストーリーを語ってもよいでしょう。")]

というように書くと

cargo run -- --help
   Compiling clap_test v0.1.0 (/Users/masakuni/Desktop/work/tmp/clap_test)
    Finished dev [unoptimized + debuginfo] target(s) in 2.44s
     Running `target/debug/clap_test --help`
ここに長めのコマンドの説明を書くことができます。
様々な情熱に動かされながらこのコマンドを作ったストーリーを語ってもよいでしょう。

Usage: clap_test [OPTIONS] --name <NAME>

Options:
  -n, --name <NAME>
          Name of the person to greet

  -c, --count <COUNT>
          Number of times to greet

          [default: 1]

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version

というようなオプションになります。

#[arg(short)]

短縮されたオプションを自動的に受け入れるようになります。冒頭のスクリプトのオプションだと namecount のオプションが自動的に -n-c で受け取れるようになります。

他にもArgsの構造体を

struct Args {
    #[arg(short, long)]
    first: bool,
    #[arg(short, long)]
    second: bool,
    #[arg(short, long)]
    third: bool,
}

のようにboolのオプションを受け取るようなものだった場合、

cargo run -- -fst

一文字だけの連続でオプションを設定可能にできたりします。

#[arg(long)]

正式なオプション名でオプションを受け取れるようにします。つまり -- (ハイフン2つ)の形式でオプションを受け取ります。ちなみにこれがなかった場合、ハイフン2つでのオプションを受け取ることができません。へー。

$ cargo run -- --count 1 --name masa
    Finished dev [unoptimized + debuginfo] target(s) in 0.10s
     Running `target/debug/clap_test --count 1 --name masa`
error: unexpected argument '--count' found

Usage: clap_test [OPTIONS] -n <NAME>

For more information, try '--help'.

#[arg(default_value_t = 1)]

オプションのデフォルト値を設定しておきます。この場合は1。 _t は何なんだ? というと型指定されているという意味らしく、例えばこの場合は countu8 が指定されるようです。この辺に言及があります。

要するに

    /// Number of times to greet
    #[arg(short, long, default_value_t = "abcd")]
    count: u8,

こういうことをやると

$ cargo run -- --count 1 --name masa
   Compiling clap_test v0.1.0 (/Users/masakuni/Desktop/work/tmp/clap_test)
error[E0308]: mismatched types
  --> src/main.rs:17:42
   |
17 |     #[arg(short, long, default_value_t = "abcd")]
   |                                          ^^^^^^ expected `u8`, found `&str`
18 |     count: u8,
   |            -- expected due to this

For more information about this error, try `rustc --explain E0308`.
error: could not compile `clap_test` (bin "clap_test") due to 2 previous errors

ビルドエラーにしてくれます。しかし、

    /// Number of times to greet
    #[arg(short, long, default_value = "abcd")]
    count: u8,

default_value のアトリビュートを使うと、型まできっちり見てくれず、この場合でもビルドは通ってしまいます。このため default_value_t の方が推奨、とのことです。

まだ指定できるものがありすぎるが

宣言的に書かれたオプションの構造体を Parser::Parse() で解析することで自在に取得できるclapすごい、というところでおしまい。ドキュメントコメントまで読んでくれるところがとても良いですね。

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?