use std::{
fmt::{Debug, Formatter, Result as FmtResult},
str::CharIndices,
};
#[derive(Clone)]
pub struct Arguments<'a> {
buf: &'a str,
indices: CharIndices<'a>,
idx: usize,
}
impl<'a> Arguments<'a> {
pub fn new(buf: &'a str) -> Self {
Self {
buf: buf.trim(),
indices: buf.trim().char_indices(),
idx: 0,
}
}
pub const fn as_str(&self) -> &'a str {
self.buf
}
pub fn into_remainder(self) -> Option<&'a str> {
self.buf.get(self.idx..)
}
}
impl<'a> Debug for Arguments<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.debug_struct("Arguments")
.field("buf", &self.buf)
.field("idx", &self.idx)
.finish()
}
}
impl<'a> Iterator for Arguments<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
if self.idx > self.buf.len() {
return None;
}
let mut start_idx = self.idx;
let mut quoted = false;
let mut started = false;
for (i, ch) in &mut self.indices {
if quoted {
if ch == '"' {
let v = self.buf.get(start_idx..i);
self.idx = i + 1;
return v.map(str::trim);
}
} else if ch == ' ' {
if started {
let v = self.buf.get(start_idx..i);
self.idx = i + 1;
return v.map(str::trim);
}
self.idx = i;
start_idx = i;
started = true;
continue;
} else if ch == '"' {
start_idx = i + 1;
quoted = true;
}
self.idx = i;
started = true;
}
self.idx = usize::max_value();
match self.buf.get(start_idx..) {
Some("") | None => None,
Some(v) => Some(v.trim()),
}
}
}
#[allow(clippy::non_ascii_literal)]
#[cfg(test)]
mod tests {
use super::Arguments;
use static_assertions::assert_impl_all;
use std::fmt::Debug;
assert_impl_all!(Arguments<'_>: Clone, Debug, Iterator, Send, Sync);
#[test]
fn test_as_str() {
let args = Arguments::new("foo bar baz");
assert_eq!("foo bar baz", args.as_str());
}
#[test]
fn test_simple_args() {
let mut args = Arguments::new("foo bar baz");
assert_eq!(Some("foo"), args.next());
assert_eq!(Some("bar"), args.next());
assert_eq!(Some("baz"), args.next());
assert_eq!(None, args.next());
}
#[test]
fn test_quoted_args() {
let mut args = Arguments::new(r#"this "is a longer argument" here"#);
assert_eq!(Some("this"), args.next());
assert_eq!(Some("is a longer argument"), args.next());
assert_eq!(Some("here"), args.next());
assert_eq!(None, args.next());
}
#[test]
fn test_quoted_close_args() {
let mut args = Arguments::new(r#""kind of weird""but okay"#);
assert_eq!(Some("kind of weird"), args.next());
assert_eq!(Some("but okay"), args.next());
assert_eq!(None, args.next());
}
#[test]
fn test_unicode_chars_1() {
let mut args = Arguments::new("๐๐ข๐ nice try");
assert_eq!(Some("๐๐ข๐"), args.next());
assert_eq!(Some("nice"), args.next());
assert_eq!(Some("try"), args.next());
assert_eq!(None, args.next());
}
#[test]
fn test_unicode_chars_2() {
let mut args = Arguments::new("Saighdiรบr rรฉalta what even");
assert_eq!(Some("Saighdiรบr"), args.next());
assert_eq!(Some("rรฉalta"), args.next());
assert_eq!(Some("what"), args.next());
assert_eq!(Some("even"), args.next());
assert_eq!(None, args.next());
}
#[test]
fn test_quoted_unicode_chars() {
let mut args = Arguments::new(r#""๐๐ข๐ | CSA" amazing try"#);
assert_eq!(Some("๐๐ข๐ | CSA"), args.next());
assert_eq!(Some("amazing"), args.next());
assert_eq!(Some("try"), args.next());
assert_eq!(None, args.next());
}
#[test]
fn test_emote() {
let mut args = Arguments::new("why an emote ๐");
assert_eq!(Some("why"), args.next());
assert_eq!(Some("an"), args.next());
assert_eq!(Some("emote"), args.next());
assert_eq!(Some("๐"), args.next());
assert_eq!(None, args.next());
}
#[test]
fn test_quoted_emote() {
let mut args = Arguments::new(r#"omg "๐ - ๐" kewl"#);
assert_eq!(Some("omg"), args.next());
assert_eq!(Some("๐ - ๐"), args.next());
assert_eq!(Some("kewl"), args.next());
assert_eq!(None, args.next());
}
}