A minimal CLI framework written in Rust

Overview

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.1"

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"))
        .description(env!("CARGO_PKG_DESCRIPTION"))
        .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"))
        .description(env!("CARGO_PKG_DESCRIPTION"))
        .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")
        .description("add command")
        .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")
        .description("sub command")
        .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"))
        .description(env!("CARGO_PKG_DESCRIPTION"))
        .author(env!("CARGO_PKG_AUTHORS"))
        .version(env!("CARGO_PKG_VERSION"))
        .usage("cli [name]")
        .action(default_action)
        .flag(
            Flag::new("bye", FlagType::Bool)
                .description("Bye flag")
                .alias("b"),
        )
        .flag(
            Flag::new("age", FlagType::Int)
                .description("Age flag")
                .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")
        .description("calc command")
        .alias("cl, c")
        .usage("cli calc(cl, c) [nums...]")
        .action(calc_action)
        .flag(
            Flag::new("operator", FlagType::String)
                .description("Operator flag(ex. cli calc --operator add 1 2 3)")
                .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
  • Panic when missing argument to string flag

    Panic when missing argument to string flag

    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 
    opened by mpizenberg 3
  • Alternative to Context.args that has filtered out flags

    Alternative to Context.args that has filtered out flags

    Hi there,

    Thanks for the great library.

    Maybe there's already a way to do this: it would be nice if when implementing a command like wc -l foo.txt, the -l isn't reported in Context.args (or maybe an alternative to Context.args with this functionality. Perhaps I'm thinking about it wrong.

    Thanks,

    opened by bbarker 2
  • How can i take multiple args as a vector?

    How can i take multiple args as a vector?

    this is what i have done so far, but it takes only the first arg.

    why it only takes the first arg

    • because it only runs once
    • because i used c.args[0]
    let mut files: Vec<&str> = vec!();
    
    match c.args.len() {
        1 => files.push(&c.args[0]),
        _ => {
            c.help();
            exit(0);
        }
    };
    
    opened by XDream8 1
  • Removing color module was a breaking change, should have bumped major version number

    Removing color module was a breaking change, should have bumped major version number

    Removing the color module caused some depending projects to fail to build.

    For example, ruget uses the color module and now cargo install ruget fails, because cargo tries to use the newest compatible version of dependencies. A work-around would be to run cargo install ruget --locked, but the more friendly solution would be to yank current effected releases and re-release it with a major version bump to 2.0

    opened by acshi 1
  • Use ? operator

    Use ? operator

    If Result value returned from a subroutine is passed as the return value of the function, you can use ? operator. This change will make match expression easier to understand.

    FYI: https://qiita.com/kanna/items/a0c10a0563573d5b2ed0

    and some tests are added.

    opened by rnitta 1
  • Enabling error handling in the action function

    Enabling error handling in the action function

    close #48

    use seahorse::{error::FlagError, Context};
    
    fn action(c: &Context) {
        match c.int_flag("int") {
            Ok(i) => println!("{}", i),
            Err(e) => match e {
                FlagError::ArgumentError => println!("argument error"),
                FlagError::TypeError => println!("type error"),
                FlagError::ValueTypeError => println!("value type error"),
                FlagError::Undefined => println!("undefined"),
                FlagError::NotFound => println!("not found"),
            },
        }
    }
    
    opened by ksk001100 1
  • help called when no flags specified.

    help called when no flags specified.

    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 
    opened by rnitta 1
  • Migrate travis to GitHub Actions

    Migrate travis to GitHub Actions

    Close #14

    cargo test with stable, beta, nightly(allow failure), macos stable and minimum suppoted rust version(1.35.0). cargo fmt --check with stable.

    opened by rnitta 1
  • Add Configurable Author and Description Options and Display Them

    Add Configurable Author and Description Options and Display Them

    Many other CLI tools support "author" and "description" attribute. So, I added them. They will be displayed when users run command in its help.

    like:

    スクリーンショット 2020-02-17 12 21 25
    opened by rnitta 1
  • nested sub command

    nested sub command

    Many parts of the implementation overlap with the App, so they will be integrated later.

    use seahorse::*;
    use std::env;
    
    fn main() {
        let app = App::new("cli").command(
            Command::new("hello")
                .alias("h")
                .action(|_| println!("Hello"))
                .command(
                    Command::new("world")
                        .alias("w")
                        .action(|_| println!("World")),
                ),
        );
    
        app.run(env::args().collect());
    }
    
    opened by ksk001100 0
  • Custom help text

    Custom help text

    This is in response to #66 and should meet what I believe the request wanted at its base.

    This adds a new usage example, docs, and a test.

    New and old tests are all passing, existing examples run as they did before.

    If I missed the mark here, let me know, so we can get it on point.

    opened by BarePotato 0
  • Custom Help Flag

    Custom Help Flag

    Is it possible to have a completely custom help message / disable the default help flag? I would love to be able to use my own formatting for the help section / use a custom help method

    enhancement good first issue 
    opened by blobcode 2
Releases(v2.1.0)
Owner
Keisuke Toyota
Keisuke Toyota
Beautiful, minimal, opinionated CLI prompts inspired by the Clack NPM package

Effortlessly build beautiful command-line apps with Rust ?? ✨ Beautiful, minimal, opinionated CLI prompts inspired by the @clack/prompts npm package.

Alexander Fadeev 7 Jul 23, 2023
⚡️ Lightning-fast and minimal calendar command line. Written in Rust 🦀

⚡️ Lightning-fast and minimal calendar command line. It's similar to cal. Written in Rust ??

Arthur Henrique 36 Jan 1, 2023
CarLI is a framework for creating single-command and multi-command CLI applications in Rust

CarLI is a framework for creating single-command and multi-command CLI applications in Rust. The framework provides error and IO types better suited for the command line environment, especially in cases where unit testing is needed.

Kevin Herrera 3 Jan 21, 2022
A small cli demo of rust&wasm hostcall framework.

A Cli Example for Rust and WebAssembly Hostcall Usage # build wasms for ervery module in the `wasm` directory and move them to the root directory # ex

BC2022 3 Aug 29, 2022
Bashly - Bash CLI Framework and Generator

Bashly - Bash CLI Framework and Generator Create feature-rich bash scripts using simple YAML configuration

Danny Ben Shitrit 1.4k Jan 4, 2023
CLI toolkit for GTD framework.

GTDF_Crabby CLI toolkit for GTD framework. How to use crabby 0. Parameters Crabby is a CLI toolkit and gets parameters as input. All the main options

akrck02 2 Feb 13, 2022
82 fun and easy to use, lightweight, spinners for Rust, with minimal overhead.

Spinners for Rust 82 fun and easy to use, lightweight, spinners for Rust, with minimal overhead, all the way from simple dots, to fun emoji based "spi

Juliette Cordor 2 May 17, 2022
Fast, minimal, feature-rich, extended formatting syntax for Rust!

Formatting Tools Fast, minimal, feature-rich, extended formatting syntax for Rust! Features include: Arbitrary expressions inside the formatting brace

Casper 58 Dec 26, 2022
A minimal browser with a super simple rendering engine for HTML and CSS, using Rust.

minimal-browser A minimal browser with a super simple rendering engine for HTML and CSS, using Rust. Credit: https://github.com/mbrubeck and https://l

Federico Baldini 3 Jan 15, 2023
A minimal window context for Rust on Windows.

winctx A minimal window context for Rust on Windows. I read msdn so you don't have to. This crate provides a minimalistic method for setting up and ru

John-John Tedro 19 Dec 25, 2023
A hackable, minimal, fast TUI file explorer, stealing ideas from nnn and fzf.

xplr A hackable, minimal, fast TUI file explorer, stealing ideas from nnn and fzf. [Quickstart] [Features] [Plugins] [Documentation] [Upgrade Guide] [

Arijit Basu 2.6k Jan 1, 2023
A minimal argument parser

Pieces An argument parser built with control in mind. Parsing The results you get are dependent on what order you parse in. If you want to say only pa

ibx34 3 Sep 30, 2021
☄🌌️ The minimal, blazing-fast, and infinitely customizable prompt for any shell

☄??️ The minimal, blazing-fast, and infinitely customizable prompt for any shell

Starship Command 31.6k Dec 30, 2022
A minimal readline with multiline and async support

RustyLine Async A minimal readline with multiline and async support. Inspired by rustyline , async-readline & termion-async-input.

Zyansheep 16 Dec 15, 2022
Minimal Pandoc compiler -> HTML

Minimal Pandoc compiler -> HTML

/c/ympfh 1 Apr 6, 2022
Minimal recursive "truncate file/directory names to meet requirements" tool

trunc_filenames ssokolow@monolith ~ % trunc_filenames --help trunc_filenames 0.1.0 Rename files and directories to fit length limits. WARNING: Will n

Stephan Sokolow 2 Nov 20, 2022
Minimal and blazing-fast file server. For real, this time.

Zy Minimal and blazing-fast file server. For real, this time. Features Single Page Application support Partial responses (Range support) Cross-Origin

Miraculous Owonubi 17 Dec 18, 2022
Minimal server (with maximal security) for turning off an X10-controlled fan over HTTP

"Fan Remote" A self-contained Rust binary to expose a single X10 command (turn off that fan) as an HTML form button. In its current form, it's highly

Stephan Sokolow 2 Oct 23, 2022