use std::iter::Peekable;
use std::ops::Range;
use std::str::CharIndices;
#[derive(Clone, Copy, Eq, PartialEq)]
enum ParsingState {
Normal,
Escaped,
SingleQuoted,
DoubleQuoted,
DoubleQuotedEscaped,
}
pub struct Parser<'a> {
state: ParsingState,
cmdline: Peekable<CharIndices<'a>>,
cmdline_len: usize,
}
impl<'a> Parser<'a> {
pub fn new(cmdline: &str) -> Parser {
Parser {
state: ParsingState::Normal,
cmdline: cmdline.char_indices().peekable(),
cmdline_len: cmdline.len(),
}
}
}
impl<'a> Iterator for Parser<'a> {
type Item = (Range<usize>, char, String);
fn next(&mut self) -> Option<Self::Item> {
use self::ParsingState::*;
let mut arg = String::new();
if let Some(&(mut start, _)) = self.cmdline.peek() {
let mut yield_value = false;
let mut was_quoted = false;
for (i, c) in &mut self.cmdline {
self.state = match (self.state, c) {
(Normal, '\\') => Escaped,
(Normal, '\'') => SingleQuoted,
(Normal, '"') => DoubleQuoted,
(Normal, c) if c == ' ' || c == '|' || c == '>' => {
if !arg.is_empty() || was_quoted || c != ' ' {
yield_value = true;
} else {
start = i + 1;
}
Normal
}
(Normal, _) | (Escaped, _) => {
arg.push(c);
Normal
}
(SingleQuoted, '\'') => {
was_quoted = true;
Normal
}
(SingleQuoted, _) => {
arg.push(c);
SingleQuoted
}
(DoubleQuoted, '"') => {
was_quoted = true;
Normal
}
(DoubleQuoted, '\\') => DoubleQuotedEscaped,
(DoubleQuoted, _)
| (DoubleQuotedEscaped, '"')
| (DoubleQuotedEscaped, '\\') => {
arg.push(c);
DoubleQuoted
}
(DoubleQuotedEscaped, _) => {
arg.push('\\');
arg.push(c);
DoubleQuoted
}
};
if yield_value {
return Some((start..i, c, arg));
}
}
if !arg.is_empty() || was_quoted {
return Some((start..self.cmdline_len, ' ', arg));
}
}
None
}
}
pub fn try_parse_range(line: &str) -> Option<Box<dyn Iterator<Item = usize>>> {
if let Some(idx) = line.find("..") {
let (start_str, end_str) = line.split_at(idx);
let (inclusive, end_str) = if let Some(rest) = end_str.strip_prefix("..=") {
(true, rest)
} else {
(false, &end_str[2..])
};
let start = if start_str.is_empty() {
0
} else if let Ok(s) = start_str.parse::<usize>() {
s
} else {
return None;
};
if end_str.is_empty() {
if inclusive {
return None;
} else {
return Some(Box::new(start..));
}
} else if let Ok(end) = end_str.parse::<usize>() {
if inclusive {
return Some(Box::new(start..=end));
} else {
return Some(Box::new(start..end));
}
} else {
return None;
};
}
None
}
pub fn try_parse_csl(line: &str) -> Option<Box<dyn Iterator<Item = usize>>> {
let mut ret = Vec::new();
let l = line.trim();
if l.is_empty() {
return None;
}
for item in l.split_terminator(',') {
match item.trim().parse::<usize>() {
Ok(num) => ret.push(num),
Err(_) => return None, }
}
Some(Box::new(ret.into_iter()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn try_parse_csl_test() {
let v: Vec<usize> = try_parse_csl("1,2,3").unwrap().collect();
assert_eq!(vec!(1, 2, 3), v);
let v: Vec<usize> = try_parse_csl("1, 2, 3").unwrap().collect();
assert_eq!(vec!(1, 2, 3), v);
let v: Vec<usize> = try_parse_csl("1, 7, 3,").unwrap().collect();
assert_eq!(vec!(1, 7, 3), v);
let v: Vec<usize> = try_parse_csl("1, 7, 3, ").unwrap().collect();
assert_eq!(vec!(1, 7, 3), v);
let v: Vec<usize> = try_parse_csl("1").unwrap().collect();
assert_eq!(vec!(1), v);
assert!(try_parse_csl("1,x,2").is_none());
assert!(try_parse_csl("pods").is_none());
assert!(try_parse_csl("").is_none());
assert!(try_parse_csl(",").is_none());
assert!(try_parse_csl("1,,2").is_none());
assert!(try_parse_csl(",,,").is_none());
assert!(try_parse_csl(",1,2,").is_none());
}
#[test]
fn try_parse_range_test() {
let v: Vec<usize> = try_parse_range("1..3").unwrap().collect();
assert_eq!(vec!(1, 2), v);
let v: Vec<usize> = try_parse_range("1..=3").unwrap().collect();
assert_eq!(vec!(1, 2, 3), v);
let v: Vec<usize> = try_parse_range("..4").unwrap().collect();
assert_eq!(vec!(0, 1, 2, 3), v);
let v: Vec<usize> = try_parse_range("6..4").unwrap().collect();
assert!(v.is_empty());
let v: Vec<usize> = try_parse_range("6..=6").unwrap().collect();
assert_eq!(vec!(6), v);
let mut r = try_parse_range("3..").unwrap();
for i in 3..10 {
assert_eq!(r.next().unwrap(), i);
}
let mut r = try_parse_range("..").unwrap();
for i in 0..10 {
assert_eq!(r.next().unwrap(), i);
}
assert!(try_parse_range(",1,2,").is_none());
assert!(try_parse_range("1").is_none());
assert!(try_parse_range("pods").is_none());
assert!(try_parse_range("1.2").is_none());
assert!(try_parse_range("1..==2").is_none());
assert!(try_parse_range("").is_none());
assert!(try_parse_range(" 1..=2").is_none());
}
}