#![doc = include_str!("../examples/example.rs")]
pub mod cmds;
pub mod flags;
mod node;
pub mod params;
use std::{
env::{self, ArgsOs},
ffi::{OsStr, OsString},
fmt,
iter::Peekable,
};
use yansi::Paint;
use self::node::{Cmd, Help, Node as Seal};
#[doc(hidden)]
pub enum Branch {
Skip(Peekable<ArgsOs>),
Help(Peekable<ArgsOs>),
Done,
}
type CmdFn = fn(&dyn Opts);
pub trait Opts: Seal {
fn flag(&self, _c: char) -> bool {
false
}
fn param(&self, _p: &str) -> Option<OsString> {
None
}
fn field(&self, _f: usize) -> Option<OsString> {
None
}
}
impl<T: Seal> Opts for T {}
pub struct Clot<T: Opts = Help> {
opts: T,
cmd_fn: Option<CmdFn>,
}
impl Clot {
pub fn new(help: &'static str) -> Self {
Self {
opts: Help::new(help),
cmd_fn: None,
}
}
}
impl<T: Opts> Clot<T> {
pub fn run(mut self, f: CmdFn) -> Self {
self.cmd_fn = Some(f);
self
}
pub fn cmd<U: Opts, F: FnOnce() -> Clot<U>>(
self,
name: &'static str,
f: F,
) -> Clot<Cmd<T, U, F>> {
let invalid_char = |c: char| (!c.is_ascii_lowercase()) && c != '-';
assert!(!name.contains(invalid_char));
assert!(name.split_terminator('-').count() <= 3);
assert!(!name.starts_with('-'));
assert!(!name.ends_with('-'));
Clot {
opts: Cmd::new(self.opts, name, f),
cmd_fn: self.cmd_fn,
}
}
pub const fn field(self) -> Self {
self
}
pub const fn param(self, _name: &'static str) -> Self {
self
}
pub const fn flag(self, flag: char) -> Self {
if !flag.is_ascii_lowercase() {
panic!("Flags must be ascii lowercase")
}
self
}
pub fn execute(self) {
let mut iter = env::args_os().peekable();
let name = iter.next().expect("Failed to get command name");
self.execute_with(name, iter);
}
fn execute_with(self, name: OsString, mut args: Peekable<ArgsOs>) {
let has_fields = self.opts.has_fields();
if args.peek().is_none() && self.cmd_fn.is_none() {
node::help(&self.opts, &name, has_fields);
}
while let Some(arg) = args.next() {
if node::maybe_help(&self.opts, &arg, &name, args.peek().is_some())
{
if let Some(arg) = args.next() {
unexpected(name, arg);
}
return;
}
args = match self.opts.branch(&arg, has_fields, &name, args) {
Branch::Skip(args) => args,
Branch::Help(_args) => {
unexpected(name, arg);
break;
}
Branch::Done => return,
}
}
if let Some(cmd_fn) = self.cmd_fn {
(cmd_fn)(&self.opts)
}
}
}
struct OsDisplay<'a>(&'a OsStr);
impl fmt::Display for OsDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0.to_string_lossy())
}
}
fn unexpected(name: OsString, arg: OsString) {
println!(
"{}: Unexpected argument `{}`\n",
"Error".red().bold(),
OsDisplay(&arg).bright().magenta(),
);
println!(
" Try `{}` for more information.\n",
format_args!("{} --help", OsDisplay(&name)).bright().blue(),
);
}