#![allow(clippy::needless_doctest_main)]
use std::clone::Clone;
use std::collections::HashSet;
use std::error::Error as StdError;
use std::fmt::Write as FmtWrite;
use std::io::{Cursor, Write};
use std::iter::Peekable;
use std::str::FromStr;
use crate::Error::*;
use crate::OptName::*;
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum OptName {
Short(char),
Long(String),
Both(char, String),
Parameter(String),
}
impl std::fmt::Display for OptName {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match *self {
Short(ref c) => write!(fmt, "'-{}'", c),
Long(ref l) => write!(fmt, "'--{}'", l),
Both(ref c, ref l) => write!(fmt, "'-{}', '--{}'", c, l),
Parameter(ref n) => write!(fmt, "{}", n),
}
}
}
#[derive(Debug)]
pub enum ArgError {
MissingArgument,
UnexpectedArgument(String),
ParseError(Box<dyn StdError>),
}
impl std::fmt::Display for ArgError {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
use crate::ArgError::*;
match *self {
ParseError(ref err) => write!(fmt, "error parsing the argument: {}", err),
UnexpectedArgument(ref arg) => write!(fmt, "unexpected argument: {}", arg),
MissingArgument => write!(fmt, "missing argument"),
}
}
}
impl StdError for ArgError {
fn cause(&self) -> Option<&dyn StdError> {
use crate::ArgError::*;
match *self {
ParseError(ref err) => Some(err.as_ref()),
_ => None,
}
}
}
#[derive(Debug)]
pub enum Error {
DuplicateLong(String),
DuplicateShort(char),
MissingLongAndShort,
Unknown(OptName),
InvalidLong(String),
InvalidArgument(OptName, ArgError),
AmbiguousPrefix(String, Vec<String>),
Multiple(OptName),
MissingOpt(OptName),
MissingParam(OptName),
Help(String),
}
impl Error {
fn invalid_arg<'a>(opt: &'a Opt<'a>, err: ArgError) -> Error {
Error::InvalidArgument(opt.optname(), err)
}
fn invalid_param<'a>(param: &'a Param<'a>, err: ArgError) -> Error {
Error::InvalidArgument(param.optname(), err)
}
}
impl std::fmt::Display for Error {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match *self {
DuplicateLong(ref long) => write!(fmt, "duplicate long option: --{}", long),
DuplicateShort(ref short) => write!(fmt, "duplicate short option: -{}", short),
Unknown(ref opt) => write!(fmt, "unknown option: {}", opt),
Multiple(ref opt) => write!(fmt, "option specified multiple times: {}", opt),
MissingOpt(ref opt) => write!(fmt, "missing required option: {}", opt),
MissingParam(ref opt) => write!(fmt, "missing required parameter: {}", opt),
AmbiguousPrefix(ref long, ref candidates) => {
write!(fmt, "ambiguous prefix: '--{}' (candidates: ", long)?;
for (i, cand) in candidates.iter().enumerate() {
if i > 0 {
write!(fmt, ", ")?
}
write!(fmt, "'--{}'", cand)?;
}
write!(fmt, ")")
}
InvalidLong(ref long) => write!(fmt, "invalid long option: {} (only characters and '_' allowed)", long),
InvalidArgument(ref opt, ref err) => write!(fmt, "invalid argument for {} ({})", opt, err),
MissingLongAndShort => write!(fmt, "missing long and short option"),
Help(..) => write!(fmt, "show help page"),
}
}
}
pub fn error_and_exit(err: &Error) -> ! {
if let Err(e) = writeln!(std::io::stderr(), "Error: {}\nTry --help for help.", err) {
panic!("{}", e);
}
std::process::exit(1);
}
pub trait DefaultName {
fn default_name() -> Option<&'static str> {
Some("<x>")
}
}
impl DefaultName for bool {
fn default_name() -> Option<&'static str> {
None
}
}
impl DefaultName for i8 {
fn default_name() -> Option<&'static str> {
Some("<i>")
}
}
impl DefaultName for i16 {
fn default_name() -> Option<&'static str> {
Some("<i>")
}
}
impl DefaultName for i32 {
fn default_name() -> Option<&'static str> {
Some("<i>")
}
}
impl DefaultName for i64 {
fn default_name() -> Option<&'static str> {
Some("<i>")
}
}
impl DefaultName for isize {
fn default_name() -> Option<&'static str> {
Some("<i>")
}
}
impl DefaultName for u8 {
fn default_name() -> Option<&'static str> {
Some("<u>")
}
}
impl DefaultName for u16 {
fn default_name() -> Option<&'static str> {
Some("<u>")
}
}
impl DefaultName for u32 {
fn default_name() -> Option<&'static str> {
Some("<u>")
}
}
impl DefaultName for u64 {
fn default_name() -> Option<&'static str> {
Some("<u>")
}
}
impl DefaultName for usize {
fn default_name() -> Option<&'static str> {
Some("<u>")
}
}
impl DefaultName for f32 {
fn default_name() -> Option<&'static str> {
Some("<f>")
}
}
impl DefaultName for f64 {
fn default_name() -> Option<&'static str> {
Some("<f>")
}
}
impl DefaultName for String {
fn default_name() -> Option<&'static str> {
Some("<s>")
}
}
pub trait CommandLineArgument {
fn parse_value(&mut self, arg: &str) -> Result<(), ArgError>;
fn set_default(&mut self) -> Result<(), ArgError> {
Err(ArgError::MissingArgument)
}
fn set_unseen(&mut self) {}
fn is_multi(&self) -> bool {
false
}
fn default_value(&self) -> Option<String> {
None
}
fn write_name(&self, name: Option<&str>) -> Option<String>;
}
impl<'a> CommandLineArgument for &'a mut bool {
fn parse_value(&mut self, arg: &str) -> Result<(), ArgError> {
Err(ArgError::UnexpectedArgument(arg.to_string()))
}
fn set_default(&mut self) -> Result<(), ArgError> {
**self = !**self;
Ok(())
}
fn write_name(&self, _name: Option<&str>) -> Option<String> {
None
}
}
pub struct Single<T>(pub T);
impl<'a, T> CommandLineArgument for Single<&'a mut T>
where
T: FromStr + DefaultName + std::fmt::Display,
<T as FromStr>::Err: StdError + 'static,
{
fn parse_value(&mut self, arg: &str) -> Result<(), ArgError> {
*self.0 = arg
.parse()
.map_err(|err| Box::new(err) as Box<dyn StdError>)
.map_err(ArgError::ParseError)?;
Ok(())
}
fn default_value(&self) -> Option<String> {
Some(format!("{}", self.0))
}
fn write_name(&self, name: Option<&str>) -> Option<String> {
#[allow(clippy::redundant_closure)]
name.or_else(|| T::default_name()).map(|n| n.to_string())
}
}
impl<'a, T> CommandLineArgument for &'a mut Option<T>
where
T: FromStr + DefaultName + std::fmt::Display,
<T as FromStr>::Err: StdError + 'static,
{
fn parse_value(&mut self, arg: &str) -> Result<(), ArgError> {
**self = arg
.parse()
.map(Some)
.map_err(|err| Box::new(err) as Box<dyn StdError>)
.map_err(ArgError::ParseError)?;
Ok(())
}
fn set_default(&mut self) -> Result<(), ArgError> {
if self.is_none() {
Err(ArgError::MissingArgument)
} else {
Ok(())
}
}
fn set_unseen(&mut self) {
**self = None
}
fn default_value(&self) -> Option<String> {
self.as_ref().map(|x| format!("{}", x))
}
fn write_name(&self, name: Option<&str>) -> Option<String> {
#[allow(clippy::redundant_closure)]
name.or_else(|| T::default_name()).map(|n| format!("[{}]", n))
}
}
pub struct Multi<T>(pub T, pub bool);
impl<'a, T> CommandLineArgument for Multi<&'a mut Vec<T>>
where
T: FromStr + DefaultName + std::fmt::Debug,
<T as FromStr>::Err: StdError + 'static,
{
fn parse_value(&mut self, arg: &str) -> Result<(), ArgError> {
if !self.1 {
self.0.clear();
self.1 = true;
}
self.0.push(
arg.parse()
.map_err(|err| Box::new(err) as Box<dyn StdError>)
.map_err(ArgError::ParseError)?,
);
Ok(())
}
fn is_multi(&self) -> bool {
true
}
fn default_value(&self) -> Option<String> {
Some(format!("{:?}", self.0))
}
fn write_name(&self, name: Option<&str>) -> Option<String> {
#[allow(clippy::redundant_closure)]
name.or_else(|| T::default_name()).map(|n| format!("[{}...]", n))
}
}
#[allow(dead_code)]
pub struct Param<'a> {
var: Box<dyn CommandLineArgument + 'a>,
description: Option<&'a str>,
name: Option<String>,
required: bool,
default: Option<String>,
seen: bool,
}
impl<'a> Param<'a> {
fn optname(&self) -> OptName {
let name = self
.var
.write_name(self.name.as_ref().map(|n| &n[..]))
.unwrap_or_else(|| "PARAM".to_string());
OptName::Parameter(name)
}
}
#[allow(dead_code)]
pub struct Opt<'a> {
var: Box<dyn CommandLineArgument + 'a>,
description: Option<&'a str>,
name: Option<String>,
required: bool,
default: Option<String>,
long: Option<String>,
short: Option<char>,
multi: bool,
seen: bool,
}
impl<'a> Opt<'a> {
fn optname(&self) -> OptName {
match (self.short, self.long.as_ref()) {
(Some(short), Some(long)) => OptName::Both(short, long.to_string()),
(None, Some(long)) => OptName::Long(long.to_string()),
(Some(short), None) => OptName::Short(short),
(None, None) => panic!("Neither long nor short option name"),
}
}
}
pub struct Parser<'a> {
opts: Vec<Opt<'a>>,
params: Vec<Param<'a>>,
stopons: Vec<&'a str>,
auto_shorts: bool,
command_name: Option<&'a str>,
usage: Option<&'a str>,
synopsis: Option<&'a str>,
version: Option<&'a str>,
help: bool,
}
#[allow(dead_code)]
pub fn clean_long(long: &str, negate: bool) -> Result<String, Error> {
let longstr: String = long.chars().map(|c| if c == '_' { '-' } else { c }).collect();
if longstr.chars().all(|c| c == '-' || c.is_alphanumeric()) {
if !negate {
Ok(longstr)
} else {
Ok(format!("no-{}", longstr))
}
} else {
Err(InvalidLong(long.to_string()))
}
}
impl<'a> Default for Parser<'a> {
fn default() -> Self {
Parser::new()
}
}
impl<'a> Parser<'a> {
pub fn new() -> Parser<'a> {
Parser {
opts: vec![],
params: vec![],
stopons: vec!["--"],
auto_shorts: true,
command_name: None,
usage: None,
synopsis: None,
version: None,
help: true,
}
}
pub fn opt<T>(&mut self, var: T) -> &mut Opt<'a>
where
T: 'a + CommandLineArgument,
{
let default = var.default_value();
self.opts.push(Opt {
var: Box::new(var),
description: None,
name: None,
required: false,
default,
long: None,
short: None,
multi: false,
seen: false,
});
let n = self.opts.len();
&mut self.opts[n - 1]
}
pub fn param<T>(&mut self, var: T) -> &mut Param<'a>
where
T: 'a + CommandLineArgument,
{
let default = var.default_value();
self.params.push(Param {
var: Box::new(var),
description: None,
name: None,
default,
required: false,
seen: false,
});
let n = self.params.len();
&mut self.params[n - 1]
}
pub fn auto_shorts(&mut self, auto_shorts: bool) {
self.auto_shorts = auto_shorts;
}
pub fn stop_on(&mut self, stopon: &'a str) {
self.stopons.push(stopon);
}
pub fn command_name(&mut self, command_name: &'a str) {
self.command_name = Some(command_name);
}
pub fn usage(&mut self, usage: &'a str) {
self.usage = Some(usage);
}
pub fn synopsis(&mut self, synopsis: &'a str) {
self.synopsis = Some(synopsis);
}
pub fn version(&mut self, version: &'a str) {
self.version = Some(version);
}
pub fn help(&mut self, help: bool) {
self.help = help;
}
pub fn parse<'b, I>(&mut self, args: I) -> Result<Vec<&'b str>, Error>
where
I: IntoIterator<Item = &'b str>,
{
for opt in &mut self.opts {
opt.seen = false;
}
for param in &mut self.params {
param.seen = false;
}
let (shortopts, longopts) = self.prepare_options()?;
let mut rest = vec![];
let mut paramidx = 0;
let mut args = args.into_iter().peekable();
while let Some(arg) = args.next() {
if self.stopons.contains(&arg) {
break;
} else if arg.len() > 2 && arg.starts_with("--") {
self.parse_long(&arg[2..], &mut args, &longopts)?;
} else if arg.len() > 1 && arg.starts_with("-") {
self.parse_short(&arg[1..], &mut args, &shortopts)?;
} else if paramidx < self.params.len() {
let param = &mut self.params[paramidx];
param
.var
.parse_value(arg)
.map_err(|err| Error::invalid_param(param, err))?;
param.seen = true;
if !param.var.is_multi() {
paramidx += 1;
}
} else {
rest.push(arg);
}
}
for opt in &mut self.opts {
if !opt.seen {
if opt.required {
return Err(MissingOpt(opt.optname()));
}
opt.var.set_unseen();
}
}
for par in &mut self.params {
if !par.seen {
if par.required {
return Err(MissingOpt(par.optname()));
}
par.var.set_unseen();
}
}
rest.extend(args);
Ok(rest)
}
fn prepare_options(&mut self) -> Result<(Vec<usize>, Vec<usize>), Error> {
if self.opts.iter().any(|opt| opt.short.is_none() && opt.long.is_none()) {
return Err(Error::MissingLongAndShort);
}
let mut shorts: HashSet<_> = self.opts.iter().filter_map(|opt| opt.short).collect();
if self.help {
if shorts.contains(&'h') {
return Err(Error::DuplicateShort('h'));
}
if self
.opts
.iter()
.any(|opt| opt.long.as_ref().map(|s| &s[..]) == Some("help"))
{
return Err(Error::DuplicateLong("help".to_string()));
}
shorts.insert('h');
}
if self.auto_shorts {
for opt in self.opts.iter_mut().filter(|opt| opt.short.is_none()) {
if let Some(ref long) = opt.long {
if let Some(short) = long.chars().find(|c| c.is_alphanumeric() && !shorts.contains(c)) {
shorts.insert(short);
opt.short = Some(short)
}
}
}
}
let mut longopts: Vec<_> = self
.opts
.iter()
.enumerate()
.filter_map(|(i, opt)| opt.long.as_ref().map(|_| i))
.collect();
longopts.sort_by_key(|&i| self.opts[i].long.as_ref());
if let Some((&i, _)) = longopts
.iter()
.zip(longopts.iter().skip(1))
.find(|&(&i, &j)| self.opts[i].long == self.opts[j].long)
{
return Err(Error::DuplicateLong(self.opts[i].long.clone().unwrap()));
}
let mut shortopts: Vec<_> = self
.opts
.iter()
.enumerate()
.filter_map(|(i, opt)| opt.short.map(|_| i))
.collect();
shortopts.sort_by_key(|&i| self.opts[i].short);
if let Some((&i, _)) = shortopts
.iter()
.zip(shortopts.iter().skip(1))
.find(|&(&i, &j)| self.opts[i].short == self.opts[j].short)
{
return Err(Error::DuplicateShort(self.opts[i].short.unwrap()));
}
Ok((shortopts, longopts))
}
fn parse_long<'b, I>(&mut self, arg: &str, args: &mut Peekable<I>, longopts: &[usize]) -> Result<(), Error>
where
I: Iterator<Item = &'b str>,
{
let end = arg.chars().position(|ch| ch == '=');
let optidx = self.find_long(&arg[..end.unwrap_or_else(|| arg.len())], longopts)?;
let opt = &mut self.opts[optidx];
opt.seen = true;
if let Some(end) = end {
opt.var
.parse_value(&arg[end + 1..])
.map_err(|err| Error::invalid_arg(opt, err))?;
} else if !opt.multi {
opt.var
.set_default()
.or_else(|err| {
args.next().ok_or(err).and_then(|arg| {
opt.var.parse_value(arg)
})
})
.map_err(|err| Error::invalid_arg(opt, err))?;
} else {
#[allow(clippy::while_let_loop)]
loop {
if let Some(arg) = args.peek() {
if self.stopons.contains(arg) || (arg.len() > 1 && arg.starts_with("-")) {
break;
}
if let Err(err) = opt.var.parse_value(arg) {
return Err(Error::invalid_arg(opt, err));
}
} else {
break;
}
args.next();
}
}
Ok(())
}
fn find_long(&self, long: &str, longopts: &[usize]) -> Result<usize, Error> {
if let Ok(idx) = longopts.binary_search_by(|i| self.opts[*i].long.as_ref().unwrap()[..].cmp(long)) {
Ok(longopts[idx])
} else {
let mut idx = None;
for &j in longopts {
if self.opts[j].long.as_ref().unwrap().starts_with(long) {
if idx.is_none() {
idx = Some(j);
} else {
let candidates = longopts.iter().filter_map(|&j| {
let l = self.opts[j].long.as_ref().unwrap();
if l.starts_with(long) {
Some(l.to_string())
} else {
None
}
});
return Err(AmbiguousPrefix(long.to_string(), candidates.collect()));
}
}
}
idx.ok_or_else(|| {
if self.help && "help".starts_with(long) {
let mut helpmsg = Cursor::new(Vec::new());
self.write_usage(&mut helpmsg).unwrap();
Help(String::from_utf8(helpmsg.into_inner()).unwrap())
} else {
Unknown(Long(long.to_string()))
}
})
}
.and_then(|idx| {
let opt = &self.opts[idx];
if opt.seen && (!opt.var.is_multi() || opt.multi) {
Err(Error::Multiple(opt.optname()))
} else {
Ok(idx)
}
})
}
fn parse_short<'b, I>(&mut self, arg: &str, args: &mut Peekable<I>, shortopts: &[usize]) -> Result<(), Error>
where
I: Iterator<Item = &'b str>,
{
let mut chars = arg.chars();
while let Some(short) = chars.next() {
let optidx = self.find_short(short, shortopts)?;
let opt = &mut self.opts[optidx];
opt.seen = true;
if let Err(err) = opt.var.set_default() {
let arg = chars.as_str();
if !arg.is_empty() {
if let Err(err) = opt.var.parse_value(arg) {
return Err(Error::invalid_arg(opt, err));
}
if !opt.multi {
return Ok(());
}
} else if !opt.multi && args.peek().is_none() {
return Err(Error::invalid_arg(opt, err));
}
loop {
if let Some(arg) = args.peek() {
if self.stopons.contains(arg) || (arg.len() > 1 && arg.starts_with("-")) {
break;
}
if let Err(err) = opt.var.parse_value(arg) {
return Err(Error::invalid_arg(opt, err));
}
} else {
break;
}
args.next();
if !opt.multi {
break;
}
}
return Ok(());
}
}
Ok(())
}
fn find_short(&self, short: char, shortopts: &[usize]) -> Result<usize, Error> {
shortopts
.binary_search_by(|i| self.opts[*i].short.unwrap().cmp(&short))
.map_err(|_| {
if self.help && short == 'h' {
let mut helpmsg = Cursor::new(Vec::new());
self.write_usage(&mut helpmsg).unwrap();
Help(String::from_utf8(helpmsg.into_inner()).unwrap())
} else {
Unknown(Short(short))
}
})
.and_then(|idx| {
let idx = shortopts[idx];
if self.opts[idx].seen && !self.opts[idx].var.is_multi() {
Err(Error::Multiple(self.opts[idx].optname()))
} else {
Ok(idx)
}
})
}
pub fn write_usage(&self, output: &mut dyn std::io::Write) -> Result<(), std::io::Error> {
if let Some(u) = self.usage {
if !u.is_empty() {
writeln!(output, "Usage: {} {}\n", self.command_name.unwrap_or("COMMAND"), u)?;
}
} else {
write!(output, "Usage: {}", self.command_name.unwrap_or("COMMAND"))?;
if !self.opts.is_empty() {
write!(output, " [OPTIONS]")?;
}
for param in &self.params {
write!(output, " {}", param.optname())?;
}
writeln!(output, "\n")?;
}
if let Some(s) = self.synopsis {
writeln!(output, "{}\n", s)?;
}
if let Some(v) = self.version {
writeln!(output, "{}\n", v)?;
}
let mut arglefts = vec![];
let mut argrights = vec![];
for param in &self.params {
arglefts.push(param.optname().to_string());
let mut right = param.description.unwrap_or("").to_string();
if !param.required {
if let Some(ref default) = param.default {
write!(right, " (default: {})", default).unwrap();
}
}
argrights.push(right);
}
let mut lefts = vec![];
let mut rights = vec![];
for opt in &self.opts {
let mut left = "".to_string();
if let Some(short) = opt.short {
left.push('-');
left.push(short);
if let Some(ref name) = opt.name {
left.push(' ');
left.push_str(name);
}
}
if let Some(ref long) = opt.long {
if opt.short.is_some() {
left.push_str(", ");
}
left.push_str("--");
left.push_str(long);
if let Some(ref name) = opt.name {
left.push('=');
left.push_str(name);
}
}
let mut right = "".to_string();
if let Some(d) = opt.description {
right.push_str(d);
if !opt.required {
if let Some(ref default) = opt.default {
write!(right, " (default: {})", default).unwrap();
}
}
}
lefts.push(left);
rights.push(right);
}
if self.help {
lefts.push("-h, --help".to_string());
rights.push("Show this help message.".to_string());
}
let width = lefts
.iter()
.chain(arglefts.iter())
.map(|l| l.chars().count())
.max()
.unwrap();
if !arglefts.is_empty() {
writeln!(output, "Parameters:")?;
for i in 0..arglefts.len() {
writeln!(
output,
" {}{:3$} {}",
arglefts[i],
"",
argrights[i],
width - arglefts[i].chars().count()
)?;
}
writeln!(output)?;
}
if !lefts.is_empty() {
writeln!(output, "Options:")?;
for i in 0..lefts.len() {
writeln!(
output,
" {}{:3$} {}",
lefts[i],
"",
rights[i],
width - lefts[i].chars().count()
)?;
}
}
Ok(())
}
}
impl<'a> Opt<'a> {
pub fn desc<'b: 'a>(&mut self, desc: &'b str) -> &mut Self {
self.description = Some(desc);
self
}
#[allow(dead_code)]
pub fn varname(&mut self, varname: &str) -> &mut Self {
assert!(!varname.is_empty());
assert!(varname.chars().all(|c| c.is_alphanumeric() || c == '-'));
if varname.len() > 1 {
self.long = Some(varname.to_string());
} else {
self.short = varname.chars().next();
}
self
}
#[allow(dead_code)]
pub fn required(&mut self, required: bool) -> &mut Self {
self.required = required;
self
}
pub fn long(&mut self, long: &str) -> &mut Self {
assert!(!long.is_empty(), "Long option must not be empty");
assert!(
long.chars().all(|c| c.is_alphanumeric() || c == '-'),
"Long options consist of alpha-numeric characters and '-' only (got: --{})",
long
);
self.long = Some(long.to_string());
self
}
pub fn short(&mut self, short: char) -> &mut Self {
assert!(
short.is_alphanumeric(),
"Short options must be alpha-numeric (got: -{})",
short
);
self.short = Some(short);
self
}
pub fn name(&mut self, name: &str) -> &mut Self {
self.name = Some(name.to_string());
self
}
pub fn multi(&mut self, multi: bool) -> &mut Self {
self.multi = multi;
self
}
}
impl<'a> Param<'a> {
pub fn desc<'b: 'a>(&mut self, desc: &'b str) -> &mut Self {
self.description = Some(desc);
self
}
pub fn name(&mut self, name: &str) -> &mut Self {
self.name = Some(name.to_string());
self
}
#[allow(dead_code)]
pub fn required(&mut self, required: bool) -> &mut Self {
self.required = required;
self
}
}
#[macro_export]
macro_rules! _opts {
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
opt $var:ident : bool $(,$par:ident : $what:expr)*;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)* [Flag, bool, $var : bool = false, $($par : $what),*])
($($_params)*)
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
opt $var:ident : bool = false;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)* [Flag, bool, $var : bool = false,])
($($_params)*)
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
opt $var:ident : bool = false, $($par:ident : $what:expr),*;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)* [Flag, bool, $var : bool = false, $($par : $what),*])
($($_params)*)
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
opt $var:ident : bool = true;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)* [NegFlag, bool, $var : bool = true,])
($($_params)*)
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
opt $var:ident : bool = true, $($par:ident : $what:expr),*;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)* [NegFlag, bool, $var : bool = true, $($par : $what),*])
($($_params)*)
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
opt $var:ident : Vec<$typ:ty> $(,$par:ident : $what:expr)*;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)* [Multi, $typ, $var : Vec<$typ> = vec![], $($par : $what),*])
($($_params)*)
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
opt $var:ident : Vec<$typ:ty> = $val:expr;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)* [Multi, $typ, $var : Vec<$typ> = $val,])
($($_params)*)
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
opt $var:ident : Vec<$typ:ty> = $val:expr, $($par:ident : $what:expr),*;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)* [Multi, $typ, $var : Vec<$typ> = $val, $($par : $what),*])
($($_params)*)
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
opt $var:ident : Option<$typ:ty> $(,$par:ident : $what:expr)*;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)* [Optional, $typ, $var : Option<$typ> = None, $($par : $what),*])
($($_params)*)
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
opt $var:ident : Option<$typ:ty> = $val:expr;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)* [Optional, $typ, $var : Option<$typ> = $val,])
($($_params)*)
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
opt $var:ident : Option<$typ:ty> = $val:expr, $($par:ident : $what:expr),*;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)* [Optional, $typ, $var : Option<$typ> = $val, $($par : $what),*])
($($_params)*)
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
opt $var:ident : $typ:ty;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)* [SingleRequired, $typ, $var : $typ = std::default::Default::default(),])
($($_params)*)
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
opt $var:ident : $typ:ty, $($par:ident : $what:expr),*;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)* [SingleRequired, $typ, $var : $typ = std::default::Default::default(), $($par : $what),*])
($($_params)*)
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
opt $var:ident : $typ:ty = $val:expr;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)* [Single, $typ, $var : $typ = $val,])
($($_params)*)
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
opt $var:ident : $typ:ty = $val:expr, $($par:ident : $what:expr),*;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)* [Single, $typ, $var : $typ = $val, $($par : $what),*])
($($_params)*)
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
param $var:ident : Vec<$typ:ty> $(,$par:ident : $what:expr)*;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)*)
($($_params)* [Multi, $typ, $var : Vec<$typ> = vec![], $($par : $what),*])
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
param $var:ident : Option<$typ:ty> $(,$par:ident : $what:expr)*;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)*)
($($_params)* [Optional, $typ, $var : Option<$typ> = None, $($par : $what),*])
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
param $var:ident : $typ:ty;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)*)
($($_params)* [SingleRequired, $typ, $var : $typ = std::default::Default::default(),])
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
param $var:ident : $typ:ty, $($par:ident : $what:expr),*;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)*)
($($_params)* [SingleRequired, $typ, $var : $typ = std::default::Default::default(), $($par : $what),*])
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
param $var:ident : $typ:ty = $val:expr;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)*)
($($_params)* [Single, $typ, $var : $typ = $val,])
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
param $var:ident : $typ:ty = $val:expr, $($par:ident : $what:expr),*;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)*)
($($_opts)*)
($($_params)* [Single, $typ, $var : $typ = std::default::Default::default(), $($par : $what),*])
$($rest)*)
};
( ($($_sets:tt)*) ($($_opts:tt)*) ($($_params:tt)*)
$which:ident $($what:expr),*;
$($rest:tt)* ) =>
{
$crate::_opts!(($($_sets)* [$which, $($what),*])
($($_opts)*)
($($_params)*)
$($rest)*)
};
( NegFlag , $var:expr ) => {&mut $var};
( Flag , $var:expr ) => {&mut $var};
( Optional, $var:expr ) => {&mut $var};
( Multi, $var:expr ) => {$crate::Multi(&mut $var, false)};
( SingleRequired, $var:expr ) => {$crate::Single(&mut $var)};
( Single, $var:expr ) => {$crate::Single(&mut $var)};
( Required, SingleRequired ) => { true };
( Required, $kind:ident ) => { false };
( Negate, NegFlag ) => { true };
( Negate, $kind:ident ) => { false };
( ($([$_sname:ident, $($_sarg:expr),*])*)
($([$_okind:ident, $_oelmtyp:ty, $_ovar:ident : $_otyp:ty = $_oval:expr, $($_opar:ident : $_owhat:expr),*])*)
($([$_pkind:ident, $_pelmtyp:ty, $_pvar:ident : $_ptyp:ty = $_pval:expr, $($_ppar:ident : $_pwhat:expr),*])*) ) => {{
struct Args {
$($_ovar : $_otyp,)*
$($_pvar : $_ptyp,)*
}
struct Parser;
impl Parser {
#[allow(dead_code)]
fn parse_or_exit(&self) -> (Args, std::vec::Vec<std::string::String>) {
match self.parse() {
Ok(result) => result,
Err($crate::Error::Help(msg)) => {
eprintln!("{}", msg);
std::process::exit(1);
},
Err(err) => $crate::error_and_exit(&err),
}
}
#[allow(dead_code)]
fn parse(&self) -> std::result::Result<(Args, std::vec::Vec<std::string::String>), $crate::Error> {
let mut it = std::env::args();
let command_name = match it.next() {
Some(path) => std::path::Path::new(&path)
.file_name()
.and_then(|f| f.to_str())
.and_then(|f| Some(f.to_string())),
None => None
};
let args: Vec<_> = it.collect();
self._parse_args(args.iter().map(|s| s.as_str()), command_name.as_ref().map(|s| s.as_str()))
.map(|(args, rest)| (args, rest.into_iter().map(|s| s.to_string()).collect()))
}
#[allow(dead_code)]
fn parse_args<'a, I>(&self, args: I)
-> std::result::Result<(Args, std::vec::Vec<&'a str>), $crate::Error>
where I: IntoIterator<Item=&'a str>
{
self._parse_args(args, None)
}
#[allow(dead_code)]
#[allow(unused_must_use)]
fn _parse_args<'a, I>(&self, args: I, command_name: std::option::Option<&'a str>)
-> std::result::Result<(Args, std::vec::Vec<&'a str>), $crate::Error>
where I: IntoIterator<Item=&'a str>
{
let mut data = Args {
$($_ovar : $_oval,)*
$($_pvar : $_pval,)*
};
let rest;
{
let mut parser = $crate::Parser::new();
if let Some(c) = command_name {
parser.command_name(c);
}
$(parser.$_sname($($_sarg),*);)*
$(parser.opt($crate::_opts!($_okind, data.$_ovar))
.varname(&$crate::clean_long(stringify!($_ovar), $crate::_opts!(Negate, $_okind))?)
.required($crate::_opts!(Required, $_okind))
$(.$_opar($_owhat))*;)*
$(parser.param($crate::_opts!($_pkind, data.$_pvar))
.name(&stringify!($_pvar).to_uppercase())
.required($crate::_opts!(Required, $_pkind))
$(.$_ppar($_pwhat))*;)*
rest = parser.parse(args);
}
match rest {
Ok(rest) => Ok((data, rest)),
Err(err) => Err(err)
}
}
}
Parser
}};
}
#[macro_export]
macro_rules! opts {
( $($rest:tt)* ) => {
$crate::_opts!(() () () $($rest)*)
}
}
#[test]
fn test_short_flag() {
let (args, rest) = opts! {
opt r:bool;
opt g:bool, short:'g';
opt l:bool, short:'l';
opt a:bool=true, short:'a';
opt b:bool=true, short:'b';
}
.parse_args(vec!["-r", "-g", "-a", "PARAM"])
.unwrap();
assert_eq!(args.r, true);
assert_eq!(args.g, true);
assert_eq!(args.l, false);
assert_eq!(args.a, false);
assert_eq!(args.b, true);
assert_eq!(rest, vec!["PARAM"]);
}
#[test]
fn test_long_flag() {
let (args, rest) = opts! {
opt long_flag:bool;
opt g:bool, long:"global";
opt l:bool, long:"long";
}
.parse_args(vec!["--global", "--long-flag", "ARG1", "ARG2"])
.unwrap();
assert!(args.long_flag);
assert!(args.g);
assert!(!args.l);
assert_eq!(rest, vec!["ARG1", "ARG2"]);
}
#[test]
fn test_long_opt() {
let (args, _) = opts! {
opt long:i32;
opt long2:i32;
}
.parse_args(vec!["--long", "42", "--long2=23"])
.unwrap();
assert_eq!(args.long, 42);
assert_eq!(args.long2, 23);
}
#[test]
#[should_panic]
fn test_long_opt_missing() {
let (_, _) = opts! {
opt long:i32;
}
.parse_args(vec!["--long"])
.unwrap();
}
#[test]
#[should_panic]
fn test_unknown_short() {
let (_, _) = opts! {
opt g:bool, long:"global";
opt l:bool, long:"long";
}
.parse_args(vec!["-g", "-u"])
.unwrap();
}
#[test]
#[should_panic]
fn test_unknown_long() {
let (_, _) = opts! {
opt g:bool, long:"global";
opt l:bool, long:"long";
}
.parse_args(vec!["--unknown"])
.unwrap();
}
#[test]
fn test_multi_param() {
let (args, rest) = opts! {
param arg1:String, desc:"BLA";
param arg2:Vec<String>, desc:"BLA";
}
.parse_args(vec!["ARG1", "ARG2", "ARG3", "--", "REST"])
.unwrap();
assert_eq!(args.arg1, "ARG1");
assert_eq!(args.arg2, vec!["ARG2".to_string(), "ARG3".to_string()]);
assert_eq!(rest, vec!["REST".to_string()]);
}
#[test]
fn test_multi_opts() {
let (args, rest) = opts! {
opt arg1:String;
opt arg2:Vec<String>, multi:true;
opt arg3:Vec<String>;
opt arg4:Vec<i32> = vec![42];
opt arg5:Vec<i32> = vec![1,2,3];
}
.parse_args(vec![
"--arg3",
"aaa",
"--arg2",
"ARG1",
"ARG2",
"--arg1",
"ARG3",
"--arg3=bbb",
"--arg5=7",
"--",
"REST",
])
.unwrap();
assert_eq!(args.arg1, "ARG3");
assert_eq!(args.arg2, vec!["ARG1".to_string(), "ARG2".to_string()]);
assert_eq!(args.arg3, vec!["aaa".to_string(), "bbb".to_string()]);
assert_eq!(args.arg4, vec![42]);
assert_eq!(args.arg5, vec![7]);
assert_eq!(rest, vec!["REST".to_string()]);
}
#[test]
#[should_panic]
fn test_multi_opts_multiple() {
let (_, _) = opts! {
opt arg1:Vec<String>, multi:true;
}
.parse_args(vec!["--arg1", "a", "b", "--arg1", "x", "y", "--", "REST"])
.unwrap();
}
#[test]
#[should_panic]
fn test_missing_args() {
let (_, _) = opts! {
param arg1:String;
param arg2:String;
}
.parse_args(vec!["ARG1"])
.unwrap();
}
#[test]
fn test_prefix() {
let (args, _) = opts! {
opt l:bool, long:"a-very-long-option";
}
.parse_args(vec!["--a-ver"])
.unwrap();
assert_eq!(args.l, true);
}
#[test]
fn test_ambiguous_prefix() {
let r = opts! {
opt l:bool, long:"a-very-long-option";
opt l2:bool, long:"a-very-long-option2";
}
.parse_args(vec!["--a-ver"]);
match r {
Err(AmbiguousPrefix(_, _)) => (),
_ => panic!("Failed"),
}
}
#[test]
fn test_exact_prefix() {
let (args, _) = opts! {
opt a_very_long_option:bool;
opt l2:bool, long:"a-very-long-option2";
}
.parse_args(vec!["--a-very-long-option"])
.unwrap();
assert_eq!(args.a_very_long_option, true);
assert_eq!(args.l2, false);
}
#[test]
fn test_optional_nodefault() {
let (args, _) = opts! {
opt a:Option<String>;
opt b:Option<i32>;
opt along:Option<String>;
opt blong:Option<String>;
opt clong:Option<String>;
}
.parse_args(vec!["-a", "abc", "--along=xyz", "--blong", "ert"])
.unwrap();
assert_eq!(args.a, Some("abc".to_string()));
assert_eq!(args.b, None);
assert_eq!(args.along, Some("xyz".to_string()));
assert_eq!(args.blong, Some("ert".to_string()));
assert_eq!(args.clong, None);
}
#[test]
fn test_optional_default() {
let (args, _) = opts! {
opt along:Option<i32> = Some(1);
opt blong:Option<i32> = Some(2);
opt clong:Option<i32> = Some(3);
}
.parse_args(vec!["--along=42", "--blong"])
.unwrap();
assert_eq!(args.along, Some(42));
assert_eq!(args.blong, Some(2));
assert_eq!(args.clong, None);
}
#[test]
#[should_panic]
fn test_required_nodefault() {
let (_, _) = opts! {
opt a:i32;
opt b:i32=1;
}
.parse_args(vec!["-b", "1"])
.unwrap();
}
#[test]
fn test_required_default() {
let (args, _) = opts! {
opt a:i32=1;
opt b:i32=2;
}
.parse_args(vec!["-a", "42"])
.unwrap();
assert_eq!(args.a, 42);
assert_eq!(args.b, 2);
}
#[test]
#[should_panic]
fn test_no_auto_shorts() {
let (_, _) = opts! {
auto_shorts false;
opt abc:bool;
}
.parse_args(vec!["-a"])
.unwrap();
}
#[test]
fn test_no_auto_shorts_on_shorts() {
let (args, _) = opts! {
auto_shorts false;
opt a:bool;
}
.parse_args(vec!["-a"])
.unwrap();
assert!(args.a);
}
#[test]
fn test_ambiguous_auto_shorts() {
let (args, _) = opts! {
opt abc:bool;
opt another:bool;
}
.parse_args(vec!["-n"])
.unwrap();
assert!(!args.abc);
assert!(args.another);
}
#[test]
fn test_ambiguous_forced_shorts() {
let (args, _) = opts! {
opt abc:bool;
opt another:bool, short:'a';
}
.parse_args(vec!["-b"])
.unwrap();
assert!(args.abc);
assert!(!args.another);
}
#[test]
fn test_no_dash_auto_short() {
let (args, _) = opts! {
opt another:bool;
opt a_flag:bool;
}
.parse_args(vec!["-f"])
.unwrap();
assert!(!args.another);
assert!(args.a_flag);
}
#[test]
fn test_negative_flag() {
let (args, _) = opts! {
opt doit:bool=true;
opt doit2:bool=true;
}
.parse_args(vec!["--no-doit"])
.unwrap();
assert!(args.doit == false);
assert!(args.doit2 == true);
}
#[test]
fn test_utf8_param() {
let (args, _) = opts! {
param arg:String;
}
.parse_args(vec!["aäöü"])
.unwrap();
assert!(args.arg == "aäöü");
}
#[test]
fn test_utf8_opt() {
let (args, _) = opts! {
opt a:bool, long:"äfläg", short:'ä';
opt another:bool, long:"änötherfläg", short:'ö';
}
.parse_args(vec!["-ä", "--änöther"])
.unwrap();
assert!(args.a);
assert!(args.another);
}