diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/args.rs | 305 | ||||
| -rw-r--r-- | src/main.rs | 68 |
2 files changed, 371 insertions, 2 deletions
diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..3cd9d8a --- /dev/null +++ b/src/args.rs @@ -0,0 +1,305 @@ +use derive_builder::Builder; +use std::collections::HashMap; + +#[allow(dead_code)] +pub enum ValueRequirement { + None, + Required(&'static str), + Optional(&'static str), +} + +#[derive(Builder)] +#[builder(pattern = "owned")] +pub struct Option { + #[builder(setter(into), default = "'\\0'")] + short: char, + long: &'static str, + #[builder(setter(into), default = "\"\"")] + description: &'static str, + #[builder(setter(name = "value"), default = "ValueRequirement::None")] + value_req: ValueRequirement, + #[builder(setter(skip))] + is_set: bool, + #[builder(setter(skip))] + value: std::option::Option<String>, +} + +pub struct Options { + options: Vec<Option>, + short: HashMap<char, usize>, + long: HashMap<&'static str, usize>, +} + +impl Option { + pub fn short(&self) -> char { + self.short + } + + pub fn long(&self) -> &'static str { + self.long + } + + pub fn description(&self) -> &'static str { + self.description + } + + pub fn is_set(&self) -> bool { + self.is_set + } + + pub fn value(&self) -> &std::option::Option<String> { + &self.value + } + + fn set(&mut self, value: std::option::Option<String>) { + self.is_set = true; + self.value = value; + } +} + +impl Options { + pub fn new() -> Self { + Options { + options: Vec::new(), + short: HashMap::new(), + long: HashMap::new(), + } + } + + pub fn push(&mut self, option: Option) -> usize { + let index = self.options.len(); + if option.short != '\0' { + self.short.insert(option.short, index); + } + if option.long != "" { + self.long.insert(option.long, index); + } + self.options.push(option); + index + } +} + +impl std::ops::Index<usize> for Options { + type Output = Option; + + fn index(&self, index: usize) -> &Self::Output { + self.options.index(index) + } +} + +pub struct Arguments { + pub program: std::option::Option<String>, + pub args: Vec<String>, +} + +pub trait Parser { + fn run(&self, options: &mut Options, args: impl IntoIterator<Item=String>) -> Result<Arguments, String>; + fn print_help(&self, options: &Options); +} + +#[allow(dead_code)] +pub struct LongOnlyParser { +} + +#[allow(dead_code)] +pub struct ShortAndLongParser { +} + +fn print_list(list: Vec<(String, &str)>) { + let mut left_len: usize = 0; + for (left, _) in &list { + left_len = std::cmp::max(left_len, left.len()); + } + for (left, right) in &list { + println!("{left:<left_len$} {right}"); + } +} + +#[allow(dead_code)] +impl LongOnlyParser { + pub fn new() -> LongOnlyParser { + LongOnlyParser { } + } +} + +impl Parser for LongOnlyParser { + fn run(&self, options: &mut Options, args: impl IntoIterator<Item=String>) -> Result<Arguments, String> { + let mut ret = Vec::new(); + let mut args_iter = args.into_iter(); + let program = args_iter.next(); + while let Some(arg) = args_iter.next() { + if arg.starts_with("-") { + if arg == "--" { + // All following arguments are just that. + while let Some(arg) = args_iter.next() { + ret.push(arg) + } + break + } + let start = 1; + let name; + let mut value; + if let Some(end) = arg.find('=') { + name = arg.get(start..end).unwrap(); + value = Some(arg.get(end + 1..).unwrap().to_string()); + } else { + name = arg.get(start..).unwrap(); + value = None; + } + if let Some(index) = options.long.get(name) { + let ref mut option = options.options[*index]; + match option.value_req { + ValueRequirement::None => { + if value.is_some() { + return Err(format!("option '{}' doesn't allow an argument", arg)) + } + }, + ValueRequirement::Required(_) => { + if value.is_none() { + value = args_iter.next(); + if value.is_none() { + return Err(format!("option '{}' requires an argument", arg)) + } + } + }, + ValueRequirement::Optional(_) => {}, + } + option.set(value); + } else { + return Err(format!("unrecognized option '{}'", arg)) + } + } else { + ret.push(arg) + } + } + Ok(Arguments { program, args: ret }) + } + + fn print_help(&self, options: &Options) { + let mut lines = Vec::new(); + for (_, index) in &options.long { + let ref option = options[*index]; + let left = match option.value_req { + ValueRequirement::None => format!("-{}", option.long()), + ValueRequirement::Required(name) => format!("-{}={}", option.long(), name), + ValueRequirement::Optional(name) => format!("-{}[={}]", option.long(), name), + }; + lines.push((left, option.description())); + } + print_list(lines); + } +} + +#[allow(dead_code)] +impl ShortAndLongParser { + pub fn new() -> ShortAndLongParser { + ShortAndLongParser { } + } +} + +impl Parser for ShortAndLongParser { + fn run(&self, options: &mut Options, args: impl IntoIterator<Item=String>) -> Result<Arguments, String> { + let mut ret = Vec::new(); + let mut args_iter = args.into_iter(); + let program = args_iter.next(); + while let Some(arg) = args_iter.next() { + if arg.starts_with("--") { + if arg.len() == 2 { + // All following arguments are just that. + while let Some(arg) = args_iter.next() { + ret.push(arg) + } + break + } + let start = 2; + let name; + let mut value; + if let Some(end) = arg.find('=') { + name = arg.get(start..end).unwrap(); + value = Some(arg.get(end + 1..).unwrap().to_string()); + } else { + name = arg.get(start..).unwrap(); + value = None; + } + if let Some(index) = options.long.get(name) { + let ref mut option = options.options[*index]; + match option.value_req { + ValueRequirement::None => { + if value.is_some() { + return Err(format!("option '{}' doesn't allow an argument", arg)) + } + }, + ValueRequirement::Required(_) => { + if value.is_none() { + value = args_iter.next(); + if value.is_none() { + return Err(format!("option '{}' requires an argument", arg)) + } + } + }, + ValueRequirement::Optional(_) => {}, + } + option.set(value); + } else { + return Err(format!("unrecognized option '{}'", arg)) + } + } else if arg.starts_with("-") && arg.len() > 1 { + for c in arg.get(1..).unwrap().chars() { + if let Some(index) = options.short.get(&c) { + let ref mut option = options.options[*index]; + let mut value = None; + match option.value_req { + ValueRequirement::None => {}, + ValueRequirement::Required(_) => { + value = args_iter.next(); + if value.is_none() { + return Err(format!("option requires an argument -- '{}", c)) + } + }, + ValueRequirement::Optional(_) => {}, + } + option.set(value); + } else { + return Err(format!("invalid option -- '{}'", c)) + } + } + } else { + ret.push(arg) + } + } + Ok(Arguments { program, args: ret }) + } + + fn print_help(&self, options: &Options) { + let mut lines = Vec::new(); + for option in &options.options { + let left: String; + if option.short() == '\0' { + left = match option.value_req { + ValueRequirement::None => format!(" --{}", option.long()), + ValueRequirement::Required(name) => + format!(" --{}={}", option.long(), name), + ValueRequirement::Optional(name) => + format!(" --{}[={}]", option.long(), name), + }; + } else if option.long() == "" { + left = match option.value_req { + ValueRequirement::None => format!("-{}", option.short()), + ValueRequirement::Required(name) => format!("-{}={}", option.short(), name), + ValueRequirement::Optional(name) => + format!("-{}[={}]", option.short(), name), + }; + } else { + left = match option.value_req { + ValueRequirement::None => format!("-{}, --{}", option.short(), option.long()), + ValueRequirement::Required(name) => + format!("-{}, --{}={}", option.short(), option.long(), name), + ValueRequirement::Optional(name) => + format!("-{}, --{}[={}]", option.short(), option.long(), name), + }; + } + lines.push((left, option.description())); + } + print_list(lines); + } +} diff --git a/src/main.rs b/src/main.rs index e7a11a9..6b7c181 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,67 @@ -fn main() { - println!("Hello, world!"); +mod args; + +use crate::args::Parser; +use std::process::ExitCode; + +fn parse_args() -> Result<bool, String> { + let mut options = args::Options::new(); + let help_idx = options.push( + args::OptionBuilder::default() + .short('h') + .long("help") + .description("display this and exit") + .build().unwrap()); + let version_idx = options.push( + args::OptionBuilder::default() + .short('V') + .long("version") + .description("display version and exit") + .build().unwrap()); + let verbose_idx = options.push( + args::OptionBuilder::default() + .long("verbose") + .description("be verbose") + .build().unwrap()); + let test_idx = options.push( + args::OptionBuilder::default() + .short('t') + .long("test") + .description("testing") + .value(args::ValueRequirement::Required("FILE")) + .build().unwrap()); + + let parser = args::ShortAndLongParser::new(); + let args = parser.run(&mut options, std::env::args())?; + let ref help = options[help_idx]; + if help.is_set() { + parser.print_help(&options); + return Ok(true); + } + let ref version = options[version_idx]; + if version.is_set() { + println!("Version is 0.0.1"); + return Ok(true); + } + let ref verbose = options[verbose_idx]; + let ref test = options[test_idx]; + println!("verbose: {}", verbose.is_set()); + println!("test: {:?}", test.value()); + println!("args: {:?}", args.args); + Ok(false) +} + +fn main() -> ExitCode { + match parse_args() { + Ok(exit) => { + if exit { + return ExitCode::SUCCESS; + } + } + Err(msg) => { + eprintln!("{}", msg); + return ExitCode::FAILURE; + } + } + + ExitCode::SUCCESS } |
