Merge branch 'automatic-help' into 'master'
Automatic help/usage See merge request arkham/arkham!2
This commit is contained in:
commit
5a725c2aef
|
@ -0,0 +1,22 @@
|
|||
Arkham is a framework for building CLI tools and applications. It provides basic
|
||||
building blocks for building attractive and smooth CLIs
|
||||
|
||||
|
||||
# CLI Features
|
||||
|
||||
## Option Parsing
|
||||
|
||||
* Opt/Flag handling for short, long command line options
|
||||
* Nested subcommands with their own flags
|
||||
* Opts are hierarchal and can be utilized from parent commands
|
||||
* Automatic usage details for subcommands and bare execution
|
||||
|
||||
## Styling
|
||||
|
||||
* Canned helper methods for generating colored and formatted outputs for common
|
||||
structures: Detail lists, headers, etc.
|
||||
|
||||
# Basic Usage
|
||||
|
||||
* [fib](https://git.5sigma.io/arkham/arkham/-/blob/master/examples/fib.rs) - An example using subcommands and command line options
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use arkham::{App, Command, Opt, OptKind};
|
||||
use arkham::{App, Command, Opt};
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
fn parse_args(c: &mut Criterion) {
|
||||
|
@ -11,20 +11,10 @@ fn parse_args(c: &mut Criterion) {
|
|||
"thing".into(),
|
||||
];
|
||||
let app = App::new()
|
||||
.opt(Opt {
|
||||
name: "user".into(),
|
||||
short: "u".into(),
|
||||
long: "user".into(),
|
||||
kind: OptKind::String,
|
||||
})
|
||||
.opt(Opt::scalar("user").short("u").long("user"))
|
||||
.command(
|
||||
Command::new("thing")
|
||||
.opt(Opt {
|
||||
name: "config".into(),
|
||||
short: "c".into(),
|
||||
long: "config".into(),
|
||||
kind: OptKind::String,
|
||||
})
|
||||
.opt(Opt::scalar("config").short("c").long("config"))
|
||||
.handler(|_, ctx, _| {
|
||||
assert_eq!(ctx.get_string("user"), Some("joe".into()));
|
||||
assert_eq!(ctx.get_string("config"), Some("c.json".into()));
|
||||
|
|
|
@ -1,11 +1,20 @@
|
|||
use arkham::{App, Context, Opt};
|
||||
use arkham::{App, Command, Context, Opt};
|
||||
|
||||
fn main() {
|
||||
let _ = App::new()
|
||||
.name("Fibonacci App")
|
||||
.version("1.0")
|
||||
.opt(Opt::scalar("count").short("n").long("num"))
|
||||
.handler(fibonacci_handler)
|
||||
.command(
|
||||
Command::new("fib")
|
||||
.opt(
|
||||
Opt::scalar("count")
|
||||
.short("n")
|
||||
.long("num")
|
||||
.desc("The index of the fibonacci number to return"),
|
||||
)
|
||||
.short_desc("Calculates a fibonacci number")
|
||||
.handler(fibonacci_handler),
|
||||
)
|
||||
.run()
|
||||
.unwrap();
|
||||
}
|
||||
|
|
16
src/app.rs
16
src/app.rs
|
@ -35,7 +35,11 @@ impl App {
|
|||
impl App {
|
||||
/// Contructs a new App instance which can have opts defined and subcommands attached.
|
||||
pub fn new() -> Self {
|
||||
App::default().command(Command::new("help").handler(help))
|
||||
App::default().command(
|
||||
Command::new("help")
|
||||
.handler(help)
|
||||
.short_desc("Displays help information"),
|
||||
)
|
||||
}
|
||||
|
||||
/// Sets the name of the application. If not set the cargo package name will be used.
|
||||
|
@ -151,7 +155,7 @@ impl App {
|
|||
/// }
|
||||
/// ```
|
||||
pub fn run(&mut self) -> Result<()> {
|
||||
self.run_with(env::args().collect())
|
||||
self.run_with(env::args().skip(1).collect())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,6 +212,12 @@ fn run_command(app: &App, cmd: &Command, args: &[String], opts: &mut Vec<ActiveO
|
|||
return run_command(app, cmd, &ignored, opts);
|
||||
}
|
||||
|
||||
// Automatic command help display
|
||||
if ignored.iter().any(|a| a == "-h" || a == "--help") {
|
||||
super::command::print_command_help(cmd, &vec![]);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// If any ignored parameters start with "-" we will throw an unknwon flag error.
|
||||
if let Some(arg) = ignored.iter().find(|a| a.starts_with("-")) {
|
||||
return Err(OptError::InvalidOpt(arg.clone()));
|
||||
|
@ -215,7 +225,7 @@ fn run_command(app: &App, cmd: &Command, args: &[String], opts: &mut Vec<ActiveO
|
|||
|
||||
// Execute the command handler
|
||||
if let Some(handler) = cmd.handler {
|
||||
handler(app, &Context::new(opts.clone()), &ignored);
|
||||
handler(app, &Context::new(cmd.clone(), opts.clone()), &ignored);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -6,11 +6,14 @@ use crate::{
|
|||
|
||||
pub type Handler = fn(&App, &Context, &[String]);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Command {
|
||||
pub name: String,
|
||||
pub commands: Vec<Command>,
|
||||
pub handler: Option<Handler>,
|
||||
pub opts: Vec<opt::Opt>,
|
||||
pub long_desc: Option<String>,
|
||||
pub short_desc: Option<String>,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
|
@ -20,6 +23,8 @@ impl Command {
|
|||
commands: vec![],
|
||||
handler: None,
|
||||
opts: vec![],
|
||||
long_desc: None,
|
||||
short_desc: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,8 +37,54 @@ impl Command {
|
|||
self.opts.push(opt);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn short_desc(mut self, short_desc: &str) -> Self {
|
||||
self.short_desc = Some(short_desc.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn long_desc(mut self, long_desc: &str) -> Self {
|
||||
self.short_desc = Some(long_desc.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn help(app: &App, _ctx: &Context, _args: &[String]) {
|
||||
pub(crate) fn help(app: &App, _ctx: &Context, args: &[String]) {
|
||||
vox::print(app.application_header());
|
||||
print_command_help(&app.root, args);
|
||||
}
|
||||
|
||||
pub(crate) fn print_command_help(cmd: &Command, args: &[String]) {
|
||||
if !args.is_empty() {
|
||||
if let Some(cmd) = cmd.commands.iter().find(|c| Some(&c.name) == args.first()) {
|
||||
return print_command_help(cmd, &args[1..]);
|
||||
}
|
||||
}
|
||||
vox::print("");
|
||||
if let Some(desc) = cmd.long_desc.as_ref().or(cmd.short_desc.as_ref()) {
|
||||
if cmd.name != "root" {
|
||||
vox::header(&cmd.name.to_uppercase());
|
||||
}
|
||||
vox::print(desc);
|
||||
vox::print("");
|
||||
}
|
||||
if !cmd.opts.is_empty() {
|
||||
vox::header("OPTIONS");
|
||||
vox::description_list(
|
||||
cmd.opts
|
||||
.iter()
|
||||
.map(|o| (o.usage(), o.desc.clone().unwrap_or_default()))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
if !cmd.commands.is_empty() {
|
||||
vox::header("Subcommands");
|
||||
vox::description_list(
|
||||
cmd.commands
|
||||
.iter()
|
||||
.map(|c| (c.name.clone(), c.short_desc.clone().unwrap_or_default()))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,26 @@
|
|||
use crate::opt::{ActiveOpt, OptKind};
|
||||
use crate::{
|
||||
opt::{ActiveOpt, OptKind},
|
||||
Command,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Context {
|
||||
opts: Vec<ActiveOpt>,
|
||||
cmd: Command,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub(crate) fn new(opts: Vec<ActiveOpt>) -> Self {
|
||||
Self { opts }
|
||||
pub(crate) fn new(cmd: Command, opts: Vec<ActiveOpt>) -> Self {
|
||||
Self { opts, cmd }
|
||||
}
|
||||
|
||||
/// Checks for the existance of a flag
|
||||
pub fn flag(&self, name: &str) -> bool {
|
||||
self.opts
|
||||
.iter()
|
||||
.any(|o| o.definition.name == name && matches!(o.definition.kind, OptKind::Flag))
|
||||
}
|
||||
|
||||
/// Returns the value of an option if one exists
|
||||
pub fn get_string(&self, name: &str) -> Option<String> {
|
||||
self.opts
|
||||
.iter()
|
||||
|
@ -28,4 +33,9 @@ impl Context {
|
|||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// Can be used to display the automatic help message for the current command.
|
||||
pub fn display_help(&self) {
|
||||
crate::command::print_command_help(&self.cmd, &vec![])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,8 @@ mod app;
|
|||
mod command;
|
||||
mod context;
|
||||
mod opt;
|
||||
// mod style;
|
||||
// mod tasks;
|
||||
pub mod vox;
|
||||
pub use app::*;
|
||||
pub use command::*;
|
||||
pub use context::*;
|
||||
pub use opt::*;
|
||||
// pub use style::*;
|
||||
|
|
32
src/opt.rs
32
src/opt.rs
|
@ -8,6 +8,7 @@ pub struct Opt {
|
|||
pub name: String,
|
||||
pub short: String,
|
||||
pub long: String,
|
||||
pub desc: Option<String>,
|
||||
pub(crate) kind: OptKind,
|
||||
}
|
||||
|
||||
|
@ -25,6 +26,7 @@ impl Opt {
|
|||
short: "".into(),
|
||||
long: "".into(),
|
||||
kind: OptKind::Flag,
|
||||
desc: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +43,7 @@ impl Opt {
|
|||
short: "".into(),
|
||||
long: "".into(),
|
||||
kind: OptKind::String,
|
||||
desc: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,6 +70,35 @@ impl Opt {
|
|||
self.long = long.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the description for the option. This is displayed when listing via help commands
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// use arkham::{Opt, App};
|
||||
/// App::new()
|
||||
/// .opt(
|
||||
/// Opt::scalar("user")
|
||||
/// .short("u")
|
||||
/// .long("user")
|
||||
/// .desc("The user to perform the action against")
|
||||
/// );
|
||||
///```
|
||||
pub fn desc(mut self, desc: &str) -> Self {
|
||||
self.desc = Some(desc.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn usage(&self) -> String {
|
||||
match self.kind {
|
||||
OptKind::Flag => {
|
||||
format!("-{}, --{}", self.short, self.long)
|
||||
}
|
||||
OptKind::String => {
|
||||
format!("-{} [value], --{} [value]", self.short, self.long)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use console::style;
|
||||
use indicatif::{HumanDuration, MultiProgress, ProgressBar, ProgressStyle};
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
|
|
Loading…
Reference in New Issue