See example/glance.rs
for more details.
#[default_options]
#[option(--https, "use https instead of http")]
#[sub_command(connect <address>, "connect to the address")]
fn connect(address: Option<Address>, opts: Opts, global_opts: GlobalOpts) {
/* .. */
}
#[default_options]
#[sub_command(disconnect <address>, "disconnect the connection")]
fn disconnect(address: Option<Address>) {
/* .. */
}
#[default_options]
#[option(--proxy <proxy_address>, "use proxy to connect")]
#[command(net, "network tool")]
fn net() { /* .. */}
fn main() {
execute!(net, [connect, disconnect]);
}
The current master
branch will no longer be supported.
And the branch pre-alpha
will be the master
branch once it's stable.
The current commander_rust
in crate.io
is no longer supported.
Before it becomes stable, github
will be used as distribution source. Add it to your dependencies.
[dependencies.commander_rust]
git = "https://github.com/MSDimos/commander-rust/"
branch = "pre-alpha"
# or
commander_rust = { git = "https://github.com/MSDimos/commander-rust/", branch = "pre-alpha" }
As I think, developers should devote more time to the realization of functions instead of leaning how to use command line interface (CLI)
.
So a crate of CLI
should be easy to use.
Specifically, it should have the following advantages:
- Firstly, it should have less
API
s. - Secondly, it should be intuitive enough to use. What u see is what u get.
- Thirdly, it should make full use of the advantages of programming language.
Inspired by Rocket and commander.js, the crate is born.
A CLI
program consists of Command
,SubCommand
,Options
and Argument
Options
is exactlyOption
, butRust
has usedOption
already, so I use it as replacement.
The relationships between them are:
- One
CLI
has ONLY ONECommand
. - One
Command
has ZERO or MORESubCommand
s. Command
andSubCommand
have ZERO or MOREOptions
.SubCommand
andOptions
can accept ZERO or MOREArgument
.
For instance, see examples below:
command <require_argument> <optional_argument> --option
command sub_command <require_argument> <optional_argument> --option
Defining options using #[option]
. Syntax shows below:
#[option([-s], --long-name <arg1> [arg2], "description about this option")]
Note, options of command
are global options, it means sub-command
can access them.
#[option(--global-option)]
#[command(test)]
fn test() {
// input
// paht/of/example test --global-option
assert!(!opts.contains_key("local-option"));
assert!(opts.contains_key("global-option"));
}
#[option(--local-option)]
#[sub_command(test_sub)]
fn test_sub(opts: Opts) {
// input:
// path/of/example test test_sub --global-option --local-option
assert!(opts.contains_key("global-option"));
assert!(opts.contains_key("local-option"))
}
Options without arguments are also called flag
or switch
(Ha, not Nintendo Switch
).
All options should be defined above command
or sub_command
.
All options defined below command
or sub_command
will be ignored. See example below:
// valid
#[option(--display, "display something")]
#[option(-v, --version, "display version")]
#[sub_command(cmd_name, "this is a sub_command")]
fn sub_cmd_fn() {}
// these below are all invalid
#[sub_command(cmd_name, "this is a sub_command")]
#[option(--display, "display something")]
#[option(-v, --version, "display version")]
fn sub_cmd_fn1() {}
#[option(--display, "display something")]
#[sub_command(cmd_name, "this is a sub_command")]
#[option(-v, --version, "display version")]
fn sub_cmd_fn2() {}
#[default_options]
#[sub_command(test)]
fn test() {}
// is equal to
#[option(-v, --version, "print version information")]
#[option(-h, --help, "print help information")]
#[sub_command(test)]
fn test() {}
Once you use #[default_options]
,
other options of this sub_command
or command
can't use short names -v
-h
or long names --version
--help
.
See example below:
#[default_options]
// Error, `-v` is reserved keyword which is used by `#[default_options]`
#[option(-v, --verbose, "display verbose information")]
// Error, `--help` is reserved keyword which is used by `#[default_options]`
#[option(--help, "need help")]
#[sub_command(test)]
fn test() {}
They have the similar syntax which are shown below:
// sub_command
#[sub_command(sub_cmd_name <arg1> [args], "this is a sub-command")]
// command without app version
// in this case, environment variable `CARGO_PKG_VERSION` (i.e., std::env!("CARGO_PKG_VERSION")) will be used as app version
#[command(cmd_name <arg1> [args], "this is a sub-command")]
// command with app version
#[command("0.0.1-pre-alpha", cmd_name <arg1> [args], "this is a sub-command")]
#[command]
is only, but #[sub_command]
s are not.
Run the cli app. If you don't call it, the cli app will not run.
Syntax is shown as example below:
// Note, these `*_fn_name*` are names of function instead of names of `sub_command` or `command`
execute!(command_fn_name, [sub_command_fn_name1, sub_command_fn_name2, ...])
// if no sub_command needed, provide a `[]`
execute!(command_fn_name, [])
Note: Because of restrictions of
Rust
, if you want to used procedural macro, you should add attribute#![feature(proc_macro_hygiene)]
. See this issuefor more details.
Because of the internal mechanism, all functions which are used in execute!()
should be at the same level of modules. It means the example below will raise error:
mod child_mod {
#[sub_command(test_sub, "test1")]
pub fn test1() {}
}
#[command(test_cmd, "test2")]
fn test2() {}
use child_mod::test1;
fn main() {
// Error, cannot find function `_commander_rust_prefix_test1_commander_rust_suffix_` in this scope
execute!(test2, [test1]);
}
There are four types of arguments. Listed below:
- required single argument:
<arg>
- required multiply arguments:
<..args>
or<...args>
- optional single argument:
[arg]
- optional multiply arguments:
[..args]
or[...args]
In fact,
required multiply arguments
is equal tooptional multiply arguments
.
Note: there are several restrictions:
- All
optional
arguments should be after allrequired
arguments.
// valid
#[option(test <a> <b> [c] [d])]
// invalid
#[option(test <a> [b] <c> [d])]
- There can be only one
multiply
argument, and it can only be used as the last parameter.
// valid
#[otpion(test <a> <..b>)]
// invalid
#[option(test <..a> <b>)]
See example below.
#[option(--user-name <name>, "login with username")]
#[option(--passwd <passwd>, "login with passwd")]
// all named arguments (e.g. here, <url>) should be used in function signature
#[command(login <url>, "login")]
fn login_fn(url: String) {}
is named argument of option
--user-name
,<passwd>
is named argument of option--passwd
. Andurl
is a named argument of commandlogin
.
Of course, you can customize the type of named arguments. Any type that implement the trait
FromArg
orFromArgs
can be used as type of named arguments.What's different between
FromArg
andFromArgs
?
FromArg
is used for type of named arguments which aresingle
arguments (e.g.,<arg>
or[arg]
).FromArgs
is used for type of named arguments which aremultiply
arguments (e.g.,<..arg>
or[..arg]
).There are several types that implement the trait
FromArg
:
String
and&str
Option<T: FromArg>
Result<T: FromArg, T::Error>
i8
i16
i32
i64
i128
u8
u16
u32
u64
u128
&Arg
Path
andPathBuf
There are several types that implement the trait
FromArgs
:
String
Vec<T: FromArg> (not T: FromArgs)
Option<T: FromArgs>
Result<T: FromArgs, T::Error>
&Args
How to implement the two traits above? Let me show u an example.
Now, I define an command
with an argument.
#[command(download <pkg>, "download an package")]
fn connect(pkg: Pkg) {
match down_load_pkg(&pkg.name, &pkg.version) {
Ok(_) => println!("success"),
Err(e) => eprintln!("{}", e),
}
}
I don't want to use String
, but I use a type named Pkg
.
I want to decode user's input which is formatted like react=16.13.1
.
Now, let's define the struct Pkg
.
struct Version(u8, u8, u8);
struct Pkg {
name: String,
version: Version,
}
Now, the highlight is coming. Let's implement the trait FromArg
, then we can use it.
impl<'a> FromArg<'a> for Pkg {
type Error = ();
// see document for more details about `Arg` and `Args`
fn from_arg(arg: &'a Arg) -> Result<Self, Self::Error> {
let splits: Vec<&str> = arg.split('=').collect();
if splits.len() != 2 {
Err(())
} else {
let name = splits[0];
let vers: Vec<&str> = splits[1].split('.').collect();
if vers.len() != 3 {
Err(())
} else {
let mut vs = [0, 0, 0];
for (idx, ver) in vers.into_iter().enumerate() {
if let Ok(v) = ver.parse::<u8>() {
vs[idx] = v;
} else {
return Err(());
}
}
Ok(Pkg {
name: name.to_string(),
version: Version(vs[0], vs[1], vs[2]),
})
}
}
}
}
Ha, it's done. Now you can use the cli app like:
$ /path/of/download react=16.13.1
But there are some bugs here.
Look the line 8
line 14
line 22
in the code above.
It returned the Err
. It means sometimes it will crash.
Try to input like this:
// Error, parse failed, can't parse input `react=16` as type `Pkg`
$ /path/of/download react
How to catch errors and handle them yourself ?
It's easy, do u remember that there are several types which implement the trait FromArg
?
Option<T: FromArg>
and Result<T: FromArg, T::Error>
are two of them.
So, change the signature of function:
#[command(download <pkg>, "download an package")]
// Or pkg: Result<Pkg, ()>, both are okay
fn connect(pkg: Option<Pkg>) {
if let Some(pkg) = pkg {
match down_load_pkg(&pkg.name, &pkg.version) {
Ok(_) => println!("success"),
Err(e) => eprintln!("{}", e),
}
} else {
// if you want to do something, do it.
eprintln!("can't parse package.");
}
}
Now, If you input:
// customize error, can't parse package.
$ /path/of/download react
If you want to download multiply packages like:
$ /path/of/download react=16.13.1 react-redux=7.2.0
Change signature of function download
like:
#[command(download <pkg>, "download an package")]
// Or pkgs: Vec<Result<Pkg, ()>>, both are okay
fn connect(pkgs: Vec<Option<Pkg>>) {
if pkgs.is_empty() {
eprintln!("no packages offered.");
} else {
for pkg in pkgs.into_iter() {
if let Some(pkg) = pkg {
match down_load_pkg(&pkg.name, &pkg.version) {
Ok(_) => printlnr!("success"),
Err(e) => eprintln!("{}", e),
}
} else {
// if you want to do something, do it.
eprintln!("can't parse package.");
}
}
}
}
I offer u two types to get options. One is Opts
,
the other one is GlobalOpts
.
By names, you should be able to know the difference between them.
#[option(-f, --force, "force to install even if this package has already installed")]
#[option(-g, --global, "install as a global package")]
#[sub_command(install <pkg>, "install a package")]
fn install_fn(pkg: Result<Pkg, ()>, opts: Opts, global_opts: GlobalOpts) {
if opts.contains_key("force") {
// do something here
}
if global_opts.contains_key("verbose") {
// do something here
}
}
Like arguments of command
, arguments of option
s have implemented the trait FromArg
or FromArgs
. See example below:
#[option(--fruit <fruit>)]
#[commmand(eat)]
fn eat(opts: Opts) {
// try to get option
if let Some(Mixed::Single(fruit)) = opts.get("fruit") {
// "apple" is default value
let fruit = String::from_arg(fruit).unwrap_or("apple".to_string());
println!("I eat a(n) {}", fruit);
}
}
Code above used type
Mixed
. Why it? See example below:#[option(--test <a> <b> <..c>)]
As you can see,
<a>
and<b>
are bothsigle
arguments. But<..c>
is multiply argument.If you want to get named arguments of
--test
by using apiget
, it will return value of typeResult<Mixed, ()>
.In this case, for
single
arguments,Mixed
isMixed::Signle
which only contains one input value. Formultiply
arguments,Mixed
isMixed::Multiply
which contains more input value.You can see document or source code for more details.
Repeat: All types of named arguments should implement the trait FromArg
(for single
argument) or FromArgs
(for multiply
arguments).
If you offer non-named arguments, the types of them should implement the trait FromApp
.
See document for more details about struct
App
.
Opts
and GlobalOpts
have already implemented the trait FromApp
. There are several types that implement the trait FromApp
.
Application
and&Application
(aliasApp
and&App
)&Command
Result<T: FromApp, T::Error>
Option<T: FromApp>
See document for more details about struct
Command
. It contains all information about you cli app.If you want to show help or version information, there are three
api
s ofCommand
you can use:
cmd.println()
-- print help information of commandcmd.println_sub("sub_name")
-- print help information of the specified sub-commandcmd.println_version()
-- print version information#[command(test)] fn npms_fn(cmd: &Command) { cmd.println(); }
How to implement the trait FromApp
. See example below:
enum DangerousThing {
Cephalosporin,
Wine,
None,
}
// by implementing the trait `FromApp`, you can do many multiple custom options types
// e.g. here, mutually exclusive options
struct MutexThing(DangerousThing);
impl<'a> FromApp<'a> for MutexThing {
type Error = String;
fn from_app(app: &'a Application) -> Result<Self, Self::Error> {
// You can use `<T as FromApp>::from_app(app)` to convert app to `T`
if let Ok(opts) = GlobalOpts::from_app(app) {
if opts.contains_key("cephalosporin") && opts.contains_key("drink-wine") {
Err(String::from("DANGER!!! DO NOT DO IT! DO NOT take cephalosporin while drinking wine!"))
} else if opts.contains_key("cephalosporin") {
Ok(MutexThing(DangerousThing::Cephalosporin))
} else {
Ok(MutexThing(DangerousThing::Wine))
}
} else {
Ok(MutexThing(DangerousThing::None))
}
}
}
// WARN: DO NOT take cephalosporin while drinking wine! It's fatal behavior!!!!!!!!
#[option(--cephalosporin, "take cephalosporin")]
#[option(--drink-wine, "drink wine")]
#[default_options]
#[command("0.0.1-fruits-eater", eat <food>, "eat food")]
// Here, do u see it?
// mutex_thing is not named argument, so it should implement the trait `FromApp`.
fn eat_fn(food: String, mutex_thing: Result<MutexThing, String>) {
match food.as_str() {
"apple" | "banana" | "pear" | "watermelon" | "orange" => println!("I eat a(n) {}, it's delicious!", food),
_ => println!("I dislike {}", food),
}
match mutex_thing {
Ok(MutexThing(DangerousThing::Cephalosporin)) => println!("DO NOT drink wine recently!"),
Ok(MutexThing(DangerousThing::Wine)) => println!("DO NOT take cephalosporin recently!"),
Ok(MutexThing(DangerousThing::None)) => println!("I want to eat more!"),
Err(note) => println!("{}", note),
}
}
- There are three traits you may will use:
Name | description |
---|---|
FromArg |
single named arguments should implement it, e.g., <arg> or [arg] |
FromArgs |
multiply named arguments should implement it, e.g., <..args> or [..args] |
FromApp |
non-named arguments of function signature(if it exists) should implement it. |
-
You can use
Opts
andGlobalOpts
to get options. -
You can use
&Command
to print help and version information by yourself. -
Run cli app by calling macro
execute!()
.
You can check examples in folder examples
of this crate for full usage of commander_rust
.
Because of something that happened to me, I stopped maintaining the previous version of this project for a long time.
After all that, I have time to maintain the project. I am sorry for those people who opened issues, because of refactoring of the project, I can't and needn't to respond them any more. Now, starting from scratch, any useful contribution is welcome.
If you find bug and fix it, please create an Merge Request
.
If you have a good idea and implement it, please create an Merge Request
.
If you have any questions, please open an issue
.
-
i18n support?
-
sub-sub*n-sub-commands support?
-
cross modules support?