1use std::convert::AsRef;
14
15#[deprecated(since = "1.2.0", note = "Use either `OptionSet` or `OptionSetEx` now, as applicable")]
16pub type Options<'a> = OptionSetEx<'a>;
17
18pub(crate) const ABBR_SUP_DEFAULT: bool = true;
20pub(crate) const MODE_DEFAULT: OptionsMode = OptionsMode::Standard;
22
23#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct OptionSetEx<'a> {
31 pub long: Vec<LongOption<'a>>,
33 pub short: Vec<ShortOption>,
34 pub mode: OptionsMode,
35 pub allow_abbreviations: bool,
36}
37
38impl<'a> Default for OptionSetEx<'a> {
39 fn default() -> Self {
40 OptionSetEx::new(0, 0)
41 }
42}
43
44#[derive(Default, Debug, Clone, PartialEq, Eq)]
52pub struct OptionSet<'r, 'a: 'r> {
53 pub long: &'r [LongOption<'a>],
55 pub short: &'r [ShortOption],
56 pub mode: OptionsMode,
57 pub allow_abbreviations: bool,
58}
59
60impl<'r, 'a: 'r> PartialEq<OptionSet<'r, 'a>> for OptionSetEx<'a> {
61 fn eq(&self, rhs: &OptionSet<'r, 'a>) -> bool {
62 rhs.eq(&self.as_fixed())
63 }
64}
65
66impl<'r, 'a: 'r> PartialEq<OptionSetEx<'a>> for OptionSet<'r, 'a> {
67 fn eq(&self, rhs: &OptionSetEx<'a>) -> bool {
68 self.eq(&rhs.as_fixed())
69 }
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum OptionsMode {
75 Standard,
78 Alternate,
80}
81
82impl Default for OptionsMode {
83 fn default() -> Self {
84 MODE_DEFAULT
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct LongOption<'a> {
91 pub name: &'a str,
94 pub expects_data: bool,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct ShortOption {
101 pub ch: char,
104 pub expects_data: bool,
106}
107
108#[derive(Debug, PartialEq, Eq)]
111pub enum OptionFlaw<'a> {
112 LongEmpty,
114 LongIncludesEquals(&'a str),
116 ShortDash,
118 ShortDup(char),
120 LongDup(&'a str),
122}
123
124impl<'a> OptionSetEx<'a> {
125 pub fn new(count_long: usize, count_short: usize) -> Self {
128 Self {
129 long: Vec::with_capacity(count_long),
130 short: Vec::with_capacity(count_short),
131 mode: MODE_DEFAULT,
132 allow_abbreviations: ABBR_SUP_DEFAULT,
133 }
134 }
135
136 pub fn as_fixed(&self) -> OptionSet<'_, 'a> {
138 OptionSet {
139 long: &self.long[..],
140 short: &self.short[..],
141 mode: self.mode,
142 allow_abbreviations: self.allow_abbreviations,
143 }
144 }
145
146 pub fn set_mode(&mut self, mode: OptionsMode) -> &mut Self {
148 self.mode = mode;
149 self
150 }
151
152 pub fn set_allow_abbreviations(&mut self, allow: bool) -> &mut Self {
154 self.allow_abbreviations = allow;
155 self
156 }
157
158 pub fn is_empty(&self) -> bool {
160 self.long.is_empty() && self.short.is_empty()
161 }
162
163 pub fn add_long(&mut self, name: &'a str) -> &mut Self {
167 self.long.push(LongOption::new(name, false));
168 self
169 }
170
171 pub fn add_short(&mut self, ch: char) -> &mut Self {
175 self.short.push(ShortOption::new(ch, false));
176 self
177 }
178
179 pub fn add_long_data(&mut self, name: &'a str) -> &mut Self {
183 self.long.push(LongOption::new(name, true));
184 self
185 }
186
187 pub fn add_short_data(&mut self, ch: char) -> &mut Self {
191 self.short.push(ShortOption::new(ch, true));
192 self
193 }
194
195 pub fn add_existing_long(&mut self, long: LongOption<'a>) -> &mut Self {
197 self.long.push(long);
198 self
199 }
200
201 pub fn add_existing_short(&mut self, short: ShortOption) -> &mut Self {
203 self.short.push(short);
204 self
205 }
206
207 #[inline(always)]
213 pub fn is_valid(&self) -> bool {
214 validation::validate_set(&self.as_fixed(), false).is_ok()
215 }
216
217 #[inline(always)]
219 pub fn validate(&self) -> Result<(), Vec<OptionFlaw<'a>>> {
220 validation::validate_set(&self.as_fixed(), true)
221 }
222
223 pub fn process<T>(&self, args: &'a [T]) -> super::analysis::Analysis<'a>
231 where T: AsRef<str>
232 {
233 super::engine::process(args, &self.as_fixed())
234 }
235}
236
237impl<'r, 'a: 'r> OptionSet<'r, 'a> {
238 pub fn to_extendible(&self) -> OptionSetEx<'a> {
242 OptionSetEx {
243 long: self.long.iter().cloned().collect(),
244 short: self.short.iter().cloned().collect(),
245 mode: self.mode,
246 allow_abbreviations: self.allow_abbreviations,
247 }
248 }
249
250 pub fn set_mode(&mut self, mode: OptionsMode) -> &mut Self {
252 self.mode = mode;
253 self
254 }
255
256 pub fn set_allow_abbreviations(&mut self, allow: bool) -> &mut Self {
258 self.allow_abbreviations = allow;
259 self
260 }
261
262 pub fn is_empty(&self) -> bool {
264 self.long.is_empty() && self.short.is_empty()
265 }
266
267 #[inline(always)]
273 pub fn is_valid(&self) -> bool {
274 validation::validate_set(self, false).is_ok()
275 }
276
277 #[inline(always)]
279 pub fn validate(&'r self) -> Result<(), Vec<OptionFlaw<'a>>> {
280 validation::validate_set(self, true)
281 }
282
283 pub fn process<T>(&self, args: &'a [T]) -> super::analysis::Analysis<'a>
291 where T: AsRef<str>
292 {
293 super::engine::process(args, self)
294 }
295}
296
297impl<'a> LongOption<'a> {
298 fn new(name: &'a str, expects_data: bool) -> Self {
302 debug_assert!(!name.is_empty(), "Long option name cannot be an empty string!");
303 debug_assert!(!name.contains('='), "Long option name cannot contain ‘=’!");
304 Self { name, expects_data, }
305 }
306}
307
308impl ShortOption {
309 fn new(ch: char, expects_data: bool) -> Self {
313 debug_assert_ne!('-', ch, "Dash (‘-’) is not a valid short option!");
314 Self { ch, expects_data, }
315 }
316}
317
318mod validation {
320 use super::{OptionSet, OptionFlaw};
321
322 pub fn validate_set<'r, 'a: 'r>(set: &OptionSet<'r, 'a>, detail: bool
327 ) -> Result<(), Vec<OptionFlaw<'a>>>
328 {
329 let mut flaws = Vec::new();
330
331 for candidate in set.long {
332 if candidate.name.is_empty() {
333 match detail {
334 true => { flaws.push(OptionFlaw::LongEmpty); },
335 false => { return Err(flaws); },
336 }
337 }
338 else if candidate.name.contains('=') {
339 match detail {
340 true => { flaws.push(OptionFlaw::LongIncludesEquals(candidate.name)); },
341 false => { return Err(flaws); },
342 }
343 }
344 }
345
346 for candidate in set.short {
347 if candidate.ch == '-' {
348 match detail {
349 true => { flaws.push(OptionFlaw::ShortDash); },
350 false => { return Err(flaws); },
351 }
352 }
353 }
354
355 let mut dupes: bool = false;
356 find_duplicates_short(set, &mut flaws, detail, &mut dupes);
357 if !detail && dupes {
358 return Err(flaws);
359 }
360 find_duplicates_long(set, &mut flaws, detail, &mut dupes);
361 if !detail && dupes {
362 return Err(flaws);
363 }
364
365 match flaws.is_empty() {
366 true => Ok(()),
367 false => Err(flaws),
368 }
369 }
370
371 fn find_duplicates_short<'r, 'a: 'r>(set: &OptionSet<'r, 'a>, flaws: &mut Vec<OptionFlaw<'a>>,
372 detail: bool, found: &mut bool)
373 {
374 let opts = set.short;
375 let mut checked: Vec<char> = Vec::with_capacity(opts.len());
376
377 let mut duplicates = Vec::new();
378 for short in opts {
379 let ch = short.ch;
380 if !duplicates.contains(&OptionFlaw::ShortDup(ch)) {
381 match checked.contains(&ch) {
382 true => {
383 match detail {
384 true => { duplicates.push(OptionFlaw::ShortDup(ch)); },
385 false => { *found = true; return; },
386 }
387 },
388 false => { checked.push(ch); },
389 }
390 }
391 }
392 if !duplicates.is_empty() {
393 flaws.append(&mut duplicates);
394 }
395 }
396
397 fn find_duplicates_long<'r, 'a: 'r>(set: &OptionSet<'r, 'a>, flaws: &mut Vec<OptionFlaw<'a>>,
398 detail: bool, found: &mut bool)
399 {
400 let opts = set.long;
401 let mut checked: Vec<&'a str> = Vec::with_capacity(opts.len());
402
403 let mut duplicates = Vec::new();
404 for long in opts {
405 let name = long.name.clone();
406 if !duplicates.contains(&OptionFlaw::LongDup(name)) {
407 match checked.contains(&name) {
408 true => {
409 match detail {
410 true => { duplicates.push(OptionFlaw::LongDup(name)); },
411 false => { *found = true; return; },
412 }
413 },
414 false => { checked.push(name); },
415 }
416 }
417 }
418 if !duplicates.is_empty() {
419 flaws.append(&mut duplicates);
420 }
421 }
422}
423
424#[cfg(test)]
425mod tests {
426 use super::*;
427
428 #[test]
434 #[cfg_attr(debug_assertions, should_panic)]
435 fn create_short_dash() {
436 let _opt = ShortOption::new('-', false); }
438
439 #[test]
441 #[cfg_attr(debug_assertions, should_panic)]
442 fn create_long_no_name() {
443 let _opt = LongOption::new("", false); }
445
446 #[test]
452 #[cfg_attr(debug_assertions, should_panic)]
453 fn create_long_with_equals() {
454 let _opt = LongOption::new("a=b", false); }
456}