1use std::collections::HashMap;
2use std::io;
3
4#[cfg(feature = "serde")]
5use serde::Serialize;
6
7use crate::types::{ConfigEntry, ConfigEntries, LabeledEntry, OptionValue};
8
9#[derive(Clone, Debug, PartialEq)]
10#[cfg_attr(feature = "serde", derive(Serialize))]
11pub struct ParseResult {
12 options: HashMap<String, OptionValue>,
13 parameters: HashMap<String, String>,
14 operands: Vec<String>,
15}
16
17impl ParseResult {
18 pub fn options(&self) -> &HashMap<String, OptionValue> {
19 &self.options
20 }
21
22 pub fn parameters(&self) -> &HashMap<String, String> {
23 &self.parameters
24 }
25
26 pub fn operands(&self) -> &[String] {
27 &self.operands
28 }
29}
30
31pub struct ParserConfig {
32 configs: HashMap<String, ConfigEntry>,
33}
34
35impl ParserConfig {
36 pub fn new(entries: ConfigEntries) -> io::Result<Self> {
37 let size: usize = entries.len();
38 let mut aliases: Vec<(String, String)> = Vec::with_capacity(size);
39 let mut configs = HashMap::with_capacity(size);
40 match entries {
41 ConfigEntries::Map(map) => {
42 for (option, config) in map {
43 configs.insert(option.to_string(), config.clone());
44 if let ConfigEntry::Alias { target } = config {
45 aliases.push((option.to_string(), target.to_string()));
46 }
47 }
48 },
49 ConfigEntries::List(list) => {
50 for LabeledEntry { option, entry } in list {
51 configs.insert(option.to_string(), entry.clone());
52 if let ConfigEntry::Alias { target } = entry {
53 aliases.push((option.to_string(), target.to_string()));
54 }
55 }
56 },
57 };
58
59 for (name, target) in aliases {
60 if !configs.contains_key(&target) {
61 let kind = io::ErrorKind::InvalidData;
62 let msg = format!(
63 "target value '{}' for option '{}' was not found",
64 target,
65 name
66 );
67 return Err(io::Error::new(kind, msg));
68 }
69 }
70
71 Ok(Self { configs })
72 }
73
74 pub fn into_inner(self) -> HashMap<String, ConfigEntry> {
75 let Self { configs } = self;
76 configs
77 }
78}
79
80pub struct ArgParser {
81 configs: HashMap<String, ConfigEntry>,
82}
83
84#[derive(Debug, Clone, PartialEq)]
85enum CliArg {
86 Long { name: String, value: Option<String> },
88
89 Short { flags: String },
91
92 Parameter(String, String),
94
95 Operand,
97}
98
99fn get_opt_value(arg: &str) -> CliArg {
100 if arg == "--" {
101 CliArg::Operand
102 } else if let Some(stripped) = arg.strip_prefix("--") {
103 let mut parts = stripped.splitn(2, '=');
104 let name = parts.next().unwrap_or_default().to_string();
105 let value = parts.next().map(|v| v.to_string());
106 if name.is_empty() {
107 CliArg::Operand
108 } else {
109 CliArg::Long { name, value }
110 }
111 } else if let Some(flags) = arg.strip_prefix('-') {
112 CliArg::Short { flags: flags.to_string() }
113 } else {
114 let mut parts = arg.splitn(2, '=');
115 let name: String = parts.next().unwrap_or_default().to_string();
116 if let Some(value) = parts.next().map(|v: &str| v.to_string()) {
117 CliArg::Parameter(name, value)
118 } else {
119 CliArg::Operand
120 }
121 }
122}
123
124impl ArgParser {
125 pub fn new(configs: ParserConfig) -> Self {
126 let configs = configs.into_inner();
127 Self { configs }
128 }
129
130 pub fn parse<I, S>(&self, arg_list: I) -> Result<ParseResult, String>
131 where
132 I: IntoIterator<Item = S>,
133 S: AsRef<str>,
134 {
135 let mut options: HashMap<String, OptionValue> = HashMap::new();
136 let mut parameters: HashMap<String, String> = HashMap::new();
137 let mut operands: Vec<String> = Vec::new();
138
139 let mut stop_parsing = false;
140 let mut args = arg_list.into_iter().peekable();
141
142 while let Some(arg) = args.next() {
143 let arg: &str = arg.as_ref();
144
145 if arg == "--" && !stop_parsing {
146 stop_parsing = true;
147 continue;
148 }
149
150 if stop_parsing {
151 operands.push(arg.into());
152 continue;
153 }
154
155 match get_opt_value(arg) {
156 CliArg::Short { flags } => {
157 let (skip, mut pairs) = self
158 .parse_short_option(&flags, args.peek())?;
159
160 for (name, value) in pairs.drain() {
161 match self.configs.get(&name) {
162 Some(ConfigEntry::Count) => {
163 match options.get_mut(&name) {
164 Some(OptionValue::Int(old)) => {
165 if let OptionValue::Int(new) = value {
166 *old += new;
167 } else {
168 todo!();
169 };
170 },
171 None => {
172 options.insert(name, value);
173 },
174 _ => { todo!(); },
175 }
176 },
177 Some(ConfigEntry::List { .. }) => {
178 match options.get_mut(&name) {
179 Some(OptionValue::List(old)) => {
180 if let OptionValue::List(new) = value {
181 old.extend_from_slice(&new);
182 } else {
183 todo!();
184 };
185 },
186 None => {
187 options.insert(name, value);
188 },
189 _ => { todo!(); },
190 }
191 },
192 _ => {
193 options.insert(name, value);
194 }
195 }
196 }
197
198 if skip {
199 let _ = args.next();
200 }
201 },
202 CliArg::Long { name, value } => {
203 let (name, value) = self
204 .parse_long_option(&name, value.as_deref())?;
205 match self.configs.get(name) {
206 Some(ConfigEntry::Count) => {
207 match options.get_mut(name) {
208 Some(OptionValue::Int(old_value)) => {
209 if let OptionValue::Int(new_value) = value {
210 *old_value += new_value;
211 } else {
212 todo!(); };
214 },
215 None => {
216 options.insert(name.into(), value);
217 },
218 _ => { todo!() },
219 }
220 },
221 Some(ConfigEntry::List { .. }) => {
222 match options.get_mut(name) {
223 Some(OptionValue::List(old_value)) => {
224 if let OptionValue::List(new_value) = value {
225 old_value.extend_from_slice(&new_value);
226 } else {
227 todo!(); };
229 },
230 None => {
231 options.insert(name.into(), value);
232 },
233 _ => { todo!() },
234 }
235 },
236 _ => {
237 options.insert(name.into(), value);
238 },
239 }
240 },
241 CliArg::Parameter(name, value) => {
242 parameters.insert(name, value);
243 },
244 CliArg::Operand => {
245 operands.push(arg.into());
246 },
247 }
248 }
249
250 Ok(ParseResult {
251 options,
252 parameters,
253 operands,
254 })
255 }
256
257 fn parse_long_option<'parser, 'option, 'result>(
258 &'parser self,
259 name: &'option str,
260 value: Option<&str>,
261 ) -> Result<(&'result str, OptionValue), String>
262 where
263 'parser: 'result,
264 'option: 'result,
265 {
266 macro_rules! flag_option {
267 ($name:ident) => {{ Ok(($name, OptionValue::Flag)) }}
268 }
269
270 macro_rules! text_option {
271 ($name:ident, $value:ident, $default:ident) => {{
272 if let Some(text) = $value {
273 Ok(($name, OptionValue::Text(text.into())))
274 } else if let Some(text) = $default {
275 Ok(($name, OptionValue::Text(text.into())))
276 } else {
277 Err("null arg".into())
278 }
279 }}
280 }
281
282 macro_rules! int_option {
283 ($name:ident, $value:ident, $default:ident) => {{
284 match $value {
285 Some(text) if !text.is_empty() => {
286 if let Ok(num) = text.parse::<i64>() {
287 Ok(($name, OptionValue::Int(num)))
288 } else {
289 Err("invalid int".into())
290 }
291 },
292 _ => {
293 if let Some(num) = $default {
294 Ok(($name, OptionValue::Int(*num)))
295 } else {
296 Err("null int".into())
297 }
298 }
299 }
300 }}
301 }
302
303 macro_rules! count_option {
304 ($name:ident, $val:ident) => {{
305 if $val.is_some() && $val.unwrap().parse::<i64>().is_err() {
306 Err("invalid int".into())
307 } else if let Some(text) = $val {
308 let num = text.parse().unwrap();
309 Ok(($name, OptionValue::Int(num)))
310 } else {
311 Ok(($name, OptionValue::Int(1)))
312 }
313 }}
314 }
315
316 macro_rules! list_option {
317 ($name:ident, $value:ident, $sep:ident) => {{
318 if $value.is_some() && $value.unwrap().is_empty() {
319 Ok(($name, OptionValue::List(Vec::new())))
320 } else if let Some(text) = $value {
321 let sep: &str = $sep.as_deref().unwrap_or(",");
322 let list: Vec<String> = text
323 .split(sep)
324 .map(|item: &str| item.to_string())
325 .collect();
326 Ok(($name, OptionValue::List(list)))
327 } else {
328 Err("null arg".into())
329 }
330 }}
331 }
332
333 if let Some(entry) = self.configs.get(name) {
334 match entry {
335 ConfigEntry::Flag => flag_option!(name),
336 ConfigEntry::Text { default } => {
337 text_option!(name, value, default)
338 },
339 ConfigEntry::Int { default } => {
340 int_option!(name, value, default)
341 },
342 ConfigEntry::Count => count_option!(name, value),
343 ConfigEntry::List { sep } => {
344 list_option!(name, value, sep)
345 },
346 ConfigEntry::Alias { target } => {
347 if let Some(target_entry) = self.configs.get(target) {
348 match target_entry {
349 ConfigEntry::Flag => flag_option!(target),
350 ConfigEntry::Text { default } => {
351 text_option!(target, value, default)
352 },
353 ConfigEntry::Int { default } => {
354 int_option!(target, value, default)
355 },
356 ConfigEntry::Count => count_option!(target, value),
357 ConfigEntry::List { sep } => {
358 list_option!(target, value, sep)
359 },
360 ConfigEntry::Alias { .. } => {
361 Err("alias to alias".into())
362 },
363 }
364 } else {
365 Err("target option not found".into())
366 }
367 },
368 }
369 } else {
370 Err("config option not found".into())
371 }
372 }
373
374 fn parse_short_option<S>(
375 &self,
376 arg: &str,
377 next_arg: Option<&S>,
378 ) -> Result<(bool, HashMap<String, OptionValue>), String>
379 where
380 S: AsRef<str>,
381 {
382 let n: usize = arg.len();
383 let mut pairs: HashMap<String, OptionValue> = HashMap::new();
384 let iter = arg.char_indices();
385
386 for (i, flag) in iter {
387 let name: String = String::from(flag);
388 let next_value: Option<&str> = next_arg.map(|v| v.as_ref());
389 let Some(entry) = self.configs.get(&name) else {
390 return Err(format!("option '{}' is not supported", name));
391 };
392
393 match entry {
394 ConfigEntry::Flag => {
395 pairs.insert(name, OptionValue::Flag);
396 },
397 ConfigEntry::Text { default } => {
398 if i < n - flag.len_utf8() {
399 let value = arg[i + flag.len_utf8()..n].to_string();
400 pairs.insert(name, OptionValue::Text(value));
401 return Ok((false, pairs));
402 } else if let Some(value) = default {
403 pairs.insert(name, OptionValue::Text(value.into()));
404 return Ok((false, pairs));
405 } else if let Some(value) = next_value {
406 pairs.insert(name, OptionValue::Text(value.into()));
407 return Ok((true, pairs));
408 }
409 return Err("null arg".into());
410 },
411 ConfigEntry::Int { default } => {
412 if i < n - flag.len_utf8() {
413 let value = arg[i + flag.len_utf8()..n].to_string();
414 if let Ok(num) = value.parse() {
415 pairs.insert(name, OptionValue::Int(num));
416 return Ok((false, pairs));
417 } else {
418 return Err("invalid int".into());
419 }
420 } else if let Some(num) = default {
421 pairs.insert(name, OptionValue::Int(*num));
422 return Ok((false, pairs));
423 } else if let Some(value) = next_value {
424 if let Ok(num) = value.parse() {
425 pairs.insert(name, OptionValue::Int(num));
426 return Ok((true, pairs));
427 } else {
428 return Err("invalid int".into());
429 }
430 }
431 return Err("null int".into());
432 },
433 ConfigEntry::Count => {
434 let default = OptionValue::Int(0);
435 let old_value: &OptionValue = pairs.get(&name)
436 .unwrap_or(&default);
437 pairs.insert(name, old_value.clone() + 1);
438 },
439 ConfigEntry::List { sep } => {
440 let sep: &str = sep.as_deref().unwrap_or(",");
441 if i < n - flag.len_utf8() {
442 let value = arg[i + flag.len_utf8()..n].to_string();
443 let parsed_value: Vec<String> = value
444 .split(sep)
445 .map(|item: &str| item.to_string())
446 .collect();
447 pairs.insert(name, OptionValue::List(parsed_value));
448 return Ok((false, pairs));
449 } else if let Some(value) = next_value {
450 let parsed_value = if value.is_empty() {
451 Vec::new()
452 } else {
453 value
454 .split(sep)
455 .map(|item: &str| item.to_string())
456 .collect()
457 };
458 pairs.insert(name, OptionValue::List(parsed_value));
459 return Ok((true, pairs));
460 }
461 return Err("null arg".into());
462 },
463 ConfigEntry::Alias { target } => {
464 if let Some(target_entry) = self.configs.get(target) {
465 let target: String = target.clone();
466 match target_entry {
467 ConfigEntry::Flag => {
468 pairs.insert(target, OptionValue::Flag);
469 },
470 ConfigEntry::Text { default } => {
471 if i < n - flag.len_utf8() {
472 let value = arg[i + flag.len_utf8()..n]
473 .to_string();
474 pairs.insert(
475 target,
476 OptionValue::Text(value)
477 );
478 return Ok((false, pairs));
479 } else if let Some(value) = default {
480 pairs.insert(
481 target,
482 OptionValue::Text(value.into())
483 );
484 return Ok((false, pairs));
485 } else if let Some(value) = next_value {
486 pairs.insert(
487 target,
488 OptionValue::Text(value.into())
489 );
490 return Ok((true, pairs));
491 }
492 return Err("null arg".into());
493 },
494 ConfigEntry::Int { default } => {
495 if i < n - flag.len_utf8() {
496 let value = arg[i + flag.len_utf8()..n]
497 .to_string();
498 if let Ok(num) = value.parse() {
499 pairs.insert(
500 target,
501 OptionValue::Int(num),
502 );
503 return Ok((false, pairs));
504 } else {
505 return Err("invalid int".into());
506 }
507 } else if let Some(num) = default {
508 pairs.insert(
509 target,
510 OptionValue::Int(*num),
511 );
512 return Ok((false, pairs));
513 } else if let Some(value) = next_value {
514 if let Ok(num) = value.parse() {
515 pairs.insert(
516 target,
517 OptionValue::Int(num),
518 );
519 return Ok((true, pairs));
520 } else {
521 return Err("invalid int".into());
522 }
523 }
524 return Err("null int".into());
525 },
526 ConfigEntry::Count => {
527 let default = OptionValue::Int(0);
528 let old_value = pairs.get(&target)
529 .unwrap_or(&default);
530 pairs.insert(target, old_value.clone() + 1);
531 },
532 ConfigEntry::List { sep } => {
533 let sep: &str = sep.as_deref().unwrap_or(",");
534 if i < n - flag.len_utf8() {
535 let value = arg[i + flag.len_utf8()..n]
536 .to_string();
537 let parsed_value: Vec<String> = value
538 .split(sep)
539 .map(|item: &str| item.to_string())
540 .collect();
541 pairs.insert(
542 target,
543 OptionValue::List(parsed_value),
544 );
545 return Ok((false, pairs));
546 } else if let Some(value) = next_value {
547 let parsed_value = if value.is_empty() {
548 Vec::new()
549 } else {
550 value
551 .split(sep)
552 .map(|item: &str| item.to_string())
553 .collect()
554 };
555 pairs.insert(
556 target,
557 OptionValue::List(parsed_value),
558 );
559 return Ok((true, pairs));
560 }
561 return Err("null arg".into());
562 },
563 ConfigEntry::Alias { .. } => {
564 return Err("alias to alias".into());
565 },
566 }
567 } else {
568 return Err("target option not found".into());
569 }
570 },
571 }
572 }
573
574 Ok((false, pairs))
575 }
576}
577
578#[cfg(test)]
579mod test_parse {
580 use super::*;
581 use crate::entries;
582
583 #[test]
584 fn parse_input() {
585 let configs = entries! {
586 "quiet" => Flag,
587 "color" => Text,
588 };
589 let parser = ArgParser::new(configs.unwrap());
590 let input = ["--quiet", "build", "CC=clang"];
591 let result = parser.parse(input);
592
593 let operands = ["build"];
594 let options: HashMap<String, OptionValue> = HashMap::from([
595 ("quiet".to_string(), OptionValue::Flag),
596 ]);
597 let parameters: HashMap<String, String> = HashMap::from([
598 ("CC".to_string(), "clang".to_string()),
599 ]);
600
601 assert_eq!(result.clone().unwrap().options, options);
602 assert_eq!(result.clone().unwrap().parameters, parameters);
603 assert_eq!(result.clone().unwrap().operands, operands);
604 }
605
606 #[test]
607 fn parse_short_options() {
608 let configs = entries! {
609 "c" => Text,
610 "s" => Text,
611 "j" => Int { default: 0 },
612 };
613 let parser = ArgParser::new(configs.unwrap());
614 let input = ["-c./some_file.txt", "some arg"];
615 let result = parser.parse(input);
616
617 let operands = ["some arg"];
618 let options: HashMap<String, OptionValue> = HashMap::from([
619 ("c".to_string(), OptionValue::Text("./some_file.txt".to_string())),
620 ]);
621
622 assert_eq!(result.clone().unwrap().options, options);
623 assert_eq!(result.clone().unwrap().operands, operands);
624 }
625}