clargs/parsing_config.rs
1use std::collections::HashMap;
2use std::collections::HashSet;
3
4
5#[derive(PartialEq, Eq, PartialOrd, Ord)]
6pub(crate) enum ArgDesc {
7 Flag,
8 Param(bool),
9 Alias(String),
10}
11
12
13/// Controls how the argument list is interpreted.
14///
15/// A `ParsingConfig` struct must be configured before parsing an argument list.
16/// It controls which syntax is allowed, how certain edge cases should be handled and the desired flags, parameters and subcommands.
17///
18/// # Syntax support
19///
20/// Clargs supports a number of common command-line argument syntaxes and options to change how certain edge cases are handled.
21/// To enable or disable syntaxes or features a `ParsingConfig` object must be constructed and modified accordingly.
22///
23/// A list of all supported syntaxes and features:
24/// - parameters, flags and unnamed parameters
25/// - double hyphen assignment syntax
26/// - double hyphen syntax
27/// - single hyphen syntax
28/// - assignment syntax
29/// - subcommands
30/// - option completion
31/// - subcommand completion
32///
33/// ### Parameters, flags and unnamed parameters
34///
35/// There are three types of options: parameters, flags and unnamed parameters.
36/// Parameters are optionally required options with a name which take a value.
37/// Flags are options with a name which do not take a value.
38/// Unnamed parameters are options without a name, they are the arguments that are not interpreted by any of the syntaxes or features.
39///
40/// ### Double hyphen assignment syntax
41///
42/// Double hyphen assignment syntax can only specify parameter options.
43///
44/// It is used as follows:
45///
46/// > --NAME=VALUE
47///
48/// Where "NAME" and "VALUE" can both be replaced by any appropriate value.
49///
50/// If option completion is enabled, "NAME" can also be any string that is a prefix of the intended option's name.
51/// Note that in that case, it is an error if "NAME" matches prefixes of multiple names.
52///
53/// ### Double hyphen syntax
54///
55/// Double hyphen syntax can specify both flags and parameters.
56///
57/// It is used as follows for flags:
58///
59/// > --NAME
60///
61/// It is used as follows for parameters:
62///
63/// > --NAME VALUE
64///
65/// Where "NAME" and "VALUE" can both be replaced by any appropriate value.
66/// In the latter case, the next argument ("VALUE") will always be used to set the parameter's value, regardless of what it might be.
67///
68/// If option completion is enabled, "NAME" can also be any string that is a prefix of the intended option's name.
69/// Note that in that case, it is an error if "NAME" matches prefixes of multiple names.
70///
71/// ### Single hyphen syntax
72///
73/// Single hyphen syntax can specify both flags and parameters.
74/// But only those that have a name of one character.
75///
76/// It is used as follows:
77///
78/// > -FLAGSP VALUE
79///
80/// Or as follows:
81///
82/// > -FLAGSP495
83///
84/// Where each character in the string "FLAGS" must be the name of a flag option.
85/// And where "P" is the name of a parameter option and "VALUE" is its value.
86/// In the first case, the next argument ("VALUE") will always be used to set the parameter's value, regardless of what it might be.
87/// In the latter case, the parameter's value will be set to "495".
88/// Once a non-alphabetic character is found in the argument, that character and all following characters are interpreted as the value to the last option that was specified.
89///
90/// Note that as much flags as desired can be specified in one argument, but at most one parameter.
91/// And if there is one, the parameter must always be the last option to be specified.
92///
93/// ##### Naked hyphen
94///
95/// The naked hyphen is a special case in the single hyphen syntax.
96/// By default it is interpreted as an unnamed parameter.
97/// If however, an empty string is added as a flag or parameter to the `ParsingConfig` object, the resulting option can be specified through use of the naked hyphen.
98///
99/// If it is a flag it can be specified as follows:
100///
101/// > \-
102///
103/// If it is a parameter it can be given a value as follows:
104///
105/// > -495
106///
107/// Where "495" can be any string not starting with an alphabetic character.
108///
109/// ##### Parameter stacking
110///
111/// If enabled, parameter stacking allows for the specification of multiple parameters in a single argument.
112///
113/// It is used as follows:
114///
115/// > -FPQLARG VALUE_P VALUE_Q VALUE_R
116///
117/// Or as follows:
118///
119/// > -FPQLAR495 VALUE_P VALUE_Q
120///
121/// Where 'F', 'L', 'A' and 'G' represent flag options and 'P', 'Q' and 'R' parameter options.
122/// In the first case, parameter 'P' will get value "VALUE_P", parameter 'Q' will get value "VALUE_Q" and parameter 'R' will get value "VALUE_R".
123/// In the latter case, parameter 'P' will get value "VALUE_P", parameter 'Q' will get value "VALUE_Q" and parameter 'R' will get value "495".
124/// Once a non-alphabetic character is found in the argument, that character and all following characters are interpreted as the value to the last option that was specified.
125/// The next arguments ("VALUE_*") will always be used to set the parameters's values, regardless of what they might be.
126///
127/// ### Assignment syntax
128///
129/// Assignment syntax can only specify parameter options.
130///
131/// It is used as follows:
132///
133/// > NAME=VALUE
134///
135/// Where "NAME" and "VALUE" can both be replaced by any appropriate value.
136///
137/// If option completion is enabled, "NAME" can also be any string that is a prefix of the intended option's name.
138/// Note that in that case, it is an error if "NAME" matches prefixes of multiple names.
139///
140/// ### Subcommands
141///
142/// Subcommands can be enabled by adding them to the `ParsingConfig` object.
143/// If enabled, any unnamed parameters that match the name of a subcommand will be interpreted as such.
144/// Once a subcommand is found, all remaining arguments are passed untouched to the subcommand.
145///
146/// If the subcommand index feature is enabled, the argument parser will look only at the specified index in the unnamed parameters list for a subcommand.
147/// The index the argument parser will use can be specified in the `ParsingConfig` object.
148/// If the subcommand completion feature is enabled as well, any string that is a prefix of a subcommand will work as a subcommand.
149/// Note that in that case, it is an error if the argument matches prefixes of multiple subcommands.
150///
151/// It is also possible to set whether a subcommand is required or not.
152/// If it is required, and the subcommand index feature is enabled as well, it is an error if no subcommand is found at the specified index in the unnamed parameter list.
153pub struct ParsingConfig {
154 pub(crate) dh_marker: bool,
155 pub(crate) store_dh_marker: bool,
156 pub(crate) sh_syntax: bool,
157 pub(crate) dh_syntax: bool,
158 pub(crate) dha_syntax: bool,
159 pub(crate) a_syntax: bool,
160
161 pub(crate) param_stacking: bool,
162 pub(crate) param_duplication: bool,
163
164 pub(crate) option_completion: bool,
165 pub(crate) subcmd_completion: bool,
166
167 pub(crate) subcmd_index: bool,
168 pub(crate) subcmd_required: bool,
169 pub(crate) subcmd_index_value: usize,
170
171 pub(crate) options: HashMap<String, ArgDesc>,
172 pub(crate) subcmds: HashSet<String>,
173}
174
175impl ParsingConfig {
176 /// Constructs and returns a `ParsingConfig` object.
177 pub fn new() -> Self {
178 Self {
179 dh_marker: true,
180 store_dh_marker: false,
181 sh_syntax: true,
182 dh_syntax: true,
183 dha_syntax: true,
184 a_syntax: false,
185
186 param_stacking: true,
187 param_duplication: false,
188
189 option_completion: true,
190 subcmd_completion: false,
191
192 subcmd_index: false,
193 subcmd_required: false,
194 subcmd_index_value: 0,
195
196 options: HashMap::new(),
197 subcmds: HashSet::new(),
198 }
199 }
200
201 /// Constructs and returns a `ParsingConfig` object with all features disabled.
202 pub fn new_all_disabled() -> Self {
203 Self {
204 dh_marker: false,
205 store_dh_marker: false,
206 sh_syntax: false,
207 dh_syntax: false,
208 dha_syntax: false,
209 a_syntax: false,
210
211 param_stacking: false,
212 param_duplication: false,
213
214 option_completion: false,
215 subcmd_completion: false,
216
217 subcmd_index: false,
218 subcmd_required: false,
219 subcmd_index_value: 0,
220
221 options: HashMap::new(),
222 subcmds: HashSet::new(),
223 }
224 }
225
226 /// Returns `true` if the `name` is a valid name for a flag, parameter, alias or subcommand.
227 ///
228 /// A name is valid if it doesn't contain whitespace characters, an equals sign, an apostrophe or a quotation mark and if it starts with an alphabetic character or if it is an empty string.
229 pub fn is_valid_name(name: &str) -> bool {
230 !name.chars().any(|x| x.is_whitespace() || x == '=' || x == '\'' || x == '"') && name.chars().next().unwrap_or('a').is_alphabetic()
231 }
232
233
234 /// Enables or disables the double hyphen marker.
235 ///
236 /// Enabled by default.
237 pub fn set_double_hyphen_marker(&mut self, value: bool) {
238 self.dh_marker = value;
239 }
240
241 /// Enables or disables the storing of the double hyphen marker.
242 ///
243 /// Disabled by default.
244 pub fn set_store_double_hyphen_marker(&mut self, value: bool) {
245 self.store_dh_marker = value;
246 }
247
248 /// Enables or disables single hyphen syntax.
249 ///
250 /// Enabled by default.
251 pub fn set_single_hyphen_syntax(&mut self, value: bool) {
252 self.sh_syntax = value;
253 }
254
255 /// Enables or disables double hyphen syntax.
256 ///
257 /// Enabled by default.
258 pub fn set_double_hyphen_syntax(&mut self, value: bool) {
259 self.dh_syntax = value;
260 }
261
262 /// Enables or disables double hyphen assignment syntax.
263 ///
264 /// Enabled by default.
265 pub fn set_double_hyphen_assignment_syntax(&mut self, value: bool) {
266 self.dha_syntax = value;
267 }
268
269 /// Enables or disables assignment syntax.
270 ///
271 /// Disabled by default.
272 pub fn set_assignment_syntax(&mut self, value: bool) {
273 self.a_syntax = value;
274 }
275
276
277 /// Enables or disables single hyphen parameter stacking.
278 ///
279 /// Enabled by default.
280 pub fn set_parameter_stacking(&mut self, value: bool) {
281 self.param_stacking = value;
282 }
283
284 /// Enables or disables parameter duplication.
285 /// Setting the value of a parameter more than once will not be seen as an error if enabled.
286 ///
287 /// Enabled by default.
288 pub fn set_parameter_duplication(&mut self, value: bool) {
289 self.param_duplication = value;
290 }
291
292
293 /// Enables or disables argument completion.
294 ///
295 /// Enabled by default.
296 pub fn set_option_completion(&mut self, value: bool) {
297 self.option_completion = value;
298 }
299
300 /// Enables or disables subcommand completion.
301 /// Subcommand completion can occur only if the subcommand index feature is enabled.
302 ///
303 /// Disabled by default.
304 pub fn set_subcommand_completion(&mut self, value: bool) {
305 self.subcmd_completion = value;
306 }
307
308
309 /// Enables or disables the index at which the subcommand is expected to be found in the unnamed parameters list.
310 /// It is a parsing error if this is enabled, a subcommand is required and there is no subcommand found at the specified position.
311 ///
312 /// Disabled by default.
313 pub fn set_subcommand_index(&mut self, value: bool) {
314 self.subcmd_index = value;
315 }
316
317 /// Enables or disables the requirement of a subcommand.
318 ///
319 /// Disabled by default.
320 pub fn set_subcommand_required(&mut self, value: bool) {
321 self.subcmd_required = value;
322 }
323
324 /// Sets the index at which the subcommand is expected to be found in the unnamed parameters list.
325 ///
326 /// The default value is zero.
327 pub fn set_subcommand_index_value(&mut self, value: usize) {
328 self.subcmd_index_value = value;
329 }
330
331
332 /// Adds a flag to the configuration.
333 ///
334 /// # Panics
335 ///
336 /// Panics if the `name` is already taken or if it is an invalid name.
337 pub fn add_flag(&mut self, name: String) {
338 assert!(Self::is_valid_name(&name), "clargs: invalid flag name");
339 assert!(!self.options.contains_key(&name), "clargs: flag name is already taken");
340 assert!(!self.subcmds.contains(&name), "clargs: flag name is already taken");
341 self.options.insert(name, ArgDesc::Flag);
342 }
343
344 /// Adds a parameter to the configuration.
345 ///
346 /// # Panics
347 ///
348 /// Panics if the `name` is already taken or if it is an invalid name.
349 pub fn add_param(&mut self, name: String, required: bool) {
350 assert!(Self::is_valid_name(&name), "clargs: invalid parameter name");
351 assert!(!self.options.contains_key(&name), "clargs: parameter name is already taken");
352 assert!(!self.subcmds.contains(&name), "clargs: parameter name is already taken");
353 self.options.insert(name, ArgDesc::Param(required));
354 }
355
356 /// Adds an alias to a flag or parameter to the configuration.
357 /// If the `target` is an alias as well, the new alias will point to that alias's `target`.
358 ///
359 /// # Panics
360 ///
361 /// Panics if the `name` is equal to the `target` or if the `target` does not point to a flag, parameter or other alias.
362 /// Or if the `name` is already taken or if it is an invalid name.
363 pub fn add_alias(&mut self, name: String, target: String) {
364 assert!(Self::is_valid_name(&name), "clargs: invalid alias name");
365 assert!(!self.options.contains_key(&name), "clargs: alias name is already taken");
366 assert!(!self.subcmds.contains(&name), "clargs: alias name is already taken");
367 assert!(name != target, "clargs: alias target cannot be the same as its name");
368 assert!(self.options.contains_key(&target), "clargs: alias must point to a valid target");
369 let target = match self.options.get(&target).unwrap() {
370 ArgDesc::Alias(target) => target.to_string(),
371 _ => target,
372 };
373 self.options.insert(name, ArgDesc::Alias(target));
374 }
375
376 /// Adds a subcommand to the configuration.
377 ///
378 /// # Panics
379 ///
380 /// Panics if the `name` is already taken, if it is an invalid name or if it is an empty string.
381 pub fn add_subcommand(&mut self, name: String) {
382 assert!(!name.is_empty(), "clargs: subcommand name cannot be an empty string");
383 assert!(Self::is_valid_name(&name), "clargs: invalid subcommand name");
384 assert!(!self.options.contains_key(&name), "clargs: subcommand name is already taken");
385 assert!(!self.subcmds.contains(&name), "clargs: subcommand name is already taken");
386 self.subcmds.insert(name);
387 }
388}