    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123

    //! Parser for the `--format` string for `cargo tree`.

    use std::iter;
    use std::str;

    pub enum RawChunk<'a> {
        /// Raw text to include in the output.
        Text(&'a str),
        /// A substitution to place in the output. For example, the argument "p"
        /// emits the package name.
        Argument(&'a str),
        /// Indicates an error in the format string. The given string is a
        /// human-readable message explaining the error.
        Error(&'static str),
    }

    /// `cargo tree` format parser.
    ///
    /// The format string indicates how each package should be displayed. It
    /// includes simple markers surrounded in curly braces that will be
    /// substituted with their corresponding values. For example, the text
    /// "{p} license:{l}" will substitute the `{p}` with the package name/version
    /// (and optionally source), and the `{l}` will be the license from
    /// `Cargo.toml`.
    ///
    /// Substitutions are alphabetic characters between curly braces, like `{p}`
    /// or `{foo}`. The actual interpretation of these are done in the `Pattern`
    /// struct.
    ///
    /// Bare curly braces can be included in the output with double braces like
    /// `{{` will include a single `{`, similar to Rust's format strings.
    pub struct Parser<'a> {
        s: &'a str,
        it: iter::Peekable<str::CharIndices<'a>>,
    }

    impl<'a> Parser<'a> {
        pub fn new(s: &'a str) -> Parser<'a> {
            Parser {
                s,
                it: s.char_indices().peekable(),
            }
        }

        fn consume(&mut self, ch: char) -> bool {
            match self.it.peek() {
                Some(&(_, c)) if c == ch => {
                    self.it.next();
                    true
                }
                _ => false,
            }
        }

        fn argument(&mut self) -> RawChunk<'a> {
            RawChunk::Argument(self.name())
        }

        fn name(&mut self) -> &'a str {
            let start = match self.it.peek() {
                Some(&(pos, ch)) if ch.is_alphabetic() => {
                    self.it.next();
                    pos
                }
                _ => return "",
            };

            loop {
                match self.it.peek() {
                    Some(&(_, ch)) if ch.is_alphanumeric() => {
                        self.it.next();
                    }
                    Some(&(end, _)) => return &self.s[start..end],
                    None => return &self.s[start..],
                }
            }
        }

        fn text(&mut self, start: usize) -> RawChunk<'a> {
            while let Some(&(pos, ch)) = self.it.peek() {
                match ch {
                    '{' | '}' => return RawChunk::Text(&self.s[start..pos]),
                    _ => {
                        self.it.next();
                    }
                }
            }
            RawChunk::Text(&self.s[start..])
        }
    }

    impl<'a> Iterator for Parser<'a> {
        type Item = RawChunk<'a>;

        fn next(&mut self) -> Option<RawChunk<'a>> {
            match self.it.peek() {
                Some(&(_, '{')) => {
                    self.it.next();
                    if self.consume('{') {
                        Some(RawChunk::Text("{"))
                    } else {
                        let chunk = self.argument();
                        if self.consume('}') {
                            Some(chunk)
                        } else {
                            for _ in &mut self.it {}
                            Some(RawChunk::Error("expected '}'"))
                        }
                    }
                }
                Some(&(_, '}')) => {
                    self.it.next();
                    if self.consume('}') {
                        Some(RawChunk::Text("}"))
                    } else {
                        Some(RawChunk::Error("unexpected '}'"))
                    }
                }
                Some(&(i, _)) => Some(self.text(i)),
                None => None,
            }
        }
    }