use crate::parser::ArgParser;
use crate::types::*;
use crate::validators::Validator;
#[derive(Clone, Debug)]
pub struct Flag {
long: String,
short: Option<char>,
description: String,
hidden: bool,
}
impl Flag {
pub fn new(long: &str) -> Self {
Self {
long: long.to_string(),
short: None,
description: String::new(),
hidden: false,
}
}
pub fn desc(mut self, description: &str) -> Self {
self.description = description.to_string();
self
}
pub fn short(mut self, ch: char) -> Self {
self.short = Some(ch);
self
}
pub fn hidden(mut self) -> Self {
self.hidden = true;
self
}
}
impl From<Flag> for FlagDef {
fn from(f: Flag) -> FlagDef {
FlagDef {
long: f.long,
short: f.short,
description: f.description,
hidden: f.hidden,
}
}
}
#[derive(Clone, Debug)]
pub struct Opt {
long: String,
short: Option<char>,
placeholder: String,
description: String,
required: bool,
default: Option<String>,
env_var: Option<String>,
multi: bool,
hidden: bool,
validator: Option<Validator>,
}
impl Opt {
pub fn new(long: &str) -> Self {
let placeholder = long.to_uppercase();
Self {
long: long.to_string(),
short: None,
placeholder,
description: String::new(),
required: false,
default: None,
env_var: None,
multi: false,
hidden: false,
validator: None,
}
}
pub fn placeholder(mut self, placeholder: &str) -> Self {
self.placeholder = placeholder.to_string();
self
}
pub fn desc(mut self, description: &str) -> Self {
self.description = description.to_string();
self
}
pub fn short(mut self, ch: char) -> Self {
self.short = Some(ch);
self
}
pub fn required(mut self) -> Self {
self.required = true;
self
}
pub fn default(mut self, value: &str) -> Self {
self.default = Some(value.to_string());
self
}
pub fn env(mut self, var_name: &str) -> Self {
self.env_var = Some(var_name.to_string());
self
}
pub fn multi(mut self) -> Self {
self.multi = true;
self
}
pub fn hidden(mut self) -> Self {
self.hidden = true;
self
}
pub fn validate(mut self, v: Validator) -> Self {
self.validator = Some(v);
self
}
}
impl From<Opt> for OptionDef {
fn from(o: Opt) -> OptionDef {
OptionDef {
long: o.long,
short: o.short,
placeholder: o.placeholder,
description: o.description,
required: o.required,
default: o.default,
env_var: o.env_var,
multi: o.multi,
hidden: o.hidden,
validator: o.validator,
}
}
}
#[derive(Clone, Debug)]
pub struct Pos {
name: String,
description: String,
required: bool,
default: Option<String>,
multi: bool,
validator: Option<Validator>,
}
impl Pos {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
description: String::new(),
required: false,
default: None,
multi: false,
validator: None,
}
}
pub fn desc(mut self, description: &str) -> Self {
self.description = description.to_string();
self
}
pub fn required(mut self) -> Self {
self.required = true;
self
}
pub fn default(mut self, value: &str) -> Self {
self.default = Some(value.to_string());
self
}
pub fn multi(mut self) -> Self {
self.multi = true;
self
}
pub fn validate(mut self, v: Validator) -> Self {
self.validator = Some(v);
self
}
}
impl From<Pos> for PositionalDef {
fn from(p: Pos) -> PositionalDef {
PositionalDef {
name: p.name,
description: p.description,
required: p.required,
default: p.default,
multi: p.multi,
validator: p.validator,
}
}
}
#[must_use = "builder does nothing until .build() is called"]
#[derive(Clone, Debug)]
pub struct ArgBuilder {
program_name: Option<String>,
program_desc: Option<String>,
version: Option<String>,
flags: Vec<FlagDef>,
options: Vec<OptionDef>,
positionals: Vec<PositionalDef>,
subcommands: Vec<SubcommandDef>,
groups: Vec<(String, Vec<String>)>,
conflicts: Vec<(String, Vec<String>)>,
}
impl ArgBuilder {
pub fn new() -> Self {
Self {
program_name: None,
program_desc: None,
version: None,
flags: Vec::new(),
options: Vec::new(),
positionals: Vec::new(),
subcommands: Vec::new(),
groups: Vec::new(),
conflicts: Vec::new(),
}
}
pub fn name(mut self, name: &str) -> Self {
self.program_name = Some(name.to_string());
self
}
pub fn description(mut self, desc: &str) -> Self {
self.program_desc = Some(desc.to_string());
self
}
pub fn version(mut self, version: &str) -> Self {
self.version = Some(version.to_string());
self
}
pub fn flag(mut self, flag: Flag) -> Self {
self.flags.push(FlagDef::from(flag));
self
}
pub fn option(mut self, opt: Opt) -> Self {
self.options.push(OptionDef::from(opt));
self
}
pub fn positional(mut self, pos: Pos) -> Self {
self.positionals.push(PositionalDef::from(pos));
self
}
pub fn subcommand(mut self, name: &str, desc: &str, parser: ArgParser) -> Self {
if let Some(existing) = self.subcommands.iter_mut().find(|s| s.name == name) {
existing.description = desc.to_string();
existing.parser = parser;
} else {
self.subcommands.push(SubcommandDef {
name: name.to_string(),
description: desc.to_string(),
parser,
});
}
self
}
pub fn group(mut self, name: &str, members: &[&str]) -> Self {
self.groups.push((name.to_string(), members.iter().map(|m| m.to_string()).collect()));
self
}
pub fn conflict(mut self, name: &str, members: &[&str]) -> Self {
self.conflicts.push((name.to_string(), members.iter().map(|m| m.to_string()).collect()));
self
}
#[must_use = "returns the built ArgParser; did you forget to assign it?"]
pub fn build(self) -> Result<ArgParser, ParseError> {
let mut seen_longs = std::collections::HashSet::new();
for flag in &self.flags {
if !seen_longs.insert(&flag.long) {
return Err(ParseError::InvalidFormat(format!(
"duplicate long argument name: --{}",
flag.long
)));
}
}
for opt in &self.options {
if !seen_longs.insert(&opt.long) {
return Err(ParseError::InvalidFormat(format!(
"duplicate long argument name: --{}",
opt.long
)));
}
}
let mut seen_shorts = std::collections::HashSet::new();
for flag in &self.flags {
if let Some(ch) = flag.short {
if !seen_shorts.insert(ch) {
return Err(ParseError::InvalidFormat(format!("duplicate short argument: -{}", ch)));
}
}
}
for opt in &self.options {
if let Some(ch) = opt.short {
if !seen_shorts.insert(ch) {
return Err(ParseError::InvalidFormat(format!("duplicate short argument: -{}", ch)));
}
}
}
if self.version.is_some() {
for flag in &self.flags {
if flag.short == Some('V') {
return Err(ParseError::InvalidFormat(
"duplicate short argument: -V (reserved for --version)".to_string(),
));
}
}
for opt in &self.options {
if opt.short == Some('V') {
return Err(ParseError::InvalidFormat(
"duplicate short argument: -V (reserved for --version)".to_string(),
));
}
}
}
for pos in &self.positionals {
if pos.required && pos.default.is_some() {
return Err(ParseError::InvalidFormat(format!(
"positional '{}' cannot be both required and have a default",
pos.name
)));
}
if pos.required && pos.multi {
return Err(ParseError::InvalidFormat(format!(
"positional '{}' cannot be both required and multi",
pos.name
)));
}
}
if let Some(pos) = self
.positionals
.iter()
.enumerate()
.find(|(i, p)| p.multi && *i < self.positionals.len() - 1)
.map(|(_, p)| p)
{
return Err(ParseError::InvalidFormat(format!(
"multi positional '{}' must be the last positional",
pos.name
)));
}
let mut validated_groups = Vec::new();
for (name, members) in &self.groups {
if members.len() < 2 {
return Err(ParseError::InvalidFormat(format!(
"group '{}' requires at least two members",
name
)));
}
for member in members {
if !self.flags.iter().any(|f| f.long == *member) && !self.options.iter().any(|o| o.long == *member) {
return Err(ParseError::InvalidFormat(format!(
"group '{}' references unknown argument: --{}",
name, member
)));
}
}
validated_groups.push(GroupDef {
name: name.clone(),
members: members.clone(),
});
}
let mut validated_conflicts = Vec::new();
for (name, members) in &self.conflicts {
if members.len() < 2 {
return Err(ParseError::InvalidFormat(format!(
"conflict '{}' requires at least two members",
name
)));
}
for member in members {
if !self.flags.iter().any(|f| f.long == *member) && !self.options.iter().any(|o| o.long == *member) {
return Err(ParseError::InvalidFormat(format!(
"conflict '{}' references unknown argument: --{}",
name, member
)));
}
}
validated_conflicts.push(ConflictDef {
name: name.clone(),
members: members.clone(),
});
}
Ok(ArgParser {
program_name: self.program_name,
program_desc: self.program_desc,
version: self.version,
flags: self.flags,
options: self.options,
positionals: self.positionals,
subcommands: self.subcommands,
groups: validated_groups,
conflicts: validated_conflicts,
})
}
}
impl Default for ArgBuilder {
fn default() -> Self {
Self::new()
}
}