cargo/ops/tree/format/parse.rs
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,
}
}
}