aopt_core/opt/
index.rs

1use std::fmt::Display;
2use std::ops::Range;
3use std::ops::RangeBounds;
4use std::ops::RangeFrom;
5use std::ops::RangeFull;
6use std::ops::RangeInclusive;
7use std::ops::RangeTo;
8use std::ops::RangeToInclusive;
9
10use crate::err::Error;
11use crate::error;
12
13/// Index using for option match.
14///
15/// The index is the position of left arguments (non-option arguments, NOA) index.
16///
17/// # Example
18///
19/// ```txt
20/// foo.exe -a=value -b value pos1 --aopt=42 pos2 --bopt value pos3
21///             |     |   |    |      |       |      |     |     |
22///             |     |   |    |      |       |      |     |     NOA @3 or @-1
23///             |     |   |    |      |       |      |     value of --bopt
24///             |     |   |    |      |       |      option --bopt
25///             |     |   |    |      |       NOA @2 or @-2
26///             |     |   |    |    option --aopt and its value 42
27///             |     |   |   NOA @1
28///             |     |   value of -b
29///             |    option -b
30///         option -a and its value
31/// ```
32///
33/// For option check, see [`SetChecker`](https://docs.rs/aopt/latest/aopt/set/trait.SetChecker.html) for more information.
34#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
35#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
36pub enum Index {
37    /// The forward index of NOA, fixed position.
38    ///
39    /// # Example
40    ///
41    /// For `["app", "--aopt", "--bopt=42", "pos1", "--copt", "pos2", "--dopt", "value", "pos3"]`:
42    ///
43    /// `@1` will matching `"pos1"`.
44    ///
45    /// `@2` will matching `"pos2"`.
46    ///
47    /// `@3` will matching `"pos3"`.
48    Forward(usize),
49
50    /// The backward index of NOA, floating position.
51    ///
52    /// # Example
53    ///
54    /// For `["app", "--aopt", "--bopt=42", "pos1", "--copt", "pos2", "--dopt", "value", "pos3"]`:
55    ///
56    /// `@-1` will matching `"pos2"`.
57    ///
58    /// `@-2` will matching `"pos1"`.
59    ///
60    /// `@-3` will matching `"app"`.
61    Backward(usize),
62
63    /// The include list of forward index of NOA, fixed position.
64    ///
65    /// # Example
66    ///
67    /// For `["app", "--aopt", "--bopt=42", "pos1", "--copt", "pos2", "--dopt", "value", "pos3"]`:
68    ///
69    /// `@[1,3]` will matching `"pos1"` or `"pos3"`.
70    ///
71    /// `@[1,2]` will matching `"pos1"` or `"pos2"`.
72    ///
73    /// `@[1,2,3]` will matching `"pos1"`, `"pos2"` or `"pos3"`.
74    List(Vec<usize>),
75
76    /// The exclude list of forward index of NOA, floating position.
77    ///
78    /// # Example
79    ///
80    /// For `["app", "--aopt", "--bopt=42", "pos1", "--copt", "pos2", "--dopt", "value", "pos3"]`:
81    ///
82    /// `@-[1,3]` will matching `"pos2"`.
83    ///
84    /// `@-[3]` will matching `"pos1"` or `"pos2"`.
85    ///
86    /// `@-[2]` will matching `"pos1"` or `"pos3"`.
87    Except(Vec<usize>),
88
89    /// The NOA which index inside in given position range with format `(m..n]`.
90    ///
91    /// If range have upper limit, the index is fixed position otherwise it is floating position.
92    ///
93    /// # Example
94    ///
95    /// For `["app", "--aopt", "--bopt=42", "pos1", "--copt", "pos2", "--dopt", "value", "pos3"]`:
96    ///
97    /// `@0..` will matching `"app"`, `"pos1"`, `"pos2"` or `"pos3"`.
98    ///
99    /// `@2..` will matching `"pos2"`, `"pos3"`.
100    ///
101    /// `@1..` will matching `"pos1"`, `"pos2"` or `"pos3"`.
102    ///
103    /// `@..4` will matching `"app"`, `"pos1"`, `"pos2"` or `"pos3"`.
104    ///
105    /// `@..2` will matching `"app"`, `"pos1"`.
106    ///
107    /// `@1..3` will matching `"pos1"`, `"pos2"`.
108    Range(usize, Option<usize>),
109
110    /// The anywhere position of NOA, floating position.
111    ///
112    /// # Example
113    ///
114    /// For `["app", "--aopt", "--bopt=42", "pos1", "--copt", "pos2", "--dopt", "value", "pos3"]`:
115    ///
116    /// `@*` will matching `"app"`, `"pos1"`, `"pos2"` or `"pos3"`.
117    AnyWhere,
118
119    #[default]
120    Null,
121}
122
123impl Index {
124    pub fn parse(dat: &str) -> Result<Self, Error> {
125        use neure::prelude::*;
126
127        let start = re::start();
128        let end = re::end();
129        let sign = "+".or("-").opt().map(|v| Ok(v != Some("-")));
130        let num = char::is_ascii_digit.repeat_one_more();
131        let num = num.map(map::from_str::<usize>());
132
133        let any_parser = "*".map(|_| Ok(Index::anywhere()));
134        let seq_parser = sign
135            .then(num.sep(",").quote("[", "]"))
136            .map(|(s, v)| Ok(if s { Index::list(v) } else { Index::except(v) }));
137        let range_parser = num
138            .opt()
139            .sep_once("..", num.opt())
140            .map(|(beg, end)| Ok(Index::range(beg, end)));
141        let pos_parser = sign.then(num).map(|(s, v)| {
142            Ok(if s {
143                Index::forward(v)
144            } else {
145                Index::backward(v)
146            })
147        });
148
149        let parser = start
150            .then(any_parser.or(seq_parser).or(range_parser).or(pos_parser))
151            ._1()
152            .then(end)
153            ._0();
154
155        CharsCtx::new(dat)
156            .ignore(char::is_ascii_whitespace.repeat_full())
157            .ctor(&parser)
158            .map_err(|_| Error::index_parse(dat, "failed parsing index"))
159    }
160
161    pub fn is_null(&self) -> bool {
162        matches!(self, Self::Null)
163    }
164
165    pub fn is_forward(&self) -> bool {
166        matches!(self, Self::Forward(_))
167    }
168
169    pub fn is_backward(&self) -> bool {
170        matches!(self, Self::Backward(_))
171    }
172
173    pub fn is_list(&self) -> bool {
174        matches!(self, Self::List(_))
175    }
176
177    pub fn is_except(&self) -> bool {
178        matches!(self, Self::Except(_))
179    }
180
181    pub fn is_range(&self) -> bool {
182        matches!(self, Self::Range(_, _))
183    }
184
185    pub fn is_anywhere(&self) -> bool {
186        matches!(self, Self::AnyWhere)
187    }
188
189    pub fn to_help(&self) -> String {
190        match self {
191            Index::Forward(offset) => {
192                format!("{}", offset)
193            }
194            Index::Backward(offset) => {
195                format!("-{}", offset)
196            }
197            Index::List(list) => {
198                format!(
199                    "[{}]",
200                    list.iter()
201                        .map(|v| v.to_string())
202                        .collect::<Vec<String>>()
203                        .join(", ")
204                )
205            }
206            Index::Except(list) => {
207                format!(
208                    "-[{}]",
209                    list.iter()
210                        .map(|v| v.to_string())
211                        .collect::<Vec<String>>()
212                        .join(", ")
213                )
214            }
215            Index::Range(start, None) => {
216                format!("{}..", start)
217            }
218            Index::Range(start, Some(end)) => {
219                format!("{}..{}", start, end)
220            }
221            Index::AnyWhere => "*".to_string(),
222            Index::Null => String::default(),
223        }
224    }
225
226    pub fn forward(index: usize) -> Self {
227        Self::Forward(index)
228    }
229
230    pub fn backward(index: usize) -> Self {
231        Self::Backward(index)
232    }
233
234    pub fn list(list: Vec<usize>) -> Self {
235        Self::List(list)
236    }
237
238    pub fn except(list: Vec<usize>) -> Self {
239        Self::Except(list)
240    }
241
242    pub fn range(start: Option<usize>, end: Option<usize>) -> Self {
243        match (start, end) {
244            (None, None) => {
245                panic!("start and end can't both None")
246            }
247            (None, _) => Self::Range(0, end),
248            (Some(start), _) => Self::Range(start, end),
249        }
250    }
251
252    pub(crate) fn from_range(range: &impl RangeBounds<usize>) -> Result<Self, Error> {
253        match (range.start_bound(), range.end_bound()) {
254            (std::ops::Bound::Included(s), std::ops::Bound::Included(e)) => {
255                Ok(Self::range(Some(*s), Some(e + 1)))
256            }
257            (std::ops::Bound::Included(s), std::ops::Bound::Excluded(e)) => {
258                Ok(Self::range(Some(*s), Some(*e)))
259            }
260            (std::ops::Bound::Included(s), std::ops::Bound::Unbounded) => {
261                Ok(Self::range(Some(*s), None))
262            }
263            (std::ops::Bound::Excluded(s), std::ops::Bound::Included(e)) => {
264                if *s == 0 {
265                    Err(error!(
266                        "start position of Index can't be negative: {:?}",
267                        range.start_bound()
268                    ))
269                } else {
270                    Ok(Self::range(Some(*s - 1), Some(e + 1)))
271                }
272            }
273            (std::ops::Bound::Excluded(s), std::ops::Bound::Excluded(e)) => {
274                if *s == 0 {
275                    Err(error!(
276                        "start position of Index can't be negative: {:?}",
277                        range.start_bound()
278                    ))
279                } else {
280                    Ok(Self::range(Some(*s - 1), Some(*e)))
281                }
282            }
283            (std::ops::Bound::Excluded(s), std::ops::Bound::Unbounded) => {
284                if *s == 0 {
285                    Err(error!(
286                        "start position of Index can't be negative: {:?}",
287                        range.start_bound()
288                    ))
289                } else {
290                    Ok(Self::range(Some(*s - 1), None))
291                }
292            }
293            (std::ops::Bound::Unbounded, std::ops::Bound::Included(e)) => {
294                Ok(Self::range(Some(0), Some(*e - 1)))
295            }
296            (std::ops::Bound::Unbounded, std::ops::Bound::Excluded(e)) => {
297                Ok(Self::range(Some(0), Some(*e)))
298            }
299            (std::ops::Bound::Unbounded, std::ops::Bound::Unbounded) => {
300                panic!("start and end of Index can't both unbounded")
301            }
302        }
303    }
304
305    pub fn anywhere() -> Self {
306        Self::AnyWhere
307    }
308
309    pub fn null() -> Self {
310        Self::Null
311    }
312
313    pub fn calc_index(&self, noa_index: usize, noa_count: usize) -> Option<usize> {
314        match self {
315            Self::Forward(offset) => {
316                let offset = *offset;
317
318                if offset < noa_count {
319                    return Some(offset);
320                }
321            }
322            Self::Backward(offset) => {
323                let offset = *offset;
324
325                if offset < noa_count {
326                    return Some(noa_count - offset - 1);
327                }
328            }
329            Self::List(list) => {
330                for offset in list {
331                    let offset = *offset;
332
333                    if offset < noa_count && offset == noa_index {
334                        return Some(offset);
335                    }
336                }
337            }
338            Self::Except(list) => {
339                if noa_index < noa_count && !list.contains(&noa_index) {
340                    return Some(noa_index);
341                }
342            }
343            Self::Range(start, end) => match (start, end) {
344                (start, None) => {
345                    let start = *start;
346
347                    if noa_index >= start {
348                        return Some(noa_index);
349                    }
350                }
351                (start, Some(end)) => {
352                    let start = *start;
353                    let end = *end;
354
355                    if noa_index >= start && noa_index < end {
356                        return Some(noa_index);
357                    }
358                }
359            },
360            Self::AnyWhere => {
361                return Some(noa_index);
362            }
363            _ => {}
364        }
365        None
366    }
367}
368
369impl Display for Index {
370    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
371        write!(
372            f,
373            "{}",
374            match self {
375                Index::AnyWhere => "*".to_string(),
376                Index::Null => String::default(),
377                Index::Forward(v) => {
378                    format!("{}", v)
379                }
380                Index::Backward(v) => {
381                    format!("-{}", v)
382                }
383                Index::Range(s, None) => {
384                    format!("{}..", s,)
385                }
386                Index::Range(s, Some(e)) => {
387                    format!("{}..{}", s, e)
388                }
389                Index::List(v) => {
390                    let strs: Vec<String> = v.iter().map(|v| format!("{}", v)).collect();
391
392                    format!("[{}]", strs.join(", "))
393                }
394                Index::Except(v) => {
395                    let strs: Vec<String> = v.iter().map(|v| format!("{}", v)).collect();
396
397                    format!("-[{}]", strs.join(", "))
398                }
399            }
400        )
401    }
402}
403
404impl TryFrom<String> for Index {
405    type Error = Error;
406
407    fn try_from(value: String) -> Result<Self, Self::Error> {
408        Self::parse(&value)
409    }
410}
411
412impl<'a> TryFrom<&'a str> for Index {
413    type Error = Error;
414
415    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
416        Self::parse(value)
417    }
418}
419
420macro_rules! impl_range_for {
421    ($range:ty) => {
422        impl TryFrom<$range> for Index {
423            type Error = Error;
424
425            fn try_from(value: $range) -> Result<Self, Self::Error> {
426                Self::from_range(&value)
427            }
428        }
429
430        impl<'a> TryFrom<&'a $range> for Index {
431            type Error = Error;
432
433            fn try_from(value: &'a $range) -> Result<Self, Self::Error> {
434                Self::from_range(value)
435            }
436        }
437    };
438}
439
440impl_range_for!(Range<usize>);
441
442impl_range_for!(RangeFrom<usize>);
443
444impl_range_for!(RangeInclusive<usize>);
445
446impl_range_for!(RangeTo<usize>);
447
448impl_range_for!(RangeToInclusive<usize>);
449
450impl_range_for!(RangeFull);
451
452macro_rules! impl_signed_ty_for {
453    ($int:ty) => {
454        impl TryFrom<$int> for Index {
455            type Error = Error;
456
457            fn try_from(value: $int) -> Result<Self, Self::Error> {
458                Ok(if value >= 0 {
459                    Self::forward(value as usize)
460                } else {
461                    Self::backward((-value) as usize)
462                })
463            }
464        }
465    };
466}
467
468impl_signed_ty_for!(isize);
469
470impl_signed_ty_for!(i128);
471
472impl_signed_ty_for!(i64);
473
474impl_signed_ty_for!(i32);
475
476impl_signed_ty_for!(i16);
477
478impl_signed_ty_for!(i8);
479
480macro_rules! impl_unsigned_ty_for {
481    ($int:ty) => {
482        impl TryFrom<$int> for Index {
483            type Error = Error;
484
485            fn try_from(value: $int) -> Result<Self, Self::Error> {
486                Ok(Self::forward(value as usize))
487            }
488        }
489    };
490}
491
492impl_unsigned_ty_for!(usize);
493
494impl_unsigned_ty_for!(u128);
495
496impl_unsigned_ty_for!(u64);
497
498impl_unsigned_ty_for!(u32);
499
500impl_unsigned_ty_for!(u16);
501
502impl_unsigned_ty_for!(u8);
503
504impl TryFrom<Vec<usize>> for Index {
505    type Error = Error;
506
507    fn try_from(value: Vec<usize>) -> Result<Self, Self::Error> {
508        Ok(Self::list(value))
509    }
510}
511
512impl<const N: usize> TryFrom<[usize; N]> for Index {
513    type Error = Error;
514
515    fn try_from(value: [usize; N]) -> Result<Self, Self::Error> {
516        Ok(Self::list(Vec::from(value)))
517    }
518}