summaryrefslogtreecommitdiff
path: root/src/args.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/args.rs')
-rw-r--r--src/args.rs305
1 files changed, 305 insertions, 0 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);
+ }
+}