Rust製CLI作成支援パッケージSeahorseの紹介
めでたくメジャーバージョンがリリースされたので、 僕が最近ちょっとだけコミットしたRustのパッケージの紹介をします。
Seahorse
Seahorseは、RustでCLIツールを制作するときに助けになるパッケージです。
CLIツールを作る人がSeahorseを使うことで、以下のような処理を書くのが容易になります。
- ツールのメタ情報の設定
- サブコマンドを用いた処理の分岐
- コマンドライン引数の設定と、与えられた引数の適切な型へのパース
下にそれぞれの使い方を簡単に説明します。
① ツールのメタ情報の設定
とは
CLIツールとしてのメタ情報(名前や使い方や説明など、ヘルプテキストに表示されるもの;ヘルプテキストとは典型的には $ <コマンド> --help
で出力されるもの )を設定することができます。
たとえば、$ cargo
を実行すると
$ cargo Rust's package manager USAGE: cargo [OPTIONS] [SUBCOMMAND] OPTIONS: -V, --version Print version info and exit --list List installed commands --explain <CODE> Run `rustc --explain CODE` -v, --verbose Use verbose output (-vv very verbose/build.rs output) -q, --quiet No output printed to stdout --color <WHEN> Coloring: auto, always, never --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date --offline Run without accessing the network -Z <FLAG>... Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details -h, --help Prints help information
こんな印字がされます。
自分でCLIツールを作る際に、これはどのように実装するでしょうか。
println!("Rust's package manager\n"); println!("USAGE:"); println!(" cargo [OPTIONS] [SUBCOMMAND]\n"); . . .
死にたくなりますね。
実装例
Seahorseを使うとこんなふうに書けます。
fn main() { let args: Vec<String> = env::args().collect(); let app = App::new("cargo") .description("Rust's package manager") .usage("cargo [OPTIONS] [SUBCOMMAND]") .flag( Flag::new("version", FlagType::Bool) .alias("v") .usage("-v, --version: Print version info and exit"), ) .flag( Flag::new("list", FlagType::Bool) .usage("--list: List installed commands"), ); app.run(args); }
これを実行してヘルプテキストを表示させると、
Name cargo Description: Rust's package manager Usage: cargo [OPTIONS] [SUBCOMMAND] -v, --version: Print version info and exit --list: List installed commands
こんなのが出力されます。
ヘルプテキストを印字するにしては過度な抽象化だと思うかもしれませんが、
③に書くようにSeahorseでフラグを設定すると引数をパースするのが簡単になるという明確なメリットがあります。
ヘルプテキストが生成できる、というのはどちらかというと副次的な機能です。
② サブコマンドを用いた処理の分岐
とは
メインコマンドの後に続くコマンドがサブコマンドです。
gitを例にとって説明しますと、
$ git
をメインコマンドとすると、$ git status
や $ git log
等を実行するときの status
や log
がサブコマンドにあたります。
実装例
さてSeahorseで、$ git status
と $ git log
を受け付けて適当な印字をするアプリケーションを作ってみましょう。
fn status_action(c: &Context) { println!("status..."); } fn log_action(c: &Context) { println!("log..."); } fn main() { let args: Vec<String> = std::env::args().collect(); let app = App::new("gitではないもの") .command(Command::new("log").action(log_action)) .command(Command::new("status").alias("st").action(status_action)); app.run(args); }
サブコマンドとはこういう使い方をするもの、という説明をするためだけに書いたので無能なgitが出来上がりました。
これでビルドすれば $ <メインコマンド> log
も $ <メインコマンド> status
も $ <メインコマンド> st
も受け付けるCLIツールができます。
③ コマンドライン引数の設定と、与えられた引数の適切な型へのパース
とは
$ git log --author rnitta -n 10
というコマンドを打ったときに、
これらのスペースで区切られた文字列群はRustの世界からは std::env::args()
で取得することができます。非常に便利な関数ですね。
std::env::args().collect::<Vec<String>>()
とすれば、["git", "log", "--author", "rnitta", "-n", "10"]
のようなVecが得られます。
これをパースして取り出しやすくしてくれる仕組みがあります。
実装例
fn main() { let args: Vec<String> = std::env::args().collect(); let app = App::new("git").command( Command::new("log") .usage("git log <options>") .action(log_action) .flag(Flag::new("author", FlagType::String).usage("git log --author <author_name>")) .flag( Flag::new("number", FlagType::Int) .usage("git log --number(-n) <n>") .alias("n"), ), ); app.run(args); } fn log_action(c: &Context) { if let Ok(author) = c.string_flag("author") { println!("Author is {}", author); } if let Ok(number) = c.int_flag("number") { // => ちゃんとisizeにパースしてくれます! println!("Number is {}", number); } }
$ cargo run -- log --author rnitta -n 2 Author is rnitta Number is 2
コマンドライン引数を適当に抽象化してくれて便利ですね。
Seahorseの特徴
Seahorseは機能がミニマルなところが特徴的だと思います。
ミニマル はともすれば「機能が少ないだけでしょ」「実装サボってるだけでしょ」と思うかもしれませんが、 ミニマルはミニマルでいいところもあります。 それは、できることが多くない代わりに学習コストが低いことです。
同じCLI作成支援パッケージであるclapの使い方を調べてみましょう。
GitHub - clap-rs/clap: A full featured, fast Command Line Argument Parser for Rust
マクロで書くこともできるし、メソッドスタイルでも書くことができるし、yamlでも設定が書ける...多機能ですが
ちょっとプロジェクトが大きくてエラーに当たったときに中身を読みに行くのがいやになってしまいますね。
一方でseahorseはREADMEを1分眺めれば大半の機能は把握できます。
- メソッドチェーン的なスタイルでメタ情報が設定できて、
- サブコマンドを設定することで複数の機能を1ツールにできて、
- コマンドライン引数の値が特定の型にパースしやすくなる。
v1.0.0時点で提供されている機能はこんなところなはずです。 コード量もかなり控えめです。
あなたが仮にgitとかffmpegぐらいサブコマンドやオプションの多く複雑なツールを作ろうとしているならSeahorseでは力不足かもしれませんが、 多くのCLIツールではSeahorseで十分なはずです。
Any Contributions are Welcome
ちょっとしたCLIツールをRustで作る時に使ってみたり 使用方法をより詳細に説明するポストを書いてみたり バグをissueに投げてみたり 新機能を提案して実装してみたり リポジトリにStarしてみたり おまちしております