aopt/parser/
checker.rs

1use std::fmt::Debug;
2use std::marker::PhantomData;
3
4use crate::opt::Index;
5use crate::opt::Opt;
6use crate::opt::Style;
7use crate::set::SetChecker;
8use crate::set::SetOpt;
9use crate::trace;
10use crate::Error;
11use crate::HashMap;
12use crate::Uid;
13
14/// Check the option base on [`Style`].
15/// The checker will used for option check of [`Policy`](crate::parser::Policy).
16pub struct DefaultSetChecker<S>(PhantomData<S>);
17
18impl<S> Clone for DefaultSetChecker<S> {
19    fn clone(&self) -> Self {
20        Self(self.0)
21    }
22}
23
24impl<S> Debug for DefaultSetChecker<S> {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        f.debug_struct("DefaultSetChecker").finish()
27    }
28}
29
30impl<S> Default for DefaultSetChecker<S> {
31    fn default() -> Self {
32        Self(PhantomData)
33    }
34}
35
36impl<S> DefaultSetChecker<S>
37where
38    S: crate::set::Set,
39    SetOpt<S>: Opt,
40{
41    pub fn new() -> Self {
42        Self(PhantomData)
43    }
44
45    pub fn clear(&mut self) {}
46
47    pub fn opt<'a>(set: &'a S, id: &Uid) -> &'a SetOpt<S> {
48        set.get(*id).unwrap()
49    }
50}
51
52impl<S> SetChecker<S> for DefaultSetChecker<S>
53where
54    S: crate::set::Set,
55    SetOpt<S>: Opt,
56{
57    type Error = Error;
58
59    /// Check if we have [`Cmd`](crate::opt::Style::Cmd),
60    /// then no force required [`Pos`](crate::opt::Style::Pos)@1 allowed.
61    fn pre_check(&self, set: &mut S) -> Result<bool, Error> {
62        let has_cmd = set.iter().any(|opt| opt.mat_style(Style::Cmd));
63
64        const MAX_INDEX: usize = usize::MAX;
65
66        trace!("in pre check {{has_cmd: {}}}", has_cmd);
67        if has_cmd {
68            for opt in set.iter() {
69                if opt.mat_style(Style::Pos) {
70                    if let Some(index) = opt.index() {
71                        let index = index.calc_index(1, MAX_INDEX).unwrap_or(MAX_INDEX);
72
73                        if index == 1 && opt.force() {
74                            // if we have cmd, can not have force required POS @1
75                            return Err(Error::unexcepted_pos().with_uid(opt.uid()));
76                        }
77                    }
78                }
79            }
80        }
81        Ok(true)
82    }
83
84    /// Call the [`valid`](crate::opt::Opt::valid) check the
85    /// options([`Argument`](crate::opt::Style::Argument),
86    /// [`Boolean`](crate::opt::Style::Boolean), [`Combined`](crate::opt::Style::Combined)),
87    /// [`Flag`](crate::opt::Style::Flag)
88    fn opt_check(&self, set: &mut S) -> Result<bool, Error> {
89        trace!("in opt check, call valid on all Opt ...");
90        for opt in set.iter().filter(|opt| {
91            opt.mat_style(Style::Argument)
92                || opt.mat_style(Style::Boolean)
93                || opt.mat_style(Style::Combined)
94                || opt.mat_style(Style::Flag)
95        }) {
96            if !opt.valid() {
97                return Err(Error::sp_opt_require(vec![opt.hint()]).with_uid(opt.uid()));
98            }
99        }
100        Ok(true)
101    }
102
103    /// Check if the [`Pos`](crate::opt::Style::Pos) is valid, it must be set if it is force reuqired.
104    fn pos_check(&self, set: &mut S) -> Result<bool, Error> {
105        let mut index_map = HashMap::<usize, Vec<Uid>>::default();
106        let mut float_vec: Vec<Uid> = vec![];
107
108        const MAX_INDEX: usize = usize::MAX;
109
110        for opt in set.iter() {
111            if opt.mat_style(Style::Pos) {
112                if let Some(index) = opt.index() {
113                    match index {
114                        Index::Forward(cnt) => {
115                            if let Some(index) = index.calc_index(*cnt, MAX_INDEX) {
116                                let entry = index_map.entry(index).or_default();
117                                entry.push(opt.uid());
118                            }
119                        }
120                        Index::List(v) => {
121                            for index in v {
122                                let entry = index_map.entry(*index).or_default();
123                                entry.push(opt.uid());
124                            }
125                        }
126                        Index::Range(start, Some(end)) => {
127                            for index in *start..*end {
128                                let entry = index_map.entry(index).or_default();
129                                entry.push(opt.uid());
130                            }
131                        }
132                        Index::Backward(_)
133                        | Index::Except(_)
134                        | Index::Range(_, _)
135                        | Index::AnyWhere => {
136                            float_vec.push(opt.uid());
137                        }
138                        Index::Null => {}
139                    }
140                }
141            }
142        }
143        let mut names = vec![];
144
145        trace!("in pos check, index: {{{index_map:?}}}, float: {{{float_vec:?}}}");
146        for (_, uids) in index_map {
147            // if any of POS is force required, then it must set by user
148            let mut pos_valid = true;
149
150            for uid in uids.iter() {
151                let opt = Self::opt(set, uid);
152                let opt_valid = opt.valid();
153
154                pos_valid = pos_valid && opt_valid;
155                trace!("checking {}: {} {opt:?}", opt.hint(), opt_valid);
156                if !opt_valid {
157                    names.push(opt.hint().to_owned());
158                }
159            }
160            if !pos_valid {
161                return Err(Error::sp_pos_require(names).with_uid(uids[0]));
162            }
163            names.clear();
164        }
165        if !float_vec.is_empty() {
166            float_vec
167                .iter()
168                .filter(|&uid| !Self::opt(set, uid).valid())
169                .for_each(|uid| {
170                    names.push(Self::opt(set, uid).hint().to_string());
171                });
172            if !names.is_empty() {
173                return Err(Error::sp_pos_require(names).with_uid(float_vec[0]));
174            }
175        }
176        Ok(true)
177    }
178
179    /// Return true if any one of [`Cmd`](Style::Cmd) matched.
180    /// Return true if no [`Cmd`](Style::Cmd) exists.
181    fn cmd_check(&self, set: &mut S) -> Result<bool, Error> {
182        let mut names = vec![];
183        let mut valid = false;
184        let mut uids = vec![];
185
186        for opt in set.iter() {
187            if opt.mat_style(Style::Cmd) {
188                valid = valid || opt.valid();
189                if valid {
190                    break;
191                } else {
192                    uids.push(opt.uid());
193                    names.push(opt.hint().to_owned());
194                }
195            }
196        }
197        trace!("in cmd check, any one of the cmd matched: {}", valid);
198        if !valid && !names.is_empty() {
199            return Err(Error::sp_cmd_require(names).with_uid(uids[0]));
200        }
201        Ok(true)
202    }
203
204    /// Call [`valid`](crate::opt::Opt::valid) on options those style are [`Main`](Style::Main).
205    fn post_check(&self, set: &mut S) -> Result<bool, Error> {
206        trace!("in post check, call valid on Main ...");
207        Ok(set
208            .iter()
209            .filter(|opt| opt.mat_style(Style::Main))
210            .all(|opt| opt.valid()))
211    }
212}