A lightweight framework that simplifies building complex command-line applications with clap.rs.
Overview
cling's name is a play on CLI-ng (as in next-gen) and "cling" the English word. It enables function handlers to cling to clap user-defined structs 😉.
While writing a command-line applications using the terrific clap crate, developers often write boilerplate functions that finds out which command the user has executed, collects the input types, then runs a handler function that does that job. This quickly gets repetitive for multi-command applications. Cling is designed to simplify that workflow and lets developers declaratively map commands to function.
Key Features
- Map CLI commands to handlers declaratively via
#[cling(run = "my_function")] - Define handlers as regular Rust unit-testable functions
- Run handlers on any level of the command tree (middlware-style)
- Handler function arguments are extracted automatically from clap input
- Handlers can return a [
State<T>] value that can be extracted by downstream handlers - Handlers can be either
syncorasyncfunctions - Uniform CLI-friendly error handling with colours
For more details, see:
Compiler support: requires rustc 1.77+
Demo
A quick-and-dirty example to show cling in action
# Cargo.toml
[]
= "cling-example"
= "0.1.0"
= "2021"
[]
= { = "4.4.1", = ["derive", "env"] }
= { = "0.1" }
= { = "1.13.0", = ["full"] }
Our main.rs might look something like this:
use *;
// -- args --
/// A simple multi-command example using cling
// Commands for the app are defined here.
// Options of "honk" command. We define cling(run=...) here to call the
// function when this command is executed.
// -- Handlers --
// We can access &CommonOpts because it derives [Collect]
// Maybe beeps need to be async!
async
// -- main --
async
Now, let's run it and verify that it works as expected
$ simple-multi-command -v honk 5
Honking 5 times
Honk Honk Honk Honk Honk !
Concepts
Runnables
Runnables refer to your clap structs that represent a CLI command. In cling,
any struct or enum that encodes the command tree must derive Run, this
includes the top-level struct of your program (e.g. MyApp in the above demo).
The Run trait tells cling that this type should be attached to a handler
function. Essentially, structs that derive Parser or Subcommand will need to derive Run.
For any struct/enum that derive Run, a #[cling(run = ...)] attribute can be used to
associate a handler with it. It's possible to design a multi-level clap
program with handlers that run on each level, let's look at a few examples to understand
what's possible.
Single Command, Single Handler
use *;
// standard clap attributes
async
Note: We derive Collect and [Clone] on MyApp in order to pass it down to
handler my_only_handler as shared reference app: &MyApp. Deriving Collect
is not necessary if your handler doesn't need access to &MyApp.
Our program will now run the code in my_only_handler
$ sample-1 --name=Steve
User is Steve
Sub-commands with handlers
Given a command structure that looks like this:
MyApp [CommonOpts]
- projects
|- create [CreateOpts]
|- list
- whoami
use *;
CommonOptsProgram-wide options. Any handler should be able to access it if it chooses to.ProjectOptsOptions forprojects [OPTIONS]CreateOptsOptions for theprojects createcommandListOptsOptions for theprojects listcommand
To understand how this works, consider the different set of options that can be passed in projects list subcommand:
Usage: myapp [COMMON-OPTIONS] projects create <NAME>
Our runnables in this design are: MyApp, Commands, ProjectCommands, and CreateOpts.
We can attach a handler to any Run type by using #[cling(run = "...")] attribute.
This handler will run before any handler of sub-commands (if any). For enums that implement
Subcommand, we must attach #[cling(run = "...")] on enum variants that do not
take any arguments (like ProjectCommands::List). However, for any enum variant that takes
arguments, the argument type itself must derive Run.
Handlers
Handlers are regular Rust functions that get executed when a clap command is run. Handlers latch
onto any type that derive Run using the #[cling(run = "function::path")] attribute.
Every type that has #[cling(run = ...)] will run its handler function before running the handlers
of the inner runnables.
Example
use *;
async
init handler will always run before any other handlers in that structure.
$ many-handlers beep
init handler!
Beep beep!
Feature Flags
| Feature | Activation | Effect |
|---|---|---|
derive |
default | Enables #[derive(Run)] and #[derive(Collect)] |
shlex |
"shlex" feature | Enables parsing from text, useful when building REPLs |
Supported Rust Versions
Cling's minimum supported rust version is 1.77.0.
License
Dual-licensed under Apache 2.0 or MIT.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be dual licensed as above, without any additional terms or conditions.