GWにRustを触り始めたら楽しくなってきました。
新しい言語を触るのはやはり楽しいです。
今回はなんやかんやするコマンドを作ろうと思って、
引数をいい感じにしてくれるライブラリないかなと思って調べてたら、
googleのarghというライブラリがあったのでそれを触った備忘録です。
google/arghはこちら
基本的に使用するCargo.tomlはinitして、dependenciesにarghを追加した下記のようなのものでした。
Cargo.toml
[package] name = "argh_opt" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] argh = "0.1.7"
目次
1.1 --hoge fugaのようなオプションを設定する
よくある ./bin/foo --hoge fuga
みたいなやつですね。
これを実装してみましょう。
src/main.rs
use argh::FromArgs; #[derive(FromArgs, Debug)] /// struct FooArg { /// hoge #[argh(option)] hoge: String, } fn main() { let a: FooArg = argh::from_env(); println!("{:?}", a); println!("{:?}", a.hoge); }
/// があるのは、descriptionを書くのが必須のようで、エラーになってしまうからこうしました。
argh(description= "...")とも書けるようですが、ドキュメンテーションコメントで書いても拾ってくれるみたいでした。
これを実行してみるとこんな感じになります。
$cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.22s Running `target/debug/argh_opt` Required options not provided: --hoge $cargo run -- --hoge fuga Finished dev [unoptimized + debuginfo] target(s) in 0.02s Running `target/debug/argh_opt --hoge fuga` FooArg { hoge: "fuga" } "fuga"
これで--hogeが必須のオプションになりました。
--hogeを必須ではなく任意にしたい場合は下記のように型を Option[String]
にするといいようです。
struct FooArg { /// hoge #[argh(option)] hoge: Option[String], }
1.2 数字やbool値を引数に渡す
次は ./bin/foo --hoge bar -- fuga --piyo 100
みたいなものを目指します。
数字は型に数字系のデータ型を指定し、
boolはoptionではなくswitchを指定することで使えるようでした。
use argh::FromArgs; #[derive(FromArgs, Debug)] /// struct FooArg { /// hoge #[argh(option)] hoge: String, /// fuga #[argh(switch)] fuga: bool, /// piyo #[argh(option)] piyo: i32, } fn main() { let a: FooArg = argh::from_env(); println!("{:?}", a); println!("{:?} {:?} {:?}", a.hoge, a.fuga, a.piyo); }
$cargo run -- --hoge foo --fuga --piyo 10 Finished dev [unoptimized + debuginfo] target(s) in 0.02s Running `target/debug/argh_opt --hoge foo --fuga --piyo 10` FooArg { hoge: "foo", fuga: true, piyo: 10 } "foo" true 10 # 数字の部分に文字列を渡したらエラーにしてくれました $cargo run -- --hoge foo --fuga --piyo bar Finished dev [unoptimized + debuginfo] target(s) in 0.35s Running `target/debug/argh_opt --hoge foo --fuga --piyo bar` Error parsing option '--piyo' with value 'bar': invalid digit found in string zsh: exit 1 cargo run -- --hoge foo --fuga --piyo bar
1.3 デフォルト値を設定する
default = "関数名"で指定してあげると良さそうでした。
ダブルクオートの中に文字列で関数を指定するのどことなく不思議な感じがしますね。
use argh::FromArgs; #[derive(FromArgs, Debug)] /// struct FooArg { /// a #[argh(option, default = "String::from(\"hoge1\")")] hoge1: String, /// a #[argh(option, default = "default_hoge()")] hoge2: String, /// a #[argh(option, default = "\"hoge\".to_string()")] hoge3: String, } fn main() { let a: FooArg = argh::from_env(); println!("{:?}", a); println!("{:?} {:?} {:}", a.hoge1, a.hoge2, a.hoge3); } fn default_hoge() -> String { return "hoge2".to_string(); }
なお、関数の型があっていないときはコンパイルエラーになりました。
例えば上記のdefault_hogeをi32が返るように変更し、
コンパイルするとエラーになった様子です。
fn default_hoge() -> i32 { return 100; }
error[E0308]: mismatched types --> src/main.rs:10:30 | 10 | #[argh(option, default = "default_hoge()")] | ^^^^^^^^^^^^^^^^- help: try using a conversion method: `.to_string()` | | | expected struct `String`, found `i32`
便利ですね〜
1.4 from_str_fnを使って文字列をstructに変換する
なんらかのフォーマットのデータを入力時に変換してもらおうと思います。
例えば 14:15
のような時間のフォーマットを変換します。
./bin/foo --time 14:15
みたいなコマンドのイメージです。
from_str_fnに、 &strを受け取りResult<T, String>を返す関数を渡すといい感じに変換してくれるみたいでした。
use argh::FromArgs; #[derive(FromArgs, Debug)] /// struct FooArg { /// a #[argh(option, from_str_fn(str_to_time))] time: Time, } #[derive(Debug)] struct Time { _h: u32, _m: u32, } fn str_to_time(v: &str) -> Result<Time, String> { let x: Vec<&str> = v.split(":").collect(); let h = match x[0].parse::<u32>() { Ok(x) => x, Err(e) => return Err(e.to_string()) }; let m = match x[1].parse::<u32>() { Ok(x) => x, Err(e) => return Err(e.to_string()) }; if h >= 24 { Err(format!("{} is too large", h)) } else if m >= 60 { Err(format!("{} is too large", m)) } else { Ok(Time { _h: h, _m: m }) } } fn main() { let a: FooArg = argh::from_env(); println!("{:?}", a); println!("{:?}", a.time); }
実行してみると
$cargo run -- --time 14:15 Finished dev [unoptimized + debuginfo] target(s) in 0.05s Running `target/debug/argh_opt --time '14:15'` FooArg { time: Time { _h: 14, _m: 15 } } Time { _h: 14, _m: 15 } # ResultのErr側に渡したStringをメッセージとして出してくれるようですね $cargo run -- --time hoge Compiling argh_opt v0.1.0 (/Users/arata/Documents/Workspace/rust/argh_opt) Finished dev [unoptimized + debuginfo] target(s) in 0.52s Running `target/debug/argh_opt --time hoge` Error parsing option '--time' with value 'hoge': invalid digit found in string
みたいな感じでした。
2.1 sub commandを使う
次はサブコマンドを実装してみます。
./bin/foo hoge
や ./bin/foo fuga
みたいなやつです。
これを実装してみます。
enumでコマンドを作成し、
それぞれarghのマクロにsubcommandと渡してあげれば良さそうでした。
use argh::FromArgs; #[derive(FromArgs, Debug)] /// struct FooArg { #[argh(subcommand)] _command: SubCommand, } #[derive(FromArgs, Debug)] #[argh(subcommand)] enum SubCommand { Hoge(HogeCommand), Fuga(FugaCommand), } #[derive(FromArgs, Debug)] /// h #[argh(subcommand, name = "hoge")] struct HogeCommand {} #[derive(FromArgs, Debug)] /// h #[argh(subcommand, name = "fuga")] struct FugaCommand {} fn main() { let a: FooArg = argh::from_env(); println!("{:?}", a); }
実行してみるとこんな感じ
$cargo run -- hoge Finished dev [unoptimized + debuginfo] target(s) in 0.06s Running `target/debug/argh_subcommand hoge` FooArg { _command: Hoge(HogeCommand) } $cargo run -- fuga Finished dev [unoptimized + debuginfo] target(s) in 0.02s Running `target/debug/argh_subcommand fuga` FooArg { _command: Fuga(FugaCommand) } $cargo run -- piyo Finished dev [unoptimized + debuginfo] target(s) in 0.01s Running `target/debug/argh_subcommand piyo` Unrecognized argument: piyo
2.2 sub command内でオプションを使う
サブコマンドにオプションをつけるのは、
サブコマンドのstructに、1.xでやったようにオプションを追加するだけでよかったです。
試しにhogeコマンドにpiyoというオプションを追加してみます。
use argh::FromArgs; #[derive(FromArgs, Debug)] /// struct FooArg { #[argh(subcommand)] command: SubCommand, } #[derive(FromArgs, Debug)] #[argh(subcommand)] enum SubCommand { Hoge(HogeCommand), Fuga(FugaCommand), } #[derive(FromArgs, Debug)] /// h #[argh(subcommand, name = "hoge")] struct HogeCommand { /// piyo #[argh(option)] piyo: String, } #[derive(FromArgs, Debug)] /// h #[argh(subcommand, name = "fuga")] struct FugaCommand {} fn main() { let a: FooArg = argh::from_env(); println!("{:?}", a); match a.command { SubCommand::Hoge(x) => println!("{}", x.piyo), SubCommand::Fuga(_) => (), } }
実行してみるとこんな感じ
cargo run -- hoge --piyo fooooo Finished dev [unoptimized + debuginfo] target(s) in 0.01s Running `target/debug/argh_subcommand hoge --piyo fooooo` FooArg { command: Hoge(HogeCommand { piyo: "fooooo" }) } fooooo