Rust-Seahorse: seahorse — A minimal CLI framework written in Rust

seahorse

crates.io releases count issues count forks count license github actions CI

Logo

A minimal CLI framework written in Rust

Features

  • Easy to use
  • No dependencies
  • Typed flags(Bool, String, Int, Float)

Documentation

Here

Usage

To use seahorse, add this to your Cargo.toml:

[dependencies]
seahorse = "1.0.0"

Example

Run example

$ git clone https://github.com/ksk001100/seahorse
$ cd seahorse
$ cargo run --example single_app -- --help
$ cargo run --example multiple_app -- --help

Quick Start

$ cargo new cli
$ cd cli
$ echo 'seahorse = "*"' >> Cargo.toml
use seahorse::{App};
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    let app = App::new(env!("CARGO_PKG_NAME"))
        .author(env!("CARGO_PKG_AUTHORS"))
        .version(env!("CARGO_PKG_VERSION"))
        .usage("cli [args]")
        .action(|c| println!("Hello, {:?}", c.args));

    app.run(args);
}
$ cargo build --release
$ ./target/release/cli --help
$ ./target/release/cli John

Multiple command application

use seahorse::{App, Context, Command};
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    let app = App::new(env!("CARGO_PKG_NAME"))
        .author(env!("CARGO_PKG_AUTHORS"))
        .version(env!("CARGO_PKG_VERSION"))
        .usage("cli [name]")
        .action(default_action)
        .command(add_command())
        .command(sub_command());

    app.run(args);
}

fn default_action(c: &Context) {
    println!("Hello, {:?}", c.args);
}

fn add_action(c: &Context) {
    let sum: i32 = c.args.iter().map(|n| n.parse::<i32>().unwrap()).sum();
    println!("{}", sum);
}

fn add_command() -> Command {
    Command::new("add")
        .alias("a")
        .usage("cli add(a****) [nums...]")
        .action(add_action)
}

fn sub_action(c: &Context) {
    let sum: i32 = c.args.iter().map(|n| n.parse::<i32>().unwrap() * -1).sum();
    println!("{}", sum);
}

fn sub_command() -> Command {
    Command::new("sub")
        .alias("s")
        .usage("cli sub(s) [nums...]")
        .action(sub_action)
}
$ cli John
Hello, ["John"]

$ cli add 32 10 43
85

$ cli sub 12 23 89
-124

Branch processing by flag

use seahorse::{App, Command, Context, Flag, FlagType, error::FlagError};
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    let app = App::new(env!("CARGO_PKG_NAME"))
        .author(env!("CARGO_PKG_AUTHORS"))
        .version(env!("CARGO_PKG_VERSION"))
        .usage("cli [name]")
        .action(default_action)
        .flag(
            Flag::new("bye", FlagType::Bool)
                .usage("cli [name] --bye(-b)")
                .alias("b"),
        )
        .flag(
            Flag::new("age", FlagType::Int)
                .usage("cli [name] --age(-a)")
                .alias("a"),
        )
        .command(calc_command());

    app.run(args);
}

fn default_action(c: &Context) {
    if c.bool_flag("bye") {
        println!("Bye, {:?}", c.args);
    } else {
        println!("Hello, {:?}", c.args);
    }

    if let Ok(age) = c.int_flag("age") {
        println!("{:?} is {} years old", c.args, age);
    }
}

fn calc_action(c: &Context) {
    match c.string_flag("operator") {
        Ok(op) => {
            let sum: i32 = match &*op {
                "add" => c.args.iter().map(|n| n.parse::<i32>().unwrap()).sum(),
                "sub" => c.args.iter().map(|n| n.parse::<i32>().unwrap() * -1).sum(),
                _ => panic!("undefined operator..."),
            };

            println!("{}", sum);
        }
        Err(e) => match e {
            FlagError::Undefined => panic!("undefined operator..."), 
            FlagError::ArgumentError => panic!("argument error..."), 
            FlagError::NotFound => panic!("not found flag..."), 
            FlagError::ValueTypeError => panic!("value type mismatch..."), 
            FlagError::TypeError => panic!("flag type mismatch..."), 
        },
    }
}

fn calc_command() -> Command {
    Command::new("calc")
        .alias("cl, c")
        .usage("cli calc(cl, c) [nums...]")
        .action(calc_action)
        .flag(
            Flag::new("operator", FlagType::String)
                .usage("cli calc [nums...] --operator(-op) [add | sub]")
                .alias("op"),
        )
}
$ cli John
Hello, ["John"]

$ cli John --bye
Bye, ["John"]

$ cli John --age 10
Hello, ["John"]
["John"] is 10 years old

$ cli John -b -a=40
Bye, ["John"]
["John"] is 40 years old

$ cli calc --operator add 1 2 3 4 5
15

$ cli calc -op sub 10 6 3 2
-21

Contributing

Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests to us.

License

This project is licensed under MIT license

Code of Conduct

Contribution to the seahorse crate is organized under the terms of the Contributor Covenant, the maintainer of seahorse, @ksk001100, promises to intervene to uphold that code of conduct.

Comments

  • zsh completion
    zsh completion

    Feb 27, 2020

                                                                                                                                                                                                            enhancement 
    Reply
  • Custom flag
    Custom flag

    May 30, 2020

    • User defined type flag
    enhancement 
    Reply
  • Flag: Change `&'static str` of Flag field to `String`
    Flag: Change `&'static str` of Flag field to `String`

    Feb 19, 2020

    Close #20

                                                                                                                                                                                                           
    Reply
  • help called when no flags specified.
    help called when no flags specified.

    Feb 23, 2020

    I made a cli tool using this, and all flags should be optional, so implemented like:

    https://github.com/rnitta/commit_artist/blob/bbbbbbb73b6fd72a81a4188edc87e7bffd9f5135/src/main.rs#L14-L40 https://github.com/rnitta/commit_artist

    and I got the problem.

    スクリーンショット 2020-02-24 9 57 46 ?

    with a flag like commit_artist -p 111111, it will do. It seems to be a bug for me.

    bug 
    Reply
  • Flag equal notation
    Flag equal notation

    Feb 24, 2020

    as you can see.

    Close #28

    Reply
  • Migrate travis to GitHub Actions
    Migrate travis to GitHub Actions

    Feb 18, 2020

    related to #14

                                                                                                                                                                                                           
    Reply
  • I want a logo image
    I want a logo image

    Feb 15, 2020

                                                                                                                                                                                                           
    Reply
  • Panic when missing argument to string flag
    Panic when missing argument to string flag

    Apr 1, 2020

    I was just playing with the API and it appears that not providing the command line argument to a string flag triggers a panic with message:

    thread 'main' panicked at 'assertion failed: index < len', <::core::macros::panic macros>:2:2
    

    Cool logo by the way!

    bug 
    Reply