#[derive(Copy, Clone)]
pub enum Nargs {
None,
Count(usize),
Remainder
}
impl Default for Nargs {
fn default() -> Self {
Nargs::None
}
}
type Handler<C> = fn(spec: &Spec<C>, ctx: &mut C, args: &Vec<String>);
pub struct Builder {
sopt: Option<char>,
lopt: Option<String>,
name: Option<String>,
nargs: Nargs,
exit: bool,
required: bool,
metanames: Vec<String>,
desc: Vec<String>,
hidden: bool
}
impl Builder {
pub fn new() -> Self {
Builder {
sopt: None,
lopt: None,
name: None,
nargs: Nargs::None,
exit: false,
required: false,
metanames: Vec::new(),
desc: Vec::new(),
hidden: false
}
}
pub fn sopt(&mut self, sopt: char) -> &mut Self {
self.sopt = Some(sopt);
self
}
pub fn lopt(&mut self, lopt: &str) -> &mut Self {
self.lopt = Some(String::from(lopt));
self
}
pub fn name(&mut self, name: &str) -> &mut Self {
self.name = Some(String::from(name));
self
}
pub fn nargs<I, S>(&mut self, nargs: Nargs, metanames: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: AsRef<str>
{
self.nargs = nargs;
self.metanames(metanames);
self
}
pub fn metanames<I, S>(&mut self, metanames: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: AsRef<str>
{
self.metanames.clear();
let nargs = match self.nargs {
Nargs::Remainder => 1,
Nargs::Count(n) => n,
_ => 0
};
let names = metanames
.into_iter()
.map(|x| String::from(x.as_ref()))
.collect::<Vec<_>>();
if nargs > 0 {
for i in 0..nargs {
if i < names.len() {
self.metanames.push(names[i].clone());
} else {
self.metanames.push("ARG".to_string());
}
}
}
self
}
pub fn exit(&mut self, exit: bool) -> &mut Self {
self.exit = exit;
self
}
pub fn required(&mut self, req: bool) -> &mut Self {
self.required = req;
self
}
pub fn hidden(&mut self, hidden: bool) -> &mut Self {
self.hidden = hidden;
self
}
pub fn help<I, S>(&mut self, text: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: AsRef<str>
{
for x in text.into_iter() {
self.desc.push(String::from(x.as_ref()));
}
self
}
pub fn build<C>(&self, proc: Handler<C>) -> Spec<C> {
Spec {
sopt: self.sopt,
lopt: self.lopt.clone(),
name: self.name.clone(),
nargs: self.nargs.clone(),
exit: self.exit,
required: self.required,
metanames: self.metanames.clone(),
desc: self.desc.clone(),
hidden: self.hidden,
proc
}
}
}
pub struct Spec<C> {
pub(crate) sopt: Option<char>,
pub(crate) lopt: Option<String>,
pub(crate) name: Option<String>,
nargs: Nargs,
pub(crate) exit: bool,
required: bool,
metanames: Vec<String>,
desc: Vec<String>,
hidden: bool,
pub(crate) proc: Handler<C>
}
impl<C> Spec<C> {
pub fn is_capture_rest(&self) -> bool {
match self.nargs {
Nargs::None => false,
Nargs::Count(_n) => false,
Nargs::Remainder => true
}
}
pub fn is_exit(&self) -> bool {
self.exit
}
pub fn is_opt(&self) -> bool {
self.sopt.is_some() || self.lopt.is_some()
}
pub fn is_pos(&self) -> bool {
self.sopt.is_none() && self.lopt.is_none()
}
pub fn is_req(&self) -> bool {
self.required
}
pub fn is_hidden(&self) -> bool {
self.hidden
}
pub fn get_nargs(&self) -> usize {
match self.nargs {
Nargs::None => 0,
Nargs::Count(n) => n,
Nargs::Remainder => {
panic!("Can't get number of arguments for a capture-all spec.");
}
}
}
pub fn req_args(&self) -> bool {
match self.nargs {
Nargs::None => false,
Nargs::Count(n) => {
if n > 0 {
return true;
}
false
}
Nargs::Remainder => false
}
}
fn get_sopt_str(&self) -> Option<String> {
if let Some(sopt) = self.sopt {
let mut ret = '-'.to_string();
let soptstr = sopt.to_string();
ret.push_str(&soptstr);
return Some(ret);
}
None
}
fn get_lopt_str(&self) -> Option<String> {
if let Some(ref lopt) = self.lopt {
let mut ret = "--".to_string();
ret.push_str(&lopt);
return Some(ret);
}
None
}
fn get_joined_meta_str(&self) -> Option<String> {
match self.nargs {
Nargs::None => None,
Nargs::Count(_n) => Some(self.metanames.join(" ")),
Nargs::Remainder => {
let metaname = if self.metanames.len() > 0 {
&self.metanames[0]
} else {
"ARG"
};
Some(metaname.to_string())
}
}
}
fn get_soptarg_str(&self) -> Option<String> {
if let Some(optstr) = self.get_sopt_str() {
let mut ret = optstr.to_owned();
if let Some(metastr) = self.get_joined_meta_str() {
ret.push_str(" ");
ret.push_str(&metastr);
}
return Some(ret);
}
None
}
fn get_loptarg_str(&self) -> Option<String> {
if let Some(optstr) = self.get_lopt_str() {
let mut ret = optstr.to_owned();
if let Some(metastr) = self.get_joined_meta_str() {
ret.push_str(" ");
ret.push_str(&metastr);
}
return Some(ret);
}
None
}
#[cfg(test)]
fn get_sopt_usage_str(&self) -> Option<String> {
if let Some(optstr) = self.get_soptarg_str() {
let mut ret: String;
if self.required {
ret = String::from("<");
} else {
ret = String::from("[");
}
ret.push_str(&optstr);
if self.required {
ret.push_str(">");
} else {
ret.push_str("]");
}
return Some(ret);
}
None
}
#[cfg(test)]
fn get_lopt_usage_str(&self) -> Option<String> {
if let Some(optstr) = self.get_loptarg_str() {
let mut ret: String;
if self.required {
ret = String::from("<");
} else {
ret = String::from("[");
}
ret.push_str(&optstr);
if self.required {
ret.push_str(">");
} else {
ret.push_str("]");
}
return Some(ret);
}
None
}
pub fn get_opts_usage_str(&self) -> String {
let mut args: Vec<String> = Vec::new();
if let Some(lstr) = self.get_soptarg_str() {
args.push(lstr);
}
if let Some(rstr) = self.get_loptarg_str() {
args.push(rstr);
}
if args.len() == 0 {
if let Some(posarg) = self.get_joined_meta_str() {
args.push(posarg);
}
}
return args.join(", ");
}
pub fn get_usage_str(&self) -> String {
let mut ret: String;
if self.required {
ret = '<'.to_string();
} else {
ret = '['.to_string();
}
if let Some(optstr) = self.get_loptarg_str() {
ret.push_str(&optstr);
} else if let Some(optstr) = self.get_soptarg_str() {
ret.push_str(&optstr);
} else if let Some(metastr) = self.get_joined_meta_str() {
let s = match self.nargs {
Nargs::Count(_) => metastr.clone(),
Nargs::Remainder => format!("{0} [{0} ...]", metastr),
_ => panic!(
"Attempted to print positional argument spec with no arguments"
)
};
ret.push_str(&s);
} else {
panic!("Unexpected state");
}
if self.required {
ret.push_str(">");
} else {
ret.push_str("]");
}
return ret;
}
pub fn get_help_title_str(&self) -> String {
let mut args: Vec<String> = Vec::new();
if let Some(lstr) = self.get_soptarg_str() {
args.push(lstr);
}
if let Some(rstr) = self.get_loptarg_str() {
args.push(rstr);
}
if args.len() == 0 {
if let Some(posarg) = self.get_joined_meta_str() {
let s = match self.nargs {
Nargs::Count(_) => posarg.clone(),
Nargs::Remainder => posarg.clone(),
_ => panic!(
"Attempted to print positional argument spec with no arguments"
)
};
args.push(s);
}
}
return args.join(", ");
}
pub fn get_help_text(&self) -> &Vec<String> {
&self.desc
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
#[derive(Default)]
pub(super) struct TestCtx {
pub(super) do_help: bool,
pub(super) verbosity: u8,
pub(super) fname: String,
pub(super) params: HashMap<String, String>
}
pub(super) fn help_proc(
_spec: &super::Spec<TestCtx>,
ctx: &mut TestCtx,
_args: &Vec<String>
) {
ctx.do_help = true;
}
pub(super) fn verbose_proc(
_spec: &super::Spec<TestCtx>,
ctx: &mut TestCtx,
_args: &Vec<String>
) {
ctx.verbosity += 1;
}
pub(super) fn file_proc(
_spec: &super::Spec<TestCtx>,
ctx: &mut TestCtx,
args: &Vec<String>
) {
ctx.fname = args[0].clone();
}
pub(super) fn param_proc(
_spec: &super::Spec<TestCtx>,
ctx: &mut TestCtx,
args: &Vec<String>
) {
ctx.params.insert(args[0].clone(), args[1].clone());
}
pub(super) fn args_proc(
_spec: &super::Spec<TestCtx>,
_ctx: &mut TestCtx,
_args: &Vec<String>
) {
}
pub(super) fn mkhelp(sopt: bool, lopt: bool) -> super::Spec<TestCtx> {
let mut bldr = super::Builder::new();
if sopt {
bldr.sopt('h');
}
if lopt {
bldr.lopt("help");
}
bldr.build(help_proc)
}
pub(super) fn mkfile(
sopt: bool,
lopt: bool,
name: bool,
argname: bool
) -> super::Spec<TestCtx> {
let mut bldr = super::Builder::new();
if sopt {
bldr.sopt('f');
}
if lopt {
bldr.lopt("file");
}
if name {
bldr.name("file");
}
if argname {
bldr.nargs(super::Nargs::Count(1), &["FILE"]);
} else {
let nm: Vec<String> = Vec::new();
bldr.nargs(super::Nargs::Count(1), &nm);
}
bldr.build(file_proc)
}
pub(super) fn mkparam(
sopt: bool,
lopt: bool,
name: bool,
defargs: usize
) -> super::Spec<TestCtx> {
let mut bldr = super::Builder::new();
if sopt {
bldr.sopt('p');
}
if lopt {
bldr.lopt("param");
}
if name {
bldr.name("param");
}
if defargs > 1 {
bldr.nargs(super::Nargs::Count(2), &["KEY", "VALUE"]);
} else if defargs == 1 {
bldr.nargs(super::Nargs::Count(1), &["KEY"]);
} else {
let nm: Vec<String> = Vec::new();
bldr.nargs(super::Nargs::Count(0), &nm);
}
bldr.build(param_proc)
}
}
#[cfg(test)]
fn expect_opt_str(optstr: &Option<String>, expect: &str) {
if let Some(optstr) = optstr {
assert_eq!(optstr, expect);
} else {
panic!("This shouldn't be possbile.");
}
}
#[test]
fn test_metanames() {
let spec = Builder::new()
.sopt('h')
.lopt("help")
.exit(true)
.build(tests::help_proc);
assert_eq!(spec.get_joined_meta_str(), None);
let spec = Builder::new()
.sopt('f')
.lopt("file")
.name("file")
.nargs(Nargs::Count(1), &["FILE"])
.build(tests::file_proc);
expect_opt_str(&spec.get_joined_meta_str(), "FILE");
expect_opt_str(&spec.get_joined_meta_str(), "FILE");
expect_opt_str(&spec.get_soptarg_str(), "-f FILE");
expect_opt_str(&spec.get_loptarg_str(), "--file FILE");
let nm: Vec<String> = Vec::new();
let spec = Builder::new()
.sopt('f')
.lopt("file")
.name("file")
.nargs(Nargs::Count(1), &nm)
.build(tests::file_proc);
expect_opt_str(&spec.get_joined_meta_str(), "ARG");
expect_opt_str(&spec.get_joined_meta_str(), "ARG");
expect_opt_str(&spec.get_soptarg_str(), "-f ARG");
expect_opt_str(&spec.get_loptarg_str(), "--file ARG");
let spec = super::Builder::new()
.sopt('p')
.lopt("param")
.name("param")
.nargs(super::Nargs::Count(2), &["KEY", "VALUE"])
.build(tests::param_proc);
expect_opt_str(&spec.get_joined_meta_str(), "KEY VALUE");
let spec = super::Builder::new()
.sopt('p')
.lopt("param")
.name("param")
.nargs(super::Nargs::Count(2), &["KEY"])
.build(tests::param_proc);
expect_opt_str(&spec.get_joined_meta_str(), "KEY ARG");
let nm: Vec<String> = Vec::new();
let spec = super::Builder::new()
.sopt('p')
.lopt("param")
.name("param")
.nargs(super::Nargs::Count(2), &nm)
.build(tests::param_proc);
expect_opt_str(&spec.get_joined_meta_str(), "ARG ARG");
}
#[test]
fn test_help_switch() {
let spec = Builder::new()
.sopt('h')
.lopt("help")
.exit(true)
.build(tests::help_proc);
assert_eq!(spec.is_exit(), true);
expect_opt_str(&spec.get_sopt_str(), "-h");
expect_opt_str(&spec.get_lopt_str(), "--help");
expect_opt_str(&spec.get_soptarg_str(), "-h");
expect_opt_str(&spec.get_loptarg_str(), "--help");
expect_opt_str(&spec.get_sopt_usage_str(), "[-h]");
expect_opt_str(&spec.get_lopt_usage_str(), "[--help]");
assert_eq!(spec.get_joined_meta_str(), None);
}
#[test]
fn test_switch_noshort() {
let spec = Builder::new()
.lopt("help")
.exit(true)
.build(tests::help_proc);
assert_eq!(spec.is_exit(), true);
let soptstr = spec.get_sopt_str();
assert_eq!(soptstr.is_none(), true);
}
#[test]
fn test_switch_nolong() {
let spec = Builder::new().sopt('h').exit(true).build(tests::help_proc);
assert_eq!(spec.is_exit(), true);
let loptstr = spec.get_lopt_str();
assert_eq!(loptstr.is_none(), true);
}
#[test]
fn test_file_optarg() {
let spec = Builder::new()
.sopt('f')
.lopt("file")
.name("file")
.nargs(Nargs::Count(1), &["FILE"])
.build(tests::file_proc);
expect_opt_str(&spec.get_joined_meta_str(), "FILE");
expect_opt_str(&spec.get_soptarg_str(), "-f FILE");
expect_opt_str(&spec.get_loptarg_str(), "--file FILE");
let nm: Vec<String> = Vec::new();
let spec = Builder::new()
.sopt('f')
.lopt("file")
.name("file")
.nargs(Nargs::Count(1), &nm)
.build(tests::file_proc);
expect_opt_str(&spec.get_joined_meta_str(), "ARG");
expect_opt_str(&spec.get_soptarg_str(), "-f ARG");
expect_opt_str(&spec.get_loptarg_str(), "--file ARG");
}
#[test]
fn test_param_optarg() {
let spec = super::Builder::new()
.sopt('p')
.lopt("param")
.name("param")
.nargs(super::Nargs::Count(2), &["KEY", "VALUE"])
.build(tests::param_proc);
expect_opt_str(&spec.get_joined_meta_str(), "KEY VALUE");
let spec = super::Builder::new()
.sopt('p')
.lopt("param")
.name("param")
.nargs(super::Nargs::Count(2), &["KEY"])
.build(tests::param_proc);
expect_opt_str(&spec.get_joined_meta_str(), "KEY ARG");
let nm: Vec<String> = Vec::new();
let spec = super::Builder::new()
.sopt('p')
.lopt("param")
.name("param")
.nargs(super::Nargs::Count(2), &nm)
.build(tests::param_proc);
expect_opt_str(&spec.get_joined_meta_str(), "ARG ARG");
}