146 lines
3.6 KiB
Rust
146 lines
3.6 KiB
Rust
use std::ops::Deref;
|
|
|
|
#[allow(unused)]
|
|
pub enum SplitArg<'a> {
|
|
Normal(&'a str),
|
|
Once(&'a str),
|
|
}
|
|
|
|
impl<'a> SplitArg<'a> {
|
|
fn once(&self) -> bool {
|
|
if let Self::Once(_) = self {
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Deref for SplitArg<'a> {
|
|
type Target = str;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
match self {
|
|
SplitArg::Normal(s) | SplitArg::Once(s) => s,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a str> for SplitArg<'a> {
|
|
fn from(value: &'a str) -> Self {
|
|
SplitArg::Normal(value)
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a String> for SplitArg<'a> {
|
|
fn from(value: &'a String) -> Self {
|
|
SplitArg::Normal(value)
|
|
}
|
|
}
|
|
|
|
/// Assuming the input string is in format `({type_name}){path}`,
|
|
/// it will return `Some(("{type_name}", "{path}"))`
|
|
pub fn parens(s: &str) -> Option<(String, String)> {
|
|
let mut chars = s.chars();
|
|
match chars.next() {
|
|
Some(c) => {
|
|
if c != '(' {
|
|
return None;
|
|
}
|
|
}
|
|
None => return None,
|
|
}
|
|
|
|
let mut working = Vec::with_capacity(s.len());
|
|
let type_name = loop {
|
|
if let Some(next) = chars.next() {
|
|
if next == ')' {
|
|
break working.into_iter().collect::<String>();
|
|
}
|
|
if !next.is_alphanumeric() {
|
|
return None;
|
|
}
|
|
working.push(next);
|
|
} else {
|
|
return None;
|
|
}
|
|
};
|
|
|
|
Some((type_name, chars.collect()))
|
|
}
|
|
|
|
pub fn tab_or_space<'a, S>(s: S) -> Vec<String>
|
|
where
|
|
S: Into<SplitArg<'a>>,
|
|
{
|
|
let value = s.into();
|
|
let mut chars = value.chars();
|
|
let mut working: Vec<char> = Vec::with_capacity(value.len());
|
|
let mut out: Vec<String> = Vec::new();
|
|
let mut match_quote = false;
|
|
while let Some(c) = chars.next() {
|
|
match c {
|
|
'\\' => {
|
|
working.push(c);
|
|
if let Some(c) = chars.next() {
|
|
working.push(c);
|
|
}
|
|
}
|
|
'"' => {
|
|
match_quote = !match_quote;
|
|
working.push(c);
|
|
}
|
|
'\t' | ' ' => {
|
|
if match_quote {
|
|
working.push(c);
|
|
} else if working.is_empty() {
|
|
continue;
|
|
} else {
|
|
out.push((&working).into_iter().collect());
|
|
working.clear();
|
|
if value.once() {
|
|
out.push(chars.collect());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
_ => working.push(c),
|
|
}
|
|
}
|
|
if !working.is_empty() {
|
|
out.push(working.into_iter().collect());
|
|
}
|
|
out
|
|
}
|
|
|
|
pub fn on_first_match(s: &str, match_set: &[char]) -> Option<((String, String), char)> {
|
|
let mut first: Vec<char> = Vec::with_capacity(s.len());
|
|
let mut chars = s.chars();
|
|
while let Some(char) = chars.next() {
|
|
if (&match_set).into_iter().any(|m| m.eq(&char)) {
|
|
return Some(((first.into_iter().collect(), chars.collect()), char));
|
|
}
|
|
first.push(char);
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
#[test]
|
|
fn test_tab_or_space_split() {
|
|
for (base, expected) in [
|
|
("hello world", vec!["hello", "world"]),
|
|
("hello\tworld", vec!["hello", "world"]),
|
|
(
|
|
r#"hello "world! I never made it!" up til now"#,
|
|
vec!["hello", "\"world! I never made it!\"", "up", "til", "now"],
|
|
),
|
|
("hello world", vec!["hello", "world"]),
|
|
] {
|
|
assert_eq!(expected, super::tab_or_space(base))
|
|
}
|
|
}
|
|
}
|