mod error;
use std::option::Option;
use std::str::FromStr;
use std::vec::Vec;
use error::{BoxResult, ArgParseError};
type HandlerFn = fn(flagparse: FlagParse) -> BoxResult<()>;
pub type Result<T> = std::result::Result<T, ArgParseError>;
pub struct Cli {
pub program_name: &'static str,
pub synopsis: &'static str,
pub root_command: Command,
pub subcommands: Vec<Command>,
pub global_flags: Vec<Flag>,
}
pub struct Command {
pub command_name: &'static str,
pub desc: &'static str,
pub handler: HandlerFn,
pub flags: Vec<Flag>,
}
pub struct Flag {
pub desc: String,
pub required: bool,
pub parameter: bool,
pub short: Option<char>,
pub long: String,
}
pub struct FlagParse<'a> {
flags: Vec<(&'a Flag, Option<String>)>,
pub args: Vec<String>,
}
impl Default for Cli {
fn default() -> Self {
Cli {
program_name: "",
synopsis: "",
root_command: Command {
..Default::default()
},
subcommands: vec![],
global_flags: vec![],
}
}
}
impl Default for Command {
fn default() -> Self {
Command {
command_name: "",
desc: "",
handler: |_flagparse: FlagParse| Ok(()),
flags: vec![],
}
}
}
impl Cli {
pub fn run(&self, args: &Vec<String>) -> Result<()> {
let mut arg_it = args.iter();
arg_it.next();
let mut next = arg_it.next();
let cmd: &Command = if let Some(cmd_name) = next {
if looks_like_flag(cmd_name) {
&self.root_command
} else {
next = arg_it.next();
self.subcommands
.iter()
.find(|c| &c.command_name == cmd_name)
.unwrap_or(&self.root_command)
}
} else {
&self.root_command
};
let mut flagparse = FlagParse::new();
while next.is_some() {
let cur_arg = next.unwrap();
let flag: Option<&Flag> = if cur_arg.starts_with("--") {
cmd.flags
.iter()
.find(|f| f.long == cur_arg[2..].to_string())
} else if cur_arg.starts_with("-") {
cmd.flags.iter().find(|f| f.short == cur_arg.chars().nth(1))
} else {
break;
};
if flag.is_none() {
return Err(ArgParseError::InvalidFlag(cur_arg.to_owned()));
}
let flag = flag.unwrap();
if flag.parameter {
let value = arg_it.next().ok_or(ArgParseError::MissingFlagValue(cur_arg.to_owned()))?;
flagparse.add_flag_with_value(flag, value);
} else {
flagparse.add_flag(flag);
}
next = arg_it.next();
}
while next.is_some() {
flagparse.args.push(next.unwrap().to_string());
next = arg_it.next();
}
let dispatch = cmd.handler;
if let Err(err) = dispatch(flagparse) {
return Err(ArgParseError::UserError(err));
}
Ok(())
}
pub fn help_message(&self) {}
}
fn looks_like_flag(token: &str) -> bool {
return token.starts_with("--") || token.starts_with("-");
}
impl Flag {
pub fn new(long: &str) -> Self {
Flag {
desc: String::new(),
required: false,
parameter: false,
short: None,
long: long.to_owned(),
}
}
pub fn desc(mut self, desc: &str) -> Self {
self.desc = desc.to_owned();
self
}
pub fn required(mut self) -> Self {
self.required = true;
self
}
pub fn parameter(mut self) -> Self {
self.parameter = true;
self
}
pub fn short(mut self, short: char) -> Self {
self.short = Some(short);
self
}
}
impl<'a> FlagParse<'a> {
pub fn get_flag_value<T: FromStr>(&self, long: &str) -> Option<T> {
let pair = self.flags.iter().find(|p| p.0.long.eq(long));
if pair.is_none() {
return None;
}
let pair = pair.unwrap();
match &pair.1 {
Some(v) => v.parse::<T>().ok(),
None => None,
}
}
pub fn get_flag(&self, long: &str) -> bool {
return self.flags.iter().find(|p| p.0.long.eq(long)).is_some();
}
fn new() -> Self {
FlagParse {
flags: Vec::new(),
args: Vec::new(),
}
}
fn add_flag(&mut self, flag: &'a Flag) {
self.flags.push((flag, None));
}
fn add_flag_with_value(&mut self, flag: &'a Flag, value: &str) {
self.flags.push((flag, Some(value.to_string())));
}
}