const WORD_SEP: &str = " ";
pub const GREEDY: &str = "string";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Fit {
Exact,
Joined,
Plain,
}
impl Fit {
pub const ORDER: [Fit; 3] = [Fit::Exact, Fit::Joined, Fit::Plain];
pub fn ways(schema: Option<&str>) -> &'static [Fit] {
match schema {
Some(_) => &Self::ORDER[..2],
None => &Self::ORDER[2..],
}
}
pub fn rank(self) -> usize {
Self::ORDER.len()
- Self::ORDER
.iter()
.position(|f| *f == self)
.unwrap_or(Self::ORDER.len())
}
pub fn take(self, schema: Option<&str>, args: &[String]) -> Option<Vec<String>> {
match (self, schema) {
(Fit::Exact, Some(schema)) => {
(schema.split_whitespace().count() == args.len()).then(|| args.to_vec())
}
(Fit::Joined, Some(schema)) => {
let types: Vec<&str> = schema.split_whitespace().collect();
let k = types.len();
if types.last() != Some(&GREEDY) || args.len() <= k {
return None;
}
let mut parts = args[..k - 1].to_vec();
parts.push(args[k - 1..].join(WORD_SEP));
Some(parts)
}
(Fit::Plain, None) => Some(args.to_vec()),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn v(words: &[&str]) -> Vec<String> {
words.iter().map(|w| (*w).to_owned()).collect()
}
#[test]
fn exact_takes_as_many_arguments_as_the_schema_has() {
assert_eq!(
Fit::Exact.take(Some("int word"), &v(&["1", "x"])),
Some(v(&["1", "x"]))
);
assert_eq!(Fit::Exact.take(Some("int word"), &v(&["1"])), None);
assert_eq!(Fit::Exact.take(Some(""), &[]), Some(Vec::new()));
}
#[test]
fn joined_folds_the_extra_words_into_a_last_greedy_string() {
assert_eq!(
Fit::Joined.take(Some("int string"), &v(&["1", "a", "b"])),
Some(v(&["1", "a b"]))
);
assert_eq!(
Fit::Joined.take(Some("int word"), &v(&["1", "a", "b"])),
None
);
assert_eq!(
Fit::Joined.take(Some("int string"), &v(&["1", "a"])),
None,
"an exact fit isn't a join"
);
}
#[test]
fn plain_takes_anything_and_only_plain_handlers() {
assert_eq!(Fit::Plain.take(None, &v(&["a", "b"])), Some(v(&["a", "b"])));
assert_eq!(Fit::Plain.take(Some("int"), &v(&["1"])), None);
assert_eq!(Fit::Exact.take(None, &[]), None);
}
#[test]
fn earlier_ways_rank_higher() {
assert!(Fit::Exact.rank() > Fit::Joined.rank() && Fit::Joined.rank() > Fit::Plain.rank());
assert_eq!(Fit::ways(None), [Fit::Plain]);
}
}