#
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}