use std::borrow::Cow;
use std::error::Error as StdError;
use std::fmt;
use std::marker::PhantomData;
use std::str::FromStr;
use uwl::Stream;
#[derive(Debug)]
#[non_exhaustive]
pub enum Error<E> {
Eos,
Parse(E),
}
impl<E> From<E> for Error<E> {
fn from(e: E) -> Self {
Error::Parse(e)
}
}
impl<E: fmt::Display> fmt::Display for Error<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::Eos => f.write_str(r#"ArgError("end of string")"#),
Self::Parse(ref e) => write!(f, "ArgError(\"{}\")", e),
}
}
}
impl<E: fmt::Debug + fmt::Display> StdError for Error<E> {}
type Result<T, E> = ::std::result::Result<T, Error<E>>;
#[derive(Debug, Clone)]
pub enum Delimiter {
Single(char),
Multiple(String),
}
impl Delimiter {
#[inline]
fn to_str(&self) -> Cow<'_, str> {
match self {
Self::Single(c) => Cow::Owned(c.to_string()),
Self::Multiple(s) => Cow::Borrowed(s),
}
}
}
impl From<char> for Delimiter {
#[inline]
fn from(c: char) -> Delimiter {
Delimiter::Single(c)
}
}
impl From<String> for Delimiter {
#[inline]
fn from(s: String) -> Delimiter {
Delimiter::Multiple(s)
}
}
impl<'a> From<&'a String> for Delimiter {
#[inline]
fn from(s: &'a String) -> Delimiter {
Delimiter::Multiple(s.clone())
}
}
impl<'a> From<&'a str> for Delimiter {
#[inline]
fn from(s: &'a str) -> Delimiter {
Delimiter::Multiple(s.to_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[allow(clippy::enum_variant_names)]
enum TokenKind {
Argument,
QuotedArgument,
}
#[derive(Debug, Clone, Copy)]
struct Token {
kind: TokenKind,
span: (usize, usize),
}
impl Token {
#[inline]
fn new(kind: TokenKind, start: usize, end: usize) -> Self {
Token {
kind,
span: (start, end),
}
}
}
#[derive(Clone, Copy)]
enum QuoteKind {
Ascii,
Apple,
}
impl QuoteKind {
fn new(c: char) -> Option<Self> {
match c {
'"' => Some(QuoteKind::Ascii),
'\u{201C}' => Some(QuoteKind::Apple),
_ => None,
}
}
fn is_ending_quote(self, c: char) -> bool {
match self {
Self::Ascii => c == '"',
Self::Apple => c == '\u{201D}',
}
}
}
fn lex(stream: &mut Stream<'_>, delims: &[Cow<'_, str>]) -> Option<Token> {
if stream.is_empty() {
return None;
}
let start = stream.offset();
if let Some(kind) = QuoteKind::new(stream.current_char()?) {
stream.next_char();
stream.take_until_char(|c| kind.is_ending_quote(c));
let is_quote = stream.current_char().map_or(false, |c| kind.is_ending_quote(c));
stream.next_char();
let end = stream.offset();
for delim in delims {
stream.eat(delim);
}
return Some(if is_quote {
Token::new(TokenKind::QuotedArgument, start, end)
} else {
Token::new(TokenKind::Argument, start, stream.len())
});
}
let mut end = start;
'outer: while !stream.is_empty() {
for delim in delims {
end = stream.offset();
if stream.eat(delim) {
break 'outer;
}
}
stream.next_char();
end = stream.offset();
}
Some(Token::new(TokenKind::Argument, start, end))
}
fn is_surrounded_with(s: &str, begin: char, end: char) -> bool {
s.starts_with(begin) && s.ends_with(end)
}
fn is_quoted(s: &str) -> bool {
if s.len() < 2 {
return false;
}
is_surrounded_with(s, '"', '"') || is_surrounded_with(s, '\u{201C}', '\u{201D}')
}
fn strip(s: &str, begin: char, end: char) -> Option<&str> {
let s = s.strip_prefix(begin)?;
s.strip_suffix(end)
}
fn remove_quotes(s: &str) -> &str {
if s.len() < 2 {
return s;
}
if let Some(s) = strip(s, '"', '"') {
return s;
}
strip(s, '\u{201C}', '\u{201D}').unwrap_or(s)
}
#[derive(Debug, Clone, Copy)]
enum State {
None,
Quoted,
Trimmed,
QuotedTrimmed,
TrimmedQuoted,
}
#[derive(Clone, Debug)]
pub struct Args {
message: String,
args: Vec<Token>,
offset: usize,
state: State,
}
impl Args {
#[must_use]
pub fn new(message: &str, possible_delimiters: &[Delimiter]) -> Self {
let delims = possible_delimiters
.iter()
.filter(|d| match d {
Delimiter::Single(c) => message.contains(*c),
Delimiter::Multiple(s) => message.contains(s),
})
.map(Delimiter::to_str)
.collect::<Vec<_>>();
let args = if delims.is_empty() {
let msg = message.trim();
let kind = if is_quoted(msg) { TokenKind::QuotedArgument } else { TokenKind::Argument };
if msg.is_empty() {
Vec::new()
} else {
vec![Token::new(kind, 0, message.len())]
}
} else {
let mut args = Vec::new();
let mut stream = Stream::new(message);
while let Some(token) = lex(&mut stream, &delims) {
if message[token.span.0..token.span.1].is_empty() {
continue;
}
args.push(token);
}
args
};
Args {
args,
message: message.to_string(),
offset: 0,
state: State::None,
}
}
#[inline]
fn span(&self) -> (usize, usize) {
self.args[self.offset].span
}
#[inline]
fn slice(&self) -> &str {
let (start, end) = self.span();
&self.message[start..end]
}
pub fn advance(&mut self) -> &mut Self {
if self.is_empty() {
return self;
}
self.offset += 1;
self
}
#[inline]
pub fn rewind(&mut self) -> &mut Self {
if self.offset == 0 {
return self;
}
self.offset -= 1;
self
}
#[inline]
pub fn restore(&mut self) {
self.offset = 0;
}
fn apply<'a>(&self, s: &'a str) -> &'a str {
fn trim(s: &str) -> &str {
let trimmed = s.trim();
let start = s.find(trimmed).unwrap_or(0);
let end = start + trimmed.len();
&s[start..end]
}
let mut s = s;
match self.state {
State::None => {},
State::Quoted => {
s = remove_quotes(s);
},
State::Trimmed => {
s = trim(s);
},
State::QuotedTrimmed => {
s = remove_quotes(s);
s = trim(s);
},
State::TrimmedQuoted => {
s = trim(s);
s = remove_quotes(s);
},
}
s
}
#[inline]
#[must_use]
pub fn current(&self) -> Option<&str> {
if self.is_empty() {
return None;
}
let mut s = self.slice();
s = self.apply(s);
Some(s)
}
pub fn trimmed(&mut self) -> &mut Self {
match self.state {
State::None => self.state = State::Trimmed,
State::Quoted => self.state = State::QuotedTrimmed,
_ => {},
}
self
}
pub fn untrimmed(&mut self) -> &mut Self {
match self.state {
State::Trimmed => self.state = State::None,
State::QuotedTrimmed | State::TrimmedQuoted => self.state = State::Quoted,
_ => {},
}
self
}
pub fn quoted(&mut self) -> &mut Self {
if self.is_empty() {
return self;
}
let is_quoted = self.args[self.offset].kind == TokenKind::QuotedArgument;
if is_quoted {
match self.state {
State::None => self.state = State::Quoted,
State::Trimmed => self.state = State::TrimmedQuoted,
_ => {},
}
}
self
}
pub fn unquoted(&mut self) -> &mut Self {
match self.state {
State::Quoted => self.state = State::None,
State::QuotedTrimmed | State::TrimmedQuoted => self.state = State::Trimmed,
_ => {},
}
self
}
#[inline]
pub fn parse<T: FromStr>(&self) -> Result<T, T::Err> {
T::from_str(self.current().ok_or(Error::Eos)?).map_err(Error::Parse)
}
#[inline]
pub fn single<T: FromStr>(&mut self) -> Result<T, T::Err> {
let p = self.parse::<T>()?;
self.advance();
Ok(p)
}
#[inline]
pub fn single_quoted<T: FromStr>(&mut self) -> Result<T, T::Err> {
let p = self.quoted().parse::<T>()?;
self.advance();
Ok(p)
}
#[inline]
pub fn iter<T: FromStr>(&mut self) -> Iter<'_, T> {
Iter {
args: self,
state: State::None,
_marker: PhantomData,
}
}
#[inline]
#[must_use]
pub fn raw(&self) -> RawArguments<'_> {
RawArguments {
tokens: &self.args,
msg: &self.message,
quoted: false,
}
}
#[inline]
#[must_use]
pub fn raw_quoted(&self) -> RawArguments<'_> {
let mut raw = self.raw();
raw.quoted = true;
raw
}
pub fn find<T: FromStr>(&mut self) -> Result<T, T::Err> {
if self.is_empty() {
return Err(Error::Eos);
}
let before = self.offset;
self.restore();
let pos = if let Some(p) = self.iter::<T>().quoted().position(|res| res.is_ok()) {
p
} else {
self.offset = before;
return Err(Error::Eos);
};
self.offset = pos;
let parsed = self.single_quoted::<T>()?;
self.args.remove(pos);
self.offset = before;
self.rewind();
Ok(parsed)
}
pub fn find_n<T: FromStr>(&mut self) -> Result<T, T::Err> {
if self.is_empty() {
return Err(Error::Eos);
}
let before = self.offset;
self.restore();
let pos = if let Some(p) = self.iter::<T>().quoted().position(|res| res.is_ok()) {
p
} else {
self.offset = before;
return Err(Error::Eos);
};
self.offset = pos;
let parsed = self.quoted().parse::<T>()?;
self.offset = before;
Ok(parsed)
}
#[inline]
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
#[inline]
#[must_use]
pub fn rest(&self) -> &str {
self.remains().unwrap_or_default()
}
#[inline]
#[must_use]
pub fn remains(&self) -> Option<&str> {
if self.is_empty() {
return None;
}
let (start, _) = self.span();
Some(&self.message[start..])
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.args.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.offset >= self.len()
}
#[inline]
#[must_use]
pub fn remaining(&self) -> usize {
if self.is_empty() {
return 0;
}
self.len() - self.offset
}
}
pub struct Iter<'a, T: FromStr> {
args: &'a mut Args,
state: State,
_marker: PhantomData<T>,
}
#[allow(clippy::missing_errors_doc)]
impl<'a, T: FromStr> Iter<'a, T> {
pub fn current(&mut self) -> Option<&str> {
self.args.state = self.state;
self.args.current()
}
pub fn parse(&mut self) -> Result<T, T::Err> {
self.args.state = self.state;
self.args.parse::<T>()
}
#[inline]
pub fn quoted(&mut self) -> &mut Self {
match self.state {
State::None => self.state = State::Quoted,
State::Trimmed => self.state = State::TrimmedQuoted,
_ => {},
}
self
}
#[inline]
pub fn trimmed(&mut self) -> &mut Self {
match self.state {
State::None => self.state = State::Trimmed,
State::Quoted => self.state = State::QuotedTrimmed,
_ => {},
}
self
}
}
impl<'a, T: FromStr> Iterator for Iter<'a, T> {
type Item = Result<T, T::Err>;
fn next(&mut self) -> Option<Self::Item> {
if self.args.is_empty() {
None
} else {
let arg = self.parse();
self.args.advance();
Some(arg)
}
}
}
#[derive(Debug)]
pub struct RawArguments<'a> {
msg: &'a str,
tokens: &'a [Token],
quoted: bool,
}
impl<'a> Iterator for RawArguments<'a> {
type Item = &'a str;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let (start, end) = self.tokens.get(0)?.span;
self.tokens = &self.tokens[1..];
let mut s = &self.msg[start..end];
if self.quoted {
s = remove_quotes(s);
}
Some(s)
}
}