use crate::errors::ClarResult;
use crate::model::ClarOption;
use std::collections::VecDeque;
const LONG_PREFIX: &str = "--";
const SHORT_PREFIX: &str = "-";
const VALUE_SEPARATOR: char = '=';
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Token {
ShortOption(char, Option<String>),
LongOption(String, Option<String>),
Argument(String),
OptionTerminator(Vec<String>),
}
#[derive(Debug, Clone, Default)]
pub struct Lexer {
tokens: VecDeque<Token>,
}
impl Lexer {
pub fn parse<I, S>(mut self, items: I) -> ClarResult<VecDeque<Token>>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
for item in items {
self.parse_item(item.as_ref())?;
}
Ok(self.tokens)
}
fn parse_item(&mut self, input: &str) -> ClarResult<()> {
if let Some(Token::OptionTerminator(values)) = self.tokens.back_mut() {
values.push(input.to_string());
} else if let Some(s) = input.strip_prefix(LONG_PREFIX) {
self.parse_long_option(s)?;
} else if let Some(s) = input.strip_prefix(SHORT_PREFIX) {
self.parse_short_option(s)?;
} else {
self.tokens.push_back(Token::Argument(input.to_string()));
}
Ok(())
}
fn parse_long_option(&mut self, input: &str) -> ClarResult<()> {
if input.is_empty() {
self.tokens.push_back(Token::OptionTerminator(vec![]));
} else {
let (label, value) = self.parse_value(input);
ClarOption::validate_long_label(&label)?;
self.tokens.push_back(Token::LongOption(label, value));
}
Ok(())
}
fn parse_short_option(&mut self, input: &str) -> ClarResult<()> {
if input.is_empty() {
self.tokens.push_back(Token::Argument(SHORT_PREFIX.to_string()));
} else {
let (option, value) = self.parse_value(input);
let mut chars = option.chars().peekable();
loop {
let label = chars.next().unwrap();
ClarOption::validate_short_label(label)?;
if chars.peek().is_none() {
self.tokens.push_back(Token::ShortOption(label, value));
break;
} else {
self.tokens.push_back(Token::ShortOption(label, None));
}
}
}
Ok(())
}
fn parse_value(&self, input: &str) -> (String, Option<String>) {
match input.split_once(VALUE_SEPARATOR) {
Some((before, after)) => (before.to_string(), Some(after.to_string())),
None => (input.to_string(), None),
}
}
}