Skip to main content

bpaf/
meta.rs

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