use crate::common::error::{inv_arg, Result};
pub fn friendly_name(name: impl AsRef<str>) -> String {
let name: &str = name.as_ref();
let mut output = "".to_string();
let mut prev_upper = false;
let mut abbreviation = false;
for c in name.chars() {
let upper = c.is_ascii_uppercase();
if upper && prev_upper {
abbreviation = true;
let prev = output.pop().unwrap().to_ascii_uppercase();
output.push(prev);
}
if abbreviation && !upper {
abbreviation = false;
}
if abbreviation {
output.push(c.to_ascii_uppercase());
} else if upper {
if let Some(prev) = output.chars().last() {
if prev != ' ' {
output.push(' ');
}
}
output.push(c.to_ascii_lowercase());
} else if c == '_' {
output.push(' ');
} else {
output.push(c);
}
prev_upper = upper;
}
output
}
pub fn friendly_enumerate(
mut items: impl Iterator<Item = impl Into<String>>,
conjunction: Option<&str>,
) -> String {
let conjunction: String = match conjunction {
Some(c) => {
let mut c: String = c.into();
c += " ";
c
}
None => "".to_string(),
};
let mut s: String;
match items.next() {
None => return "<nothing>".to_string(),
Some(x) => s = x.into(),
}
let mut first = true;
let mut next = items.next();
loop {
let cur = next;
next = items.next();
match cur {
Some(c) => {
if next.is_none() {
if first && !conjunction.is_empty() {
s += " ";
} else {
s += ", ";
}
s += &conjunction;
} else {
s += ", ";
}
let c: String = c.into();
s += &c;
}
None => break,
}
first = false;
}
s
}
pub fn friendly_enum_parse<E, I>(s: &str) -> Result<E>
where
E: std::str::FromStr
+ strum::IntoEnumIterator<Iterator = I>
+ named_type::NamedType
+ std::fmt::Display,
I: Iterator<Item = E>,
{
let mut s: String = s.into();
s.make_ascii_lowercase();
let mut matches = vec![];
for var in E::iter() {
let mut var_str: String = var.to_string();
var_str.make_ascii_lowercase();
if var_str.starts_with(&s) {
matches.push((var_str, var));
}
}
match matches.len() {
0 => inv_arg(format!(
"{} is not a valid {}, valid values are {}",
s,
friendly_name(E::short_type_name()),
friendly_enumerate(
E::iter().map(|e| format!("{}", e).to_lowercase()),
Some("or")
)
)),
1 => Ok(matches.into_iter().next().unwrap().1),
_ => inv_arg(format!(
"{} is an ambiguous {}, it could mean either {}",
s,
friendly_name(E::short_type_name()),
friendly_enumerate(matches.into_iter().map(|x| x.0), Some("or"))
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use named_type::NamedType;
use named_type_derive::*;
use std::str::FromStr;
use strum::ParseError;
use strum_macros::{Display, EnumIter, EnumString};
#[derive(NamedType, Display, EnumIter, EnumString, Clone, PartialEq, Debug)]
enum TestEnum {
#[strum(to_string = "Foo", serialize = "foo", serialize = "f")]
Foo,
#[strum(to_string = "Bar", serialize = "bar", serialize = "b")]
Bar,
#[strum(to_string = "Baz", serialize = "baz", serialize = "z")]
Baz,
}
#[test]
fn parse_error() {
assert_eq!(TestEnum::from_str("Foo"), Ok(TestEnum::Foo));
assert_eq!(TestEnum::from_str("f"), Ok(TestEnum::Foo));
assert_eq!(TestEnum::from_str("ba"), Err(ParseError::VariantNotFound));
assert_eq!(
TestEnum::from_str("banana"),
Err(ParseError::VariantNotFound)
);
}
#[test]
fn friendlyname() {
assert_eq!(friendly_name("String"), "string");
assert_eq!(friendly_name("EnumVariants"), "enum variants");
assert_eq!(friendly_name("TestABC"), "test ABC");
assert_eq!(friendly_name("TestABCtestTest"), "test ABCtest test");
assert_eq!(friendly_name("TestABC_TestTest"), "test ABC test test");
}
#[test]
fn friendlyenumerate() {
assert_eq!(
friendly_enumerate(vec!["a", "b", "c"].into_iter(), Some("or")),
"a, b, or c"
);
assert_eq!(
friendly_enumerate(vec!["x", "y"].into_iter(), Some("and")),
"x and y"
);
assert_eq!(friendly_enumerate(vec!["x", "y"].into_iter(), None), "x, y");
assert_eq!(
friendly_enumerate(vec!["foo", "bar", "baz"].into_iter(), None),
"foo, bar, baz"
);
assert_eq!(
friendly_enumerate(vec!["one"].into_iter(), Some("and")),
"one"
);
assert_eq!(friendly_enumerate(vec!["one"].into_iter(), None), "one");
let empty: Vec<String> = vec![];
assert_eq!(friendly_enumerate(empty.into_iter(), None), "<nothing>");
}
#[test]
fn friendlyenumparse() {
assert_eq!(
friendly_enum_parse::<TestEnum, _>("bar").unwrap(),
TestEnum::Bar
);
assert_eq!(
friendly_enum_parse::<TestEnum, _>("BAR").unwrap(),
TestEnum::Bar
);
assert_eq!(
friendly_enum_parse::<TestEnum, _>("Bar").unwrap(),
TestEnum::Bar
);
assert_eq!(
friendly_enum_parse::<TestEnum, _>("baz").unwrap(),
TestEnum::Baz
);
assert_eq!(
friendly_enum_parse::<TestEnum, _>("BA")
.unwrap_err()
.to_string(),
"Invalid argument: ba is an ambiguous test enum, it could mean either bar or baz"
);
assert_eq!(
friendly_enum_parse::<TestEnum, _>("bla")
.unwrap_err()
.to_string(),
"Invalid argument: bla is not a valid test enum, valid values are foo, bar, or baz"
);
assert_eq!(
friendly_enum_parse::<TestEnum, _>("")
.unwrap_err()
.to_string(),
"Invalid argument: is an ambiguous test enum, it could mean either foo, bar, or baz"
);
}
}