🦀

RustのコマンドラインライブラリArghの使い方

2021/11/02に公開

Rustでコマンドライン引数を渡す時に便利な、Arghというライブラリの利用方法をまとめます。
tokio-rs/tlsのexamplesコードがすっきり書けていて、何のライブラリを使っているのかと調べてみるとArghというライブラリでした。Googleのレポジトリにあるので、Googleの人が作っているようです。

Arghとは

  • コマンドライン引数をパースするためのライブラリです。
  • パースする引数はアトリビュートを使って指定するので、コードがとてもすっきり書けます。

Arghでサポートされている機能

リッチな機能はありませんが、1.6時点で主に以下の機能がサポートされていました。

  • 通常の引数(--argのようなタイプ)のサポート。
  • 通常の引数の短縮指定のサポート(--loglevel-lみたいに書く)やデフォルト値の設定、任意or必須かの指定。
  • Positional Arguments(command arg1 arg2 ..の様に順番で引数が決定するタイプ)のサポート。
  • サブコマンドのサポート。

利用例

それでは、実際のコードサンプルを見ていきましょう。Cargo.tomlには、0.1dependenciesに指定しましたが、執筆時のバージョンは0.16です。

[dependencies]
argh = "0.1"

例1. 通常の引数

READMEにあった例にもう少しサンプルを加えたものです。

use argh::FromArgs;

#[derive(FromArgs)]
/// Reach new heights.
struct GoUp {
    /// whether or not to jump
    #[argh(switch, short = 'j')]
    jump: bool,

    /// how high to go
    #[argh(option, default = "default_height()")]
    height: usize,

    /// an optional nickname for the pilot
    #[argh(option)]
    pilot_nickname: Option<String>,

    /// an optional direction which is "up" by default
    #[argh(option, default = "String::from(\"only up\")")]
    direction: String,
}

fn default_height() -> usize {
    5
}

fn main() {
    let up: GoUp = argh::from_env();
    println!("height is {}", up.height);

    println!("direction is {}", up.direction);

    if up.jump {
      println!("jump!");
    }

    match up.pilot_nickname {
        None => println!("pilot name is not specified"),
        Some(name) => {
            println!("pilot name is {}", name)
        },
    }
}

これをオプションを付けて実行すると次のようになります。

$ simple --pilot-nickname Mike -j
height is 5
direction is only up
jump!
pilot name is Mike

例2. Positional Arguments

次は、./command arg1 arg2 arg3 ...のように順番でコマンドの引数を特定する例です。
struct内の順序が引数の順序になり、最後の引数をvectorにすれば、可変長の引数を渡すことも可能です。

use argh::FromArgs;

#[derive(FromArgs)]
/// Demonstration for positional arguments.
struct PositionalDemo {
    #[argh(positional)]
    first: String,

    #[argh(positional)]
    others: Vec<String>,
}

fn main() {
    let demo: PositionalDemo = argh::from_env();

    println!("the first arg is {}", demo.first);
    println!("the other argument is {:?}", demo.others);
}

これをオプションを付けて実行すると次のようになります。

$ positional arg1 arg2 arg3 arg4
the first arg is arg1
the other argument is ["arg2", "arg3", "arg4"]

例3. サブコマンド

最後にサブコマンドの使い方です。これもREADMEにある例と同じですが、実行時のアウトプットも参考に載せておきます。

use argh::FromArgs;

#[derive(FromArgs, PartialEq, Debug)]
/// Top-level command.
struct TopLevel {
    #[argh(subcommand)]
    nested: MySubCommandEnum,
}

#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
enum MySubCommandEnum {
    One(SubCommandOne),
    Two(SubCommandTwo),
}

#[derive(FromArgs, PartialEq, Debug)]
/// First subcommand.
#[argh(subcommand, name = "one")]
struct SubCommandOne {
    #[argh(option)]
    /// how many x
    x: usize,
}

#[derive(FromArgs, PartialEq, Debug)]
/// Second subcommand.
#[argh(subcommand, name = "two")]
struct SubCommandTwo {
    #[argh(switch)]
    /// whether to fooey
    fooey: bool,
}

fn main() {
    let demo: TopLevel = argh::from_env();

    println!("command:\n {:?}", demo.nested);
}

onetwoというサブコマンドができ、以下のようにサブコマンドのコマンドも指定できます。

$ subcommand one --x 5
command:
 One(SubCommandOne { x: 5 })
$ subcommand two --fooey
command:
 Two(SubCommandTwo { fooey: true })

まとめ

Go言語のCobraのようなリッチな機能はないものの、アトリビュートですっきりしたコードが書けるのはとても魅力的でした。
実際に動かしながら理解したいという方のために、サンプルコードが少しでも時間短縮に役立てば幸いです。

Discussion