nikkie-ftnextの日記

イベントレポートや読書メモを発信

あの日argparseで書いたプログラムのRust版を僕はまだ知らない。第1話 超位置引数パーサーズ

はじめに

実はいくおな、nikkieです。

PyCon mini 東海 2024をきっかけにRustでCLIを作ることに興味を持ちました。
これまでのアウトプットと絡めて小さく素振りしていきます。

目次

「pytestでRust製CLIをe2eテストしてみよう」

attakeiさんによるトーク
https://tokai.pycon.jp/2024/#event-talk-2

自作されたageを題材に、pytestでe2eを書いているというお話でした1
聞く中で私はageの実装にも興味を持ちます。
CLAPなるものを使っているようでした

また、過去に『Rustの練習帳』で、出力するだけと簡単なコマンドのe2eを書いたことも思い出しました。
assert_cmdというものを使います。

CLAPの素振り:まずは位置引数だけを扱ってみよう!

RustでCLIとそのテストを実装してみたくなった私が思い付いたのは、過去に書いたプログラムの書き換えです。
私はCLIに思い入れがあり、Pythonのargparseで色々と書いてきました2

ハードルを下げるためにとにかく小さくしたかったので(だってRustは難しいじゃないですか!)、最初は位置引数だけ扱ってみることにしました。
これだけでも私には冒険です!

argparseバージョンの記事はこちら

$ python repeat.py シオン 3
シオンシオンシオン

これをRustでCLAPを使って書いていきます。
できあがったのがこちら

$ cargo run --quiet シオン 3
シオンシオンシオン

CLAPで位置引数を扱う

Command Line Argument Parser

CLAPのドキュメントから、「Command Line Applications in Rust」を見つけます。

grepを再実装していく(その名もgrrs)のですが、位置引数の扱いは1.2にありました。
https://rust-cli.github.io/book/tutorial/cli-args.html#parsing-cli-arguments-with-clap

CLAPのドキュメントのExampleも合わせて参考にします。
https://docs.rs/clap/latest/clap/#example

Cargo.toml(該当箇所のみ)

[dependencies]
clap = { version = "4", features = ["derive"] }

src/main.rs3

use clap::Parser;

#[derive(Parser)]
struct Cli {
    string: String,
    number: usize,
}

fn main() {
    let args = Cli::parse();
    println!("{}", args.string.repeat(args.number));
}

derive featureなるものがまるで魔法ですね。
これでargparseと同じ振る舞いのCLIが実装できました!

追加の学び

位置引数にデフォルト値を与える

これはめちゃめちゃ簡単でした(魔法だ...)

#[derive(Parser)]
struct Cli {
    string: String,
+    #[clap(default_value = "3")]
    number: usize,
}

デフォルト値を与えたことで、argparse版同様に省略できる位置引数となりました

$ cargo run --quiet シオン  
シオンシオンシオン

assert_cmdでコマンドのテスト

tests/cli.rs

use assert_cmd::Command;

#[test]
fn 文字列と回数を指定して繰り返す() {
    let mut cmd = Command::cargo_bin("repeat").unwrap();
    let assert = cmd.arg("大好き").arg("5").assert();
    assert.success().stdout("大好き大好き大好き大好き大好き\n");
}

今回のCLIは出力するだけなので、assert_cmdを使ってテストも書けています。
正常終了して、かつ、標準出力がこれこれと検証していますね。

$ cargo test

running 2 tests
test 文字列だけ指定した場合は3回繰り返す ... ok
test 文字列と回数を指定して繰り返す ... ok

終わりに

PyCon mini 東海でぐぐっと上がったRust製CLIへのモチベーションから、CLAPを素振りしました。

  • 位置引数だけのCLIが作れました!
    • derive feature、すっげ〜(どういう仕組みなの!?)
  • assert_cmdも使ってテストも合わせて書けています
    • PythonCLIのテストを書けるようになるまでに、私はだいぶ時間がかかりました

argparseの経験や『Rustの練習帳』の素振りの経験が重なって、今回のトピックは小さな背伸びという感じでした。
引き続きCLAPを素振りしていくぞ!(Next...

今回のソースコードはこちらです


  1. pytestについても多くを学べる発表でした
  2. argparseの技術同人誌も書きました
  3. main.rsのmain関数に書くことを思い出しました