A simple shell wrapper that handles argument parsing
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Rework short arguments

Fly 882e516c ab27d891

+74 -50
+5 -6
README.md
··· 1 1 # argsh 2 2 3 - This is a simple shell wrapper for scripts that handles argument parsing, because argument parsing in bash is a pain in the ass. 3 + This is a somewhat simple shell wrapper for scripts that handles argument parsing, because argument parsing in bash is a pain in the ass. 4 4 5 5 ## Installation 6 6 ··· 26 26 27 27 Any additional positional arguments passed to the script get appended after the required arguments. 28 28 29 + Short arguments are supported, but only the first argument that starts with a certain character will be matched by the short argument, for example, with the arguments `foo;fuzz`, `-f` will only match `foo`, to be able to match both with short arguments, override the short argument for `fuzz` like `foo;fuzz:z`, now `-f` will match `foo` and `-z` will match `fuzz`. 30 + **NOTE:** matching is based on order, so an explicit short argument won't take priority over an implicit one that came first, for example, in a case such as `foo;bar:f`, `-f` will still match `foo`, as it is defined first. 31 + 29 32 Optionally, you can set the shell to use as such `bash|name;count;etc`, defaults to `sh` if ommited. 30 33 31 - ## Limitations 32 - 33 - For the short version of arguments to work properly, all arguments must start with different characters. 34 - 35 - Limit of 256 required arguments (if you have anything close to that please use an actual programming language). 34 + There is a limit of 256 required arguments (if you have anything close to that please use an actual programming language). 36 35 37 36 ## Testing 38 37
+68 -43
src/main.rs
··· 25 25 26 26 mod config { 27 27 #[derive(Debug, Eq, PartialEq, std::hash::Hash)] 28 - pub enum Arg { 29 - Required(String, u8), 30 - Optional(String), 31 - Flag(String), 28 + pub struct Arg { 29 + pub name: String, 30 + pub r#type: ArgType, 31 + pub short: Option<char>, 32 + } 33 + 34 + #[derive(Debug, Eq, PartialEq, std::hash::Hash)] 35 + pub enum ArgType { 36 + Required(u8), 37 + Optional, 38 + Flag, 32 39 } 33 40 34 41 fn is_valid_name(s: &str) -> bool { 35 - !s.is_empty() && s.chars().all(|c| matches!(c, 'a'..='z' | '-')) 42 + !s.is_empty() && s.chars().all(|c| matches!(c, 'a'..='z' | '0'..='9' | '-')) 36 43 } 37 44 38 45 pub struct Config { ··· 41 48 } 42 49 impl Config { 43 50 pub fn parse(format: &str) -> Result<Self, lexopt::Error> { 44 - let (shell, args_raw) = { 45 - let mut split: Vec<&str> = format.splitn(2, '|').collect(); 46 - 47 - let shell = match split.len() { 48 - 1 => None, 49 - 2 => Some(split.remove(0)), 50 - _ => panic!("what the fuck"), 51 - }; 52 - 53 - (shell, split.pop().unwrap_or_default()) 54 - }; 51 + let (shell, args_raw) = format 52 + .split_once('|') 53 + .map(|(shell, args)| (Some(shell), args)) 54 + .unwrap_or((None, format)); 55 55 56 56 let mut args: Vec<Arg> = Vec::new(); 57 57 let mut pos: u8 = 0; 58 58 59 59 for arg in args_raw.split(';') { 60 + let (arg, short): (&str, Option<char>) = 61 + if let Some((arg, short)) = arg.split_once(":") { 62 + if let Ok(short) = short.parse::<char>() 63 + && matches!(short, 'a'..='z' | '0'..='9') 64 + { 65 + (arg, Some(short)) 66 + } else { 67 + return Err( 68 + "Short argument must be a single character and one of a-z or 0-9" 69 + .into(), 70 + ); 71 + } 72 + } else { 73 + (arg, None) 74 + }; 75 + 60 76 if let Some(arg) = arg.strip_prefix('#') { 61 77 if is_valid_name(arg) { 62 - args.push(Arg::Optional(arg.to_owned())); 78 + args.push(Arg { 79 + name: arg.to_owned(), 80 + r#type: ArgType::Optional, 81 + short, 82 + }); 63 83 } else { 64 84 return Err("Invalid argument name".into()); 65 85 } 66 86 } else if let Some(arg) = arg.strip_prefix('?') { 67 87 if is_valid_name(arg) { 68 - args.push(Arg::Flag(arg.to_owned())); 88 + args.push(Arg { 89 + name: arg.to_owned(), 90 + r#type: ArgType::Flag, 91 + short, 92 + }); 69 93 } else { 70 94 return Err("Invalid argument name".into()); 71 95 } 72 96 } else { 73 97 if is_valid_name(arg) { 74 98 pos += 1; 75 - args.push(Arg::Required(arg.to_owned(), pos)); 99 + args.push(Arg { 100 + name: arg.to_owned(), 101 + r#type: ArgType::Required(pos), 102 + short, 103 + }); 76 104 } else { 77 105 return Err("Invalid argument name".into()); 78 106 } ··· 86 114 } 87 115 88 116 pub fn match_arg(&self, arg_to_match: &str) -> Option<&Arg> { 89 - self.args.iter().find(|arg| match arg { 90 - Arg::Required(arg, _) => arg == arg_to_match, 91 - Arg::Optional(arg) => arg == arg_to_match, 92 - Arg::Flag(arg) => arg == arg_to_match, 93 - }) 117 + self.args.iter().find(|arg| arg.name == arg_to_match) 94 118 } 95 119 96 120 pub fn match_arg_short(&self, arg_to_match: char) -> Option<&Arg> { 97 - self.args.iter().find(|arg| match arg { 98 - Arg::Required(arg, _) => { 99 - arg.chars().nth(0).map_or(false, |arg| arg == arg_to_match) 100 - } 101 - Arg::Optional(arg) => arg.chars().nth(0).map_or(false, |arg| arg == arg_to_match), 102 - Arg::Flag(arg) => arg.chars().nth(0).map_or(false, |arg| arg == arg_to_match), 121 + self.args.iter().find(|arg| { 122 + arg.short 123 + .unwrap_or_else(|| arg.name.chars().next().unwrap()) 124 + == arg_to_match 103 125 }) 104 126 } 105 127 } ··· 121 143 match arg { 122 144 Short(short) => { 123 145 if let Some(arg) = config.match_arg_short(short) { 124 - match arg { 125 - config::Arg::Flag(_) => args.insert(arg, String::new()), 146 + match arg.r#type { 147 + config::ArgType::Flag => args.insert(arg, String::new()), 126 148 _ => args.insert(arg, parser.value()?.parse()?), 127 149 }; 128 150 } else { ··· 131 153 } 132 154 Long(long) => { 133 155 if let Some(arg) = config.match_arg(long) { 134 - match arg { 135 - config::Arg::Flag(_) => args.insert(arg, String::new()), 156 + match arg.r#type { 157 + config::ArgType::Flag => args.insert(arg, String::new()), 136 158 _ => args.insert(arg, parser.value()?.parse()?), 137 159 }; 138 160 } else { ··· 150 172 let mut found_required_args: Vec<String> = Vec::new(); 151 173 152 174 for (arg, value) in args { 153 - match arg { 154 - config::Arg::Required(arg, pos) => { 155 - found_required_args.push(arg.to_owned()); 175 + match arg.r#type { 176 + config::ArgType::Required(pos) => { 177 + found_required_args.push(arg.name.clone()); 156 178 required_args.insert(pos - 1, value); 157 179 } 158 - config::Arg::Optional(arg) => { 180 + config::ArgType::Optional => { 159 181 optional_args.push(( 160 - format!("ARGSH_{}", arg.replace('-', "_").to_uppercase()), 182 + format!("ARGSH_{}", arg.name.replace('-', "_").to_uppercase()), 161 183 value, 162 184 )); 163 185 } 164 - config::Arg::Flag(arg) => { 165 - flags.push(format!("ARGSH_{}", arg.replace('-', "_").to_uppercase())); 186 + config::ArgType::Flag => { 187 + flags.push(format!( 188 + "ARGSH_{}", 189 + arg.name.replace('-', "_").to_uppercase() 190 + )); 166 191 } 167 192 } 168 193 } 169 194 170 - for arg in config.args.into_iter().filter_map(|arg| match arg { 171 - config::Arg::Required(arg, _) => Some(arg), 195 + for arg in config.args.into_iter().filter_map(|arg| match arg.r#type { 196 + config::ArgType::Required(_) => Some(arg.name), 172 197 _ => None, 173 198 }) { 174 199 if !found_required_args.contains(&arg) {
+1 -1
test
··· 1 - #!./target/debug/argsh bash|required;also-required;?flag;#optional;#second-optional 1 + #!./target/debug/argsh bash|required;also-required:q;?flag;#optional:o;#second-optional:p 2 2 3 3 echo "--required on pos 1: $1" 4 4 echo "--also-required on pos 2: $2"