clap-action-command

Crates.ioclap-action-command
lib.rsclap-action-command
version0.1.0
sourcesrc
created_at2023-01-20 01:39:29.303888
updated_at2023-01-20 01:39:29.303888
descriptionA command-map pattern layered on Clap subcommands
homepagehttps://github.com/mysteriouspants/clap-action-command
repository
max_upload_size
id763060
size28,684
Chris R. Miller (mysteriouspants)

documentation

README

Clap Action Command

A command-map pattern for easily configuring and dispatching modular subcommands using the Clap argument parser. This makes building and modifying large CLI projects more ergonomic by reducing the touch points required to add or modify additional subcommands.

Define modular subcommands by implementing the ActionCommand trait. Dispatch to the modular subcommand by placing it in a CommandMap.

use std::error::Error;
use clap::{Arg, ArgMatches, builder::NonEmptyStringValueParser, Command};
use clap_action_command::{
    ActionCommand, CommandMap, get_one, vec1::{Vec1, vec1}
};

static NAME_ARG: &str = "name";

struct HelloWorldCommand {}

impl ActionCommand for HelloWorldCommand {
    fn name(&self) -> &'static str {
        "hello-world"
    }

    fn command(&self, command: Command) -> Command {
        command
            .about("Say hello to the world")
            .alias("h")
            .arg(
                Arg::new(NAME_ARG)
                    .short('n')
                    .value_name("NAME")
                    .required(false)
                    .value_parser(NonEmptyStringValueParser::new())
            )
    }

    fn action(
            &self, matches: Vec1<&ArgMatches>
    ) -> Result<(), Box<dyn Error>> {
        if let Some(name) = get_one::<String>(&matches, NAME_ARG) {
            println!("Hello, {}!", name);
        } else {
            println!("Hello, world!");
        }

        Ok(())
    }
}

let command_map = CommandMap::builder()
    .push(HelloWorldCommand {})
    // adding a new subcommand is as easy as referencing it here
    .build();
let command = Command::new("my-program").subcommands(command_map.commands());
let matches = command.get_matches_from([
    "my-program", "hello-world", "-n", "Steeve"
]);
command_map.dispatch(vec1![&matches]);

On Asynchrony

Tokio examples place an attribute on the main function.

#[tokio::main]
fn main() {
    println!("Hello, world!");
}

ActionCommand, however, is not asynchronous. Move the Tokio attribute below the ActionCommand to use ActionCommand to start an asynchronous program.

struct AsyncCommand {}

impl AsyncCommand {
    #[tokio::main]
    async fn async_action(&self) -> Result<(), Error> {
        println!("Hello, world!");

        Ok(())
    }
}

impl ActionCommand for AsyncCommand {
    fn name(&self) -> &'static str {
        "async-command"
    }

    fn command(&self, command: Command) -> Command {
        command
            .about("Say hello to the world")
    }

    fn action(
        &self, matches: Vec1<&ArgMatches>,
    ) -> Result<(), Box<dyn Error>> {
        self.async_action()?;

        Ok(())
    }
}

License

I want you to be able to use this software regardless of who you may be, what you are working on, or the environment in which you are working on it - I hope you'll use it for good and not evil! To this end, the Clap Action Command source code is licensed under the 1-clause BSD license, with other licenses available by request. Happy coding!

Commit count: 0

cargo fmt