{"id":18338607,"url":"https://github.com/mqnfred/clishe","last_synced_at":"2025-04-06T05:31:46.951Z","repository":{"id":57605749,"uuid":"264766945","full_name":"mqnfred/clishe","owner":"mqnfred","description":"Opinionated CLI (with shell) framework in rust","archived":false,"fork":false,"pushed_at":"2023-09-11T01:54:56.000Z","size":75,"stargazers_count":33,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-28T22:13:46.073Z","etag":null,"topics":["autocomplete","cli","rust","shell"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mqnfred.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-05-17T22:15:38.000Z","updated_at":"2024-10-04T08:55:11.000Z","dependencies_parsed_at":"2024-11-05T20:25:41.618Z","dependency_job_id":null,"html_url":"https://github.com/mqnfred/clishe","commit_stats":{"total_commits":49,"total_committers":1,"mean_commits":49.0,"dds":0.0,"last_synced_commit":"e1c7c91aaf29c07f16188c20fa6ca515a191e1a8"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mqnfred%2Fclishe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mqnfred%2Fclishe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mqnfred%2Fclishe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mqnfred%2Fclishe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mqnfred","download_url":"https://codeload.github.com/mqnfred/clishe/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247440346,"owners_count":20939221,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["autocomplete","cli","rust","shell"],"created_at":"2024-11-05T20:14:42.206Z","updated_at":"2025-04-06T05:31:46.588Z","avatar_url":"https://github.com/mqnfred.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Clishé\n\n[![crates.io](https://img.shields.io/crates/v/clishe)](https://crates.io/clishe)\n[![badge](https://docs.rs/clishe/badge.svg)](https://docs.rs/clishe/)\n\nClishé is a cli mini-framework in rust. Write your CLI using an idiom focused\non the following values:\n\n 1. Reduction of boilerplate for repetitive command declaration\n 2. Command implementation and definition live side-to-side\n 3. Control-flow skeleton around args, mutable state (context), return type\n 4. We should be able to generate shells on-demand based on a cli\n 5. Auto-complete is a first-class citizen, in and out of the inner shell [wip]\n\nWhen I work on my rust ecosystem, I sometimes find it necessary to \"try out a\npiece\" by calling some specific endpoint or library API. It should be trivial\nto create small, re-usable clis for these purposes. This is what clishé aims to\nachieve by being opinionated and limiting scaffolding.\n\nClishé is a surprisingly thin wrapper around the following technologies:\n\n - `anyhow` for error handling\n - `clap` for cli building\n - `rustyline`/`shellwords` for shell generation\n\nThose libraries contribute most of its power to this framework, which is not\nmuch more than a collection of technologies, a thin\n[DSL](https://en.wikipedia.org/wiki/Domain-specific_language) and an opinion.\n\n## Example\n\nThis is a simple app with dummy commands to display the spirit of clishe.\n\n```rust\n#[macro_use]\nextern crate clap;\n#[macro_use]\nextern crate clishe;\nuse ::clishe::prelude::*;\n\nfn main() {\n    let mut ctx = Context(\"\".to_owned());\n    // The same static methods available to the ::clap::Parser trait are\n    // available here. If you have a vector of arguments, just use\n    // `parse_from()`. If you want to capture the parsing errors instead of\n    // letting clap print them and exit them, you should use `try_parse()`\n    //                      vvvvv\n    if let Err(err) = Food::parse().run(\u0026mut ctx) {\n        // ^^^^^^^^ We ignore the Ok(_) scenario here since Returned is a\n        // useless unit struct, but this is where we would handle it if the\n        // returned value was meaningful.\n        eprint!(\"error: {}\", err);\n    }\n}\n\n// Could also be called Database, State, ... depending on the domain of your\n// CLI. This is the single object available in commands aside from\n// arguments/options. The context has two likely lifetimes:\n//\n//  - Created right before, handed to this command and dies with this command\n//  - Created at the beginning of the shell, passed from one command to another\npub struct Context(String);\n\n// Could be anything. This turns the cli app into a function(Context, args from\n// user) = Returned.\n//\n// This type offers us two approaches for our CLI apps: procedural and\n// functional in nature. In the first one, one would apply side-effects inside\n// of the application tree. On the other hand, one could aggregate functionally\n// all side-effects to the Returned type and execute them at the scope of the\n// main.\npub struct Returned;\n\n// Dispatchers are commands which hold sub-commands. The root of a cli-like\n// application is often a dispatcher. The application then takes the shape of a\n// tree of dispatcher nodes, with commands!{} implementations as leaves.\ndispatchers! {\n    // Any of the clap attributes you would use on top of the main clap app,\n    // we will use here on the Food dispatcher, as we have chosen it to be the\n    // root of our cli application.\n    #[clap(\n        name = \"clishe\",\n        version = \"0.2.0\",\n        about = \"Food market\",\n        before_help = \"In case you're hungry, here is a\",\n        after_help = \"For you\",\n    )]\n    Food(self, _: \u0026mut Context) -\u003e Result\u003cReturned\u003e [\n        Veggies: veggies::Veggies,\n        Meat: meat::Meat,\n        // The shell command comes with the clishe library. It usually takes a\n        // dispatcher and starts a shell using the rustyline library in which\n        // all sub-commands of the dispatcher are available as first-level\n        // commands. From the point-of-view of the user of the binary, it will\n        // look something like this:\n        //\n        //     $ cargo run --example complete shell\n        //     \u003e veggies lettuce friend\n        //     Welcome to the table, friend\n        //     \u003e \n        #[clap(alias = \"sh\", about = \"Subcommands of this in a shell\")]\n        Shell: Shell\u003cContext, Returned, Food\u003e,\n    ],\n}\n\nmod veggies {\n    use ::clishe::prelude::*;\n\n    // All dispatchers are created equal, they\n    // could all be used as the root of an app.\n    dispatchers! {\n        // All clap macro attributes available on\n        // top of clap commands can be used here.\n        #[clap(about = \"Welcome to the Jungle\")]\n        // The name under which commands are declared inside a dispatcher is\n        // the name that is used. This is just the name of the structure\n        // vvvv inside of the program. Same for dispatchers.\n        Veggies(self, _: \u0026mut crate::Context) -\u003e Result\u003ccrate::Returned\u003e [\n            Carrots: Carrots,\n            Lettuce: Lettuce,\n        ],\n    }\n\n    // These are clap commands, they contain concrete command implementations.\n    // They are used as leaves under the dispatchers. They could also be used\n    // as the root of the application, making most of the point of the\n    // framework moot!\n    commands! {\n        Carrots(self, _ctx: \u0026mut crate::Context) -\u003e Result\u003ccrate::Returned\u003e {\n            Ok(crate::Returned)\n        } struct {\n            // All clap macro attributes available on top\n            // of clap command fields can be used here.\n            #[clap(short, long)]\n            name: Option\u003cString\u003e,\n        },\n\n        // The return type must be the same for every command and dispatcher\n        // in the command hierarchy.                       vvvvvvvvvvvvvvv\n        Lettuce(self, _ctx: \u0026mut crate::Context) -\u003e Result\u003ccrate::Returned\u003e {\n            println!(\"Welcome to the table, {}\", self.name.as_ref().map(|s| {\n                s.as_ref()\n            }).unwrap_or(\"unknown\"));\n            Ok(crate::Returned)\n        } struct {\n            name: Option\u003cString\u003e,\n        },\n    }\n}\n\nmod meat {\n    use ::clishe::prelude::*;\n\n    dispatchers! {\n        // Overriding the command name at this level is not going to work.\n        #[clap(name = \"carne\", about = \"Aimez la viande, mangez-en mieux\")]\n        Meat(self, _: \u0026mut crate::Context) -\u003e Result\u003ccrate::Returned\u003e [\n            // All sub-commands' clap attributes are shadowed by\n            // attributes applied at higher levels in the command hierarchy.\n            #[clap(about = \"Le boeuf. C'est ça qu'on mange\")]\n            // If you want to override the name of a command, do it here.\n            Boeuf: Beef,\n            // You could use the clap macro attribute. With different abouts.\n            #[clap(name = \"vaca\", about = \"Vaca. Lo que vamos a comer\")]\n            Beef: Beef,\n        ],\n    }\n\n    commands! {\n        // The \"about\" override here and the \"name\"\n        // override in the Meat dispatcher will combine.\n        #[clap(about = \"Beef. It's What for Dinner\")]\n        Beef(self, ctx: \u0026mut crate::Context) -\u003e Result\u003ccrate::Returned\u003e {\n            // All fields are available as owned in here vvvvvvvvv\n            ctx.0 = format!(\"Welcome to the table, {}!\", self.name);\n            Ok(crate::Returned)\n        } struct {\n            name: String,\n        },\n    }\n}\n```\n\nThis code will provide you with the following program:\n\n```\n$ cargo run --example complete\nclishe\n\nUSAGE:\n    complete \u003cSUBCOMMAND\u003e\n\nFLAGS:\n    -h, --help       Prints help information\n    -V, --version    Prints version information\n\nSUBCOMMANDS:\n    help       Prints this message or the help of the given subcommand(s)\n    meat       Aimez la viande, mangez-en mieux\n    shell      The subcommands of this command in a shell\n    veggies    Welcome to the Jungle\n```\n\nYou can also invoke the shell and \"enter\" the cli:\n\n```\n$ cargo run --example complete shell\n\u003e veggies lettuce friend\nWelcome to the table, friend\n\u003e _\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmqnfred%2Fclishe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmqnfred%2Fclishe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmqnfred%2Fclishe/lists"}