Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/mqnfred/clishe
Opinionated CLI (with shell) framework in rust
https://github.com/mqnfred/clishe
autocomplete cli rust shell
Last synced: 3 days ago
JSON representation
Opinionated CLI (with shell) framework in rust
- Host: GitHub
- URL: https://github.com/mqnfred/clishe
- Owner: mqnfred
- Created: 2020-05-17T22:15:38.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2023-09-11T01:54:56.000Z (about 1 year ago)
- Last Synced: 2024-04-24T20:42:08.156Z (7 months ago)
- Topics: autocomplete, cli, rust, shell
- Language: Rust
- Homepage:
- Size: 73.2 KB
- Stars: 31
- Watchers: 4
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Clishé
[![crates.io](https://img.shields.io/crates/v/clishe)](https://crates.io/clishe)
[![badge](https://docs.rs/clishe/badge.svg)](https://docs.rs/clishe/)Clishé is a cli mini-framework in rust. Write your CLI using an idiom focused
on the following values:1. Reduction of boilerplate for repetitive command declaration
2. Command implementation and definition live side-to-side
3. Control-flow skeleton around args, mutable state (context), return type
4. We should be able to generate shells on-demand based on a cli
5. Auto-complete is a first-class citizen, in and out of the inner shell [wip]When I work on my rust ecosystem, I sometimes find it necessary to "try out a
piece" by calling some specific endpoint or library API. It should be trivial
to create small, re-usable clis for these purposes. This is what clishé aims to
achieve by being opinionated and limiting scaffolding.Clishé is a surprisingly thin wrapper around the following technologies:
- `anyhow` for error handling
- `clap` for cli building
- `rustyline`/`shellwords` for shell generationThose libraries contribute most of its power to this framework, which is not
much more than a collection of technologies, a thin
[DSL](https://en.wikipedia.org/wiki/Domain-specific_language) and an opinion.## Example
This is a simple app with dummy commands to display the spirit of clishe.
```rust
#[macro_use]
extern crate clap;
#[macro_use]
extern crate clishe;
use ::clishe::prelude::*;fn main() {
let mut ctx = Context("".to_owned());
// The same static methods available to the ::clap::Parser trait are
// available here. If you have a vector of arguments, just use
// `parse_from()`. If you want to capture the parsing errors instead of
// letting clap print them and exit them, you should use `try_parse()`
// vvvvv
if let Err(err) = Food::parse().run(&mut ctx) {
// ^^^^^^^^ We ignore the Ok(_) scenario here since Returned is a
// useless unit struct, but this is where we would handle it if the
// returned value was meaningful.
eprint!("error: {}", err);
}
}// Could also be called Database, State, ... depending on the domain of your
// CLI. This is the single object available in commands aside from
// arguments/options. The context has two likely lifetimes:
//
// - Created right before, handed to this command and dies with this command
// - Created at the beginning of the shell, passed from one command to another
pub struct Context(String);// Could be anything. This turns the cli app into a function(Context, args from
// user) = Returned.
//
// This type offers us two approaches for our CLI apps: procedural and
// functional in nature. In the first one, one would apply side-effects inside
// of the application tree. On the other hand, one could aggregate functionally
// all side-effects to the Returned type and execute them at the scope of the
// main.
pub struct Returned;// Dispatchers are commands which hold sub-commands. The root of a cli-like
// application is often a dispatcher. The application then takes the shape of a
// tree of dispatcher nodes, with commands!{} implementations as leaves.
dispatchers! {
// Any of the clap attributes you would use on top of the main clap app,
// we will use here on the Food dispatcher, as we have chosen it to be the
// root of our cli application.
#[clap(
name = "clishe",
version = "0.2.0",
about = "Food market",
before_help = "In case you're hungry, here is a",
after_help = "For you",
)]
Food(self, _: &mut Context) -> Result [
Veggies: veggies::Veggies,
Meat: meat::Meat,
// The shell command comes with the clishe library. It usually takes a
// dispatcher and starts a shell using the rustyline library in which
// all sub-commands of the dispatcher are available as first-level
// commands. From the point-of-view of the user of the binary, it will
// look something like this:
//
// $ cargo run --example complete shell
// > veggies lettuce friend
// Welcome to the table, friend
// >
#[clap(alias = "sh", about = "Subcommands of this in a shell")]
Shell: Shell,
],
}mod veggies {
use ::clishe::prelude::*;// All dispatchers are created equal, they
// could all be used as the root of an app.
dispatchers! {
// All clap macro attributes available on
// top of clap commands can be used here.
#[clap(about = "Welcome to the Jungle")]
// The name under which commands are declared inside a dispatcher is
// the name that is used. This is just the name of the structure
// vvvv inside of the program. Same for dispatchers.
Veggies(self, _: &mut crate::Context) -> Result [
Carrots: Carrots,
Lettuce: Lettuce,
],
}// These are clap commands, they contain concrete command implementations.
// They are used as leaves under the dispatchers. They could also be used
// as the root of the application, making most of the point of the
// framework moot!
commands! {
Carrots(self, _ctx: &mut crate::Context) -> Result {
Ok(crate::Returned)
} struct {
// All clap macro attributes available on top
// of clap command fields can be used here.
#[clap(short, long)]
name: Option,
},// The return type must be the same for every command and dispatcher
// in the command hierarchy. vvvvvvvvvvvvvvv
Lettuce(self, _ctx: &mut crate::Context) -> Result {
println!("Welcome to the table, {}", self.name.as_ref().map(|s| {
s.as_ref()
}).unwrap_or("unknown"));
Ok(crate::Returned)
} struct {
name: Option,
},
}
}mod meat {
use ::clishe::prelude::*;dispatchers! {
// Overriding the command name at this level is not going to work.
#[clap(name = "carne", about = "Aimez la viande, mangez-en mieux")]
Meat(self, _: &mut crate::Context) -> Result [
// All sub-commands' clap attributes are shadowed by
// attributes applied at higher levels in the command hierarchy.
#[clap(about = "Le boeuf. C'est ça qu'on mange")]
// If you want to override the name of a command, do it here.
Boeuf: Beef,
// You could use the clap macro attribute. With different abouts.
#[clap(name = "vaca", about = "Vaca. Lo que vamos a comer")]
Beef: Beef,
],
}commands! {
// The "about" override here and the "name"
// override in the Meat dispatcher will combine.
#[clap(about = "Beef. It's What for Dinner")]
Beef(self, ctx: &mut crate::Context) -> Result {
// All fields are available as owned in here vvvvvvvvv
ctx.0 = format!("Welcome to the table, {}!", self.name);
Ok(crate::Returned)
} struct {
name: String,
},
}
}
```This code will provide you with the following program:
```
$ cargo run --example complete
clisheUSAGE:
completeFLAGS:
-h, --help Prints help information
-V, --version Prints version informationSUBCOMMANDS:
help Prints this message or the help of the given subcommand(s)
meat Aimez la viande, mangez-en mieux
shell The subcommands of this command in a shell
veggies Welcome to the Jungle
```You can also invoke the shell and "enter" the cli:
```
$ cargo run --example complete shell
> veggies lettuce friend
Welcome to the table, friend
> _
```