1const WORD_SEP: &str = " ";
8pub const GREEDY: &str = "string";
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Fit {
14 Exact,
16 Joined,
19 Plain,
21}
22
23impl Fit {
24 pub const ORDER: [Fit; 3] = [Fit::Exact, Fit::Joined, Fit::Plain];
26
27 pub fn ways(schema: Option<&str>) -> &'static [Fit] {
29 match schema {
30 Some(_) => &Self::ORDER[..2],
31 None => &Self::ORDER[2..],
32 }
33 }
34
35 pub fn rank(self) -> usize {
37 Self::ORDER.len()
38 - Self::ORDER
39 .iter()
40 .position(|f| *f == self)
41 .unwrap_or(Self::ORDER.len())
42 }
43
44 pub fn take(self, schema: Option<&str>, args: &[String]) -> Option<Vec<String>> {
47 match (self, schema) {
48 (Fit::Exact, Some(schema)) => {
49 (schema.split_whitespace().count() == args.len()).then(|| args.to_vec())
50 }
51 (Fit::Joined, Some(schema)) => {
52 let types: Vec<&str> = schema.split_whitespace().collect();
53 let k = types.len();
54 if types.last() != Some(&GREEDY) || args.len() <= k {
55 return None;
56 }
57 let mut parts = args[..k - 1].to_vec();
58 parts.push(args[k - 1..].join(WORD_SEP));
59 Some(parts)
60 }
61 (Fit::Plain, None) => Some(args.to_vec()),
62 _ => None,
63 }
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 fn v(words: &[&str]) -> Vec<String> {
72 words.iter().map(|w| (*w).to_owned()).collect()
73 }
74
75 #[test]
76 fn exact_takes_as_many_arguments_as_the_schema_has() {
77 assert_eq!(
78 Fit::Exact.take(Some("int word"), &v(&["1", "x"])),
79 Some(v(&["1", "x"]))
80 );
81 assert_eq!(Fit::Exact.take(Some("int word"), &v(&["1"])), None);
82 assert_eq!(Fit::Exact.take(Some(""), &[]), Some(Vec::new()));
83 }
84
85 #[test]
86 fn joined_folds_the_extra_words_into_a_last_greedy_string() {
87 assert_eq!(
88 Fit::Joined.take(Some("int string"), &v(&["1", "a", "b"])),
89 Some(v(&["1", "a b"]))
90 );
91 assert_eq!(
92 Fit::Joined.take(Some("int word"), &v(&["1", "a", "b"])),
93 None
94 );
95 assert_eq!(
96 Fit::Joined.take(Some("int string"), &v(&["1", "a"])),
97 None,
98 "an exact fit isn't a join"
99 );
100 }
101
102 #[test]
103 fn plain_takes_anything_and_only_plain_handlers() {
104 assert_eq!(Fit::Plain.take(None, &v(&["a", "b"])), Some(v(&["a", "b"])));
105 assert_eq!(Fit::Plain.take(Some("int"), &v(&["1"])), None);
106 assert_eq!(Fit::Exact.take(None, &[]), None);
107 }
108
109 #[test]
110 fn earlier_ways_rank_higher() {
111 assert!(Fit::Exact.rank() > Fit::Joined.rank() && Fit::Joined.rank() > Fit::Plain.rank());
112 assert_eq!(Fit::ways(None), [Fit::Plain]);
113 }
114}