bpaf/
meta.rs

1use crate::{buffer::Doc, item::Item};
2
3#[doc(hidden)]
4#[derive(Clone, Debug)]
5pub enum Meta {
6    /// All arguments listed in a vector must be present
7    And(Vec<Meta>),
8    /// One of arguments listed in a vector must be present
9    Or(Vec<Meta>),
10    /// Arguments are optional and encased in [] when rendered
11    Optional(Box<Meta>),
12    /// Arguments are requred and encased in () when rendered
13    Required(Box<Meta>),
14    /// Argumens are required to be adjacent to each other
15    Adjacent(Box<Meta>),
16    /// Primitive argument as described
17    Item(Box<Item>),
18    /// Accepts multiple arguments
19    Many(Box<Meta>),
20    /// Arguments form a subsection with buffer being it's header
21    ///
22    /// whole set of arguments go into the same section as the first one
23    Subsection(Box<Meta>, Box<Doc>),
24    /// Buffer is rendered after
25    Suffix(Box<Meta>, Box<Doc>),
26    /// This item is not rendered in the help message
27    Skip,
28    /// TODO make it Option<Box<Doc>>
29    CustomUsage(Box<Meta>, Box<Doc>),
30    /// this meta must be prefixed with -- in unsage group
31    Strict(Box<Meta>),
32}
33
34// to get std::mem::take to work
35impl Default for Meta {
36    fn default() -> Self {
37        Meta::Skip
38    }
39}
40
41// Meta::Strict should bubble up to one of 3 places:
42// - top level
43// - one of "and" elements
44// - one of "or" elements
45#[derive(Debug, Clone, Copy)]
46enum StrictNorm {
47    /// starting at the top and looking for Strict inside
48    Pull,
49    Push,
50    /// Already accounted for one, can strip the rest
51    Strip,
52}
53
54impl StrictNorm {
55    fn push(&mut self) {
56        match *self {
57            StrictNorm::Pull => *self = StrictNorm::Push,
58            StrictNorm::Push | StrictNorm::Strip => {}
59        }
60    }
61}
62
63impl Meta {
64    /// Used by normalization function to collapse duplicated commands.
65    /// It seems to be fine to strip section information but not anything else
66    fn is_command(&self) -> bool {
67        match self {
68            Meta::Item(i) => matches!(i.as_ref(), Item::Command { .. }),
69            Meta::Subsection(m, _) => m.is_command(),
70            _ => false,
71        }
72    }
73
74    /// do a nested invariant check
75    pub(crate) fn positional_invariant_check(&self, verbose: bool) {
76        fn go(meta: &Meta, is_pos: &mut bool, v: bool) {
77            match meta {
78                Meta::And(xs) => {
79                    for x in xs {
80                        go(x, is_pos, v);
81                    }
82                }
83                Meta::Or(xs) => {
84                    let mut out = *is_pos;
85                    for x in xs {
86                        let mut this_pos = *is_pos;
87                        go(x, &mut this_pos, v);
88                        out |= this_pos;
89                    }
90                    *is_pos = out;
91                }
92                Meta::Item(i) => {
93                    match (*is_pos, i.is_pos()) {
94                        (true, true) | (false, false) => {}
95                        (true, false) => {
96                            panic!("bpaf usage BUG: all positional and command items must be placed in the right \
97                        most position of the structure or tuple they are in but {:?} breaks this rule. \
98                        See bpaf documentation for `positional` for details.", i);
99                        }
100                        (false, true) => {
101                            *is_pos = true;
102                        }
103                    }
104                    if let Item::Command { meta, .. } = &**i {
105                        let mut command_pos = false;
106                        if v {
107                            println!("Checking\n{:#?}", meta);
108                        }
109                        go(meta, &mut command_pos, v);
110                    }
111                }
112                Meta::Adjacent(m) => {
113                    if let Some(i) = Meta::first_item(m) {
114                        if i.is_pos() {
115                            go(m, is_pos, v);
116                        } else {
117                            let mut inner = false;
118                            go(m, &mut inner, v);
119                        }
120                    }
121                }
122                Meta::Optional(m)
123                | Meta::Required(m)
124                | Meta::Many(m)
125                | Meta::CustomUsage(m, _)
126                | Meta::Subsection(m, _)
127                | Meta::Strict(m)
128                | Meta::Suffix(m, _) => go(m, is_pos, v),
129                Meta::Skip => {}
130            }
131        }
132        let mut is_pos = false;
133        if verbose {
134            println!("Checking\n{:#?}", self);
135        }
136        go(self, &mut is_pos, verbose);
137    }
138
139    pub(crate) fn normalized(&self, for_usage: bool) -> Meta {
140        let mut m = self.clone();
141        let mut norm = StrictNorm::Pull;
142        m.normalize(for_usage, &mut norm);
143        // stip outer () around meta unless inner
144        if let Meta::Required(i) = m {
145            m = *i;
146        }
147        if matches!(m, Meta::Or(_)) {
148            m = Meta::Required(Box::new(m));
149        }
150        if matches!(norm, StrictNorm::Push) {
151            m = Meta::Strict(Box::new(m));
152        }
153        m
154    }
155
156    /// Used by adjacent parsers since it inherits behavior of the front item
157    pub(crate) fn first_item(meta: &Meta) -> Option<&Item> {
158        match meta {
159            Meta::And(xs) => xs.first().and_then(Self::first_item),
160            Meta::Item(item) => Some(item),
161            Meta::Skip | Meta::Or(_) => None,
162            Meta::Optional(x)
163            | Meta::Strict(x)
164            | Meta::Required(x)
165            | Meta::Adjacent(x)
166            | Meta::Many(x)
167            | Meta::Subsection(x, _)
168            | Meta::Suffix(x, _)
169            | Meta::CustomUsage(x, _) => Self::first_item(x),
170        }
171    }
172
173    /// Normalize meta info for display as usage. Required propagates outwards
174    fn normalize(&mut self, for_usage: bool, norm: &mut StrictNorm) {
175        fn normalize_vec(
176            xs: &mut Vec<Meta>,
177            for_usage: bool,
178            norm: &mut StrictNorm,
179            or: bool,
180        ) -> Option<Meta> {
181            let mut final_norm = *norm;
182            for m in xs.iter_mut() {
183                let mut this_norm = *norm;
184                m.normalize(for_usage, &mut this_norm);
185                let target: &mut StrictNorm = if or { &mut final_norm } else { norm };
186
187                match (*target, this_norm) {
188                    (_, StrictNorm::Pull) | (StrictNorm::Strip, _) => {}
189                    (StrictNorm::Pull, StrictNorm::Push) => {
190                        *m = Meta::Strict(Box::new(std::mem::take(m)));
191                        *target = StrictNorm::Strip;
192                    }
193                    _ => {
194                        *target = this_norm;
195                    }
196                }
197            }
198            xs.retain(|m| !matches!(m, Meta::Skip));
199
200            *norm = final_norm;
201
202            match xs.len() {
203                0 => Some(Meta::Skip),
204                1 => Some(xs.remove(0)),
205                _ => None,
206            }
207        }
208
209        match self {
210            Meta::And(xs) => {
211                if let Some(replacement) = normalize_vec(xs, for_usage, norm, false) {
212                    *self = replacement;
213                }
214            }
215            // or should have either () or [] around it
216            Meta::Or(xs) => {
217                if let Some(replacement) = normalize_vec(xs, for_usage, norm, true) {
218                    *self = replacement;
219                } else {
220                    let mut saw_cmd = false;
221                    // drop all the commands apart from the first one
222                    xs.retain(|m| {
223                        let is_cmd = m.is_command();
224                        let keep = !(is_cmd && saw_cmd);
225                        saw_cmd |= is_cmd;
226                        keep
227                    });
228                    match xs.len() {
229                        0 => *self = Meta::Skip,
230                        1 => *self = xs.remove(0),
231                        _ => *self = Meta::Required(Box::new(std::mem::take(self))),
232                    }
233                }
234            }
235            Meta::Optional(m) => {
236                m.normalize(for_usage, norm);
237                if matches!(**m, Meta::Skip) {
238                    // Optional(Skip) => Skip
239                    *self = Meta::Skip;
240                } else if let Meta::Required(mm) | Meta::Optional(mm) = m.as_mut() {
241                    // Optional(Required(m)) => Optional(m)
242                    // Optional(Optional(m)) => Optional(m)
243                    *m = std::mem::take(mm);
244                } else if let Meta::Many(many) = m.as_mut() {
245                    // Optional(Many(Required(m))) => Many(Optional(m))
246                    if let Meta::Required(x) = many.as_mut() {
247                        *self = Meta::Many(Box::new(Meta::Optional(std::mem::take(x))));
248                    }
249                }
250            }
251            Meta::Required(m) => {
252                m.normalize(for_usage, norm);
253                if matches!(**m, Meta::Skip) {
254                    // Required(Skip) => Skip
255                    *self = Meta::Skip;
256                } else if matches!(**m, Meta::And(_) | Meta::Or(_)) {
257                    // keep () around composite parsers
258                } else {
259                    // and strip them elsewhere
260                    *self = std::mem::take(m);
261                }
262            }
263            Meta::Many(m) => {
264                m.normalize(for_usage, norm);
265                if matches!(**m, Meta::Skip) {
266                    *self = Meta::Skip;
267                }
268            }
269            Meta::Adjacent(m) | Meta::Subsection(m, _) | Meta::Suffix(m, _) => {
270                m.normalize(for_usage, norm);
271                *self = std::mem::take(m);
272            }
273            Meta::Item(i) => i.normalize(for_usage),
274            Meta::Skip => {
275                // nothing to do with items and skip just bubbles upwards
276            }
277            Meta::CustomUsage(m, u) => {
278                m.normalize(for_usage, norm);
279                // strip CustomUsage if we are not in usage so writer can simply render it
280                if for_usage {
281                    if u.is_empty() {
282                        *self = Meta::Skip;
283                    }
284                } else {
285                    *self = std::mem::take(m);
286                }
287            }
288            Meta::Strict(m) => {
289                m.normalize(for_usage, norm);
290                norm.push();
291                *self = std::mem::take(m);
292            }
293        }
294    }
295}
296
297impl From<Item> for Meta {
298    fn from(value: Item) -> Self {
299        Meta::Item(Box::new(value))
300    }
301}
302
303impl Meta {
304    fn alts(self, to: &mut Vec<Meta>) {
305        match self {
306            Meta::Or(mut xs) => to.append(&mut xs),
307            Meta::Skip => {}
308            meta => to.push(meta),
309        }
310    }
311
312    pub(crate) fn or(self, other: Meta) -> Self {
313        let mut res = Vec::new();
314        self.alts(&mut res);
315        other.alts(&mut res);
316        match res.len() {
317            0 => Meta::Skip,
318            1 => res.remove(0),
319            _ => Meta::Or(res),
320        }
321    }
322
323    /// collect different kinds of short names for disambiguation
324    pub(crate) fn collect_shorts(&self, flags: &mut Vec<char>, args: &mut Vec<char>) {
325        match self {
326            Meta::And(xs) | Meta::Or(xs) => {
327                for x in xs {
328                    x.collect_shorts(flags, args);
329                }
330            }
331            Meta::Item(m) => match &**m {
332                Item::Any { .. } | Item::Positional { .. } => {}
333                Item::Command { meta, .. } => {
334                    meta.collect_shorts(flags, args);
335                }
336                Item::Flag { shorts, .. } => flags.extend(shorts),
337                Item::Argument { shorts, .. } => args.extend(shorts),
338            },
339            Meta::CustomUsage(m, _)
340            | Meta::Required(m)
341            | Meta::Optional(m)
342            | Meta::Adjacent(m)
343            | Meta::Subsection(m, _)
344            | Meta::Suffix(m, _)
345            | Meta::Many(m) => {
346                m.collect_shorts(flags, args);
347            }
348            Meta::Skip | Meta::Strict(_) => {}
349        }
350    }
351}