cargo/ops/tree/format/
parse.rs

1//! Parser for the `--format` string for `cargo tree`.
2
3use std::iter;
4use std::str;
5
6pub enum RawChunk<'a> {
7    /// Raw text to include in the output.
8    Text(&'a str),
9    /// A substitution to place in the output. For example, the argument "p"
10    /// emits the package name.
11    Argument(&'a str),
12    /// Indicates an error in the format string. The given string is a
13    /// human-readable message explaining the error.
14    Error(&'static str),
15}
16
17/// `cargo tree` format parser.
18///
19/// The format string indicates how each package should be displayed. It
20/// includes simple markers surrounded in curly braces that will be
21/// substituted with their corresponding values. For example, the text
22/// "{p} license:{l}" will substitute the `{p}` with the package name/version
23/// (and optionally source), and the `{l}` will be the license from
24/// `Cargo.toml`.
25///
26/// Substitutions are alphabetic characters between curly braces, like `{p}`
27/// or `{foo}`. The actual interpretation of these are done in the `Pattern`
28/// struct.
29///
30/// Bare curly braces can be included in the output with double braces like
31/// `{{` will include a single `{`, similar to Rust's format strings.
32pub struct Parser<'a> {
33    s: &'a str,
34    it: iter::Peekable<str::CharIndices<'a>>,
35}
36
37impl<'a> Parser<'a> {
38    pub fn new(s: &'a str) -> Parser<'a> {
39        Parser {
40            s,
41            it: s.char_indices().peekable(),
42        }
43    }
44
45    fn consume(&mut self, ch: char) -> bool {
46        match self.it.peek() {
47            Some(&(_, c)) if c == ch => {
48                self.it.next();
49                true
50            }
51            _ => false,
52        }
53    }
54
55    fn argument(&mut self) -> RawChunk<'a> {
56        RawChunk::Argument(self.name())
57    }
58
59    fn name(&mut self) -> &'a str {
60        let start = match self.it.peek() {
61            Some(&(pos, ch)) if ch.is_alphabetic() => {
62                self.it.next();
63                pos
64            }
65            _ => return "",
66        };
67
68        loop {
69            match self.it.peek() {
70                Some(&(_, ch)) if ch.is_alphanumeric() => {
71                    self.it.next();
72                }
73                Some(&(end, _)) => return &self.s[start..end],
74                None => return &self.s[start..],
75            }
76        }
77    }
78
79    fn text(&mut self, start: usize) -> RawChunk<'a> {
80        while let Some(&(pos, ch)) = self.it.peek() {
81            match ch {
82                '{' | '}' => return RawChunk::Text(&self.s[start..pos]),
83                _ => {
84                    self.it.next();
85                }
86            }
87        }
88        RawChunk::Text(&self.s[start..])
89    }
90}
91
92impl<'a> Iterator for Parser<'a> {
93    type Item = RawChunk<'a>;
94
95    fn next(&mut self) -> Option<RawChunk<'a>> {
96        match self.it.peek() {
97            Some(&(_, '{')) => {
98                self.it.next();
99                if self.consume('{') {
100                    Some(RawChunk::Text("{"))
101                } else {
102                    let chunk = self.argument();
103                    if self.consume('}') {
104                        Some(chunk)
105                    } else {
106                        for _ in &mut self.it {}
107                        Some(RawChunk::Error("expected '}'"))
108                    }
109                }
110            }
111            Some(&(_, '}')) => {
112                self.it.next();
113                if self.consume('}') {
114                    Some(RawChunk::Text("}"))
115                } else {
116                    Some(RawChunk::Error("unexpected '}'"))
117                }
118            }
119            Some(&(i, _)) => Some(self.text(i)),
120            None => None,
121        }
122    }
123}