RustのCLIフレームワークを作ってみた
作った経緯
RustのCLIフレームワークはclapが有名ですが機能が多く、そこまでの機能は必要じゃないけどCLIを簡単に構築できるライブラリをRustの勉強がてら作ってみました。
作ったCLIフレームワーク
A minimal CLI framework written in Rust
アイコンは開発を手伝って頂いている@rnitta さんが作ってくれました。 やはりコミットハッシュがキレイな人はセンスがありますね。
特徴
- ミニマル
- 難しい設定などはなく割と簡単に使える
- 型(Bool, String, Int, Float)指定できるオプションフラグ
- 依存クレートがない
- アイコンがスゴく可愛い
Quick Start
最小限の使い方は以下の通りです。
$ cargo new cli_tool $ cd cli_tool $ echo 'seahorse = "0.6.2"' >> Cargo.toml
use std::env; use seahorse::{App}; fn main() { let args: Vec<String> = env::args().collect(); let app = App::new() .name(env!("CARGO_PKG_NAME")) .author(env!("CARGO_PKG_AUTHORS")) .version(env!("CARGO_PKG_VERSION")) .usage("cli_tool [args]") .action(|c| println!("Hello, {:?}", c.args)); app.run(args); }
$ cargo build --release
$ ./target/release/cli_tool --help
Name:
cli_tool
Author:
hogehoge <hogehoge@gmail.com>
Usage:
cli_tool [args]
Version:
0.1.0
$ ./target/release/cli_tool John
Hello, ["John"]
ActionとContext
Action
がコマンドの処理部分になります。
Action
の定義はContext
を引数とした関数型です。
type Action = fn(Context);
Context
はAction
の引数でのみ使う型になります。
Context
はコマンドラインから受け取った引数からフラグとフラグの値を取り除いて残った配列をフィールドとして持ちます。
また、Context
は各フラグの値を取り出すためのメソッドを持ちます。
fn action(c: &Context) { println!("{:?}", c.args); println!("{}", c.bool_flag("bool"); match c.string_flag("string") { Some(s) => println!("{}", s), None => println!("No string..."), } match c.int_flag("int") { Some(i) => println!("{}", i), None => println!("No integer..."), } match c.float_flag("float") { Some(f) => println!("{}", f), None => println!("No float..."), } }
Flag
Flag
は--flag
や-f
のようなコマンドラインでフラグを渡す場合に定義します。
フラグとして受け取る値の型はFlagType
から選択できます。
enum FlagType { Bool, String, Int, Float, }
use std::env; use seahorse::{App, Context, Flag, FlagType}; fn main() { let args: Vec<String> = env::args().collect(); let app = App::new() .name(env!("CARGO_PKG_NAME")) .author(env!("CARGO_PKG_AUTHORS")) .version(env!("CARGO_PKG_VERSION")) .usage("cli_tool [args]") .action(action) .flag(Flag::new("bye", "cli [arg] --bye", FlagType::Bool)) .flag(Flag::new("age", "cli [arg] --age [age]", FlagType::Int)); app.run(args); } fn action(c: &Context) { if c.bool_flag("bye") { println!("Bye, {:?}", c.args); } else { println!("Hello, {:?}", c.args); } if let Some(age) = c.int_flag("age") { println!("{:?} is {} years old.", c.args, age); } }
$ cli_tool John --bye Bye, ["John"] $ cli_tool John --age 20 ["John"] is 20 years old. $ cli_tool John --bye --age=30 Bye, ["John"] ["John"] is 30 years old.
エイリアス
フラグにエイリアスを設定する場合はalias
メソッドをメソッドチェーンします。
let int_flag = Flag::new("integer", "cli [arg] --integer(-int, -i) [integer]", FlagType::Int) .alias("int") .alias("i");
Command
アクションを複数登録する場合はCommand
を使います。
use seahorse::{App, Command, Context, Flag, FlagType}; use std::env; fn main() { let args: Vec<String> = env::args().collect(); let app = App::new() .name(env!("CARGO_PKG_NAME")) .author(env!("CARGO_PKG_AUTHORS")) .version(env!("CARGO_PKG_VERSION")) .usage("cli_tool [command] [x] [y]") .command( Command::new() .name("add") .usage("cli add [x] [y]") .action(add), ) .command( Command::new() .name("sub") .usage("cli sub [x] [y]") .action(sub), ); app.run(args); } fn add(c: &Context) { let x: i32 = c.args[0].parse().unwrap(); let y: i32 = c.args[1].parse().unwrap(); println!("{} + {} = {}", x, y, x + y); } fn sub(c: &Context) { let x: i32 = c.args[0].parse().unwrap(); let y: i32 = c.args[1].parse().unwrap(); println!("{} - {} = {}", x, y, x - y); }
$ cli_tool add 10 3 10 + 3 = 13 $ cli_tool sub 34 10 34 - 10 = 24
また、Command
はApp
と同じようにflag
メソッドでオプションフラグを設定することができます。
let command = Command::new() .name("hello") .usage("cli hello [arg]") .action(action) .flag(Flag::new("bye", "cli hello [arg] --bye", FlagType::Bool));
さいごに
ざっくりとした機能は以上になります。
issue, PR, Starお待ちしています!! https://github.com/ksk001100/seahorse
Thanks for reading!