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}