cliparser/
lib.rs

1use std::collections::{HashSet, HashMap};
2
3#[derive(Clone, Debug, Eq, PartialEq)]
4pub struct CLIParser {
5
6	/// **Positional arguments**.
7	/// 
8	/// These are the standard arguments without any special syntax. 
9	/// 
10	/// Example:
11	/// ```bash
12	/// ./my_program posit_argument_1 posit_argument_2
13	/// ```
14	pub posits: Vec<String>,
15
16	/// **Flags**.
17	/// 
18	/// These arguments are prefixed with a singular dash line. They are unique, unordered and don't take any values.
19	/// 
20	/// Example:
21	/// ```bash
22	/// ./my_program -test_mode -verbose
23	/// ```
24	pub flags: HashSet<String>,
25
26	/// **Key - value pairs**.
27	/// 
28	/// These arguments are prefixed with a double dash line. They need to be connected to their value with an equal sign.
29	/// 
30	/// Example
31	/// ```bash
32	/// ./my_program --debug_level=2 --id=5 --name="John Smith"
33	/// ```
34	pub pairs: HashMap<String, String>,
35}
36
37
38#[derive(Clone, Debug, Eq, PartialEq)]
39pub enum CLIError {
40	FlagWithSign(String),
41	FlagMalformed(String),
42	PairMissingSign(String),
43	PairBadSign(String),
44	PairMalformed(String),
45	DashesMalformed(String)
46}
47
48
49// All of these are baseline error with no underlying cause. Simply bad CLI arguments.
50impl std::error::Error for CLIError {
51    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
52        match *self {
53            CLIError::FlagWithSign(_) => None,
54            CLIError::FlagMalformed(_) => None,
55            CLIError::PairMissingSign(_) => None,
56            CLIError::PairBadSign(_) => None,
57            CLIError::PairMalformed(_) => None,
58            CLIError::DashesMalformed(_) => None,
59        }
60    }
61}
62
63
64impl std::fmt::Display for CLIError {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        match *self {
67            CLIError::FlagWithSign(ref arg) => write!(f, "Equal signs not allowed in flags: `{0}`\nProper syntax: `./my_program -flag`", arg),
68            CLIError::FlagMalformed(ref arg) => write!(f, "Malformed flag: `{0}`\nProper syntax: `./my_program -flag`", arg),
69            CLIError::PairMissingSign(ref arg) => write!(f, "Key-value pair arguments need an equal sign: `{0}`\nProper syntax: `./my_program --key=value`", arg),
70            CLIError::PairBadSign(ref arg) => write!(f, "Improper use of equal sign in key-value pair: `{0}`\nProper syntax: `./my_program --key=value`", arg),
71            CLIError::PairMalformed(ref arg) => write!(f, "Malformed key-value pair: `{0}`\nProper syntax: `./my_program --key=value`", arg),
72            CLIError::DashesMalformed(ref arg) => write!(f, "Arguments cannot start with 3 or more dash lines: `{0}`", arg),
73        }
74    }
75}
76
77impl Default for CLIParser {
78	fn default() -> Self {
79		Self {
80			posits: Vec::new(),
81			flags: HashSet::new(),
82			pairs: HashMap::new(),
83		}
84	}
85}
86
87impl CLIParser {
88	
89	/// Creates a new cli-parser object, with empty data structures. 
90	pub fn new() -> Self {
91		Self::default()
92	}
93
94	/// Parses the `std::env::args()` and collects them into data structures.
95	/// 
96	/// Will throw error if CLI arguments are considered malformed by this crate.
97	/// 
98	/// ```
99	/// // Initialize parser
100	/// let parser = cliparser::CLIParser::new().init().unwrap();
101	/// 
102	/// // Extract parsed data structures
103	/// let posit_arguments = parser.posits.clone(); // Vector
104	/// let flags = parser.flags.clone(); // HashSet
105	/// let pairs = parser.pairs.clone(); // HashMap
106	/// ```
107	pub fn init(mut self) -> Result<Self, CLIError> {
108		
109		for argument in std::env::args() {
110
111			// Positional
112			if !argument.starts_with("-") {
113				self.posits.push(argument);
114				continue;
115			}
116
117			else if !argument.starts_with("--") {
118				
119				if argument.contains("=") {
120					return Err(CLIError::FlagWithSign(argument));
121				}
122
123				if argument.len() < 2 {
124					return Err(CLIError::FlagWithSign(argument));
125				}
126
127				self.flags.insert(argument[1..].to_string());
128				continue;
129			}
130			
131			else if !argument.starts_with("---") {
132				if !argument.contains("=") {
133					return Err(CLIError::PairMissingSign(argument));
134				}
135
136				if argument.len() < 5 {
137					return Err(CLIError::PairMalformed(argument));
138				}
139
140				let equal_sign_pos: usize = argument.find('=').unwrap();
141				if equal_sign_pos == 2 || equal_sign_pos == argument.len() - 1 {
142					return Err(CLIError::PairBadSign(argument));
143				}
144
145				let kwarg: (&str, &str) = argument.split_once("=").unwrap();
146				self.pairs.insert(kwarg.0[2..].to_string(), kwarg.1.to_string());
147			}
148
149			else {
150				return Err(CLIError::DashesMalformed(argument));
151			}
152		}
153
154		Ok(self)
155	}
156
157}
158