mod raw;
mod fmt;
mod pattern;
use std::ops::Index;
use std::collections::HashMap;
use pattern::{ Pattern, PatternType };
pub use raw::Raw;
use std::process::exit;
#[doc(hidden)]
#[derive(PartialEq, Eq)]
#[derive(Debug, Clone)]
pub enum ArgumentType {
RequiredSingle,
OptionalSingle,
RequiredMultiple,
OptionalMultiple,
}
#[doc(hidden)]
#[derive(PartialEq, Eq)]
#[derive(Clone)]
pub struct Argument {
pub name: String,
pub ty: ArgumentType,
}
pub struct Application {
pub name: String,
pub desc: String,
pub cmds: Vec<Command>,
pub opts: Vec<Options>,
pub direct_args: Vec<Argument>,
}
#[doc(hidden)]
pub struct Command {
pub name: String,
pub args: Vec<Argument>,
pub desc: Option<String>,
pub opts: Vec<Options>,
}
#[derive(Debug)]
#[doc(hidden)]
pub struct Options {
pub short: String,
pub long: String,
pub arg: Option<Argument>,
pub desc: Option<String>,
}
#[derive(Debug, Eq, PartialEq)]
#[doc(hidden)]
pub struct Instance {
pub name: String,
pub args: Vec<String>,
}
#[derive(Debug)]
#[doc(hidden)]
pub struct Cmd {
pub name: String,
pub raws: Vec<Raw>,
pub opt_raws: HashMap<String, Raw>,
}
#[derive(Debug)]
pub struct Cli {
pub cmd: Option<Cmd>,
pub global_raws: HashMap<String, Raw>,
pub direct_args: Vec<Raw>,
}
impl Application {
#[doc(hidden)]
pub fn derive(&mut self) {
self.opts.push(Options {
short: String::from("h"),
long: String::from("help"),
arg: None,
desc: Some(String::from("output usage information")),
});
self.opts.push(Options {
short: String::from("V"),
long: String::from("version"),
arg: None,
desc: Some(String::from("output the version number")),
});
self.derive_cmds();
}
#[doc(hidden)]
fn derive_cmds(&mut self) {
for cmd in &mut self.cmds {
cmd.derive();
}
}
pub fn contains_key(&self, idx: &str) -> bool {
for opt in self.opts.iter() {
if opt.long == idx || opt.short == idx {
return true;
}
}
for cmd in self.cmds.iter() {
for opt in cmd.opts.iter() {
if opt.long == idx || opt.short == idx {
return true;
}
}
}
false
}
}
impl Command {
#[doc(hidden)]
pub fn derive(&mut self) {
self.opts.push(Options {
short: String::from("h"),
long: String::from("help"),
arg: None,
desc: Some(String::from("output usage information")),
});
}
}
impl Cli {
#[doc(hidden)]
pub fn empty() -> Cli {
Cli {
cmd: None,
global_raws: HashMap::new(),
direct_args: vec![],
}
}
pub fn get(&self, idx: &str) -> Raw {
if self.cmd.is_some() && self.cmd.as_ref().unwrap().has(idx) {
self.cmd.as_ref().unwrap()[idx].clone()
} else if self.global_raws.contains_key(idx) {
self.global_raws[idx].clone()
} else {
Raw::new(vec![])
}
}
pub fn get_or<T: From<Raw>>(&self, idx: &str, d: T) -> T {
if self.has(idx) {
self.get(idx).into()
} else {
d
}
}
pub fn get_or_else<T: From<Raw>, F>(&self, idx: &str, f: F) -> T
where F: FnOnce() -> T {
if self.has(idx) {
self.get(idx).into()
} else {
f()
}
}
pub fn has(&self, idx: &str) -> bool {
(self.cmd.is_some() && self.cmd.as_ref().unwrap().has(idx)) || self.global_raws.contains_key(idx)
}
#[doc(hidden)]
pub fn from(instances: &Vec<Instance>, app: &Application) -> Option<Cli> {
if instances.is_empty() {
None
} else {
let cmd = Cmd::from(instances, &app.cmds);
let mut global_raws = HashMap::new();
for ins in instances.iter() {
let matched = app.opts.iter().find(|o| o.long == ins.name || o.short == ins.name);
if let Some(matched) = matched {
if let Some(cmd) = &cmd {
if !cmd.has(&matched.long) {
let raw = Raw::divide_opt(ins, &matched.arg);
global_raws.insert(matched.long.clone(), raw.clone());
global_raws.insert(matched.short.clone(), raw);
}
} else {
let raw = Raw::divide_opt(ins, &matched.arg);
global_raws.insert(matched.long.clone(), raw.clone());
global_raws.insert(matched.short.clone(), raw);
}
}
}
Some(Cli {
cmd,
global_raws,
direct_args: {
if instances[0].is_empty() && !instances[0].args.is_empty() {
Raw::divide_cmd(&instances[0], &app.direct_args)
} else {
vec![]
}
}
})
}
}
#[doc(hidden)]
pub fn get_raws(&self) -> Vec<Raw> {
if let Some(cmd) = &self.cmd {
cmd.raws.clone()
} else {
vec![]
}
}
#[doc(hidden)]
pub fn get_name(&self) -> String {
if let Some(cmd) = &self.cmd {
cmd.name.clone()
} else {
String::new()
}
}
}
impl Cmd {
#[doc(hidden)]
fn new(name: String) -> Cmd {
Cmd {
name,
raws: vec![],
opt_raws: HashMap::new(),
}
}
#[doc(hidden)]
fn push(&mut self, arg: Raw) {
self.raws.push(arg);
}
#[doc(hidden)]
fn insert(&mut self, key: String, arg: Raw) {
if !self.opt_raws.contains_key(&key) {
self.opt_raws.insert(key, arg);
}
}
#[doc(hidden)]
fn append(&mut self, raws: Vec<Raw>) {
raws.into_iter().for_each(|r| self.push(r));
}
fn get_cmd_idx(instances: &Vec<Instance>, commands: &Vec<Command>) -> Option<usize> {
for (idx, ins) in instances.iter().enumerate() {
if commands.iter().any(|c| c.name == ins.name) {
return Some(idx);
}
}
None
}
#[doc(hidden)]
pub fn has(&self, idx: &str) -> bool {
self.opt_raws.contains_key(idx)
}
#[doc(hidden)]
pub fn from(instances: &Vec<Instance>, commands: &Vec<Command>) -> Option<Cmd> {
let mut result = Cmd::new(String::new());
if instances.is_empty() {
None
} else {
let idx = Cmd::get_cmd_idx(instances, commands);
let head;
let n;
if let Some(idx) = idx {
head = instances.get(idx).unwrap();
n = idx + 1;
} else {
return None;
}
let cmd = commands.iter().find(|c| c.name == head.name);
if let Some(sub_cmd) = cmd {
let raws = Raw::divide_cmd(head, &sub_cmd.args);
result.name = sub_cmd.name.clone();
result.append(raws);
for ins in instances.iter().skip(n) {
let matched = sub_cmd.opts.iter().find(|o| (o.long == ins.name || o.short == ins.name));
if let Some(matched) = matched {
let raw = Raw::divide_opt(ins, &matched.arg);
result.insert(matched.long.clone(), raw.clone());
result.insert(matched.short.clone(), raw);
}
}
Some(result)
} else {
None
}
}
}
}
impl Index<&str> for Cmd {
type Output = Raw;
fn index(&self, idx: &str) -> &Raw {
&self.opt_raws[idx]
}
}
impl Instance {
#[doc(hidden)]
pub fn is_empty(&self) -> bool {
self.name.is_empty()
}
#[doc(hidden)]
pub fn empty() -> Instance {
Instance {
name: String::new(),
args: vec![],
}
}
#[doc(hidden)]
pub fn new(name: &str) -> Instance {
Instance {
name: String::from(name),
args: vec![],
}
}
}
pub fn normalize(args: Vec<String>, app: &Application) -> Vec<Instance> {
let mut instances = vec![];
let mut head = Instance::empty();
let mut args = args.into_iter().skip(1);
let mut flag = false;
while let Some(arg) = args.next() {
let reg = Pattern::match_str(&arg);
match reg.ty {
PatternType::Stmt => {
if app.contains_key(reg.groups[0]) {
let mut all_opts: Vec<&str> = reg.groups[1].split_terminator(" ").collect();
if !head.is_empty() || (!head.args.is_empty() && instances.is_empty()) {
instances.push(head);
}
head = Instance::empty();
all_opts.dedup_by(|a, b| a == b);
all_opts.retain(|x| !x.is_empty());
instances.push(Instance {
name: String::from(reg.groups[0]),
args: all_opts.into_iter().map(|x| String::from(x)).collect(),
});
} else {
eprintln!("Unknown option: --{}", reg.groups[0]);
exit(-1);
}
},
PatternType::Short => {
let mut all_opts: Vec<&str> = reg.groups[0].split("").collect();
all_opts.dedup_by(|a, b| a == b);
all_opts.retain(|x| !x.is_empty());
if !head.is_empty() || (!head.args.is_empty() && instances.is_empty()) {
instances.push(head);
}
for x in all_opts.into_iter() {
if x.len() == 1 {
if app.contains_key(x) {
instances.push(Instance::new(x));
} else {
eprintln!("Unknown option: -{}", x);
exit(-1);
}
}
}
head = instances.pop().unwrap_or(Instance::empty());
},
PatternType::Long => {
if app.contains_key(reg.groups[0]) {
if !head.is_empty() || (!head.args.is_empty() && instances.is_empty()) {
instances.push(head);
}
head = Instance::new(reg.groups[0]);
} else {
eprintln!("Unknown option: --{}", reg.groups[0]);
exit(-1);
}
},
PatternType::Word => {
if app.cmds.iter().any(|c| c.name == arg) && !flag {
if !head.is_empty() || (!head.args.is_empty() && instances.is_empty()) {
instances.push(head);
}
head = Instance::new(&arg);
flag = true;
} else {
head.args.push(arg);
}
},
_ => {
head.args.push(arg);
},
}
}
if !head.is_empty() || (!head.args.is_empty() && instances.is_empty()) {
instances.push(head);
}
instances
}