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}