initiative_core/command/token/
name.rs1use crate::app::AppMeta;
6use crate::command::prelude::*;
7use crate::utils::{quoted_phrases, CaseInsensitiveStr};
8
9use std::pin::Pin;
10
11use async_stream::stream;
12use futures::join;
13use futures::prelude::*;
14
15pub fn match_input<'a, 'b>(
16 token: &'a Token,
17 input: &'a str,
18 app_meta: &'b AppMeta,
19) -> Pin<Box<dyn Stream<Item = FuzzyMatch<'a>> + 'b>>
20where
21 'a: 'b,
22{
23 assert!(matches!(token.token_type, TokenType::Name));
24
25 let phrases: Vec<_> = quoted_phrases(input).collect();
26 if phrases.is_empty() {
27 return Box::pin(stream::empty());
28 }
29
30 Box::pin(stream! {
31 let first_phrase = phrases.first().unwrap();
32
33 let full_phrase = phrases.last().unwrap();
35
36 let records: Vec<_> = if first_phrase.is_quoted() {
37 let (unquoted_name, quoted_name_matches) = join!(
39 app_meta.repository.get_by_name(first_phrase.as_str()),
40 app_meta.repository.get_by_name_start(first_phrase.as_outer_str()),
41 );
42
43 let mut quoted_name_matches = quoted_name_matches.unwrap_or_default();
44 if let Ok(unquoted_name) = unquoted_name {
45 quoted_name_matches.push(unquoted_name);
46 }
47
48 quoted_name_matches
49 } else {
50 app_meta.repository
51 .get_by_name_start(first_phrase.as_str())
52 .await
53 .unwrap_or_default()
54 };
55
56 for record in records.into_iter() {
57 let thing_name = record.thing.name().value().unwrap();
59
60 if thing_name.eq_ci(full_phrase) {
61 yield FuzzyMatch::Exact(TokenMatch::new(token, record));
62 continue;
63 } else if full_phrase.can_complete() {
64 if let Some(completion) = thing_name.strip_prefix_ci(full_phrase).map(str::to_string) {
65 yield FuzzyMatch::Partial(
66 TokenMatch::new(token, record),
67 Some(completion),
68 );
69
70 continue;
71 }
72 }
73
74 for (i, phrase) in phrases[0..phrases.len() - 1].iter().enumerate() {
75 if thing_name.eq_ci(phrase) || (i == 0 && thing_name.eq_ci(phrase.as_outer_str())) {
76 yield FuzzyMatch::Overflow(
77 TokenMatch::new(token, record),
78 phrase.after()
79 );
80 break;
81 }
82 }
83 }
84 })
85}
86
87#[cfg(test)]
88mod test {
89 use super::*;
90
91 use crate::storage::{Record, RecordStatus};
92 use crate::test_utils as test;
93 use uuid::Uuid;
94
95 #[derive(Hash)]
96 enum Marker {
97 Token,
98 }
99
100 #[tokio::test]
101 async fn match_input_test_simple() {
102 let token = name_m(Marker::Token);
103
104 test::assert_eq_unordered!(
105 [FuzzyMatch::Exact(TokenMatch::new(
106 &token,
107 Record {
108 status: RecordStatus::Unsaved,
109 thing: test::thing::odysseus(),
110 },
111 ))],
112 match_input(&token, "Odysseus", &test::app_meta::with_test_data().await)
113 .collect::<Vec<_>>()
114 .await,
115 );
116 }
117
118 #[tokio::test]
119 async fn match_input_test_empty() {
120 test::assert_empty!(
121 match_input(&name(), " ", &test::app_meta::with_test_data().await)
122 .collect::<Vec<_>>()
123 .await,
124 );
125 }
126
127 #[tokio::test]
128 async fn match_input_test_quoted() {
129 let token = name();
130
131 let things = [
132 test::npc().name("Medium").build_thing(Uuid::new_v4()),
133 test::npc().name("\"Medium\"").build_thing(Uuid::new_v4()),
134 test::npc()
135 .name("\"Medium\" Dave Lilywhite")
136 .build_thing(Uuid::new_v4()),
137 ];
138
139 let app_meta = test::app_meta::with_data_store::memory::with(things.clone(), []);
140
141 test::assert_eq_unordered!(
142 [
143 FuzzyMatch::Overflow(
144 TokenMatch::new(
145 &token,
146 Record {
147 status: RecordStatus::Saved,
148 thing: things[0].clone(),
149 },
150 ),
151 " Dave Lily".into(),
152 ),
153 FuzzyMatch::Overflow(
154 TokenMatch::new(
155 &token,
156 Record {
157 status: RecordStatus::Saved,
158 thing: things[1].clone(),
159 },
160 ),
161 " Dave Lily".into(),
162 ),
163 FuzzyMatch::Partial(
164 TokenMatch::new(
165 &token,
166 Record {
167 status: RecordStatus::Saved,
168 thing: things[2].clone(),
169 },
170 ),
171 Some("white".to_string()),
172 ),
173 ],
174 match_input(&token, "\"Medium\" Dave Lily", &app_meta)
175 .collect::<Vec<_>>()
176 .await,
177 );
178 }
179}