1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
use crate::app::AppMeta;
use async_trait::async_trait;
use serde::Serialize;
use std::borrow::Cow;

#[async_trait(?Send)]
pub trait Runnable: Sized {
    async fn run(self, input: &str, app_meta: &mut AppMeta) -> Result<String, String>;
}

#[async_trait(?Send)]
pub trait ContextAwareParse: Sized {
    async fn parse_input(input: &str, app_meta: &AppMeta) -> CommandMatches<Self>;
}

#[async_trait(?Send)]
pub trait Autocomplete {
    async fn autocomplete(input: &str, app_meta: &AppMeta) -> Vec<AutocompleteSuggestion>;
}

#[track_caller]
#[cfg(test)]
pub fn assert_autocomplete(
    expected_suggestions: &[(&'static str, &'static str)],
    actual_suggestions: Vec<AutocompleteSuggestion>,
) {
    let mut expected: Vec<_> = expected_suggestions
        .iter()
        .map(|(a, b)| ((*a).into(), (*b).into()))
        .collect();
    expected.sort();

    let mut actual: Vec<(Cow<'static, str>, Cow<'static, str>)> = actual_suggestions
        .into_iter()
        .map(|suggestion| suggestion.into())
        .collect();
    actual.sort();

    assert_eq!(expected, actual);
}

#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)]
#[serde(into = "(Cow<'static, str>, Cow<'static, str>)")]
pub struct AutocompleteSuggestion {
    pub term: Cow<'static, str>,
    pub summary: Cow<'static, str>,
}

impl AutocompleteSuggestion {
    pub fn new(term: impl Into<Cow<'static, str>>, summary: impl Into<Cow<'static, str>>) -> Self {
        Self {
            term: term.into(),
            summary: summary.into(),
        }
    }
}

impl From<AutocompleteSuggestion> for (Cow<'static, str>, Cow<'static, str>) {
    fn from(input: AutocompleteSuggestion) -> Self {
        (input.term, input.summary)
    }
}

