From b2cdb5d3cacbfec0af3e7168c01f07b6480a3c5b Mon Sep 17 00:00:00 2001 From: teesh3rt Date: Sat, 22 Mar 2025 14:05:55 +0200 Subject: [PATCH] refact: refactor the argparser for the future --- coreutils/src/commands/mkdir.rs | 25 ++-- utils/src/args.rs | 221 ++++++++++++++++++++++++-------- 2 files changed, 178 insertions(+), 68 deletions(-) diff --git a/coreutils/src/commands/mkdir.rs b/coreutils/src/commands/mkdir.rs index bd256ad..2ef5ccf 100644 --- a/coreutils/src/commands/mkdir.rs +++ b/coreutils/src/commands/mkdir.rs @@ -1,4 +1,4 @@ -use boxutils::args::Args; +use boxutils::args::ArgParser; use boxutils::commands::Command; use std::env; use std::fs; @@ -8,27 +8,24 @@ pub struct Mkdir; impl Command for Mkdir { fn execute(&self) { let raw_args: Vec = env::args().collect::>(); - let args = Args::new("mkdir", raw_args); - - if args.get_args().len() == 0 { - panic!( - "{}", - String::from("Usage: mkdir [DIR1] [DIR1] etc. pp. [-p, --parents]") - ); - } + let args = ArgParser::builder() + .add_flag("-p") + .add_flag("--parents") + .add_flag("--help") + .parse("mkdir", raw_args); if args.get_flag("--help") { println!("Usage: mkdir [DIR1] [DIR2] etc. pp. [-p, --parents]"); return; } + if args.get_normal_args().len() == 0 { + panic!("Usage: mkdir [DIR1] [DIR1] etc. pp. [-p, --parents]"); + } + let parented = args.get_flag("-p") || args.get_flag("--parents"); - let to_create = args - .get_args() - .into_iter() - .filter(|x| !x.starts_with("-")) - .collect::>(); + let to_create = args.get_normal_args(); for dir in to_create { if parented { diff --git a/utils/src/args.rs b/utils/src/args.rs index 98d0cbb..b5492ed 100644 --- a/utils/src/args.rs +++ b/utils/src/args.rs @@ -1,83 +1,196 @@ -use super::commands::get_args; +use std::collections::{HashMap, HashSet}; -pub struct Args { - args: Vec, +use crate::commands::get_args; + +pub struct ArgParser { + #[allow(dead_code)] + flags: HashSet, + #[allow(dead_code)] + options: HashSet, + parsed_flags: HashSet, + parsed_options: HashMap, + normal_args: Vec, } -impl Args { - pub fn new(command: &str, args: Vec) -> Args { - let args = get_args(command.into(), args); - - Args { args } +impl ArgParser { + pub fn builder() -> ArgParserBuilder { + ArgParserBuilder::new() } pub fn get_flag(&self, flag: &str) -> bool { - self.args - .iter() - .any(|arg| arg == flag || arg.starts_with(flag)) + self.parsed_flags.contains(flag) } - pub fn get_args(&self) -> Vec { - self.args.clone() + pub fn get_option(&self, option: &str) -> Option<&str> { + self.parsed_options.get(option).map(String::as_str) } - pub fn get_option(&self, option: &str) -> Option { - // Check for the option as a separate argument - if let Some(i) = self.args.iter().position(|x| x == option) { - if i + 1 < self.args.len() { - return Some(self.args[i + 1].clone()); - } - } - - // Check for the option as a combined argument (-fvalue) - for arg in &self.args { - if arg.starts_with(option) && arg.len() > option.len() { - return Some(arg[option.len()..].to_string()); - } - } - - None + pub fn get_normal_args(&self) -> Vec { + self.normal_args.clone() } } +pub struct ArgParserBuilder { + flags: HashSet, + options: HashSet, +} + +impl ArgParserBuilder { + pub fn new() -> Self { + Self { + flags: HashSet::new(), + options: HashSet::new(), + } + } + + pub fn add_flag(mut self, flag: &str) -> Self { + self.flags.insert(flag.to_string()); + self + } + + pub fn add_flags(mut self, flags: Vec<&str>) -> Self { + for flag in flags { + self.flags.insert(flag.to_string()); + } + self + } + + pub fn add_option(mut self, option: &str) -> Self { + self.options.insert(option.to_string()); + self + } + + pub fn add_options(mut self, options: Vec<&str>) -> Self { + for option in options { + self.options.insert(option.to_string()); + } + self + } + + pub fn parse(self, program_name: &str, args: Vec) -> ArgParser { + let args = get_args(program_name.to_string(), args); + + let mut parsed_flags = HashSet::new(); + let mut parsed_options = HashMap::new(); + let mut normal_args = Vec::new(); + + let mut iter = args.into_iter(); + while let Some(arg) = iter.next() { + if self.flags.contains(&arg) { + parsed_flags.insert(arg.clone()); + } else if self.options.contains(&arg) { + if let Some(value) = iter.next() { + parsed_options.insert(arg.clone(), value); + } + } else if arg.len() > 2 && arg.starts_with('-') && !arg.starts_with("--") { + let mut chars = arg.chars().skip(1).peekable(); + while let Some(ch) = chars.next() { + let flag = format!("-{}", ch); + if self.flags.contains(&flag) { + parsed_flags.insert(flag); + } else if self.options.contains(&flag) { + let value: String = chars.collect(); + if !value.is_empty() { + parsed_options.insert(flag, value); + } else if let Some(next_value) = iter.next() { + parsed_options.insert(flag, next_value); + } + break; + } + } + } else { + normal_args.push(arg); + } + } + + ArgParser { + flags: self.flags, + options: self.options, + parsed_flags, + parsed_options, + normal_args, + } + } +} + +#[cfg(test)] mod tests { + use super::*; + #[test] - fn test_flag() { - let args = super::Args::new("sometool", vec!["sometool".into(), "-f".into()]); - assert_eq!(args.get_flag("-f"), true); + fn test_single_flag() { + let args = ArgParser::builder() + .add_flag("-r") + .parse("sometool", vec!["sometool".to_string(), "-r".to_string()]); + assert!(args.get_flag("-r")); } #[test] - fn test_no_flag() { - let args = super::Args::new("sometool", vec!["sometool".into(), "-f".into()]); - assert_eq!(args.get_flag("-g"), false); + fn test_multiple_flags() { + let args = ArgParser::builder() + .add_flag("-r") + .add_flag("-f") + .add_flag("-v") + .parse("sometool", vec!["sometool".to_string(), "-rfv".to_string()]); + assert!(args.get_flag("-r")); + assert!(args.get_flag("-f")); + assert!(args.get_flag("-v")); } #[test] - fn test_option() { - let args = super::Args::new("sometool", vec![ - "sometool".into(), - "-f".into(), - "value".into(), - ]); - assert_eq!(args.get_option("-f"), Some("value".into())); + fn test_single_option() { + let args = ArgParser::builder() + .add_option("-O") + .parse("sometool", vec![ + "sometool".to_string(), + "-O".to_string(), + "fast".to_string(), + ]); + assert_eq!(args.get_option("-O"), Some("fast")); } #[test] - fn test_no_option() { - let args = super::Args::new("sometool", vec!["sometool".into(), "-f".into()]); - assert_eq!(args.get_option("-g"), None); + fn test_multiple_options() { + let args = ArgParser::builder() + .add_option("-O") + .add_option("-hello") + .parse("sometool", vec![ + "sometool".to_string(), + "-O".to_string(), + "fast".to_string(), + "-hello".to_string(), + "world".to_string(), + ]); + assert_eq!(args.get_option("-O"), Some("fast")); + assert_eq!(args.get_option("-hello"), Some("world")); } #[test] - fn test_combined_option() { - let args = super::Args::new("sometool", vec!["sometool".into(), "-fvalue".into()]); - assert_eq!(args.get_option("-f"), Some("value".into())); - } - - #[test] - fn test_args() { - let args = super::Args::new("sometool", vec!["sometool".into(), "-f".into()]); - assert_eq!(args.get_args(), vec!["-f".to_string()]); + fn test_all_together_now() { + let args = ArgParser::builder() + .add_flags(vec!["-r", "-f", "-v", "--do-it"]) + .add_options(vec!["-O", "-hello", "--another"]) + .parse("sometool", vec![ + "sometool".to_string(), + "-rf".to_string(), + "-v".to_string(), + "-Ofast".to_string(), + "-hello".to_string(), + "world".to_string(), + "--do-it".to_string(), + "somefile.txt".to_string(), + "--another".to_string(), + "file.txt".to_string(), + ]); + assert!(args.get_flag("-r")); + assert!(args.get_flag("-f")); + assert!(!args.get_flag("-g")); + assert!(args.get_flag("-v")); + assert!(args.get_flag("--do-it")); + assert_eq!(args.get_option("-O"), Some("fast")); + assert_eq!(args.get_option("-hello"), Some("world")); + assert_eq!(args.get_option("--another"), Some("file.txt")); + assert_eq!(args.get_option("--does-not-exist"), None); + assert_eq!(args.get_normal_args(), vec!["somefile.txt".to_string()]); } }