combu
combu is a customizable cli framework.
The library name "combu" comes from command + 昆布(konbu, it means kelp in japanese).
combu has no dependencies(or depends on only std library).
Crate.io's page is here.
combu(com + 昆布)は柔軟に CLI を組み上げられることを目標とした、カスタマイズ可能な CLI フレームワークです(一時クレートの名前が cmb だったこともありましたが、現在は combu です)。
Features
- flag parsing in Unix format (Unix 形式でのフラグパース)
- Nestable sub commmands (サブコマンド(多重可能))
- No dependencies; combu depends on only std library (標準ライブラリ以外への依存ライブラリなし)
- Typed flag: Bool, String, Int and Float, inspired from seahorse(seahorse を参考にした Bool, String, Int, Float の型つきフラグ)
- common flag, local flag (コモンフラグ、ローカルフラグ両方を設定可能)
- flag parsing before sub command args(サブコマンド前のフラグの受付)
- 独自でパース等を行いたい場合に再利用できる構造体の設定
- 似たような CLI フレームワークを作りたいときに使用できる部品を用意
- Useful presets (コマンド、フラグ等のプリセット)
- Return the result of run as Result<ActionResult, ActionError> (実行結果を Result に込めて実行後返却)
Documentation
Here
Installation to your project
Combu exists on crates.io.
You can use(or import) this crate like other crate that exists on crates.io.
Edit cargo.toml manually
Add
combu="[version you want to use]"
to cargo.toml.
Use cargo-edit (Recommended)
If you installed cargo-edit, exec below command under the target project:
cargo add combu
Quick Start
use combu::{ActionError, ActionResult, Command, Context, Flag, FlagValue};
use std::env;
fn main() {
Command::new()
.name(env!("CARGO_PKG_NAME"))
.authors(env!("CARGO_PKG_AUTHORS"))
.version(env!("CARGO_PKG_VERSION"))
.usage(env!("CARGO_PKG_NAME").to_string() + " [args]")
.common_flag(Flag::new_bool("help").short_alias('h'))
.action(act)
.run_from_args(env::args().collect())
}
fn act(c: Context) -> Result<ActionResult, ActionError> {
if Some(FlagValue::Bool(true)) == c.get_flag_value_of("help") {
return Ok(ActionResult::ShowHelpRequest(c));
}
println!("Hello, combu - {:?}", c.args);
Ok(ActionResult::Done)
}
If you want to run quick start as example, exec
cargo run --example quick_start
cargo run --example quick_start --help
Example
Simple (command has flags, but not has subcommand)
Code
use combu::{ActionError, ActionResult, Command, Context, Flag, FlagValue};
use std::env;
fn main() {
let _ = Command::with_name("single")
.action(act)
.local_flag(Flag::new_bool("reverse").short_alias('r'))
.single_run(env::args().collect::<Vec<String>>());
}
fn act(c: Context, cmd: Command) -> Result<ActionResult, ActionError> {
let r = c.get_flag_value_of("reverse", &cmd).unwrap();
println!(
"{}",
match r {
FlagValue::Bool(true) => {
c.args
.iter()
.rev()
.fold(String::new(), |concated, arg| concated + arg)
}
_ => {
c.args
.iter()
.fold(String::new(), |concated, arg| concated + arg)
}
}
);
Ok(ActionResult::Done)
}
Run
$ cargo run --example single a b c d e
abcde
$ cargo run --example single a b c d e -r
edcba
Multi (Have Sub command)
Code
use combu::{command::presets, done, ActionError, ActionResult, Command, Context, Flag, FlagValue};
fn main() {
let _ = root_command().run_from_args(std::env::args().collect());
}
fn root_command() -> Command {
Command::with_name("multi")
.common_flag(Flag::new_bool("help").short_alias('h'))
.common_flag(Flag::new_bool("reverse").short_alias('r'))
.local_flag(Flag::new_bool("by-char").short_alias('c'))
.action(print_args)
.sub_command(add_command())
.sub_command(sub_command())
}
fn call_help(c: &Context, cur_cmd: &Command) -> Result<ActionResult, ActionError> {
println!("{}", presets::help(c, cur_cmd));
done!()
}
fn print_args(context: Context, current_command: Command) -> Result<ActionResult, ActionError> {
if called_help(&context, ¤t_command) {
return call_help(&context, ¤t_command);
}
let r: bool =
context.get_flag_value_of("reverse", ¤t_command) == Some(FlagValue::Bool(true));
let c: bool =
context.get_flag_value_of("by-char", ¤t_command) == Some(FlagValue::Bool(true));
let str = {
let str = if r && !c {
context
.args
.iter()
.rev()
.fold(String::new(), |c, arg| c + arg)
} else {
context.args.iter().fold(String::new(), |c, arg| c + arg)
};
if c {
str.chars().rev().collect::<String>()
} else {
str
}
};
println!("{}", str);
Ok(ActionResult::Done)
}
fn called_help(c: &Context, cc: &Command) -> bool {
Some(FlagValue::Bool(true)) == c.get_flag_value_of("help", cc)
}
fn add_command() -> Command {
Command::new()
.name("add")
.alias("a")
.action(add_action)
.local_flag(Flag::new_bool("detail").short_alias('d'))
}
fn add_action(c: Context, cmd: Command) -> Result<ActionResult, ActionError> {
if called_help(&c, &cmd) {
return call_help(&c, &cmd);
}
let f = |(str, sum), num: f64| (format!("{} {} +", str, num), sum + num);
let (mut str, sum): (String, f64) =
if c.get_flag_value_of("reverse", &cmd) == Some(FlagValue::Bool(true)) {
c.args
.iter()
.rev()
.filter_map(|arg| arg.parse().ok())
.fold((String::new(), 0.0), f)
} else {
c.args
.iter()
.filter_map(|arg| arg.parse().ok())
.fold((String::new(), 0.0), f)
};
str.pop();
str.pop();
if c.get_flag_value_of("detail", &cmd).unwrap().is_bool_true() {
println!("{} = {}", str, sum);
} else {
println!("{}", sum);
}
Ok(ActionResult::Done)
}
fn sub_command() -> Command {
Command::new()
.name("sub")
.alias("s")
.action(sub_action)
.local_flag(Flag::new_bool("sort").short_alias('s'))
}
fn sub_action(c: Context, cmd: Command) -> Result<ActionResult, ActionError> {
if called_help(&c, &cmd) {
return call_help(&c, &cmd);
}
let f = |(str, sum), (index, num): (usize, f64)| {
(
format!("{} {} -", str, num),
if index < 1 { num } else { sum - num },
)
};
let filter_map_f = |arg: &String| arg.parse().ok();
let (mut str, result): (String, f64) =
if c.get_flag_value_of("reverse", &cmd) == Some(FlagValue::Bool(true)) {
c.args
.iter()
.rev()
.filter_map(filter_map_f)
.enumerate()
.fold((String::new(), 0.0), f)
} else if c.get_flag_value_of("sort", &cmd).unwrap().is_bool_true() {
let mut fvec = c.args.iter().filter_map(filter_map_f).collect::<Vec<f64>>();
fvec.sort_by(|a, b| a.partial_cmp(b).unwrap());
fvec
.iter_mut()
.enumerate()
.fold((String::new(), 0.0), |s, (index, fl)| f(s, (index, *fl)))
} else {
c.args
.iter()
.filter_map(filter_map_f)
.enumerate()
.fold((String::new(), 0.0), f)
};
str.pop();
str.pop();
println!("{} = {}", str, result);
Ok(ActionResult::Done)
}
Run
cargo run --example multi -- a 1 2 3 4 5
15
cargo run --example multi -- s 1 2 3 4 5
-13
Inspired
TODO(or Features to be implemented)
- ドキュメントコメントを分かりやすくする(いつになるかは無期限未定)
- 必要そうなテストの実装(
command.rs
のフラグ解析テストは実装した)
- コマンド構築にあたってのプリセット実装
License
This is licensed under MIT LICENSE