impl From<(String, &'static str)> for AutocompleteSuggestion {
    fn from(input: (String, &'static str)) -> Self {
        AutocompleteSuggestion::new(input.0, input.1)
    }
}

impl From<(&'static str, String)> for AutocompleteSuggestion {
    fn from(input: (&'static str, String)) -> Self {
        AutocompleteSuggestion::new(input.0, input.1)
    }
}

impl From<(String, String)> for AutocompleteSuggestion {
    fn from(input: (String, String)) -> Self {
        AutocompleteSuggestion::new(input.0, input.1)
    }
}

impl From<(&'static str, &'static str)> for AutocompleteSuggestion {
    fn from(input: (&'static str, &'static str)) -> Self {
        AutocompleteSuggestion::new(input.0, input.1)
    }
}

/// Represents all possible parse results for a given input.
///
/// One of the key usability features (and major headaches) of initiative.sh is its use of fuzzy
/// command matching. Most parsers assume only one possible valid interpretation, but initiative.sh
/// has the concept of *canonical* and *fuzzy* matches.
///
/// **Canonical matches** are inputs that cannot possibly conflict with one another. This is
/// achieved using differing prefixes, eg. all SRD reference lookups are prefixed as "srd item
/// shield" (or "srd spell shield").
///
/// **Fuzzy matches** are commands that the user could possibly have meant with a given input. The
/// reason we need to be particularly careful with fuzzy matches is because the user can add
/// arbitrary names to their journal, and typing that arbitrary name is *usually* enough to pull it
/// up again. However, that arbitrary name could easily be another command, which the user may not
/// want to overwrite. (The notable built-in conflict is the example above, where "shield" is both
/// an item and a spell.)
///
/// || Canonical matches || Fuzzy matches || Result ||
/// | 0 | 0 | Error: "Unknown command: '...'" |
/// | 0 | 1 | The fuzzy match is run. |
/// | 0 | 2+ | Error: "There are several possible interpretations of this command. Did you mean:" |
/// | 1 | 0 | The canonical match is run. |
/// | 1 | 1+ | The canonical match is run, suffixed with the error: "There are other possible interpretations of this command. Did you mean:" |
///
/// Since the parsing logic in the code base runs several layers deep across a number of different
/// structs, this struct provides utilities for combining multiple CommandMatches instances of
/// differing types, and for transforming the inner type as needed.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CommandMatches<T> {
    pub canonical_match: Option<T>,
    pub fuzzy_matches: Vec<T>,
}

impl<T: std::fmt::Debug> CommandMatches<T> {
    /// Create a new instance of CommandMatches with a canonical match.
    pub fn new_canonical(canonical_match: T) -> Self {
        CommandMatches {
            canonical_match: Some(canonical_match),
            fuzzy_matches: Vec::new(),
        }
    }

    /// Create a new instance of `CommandMatches` with a single fuzzy match.
    pub fn new_fuzzy(fuzzy_match: T) -> Self {
        CommandMatches {
            canonical_match: None,
            fuzzy_matches: vec![fuzzy_match],
        }
    }

    /// Push a new canonical match. Panics if a canonical match is already present.
    pub fn push_canonical(&mut self, canonical_match: T) {
        if let Some(old_canonical_match) = &self.canonical_match {
            panic!(
                "trying to overwrite existing canonical match {:?} with new match {:?}",
                old_canonical_match, canonical_match,
            );
        }

        self.canonical_match = Some(canonical_match);
    }

    /// Add a new fuzzy match to the list of possibilities.
    pub fn push_fuzzy(&mut self, fuzzy_match: T) {
        self.fuzzy_matches.push(fuzzy_match);
    }

    /// Combine the current CommandMatches with another object that can be massaged into the same
    /// type. The Vecs of fuzzy matches are combined. Panics if both objects lay claim to a
    /// canonical match.
    pub fn union<O>(mut self, other: CommandMatches<O>) -> Self
    where
        O: std::fmt::Debug,
        T: From<O>,
    {
        let CommandMatches {
            canonical_match,
            fuzzy_matches,
        } = other.into_subtype::<T>();

        if let Some(canonical_match) = canonical_match {
            self.push_canonical(canonical_match);
        }

        self.fuzzy_matches.reserve(fuzzy_matches.len());
        fuzzy_matches
            .into_iter()
            .for_each(|fuzzy_match| self.push_fuzzy(fuzzy_match));

        self
    }

    /// Variant of union() that resolves canonical conflicts by overwriting self.canonical_match
    /// with other.canonical_match instead of panicking.
    pub fn union_with_overwrite<O>(mut self, other: CommandMatches<O>) -> Self
    where
        O: std::fmt::Debug,
        T: From<O>,
    {
        let self_canonical_match = self.canonical_match.take();

        let mut result = self.union(other);

        if result.canonical_match.is_none() {
            result.canonical_match = self_canonical_match;
        }

        result
    }

    /// Convert a `CommandMatches<T>' into a `CommandMatches<O>`, massaging the inner type using
    /// its `From<T>` trait.
    ///
    /// This could be an impl of `From<CommandMatches<T>> for CommandMatches<O>`, but that produces
    /// a trait conflict due to limitations of Rust's type system.
    pub fn into_subtype<O>(self) -> CommandMatches<O>
    where
        O: From<T> + std::fmt::Debug,
    {
        CommandMatches {
            canonical_match: self
                .canonical_match
                .map(|canonical_match| canonical_match.into()),
            fuzzy_matches: self
                .fuzzy_matches
                .into_iter()
                .map(|fuzzy_match| fuzzy_match.into())
                .collect(),
        }
    }

    /// Consumes the struct, returning the following in priority order:
    ///
    /// 1. The canonical match, if present.
    /// 2. The first fuzzy match, if any are present.
    /// 3. `None`
    pub fn take_best_match(self) -> Option<T> {
        self.canonical_match
            .or_else(|| self.fuzzy_matches.into_iter().next())
    }
}

impl<T> From<T> for CommandMatches<T> {
    fn from(input: T) -> Self {
        CommandMatches {
            canonical_match: Some(input),
            fuzzy_matches: Vec::default(),
        }
    }
}

impl<T> Default for CommandMatches<T> {
    fn default() -> Self {
        CommandMatches {
            canonical_match: None,
            fuzzy_matches: Vec::default(),
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn command_matches_new_test() {
        {
            let command_matches = CommandMatches::new_canonical(true);
            assert_eq!(Some(true), command_matches.canonical_match);
            assert!(command_matches.fuzzy_matches.is_empty());
        }

        {
            let command_matches = CommandMatches::new_fuzzy(true);
            assert_eq!(Option::<bool>::None, command_matches.canonical_match);
            assert_eq!([true][..], command_matches.fuzzy_matches[..]);
        }

        {
            let command_matches = CommandMatches::default();
            assert_eq!(Option::<bool>::None, command_matches.canonical_match);
            assert!(command_matches.fuzzy_matches.is_empty());
        }
    }

    #[test]
    fn command_matches_push_test() {
        {
            let mut command_matches = CommandMatches::default();
            command_matches.push_canonical(true);
            assert_eq!(Some(true), command_matches.canonical_match);
            assert!(command_matches.fuzzy_matches.is_empty());
        }

        {
            let mut command_matches = CommandMatches::default();
            command_matches.push_fuzzy(1u8);
            command_matches.push_fuzzy(2);
            assert_eq!(Option::<u8>::None, command_matches.canonical_match);
            assert_eq!([1u8, 2][..], command_matches.fuzzy_matches[..]);
        }
    }

    #[test]
    #[should_panic(
        expected = "trying to overwrite existing canonical match true with new match false"
    )]
    fn command_matches_push_test_with_conflict() {
        let mut command_matches = CommandMatches::default();
        command_matches.push_canonical(true);
        command_matches.push_canonical(false);
    }

    #[test]
    fn command_matches_union_test() {
        let command_matches_1 = {
            let mut command_matches = CommandMatches::new_canonical(1u16);
            command_matches.push_fuzzy(2);
            command_matches.push_fuzzy(3);
            command_matches
        };
        let command_matches_2 = CommandMatches::new_fuzzy(4u8);

        let command_matches_result = command_matches_1.union(command_matches_2);

        assert_eq!(Some(1u16), command_matches_result.canonical_match);
        assert_eq!([2u16, 3, 4][..], command_matches_result.fuzzy_matches[..]);
    }

    #[test]
    #[should_panic(
        expected = "trying to overwrite existing canonical match true with new match false"
    )]
    fn command_matches_union_test_with_conflict() {
        CommandMatches::new_canonical(true).union(CommandMatches::new_canonical(false));
    }

    #[test]
    fn command_matches_union_with_overwrite_test() {
        assert_eq!(
            Some(2u16),
            CommandMatches::new_canonical(1u16)
                .union_with_overwrite(CommandMatches::new_canonical(2u8))
                .canonical_match,
        );
    }

    #[test]
    fn command_matches_take_best_match_test() {
        assert_eq!(
            Some(true),
            CommandMatches::new_canonical(true).take_best_match(),
        );
        assert_eq!(
            Some(true),
            CommandMatches::new_fuzzy(true).take_best_match(),
        );

        {
            let mut command_matches = CommandMatches::new_canonical(true);
            command_matches.push_fuzzy(false);
            assert_eq!(Some(true), command_matches.take_best_match());
        }

        assert_eq!(
            Option::<bool>::None,
            CommandMatches::<bool>::default().take_best_match(),
        );
    }
}