Skip to main content

parade_rs/action/
parse.rs

1use std::{
2    fmt::{Display, Formatter},
3    str::FromStr,
4};
5
6use itertools::Itertools;
7use serde::{Deserialize, Serialize};
8
9use strum::{Display, EnumIter, EnumString, IntoEnumIterator};
10
11use crate::vessel::Article;
12
13/// The name of an [Action], for use in parsing and help messages.
14#[derive(
15    Clone,
16    Copy,
17    Debug,
18    PartialEq,
19    Eq,
20    PartialOrd,
21    Ord,
22    Serialize,
23    Deserialize,
24    EnumIter,
25    EnumString,
26    Display,
27)]
28#[strum(ascii_case_insensitive)]
29pub enum Verb {
30    /// Displays general information on Parade, or information on a specific action or topic.
31    Learn,
32    /// Creates a new vessel adjacent to the user's.
33    Create,
34    /// Takes control of an adjacent vessel.
35    Become,
36    /// Moves the controlled vessel into an adjacent vessel.
37    Enter,
38    /// Moves the controlled vessel out of its parent, into its parent's parent.
39    Leave,
40    /// Moves an adjacent vessel into the controlled vessel.
41    Take,
42    /// Moves a vessel inside the controlled vessel into the controlled vessel's parent.
43    Drop,
44    /// Moves an adjacent vessel into another adjacent vessel.
45    Move,
46    /// Displays a list of the adjacent vessels, or of the vessels inside an adjacent vessel.
47    Look,
48    /// Renames the controlled vessel.
49    Transform,
50    /// Assigns a description to the parent of the controlled vessel.
51    Note,
52    /// Moves the controlled vessel into another vessel, regardless of adjacency.
53    Warp,
54    /// Begins programming a task for an adjacent vessel.
55    Program,
56    /// Executes an adjacent vessel's program.
57    Use,
58    /// Executes a distant vessel's program as another vessel.
59    Cast,
60    /// Exits parade, or stops programming a vessel if the user is doing so.
61    Exit,
62    /// Saves the state of all vessels to a file.
63    Save,
64    /// Loads the state of all vessels from a file.
65    Load,
66    /// Displays the state of all vessels as raw json data.
67    Debug,
68}
69impl Verb {
70    /// Returns the short help text for the given [Verb].
71    pub fn short_help_text(&self) -> String {
72        match self {
73            Verb::Learn =>       r#"Learn [about] [action]:              Display general information on Parade, or information on a specific action or topic."#,
74            Verb::Create =>      r#"Create <vessel>:                     Create a new vessel adjacent to yours."#,
75            Verb::Become =>      r#"Become <vessel>:                     Take control of an adjacent vessel."#,
76            Verb::Enter =>       r#"Enter <vessel>:                      Move your vessel into an adjacent vessel."#,
77            Verb::Leave =>       r#"Leave:                               Move your vessel out of the vessel it is in (unless it is inside itself)."#,
78            Verb::Take =>        r#"Take <vessel>:                       Move an adjacent vessel inside yours."#,
79            Verb::Drop =>        r#"Drop <vessel>:                       Move a vessel inside yours outside, so it is adjacent to yours."#,
80            Verb::Move =>        r#"Move <vessel> <to/into> <vessel>:    Move an adjacent vessel into another adjacent vessel."#,
81            Verb::Look =>        r#"Look [[at/in] <vessel>]:             See a list of the adjacent vessels, or of the vessels inside an adjacent vessel."#,
82            Verb::Transform =>   r#"Transform [to/into] <vessel>:        Change your vessel's name."#,
83            Verb::Note =>        r#"Note [text]:                         Assign a description to the vessel you are inside."#,
84            Verb::Warp =>        r#"Warp [to/into] <vessel>:             Move your vessel into another vessel, anywhere."#,
85            Verb::Program =>     r#"Program [vessel]:                    Begin programming a task for a vessel, which can be activated with Use or Cast."#,
86            Verb::Use =>         r#"Use <vessel>:                        Execute an adjacent vessel's program."#,
87            Verb::Cast =>        r#"Cast <vessel> <at/on> <vessel>:      Executes a distant vessel's program as another vessel."#,
88            Verb::Exit =>        r#"Exit:                                Exit parade, or stop programming a vessel if you're doing so."#,
89            Verb::Save =>        r#"Save [filename]:                     Save the state of all vessels to a file."#,
90            Verb::Load =>        r#"Load [filename]:                     Load the state of all vessels from a file."#,
91            Verb::Debug =>       r#"Debug:                               Display the state of all vessels as raw json data."#,
92        }.to_string()
93    }
94    /// Returns the long help text for the given [Verb].
95    pub fn help_text(&self) -> String {
96        match self {
97            Verb::Learn => r#"Learn [about] [Action]: Learn about Parade.
98
99If used on its own, explains what Parade is, how to use it, and lists the available actions.
100If used with an action, displays more in-depth information on that action.
101
102Action descriptions begin with their parameters.
103Parameters in square brackets (e.g. "[Action]") are optional, whereas those in angle brackets ("<noun>") are required."#,
104            Verb::Create => r#"Create [article] <noun>: Create a new vessel adjacent to yours.
105
106"Adjacent", here, means that it is inside the same parent vessel.
107This action will not work if there is already a vessel in your world with the same name as the vessel you are trying to create. This applies regardless of where in your world the existing vessel is.
108
109Vessel names can contain any number of words: You can create a vessel named "pink pony", and it will be distinct from a vessel named just "pony".
110
111If an article is provided, parade will refer to the vessel using that article: e.g. "the library", "a ghost", etc.
112If no article is provided, parade will refer to the vessel without an article: e.g. "Parade", "Jack the Ripper", etc."#,
113            Verb::Become => r#"Become [article] <noun>: Take control of an adjacent vessel.
114
115"Adjacent", here, means that it is inside the same parent vessel.
116This action will not work if the selected vessel does not exist, or is not adjacent to yours.
117
118This does not change your vessel or the selected vessel in any way, aside from changing which one you are controlling."#,
119            Verb::Enter => r#"Enter [article] <noun>: Move your vessel into an adjacent vessel.
120
121"Adjacent", here, means that it is inside the same parent vessel as yours.
122This action will not work if the selected vessel does not exist, or is not adjacent to yours."#,
123            Verb::Leave => r#"Leave: Move your vessel out of the vessel it is in.
124
125If your vessel is inside itself, this will do nothing."#,
126            Verb::Take => r#"Take [article] <noun>: Move an adjacent vessel inside yours.
127
128"Adjacent", here, means that it is inside the same parent vessel.
129This action will not work if the selected vessel does not exist, or is not adjacent to yours."#,
130            Verb::Drop => r#"Drop [article] <noun>: Move a vessel inside yours outside, so it is adjacent to yours.
131
132"Adjacent", here, means that it is inside the same parent vessel.
133This action will not work if the selected vessel does not exist, or is not inside yours."#,
134            Verb::Move => r#"Move [article] <noun> <preposition> [article] <noun>: Move an adjacent vessel into another adjacent vessel.
135
136"Adjacent", here, means that it is inside the same parent vessel.
137This action will not work if either of the selected vessels do not exist, or are not adjacent to yours.
138
139Because vessel names can contain any number of words, you must separate the two vessel names a preposition.
140The prepositions available here are "to" and "into"."#,
141            Verb::Look => r#"Look [[preposition] [article] <noun>]: See a list of the vessels adjacent to yours or inside the selected adjacent vessel.
142
143"Adjacent", here, means that it is inside the same parent vessel.
144This action will not work if the selected vessel does not exist, or is not adjacent to yours.
145
146If used on its own, this will display a list of the adjacent vessels.
147If used with a noun, this will display a list of the vessels inside it.
148
149The prepositions available here are "at" and "in"."#,
150            Verb::Transform => r#"Transform [preposition] [article] <noun>: Change your vessel's name to the provided name.
151
152This action will not work if there is already a vessel in your world with the same name as the one you are trying to take. This applies regardless of where in your world the existing vessel is.
153
154The prepositions available here are "into" and "to"."#,
155            Verb::Note => r#"Note [text]: Assign a description to the vessel you are inside.
156
157The description will be displayed to anyone inside the vessel, or who Looks at it."#,
158            Verb::Warp => r#"Warp [preposition] [article] <noun>: Move your vessel into another vessel, anywhere.
159
160This can be used to move your vessel inside itself.
161
162This action will not work if the selected vessel does not exist.
163
164The prepositions available here are "into" and "to"."#,
165            Verb::Program => r#"Program [article] [noun]: Begin programming a task for a vessel, which can be activated with Use.
166
167If a vessel is provided, it must be adjacent. If no vessel is provided, the parent vessel will be selected.
168
169"Adjacent", here, means that it is inside the same parent vessel.
170This action will not work if the selected vessel does not exist, or is not adjacent to yours.
171
172While programming a vessel, all actions will be added to its "program", instead of being executed normally.
173The only exception to this is Exit, which will cause you to stop programming the vessel - Unless you're currently programming the vessel to program another vessel. You can nest programs as much as you like.
174
175Nouns mentioned in actions you add to a program are evaluated when you run the program, instead of when you add them to it.
176This means that you can reference vessels which do not currently exist or which are not yet accessible."#,
177            Verb::Use => r#"Use [article] <noun>: Execute an adjacent vessel's program.
178
179"Adjacent", here, means that it is inside the same parent vessel.
180This action will not work if the selected vessel does not exist, if it is not adjacent to yours, or it if does not have a program.
181
182No actions used by the program will display information to you, and the Save and Load actions will do nothing.
183When the program executes, it executes with control of the program's vessel, not yours. It can still, however, Become other vessels."#,
184            Verb::Cast => r#"Cast [article] <noun> <preposition> [article] <noun>: Execute a vessel's program as an adjacent vessel.
185
186"Adjacent", here, means that it is inside the same parent vessel.
187This action will not work if either of the selected vessels do not exist, if the first vessel does not have a program, or if the second vessel is not adjacent to yours.
188
189The first named vessel is the vessel whose program will be cast, and the second named vessel is the vessel which will have the program cast on/as it.
190
191This action otherwise functions as Use.
192
193The prepositions available here are "at" and "on"."#,
194            Verb::Exit => r#"Exit: Exit parade, or stop programming a vessel if you're doing so.
195
196Exits parade if you are outside of the Program interface.
197If you are inside the Program interface, exits it.
198If you are programming a vessel to program another vessel, this will add "Exit" to the former vessel's program.
199Parade-rs supports unlimited nesting of programs."#,
200            Verb::Save => r#"Save [text]: Save the state of all vessels to a file.
201
202If a name is provided, this will save to the file with that name.
203Otherwise, the name "parade_save.json" will be used."#,
204            Verb::Load => r#"Load [text]: Load the state of all vessels from a file.
205
206If a name is provided, this will save to the file with that name.
207Otherwise, the name "parade_save.json" will be used."#,
208            Verb::Debug => r#"Debug: Display the state of all vessels as raw json data."#,
209        }.to_string()
210    }
211    /// Returns the a basic help message, alongside the [Self::short_help_text] for every [Verb].
212    pub fn help_list_text() -> String {
213        Verb::iter().fold(r#"This is Parade (or, specifically, parade-rs).
214
215Parade is an experimental interactive-fiction playground, or maybe a filesystem or something.
216Parade-rs is based on paradise (https://wiki.xxiivv.com/site/paradise.html) and parade (https://wiki.xxiivv.com/site/parade.html).
217
218You interact with parade using the following actions:
219"#.to_string(), |acc, topic| {
220            format!("{acc}- {}\n", topic.short_help_text())
221        })
222    }
223}
224
225/// An action to be executed by the system interface.
226#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
227pub enum Action {
228    /// Creates a new vessel adjacent to the user's.
229    Create(VesselRequest),
230    /// Takes control of an adjacent vessel.
231    Become(VesselRequest),
232    /// Moves the controlled vessel into an adjacent vessel.
233    Enter(VesselRequest),
234    /// Moves the controlled vessel out of its parent, into its parent's parent.
235    Leave,
236    /// Moves an adjacent vessel into the controlled vessel.
237    Take(VesselRequest),
238    /// Moves a vessel inside the controlled vessel into the controlled vessel's parent.
239    Drop(VesselRequest),
240    /// Moves an adjacent vessel into another adjacent vessel.
241    Move {
242        /// The optional [Article] with which to refer to the object being moved.
243        object_article: Option<Article>,
244        /// The name/key of the object being moved.
245        object_name: String,
246        /// The preposition separating the two [crate::vessel::Vessel]s.
247        preposition: IntoPrep,
248        /// The optional [Article] with which to refer to the target being moved to.
249        target_article: Option<Article>,
250        /// The name/key of the target being moved to.
251        target_name: String,
252    },
253    /// Displays a list of the adjacent vessels, or of the vessels inside an adjacent vessel.
254    Look {
255        /// An optional preposition before the [crate::vessel::Vessel] being specified.
256        preposition: Option<AtPrep>,
257        /// An optional set of identifiers for a [crate::vessel::Vessel] to look at/inside.
258        basic: Option<VesselRequest>,
259    },
260    /// Renames the controlled vessel.
261    Transform {
262        /// An optional preposition before the [crate::vessel::Vessel] being specified.
263        preposition: Option<IntoPrep>,
264        /// A set of identifiers for the [crate::vessel::Vessel]'s new name and [Article].
265        basic: VesselRequest,
266    },
267    /// Assigns a description to the parent of the controlled vessel.
268    Note {
269        /// The description text, to which the note will be set.
270        text: Option<String>,
271    },
272    /// Moves the controlled vessel into another vessel, regardless of adjacency.
273    Warp {
274        /// An optional preposition before the [crate::vessel::Vessel] being specified.
275        preposition: Option<IntoPrep>,
276        /// A set of identifiers for the [crate::vessel::Vessel] being moved to.
277        basic: VesselRequest,
278    },
279    /// Begins programming a task for an adjacent vessel.
280    Program(Option<VesselRequest>),
281    /// Executes an adjacent vessel's program.
282    Use(VesselRequest),
283    /// Executes a distant vessel's program as another vessel.
284    Cast {
285        /// A set of identifiers for the [crate::vessel::Vessel] whose program should be run.
286        spell: VesselRequest,
287        /// The preposition separating the two [crate::vessel::Vessel]s.
288        preposition: OnPrep,
289        /// A set of identifiers for the [crate::vessel::Vessel] for the program to run as.
290        target: VesselRequest,
291    },
292    /// Saves the state of all vessels to a file.
293    Save {
294        /// The name of the file to be saved to.
295        filename: Option<String>,
296    },
297    /// Loads the state of all vessels from a file.
298    Load {
299        /// The name of the file to be loaded from.
300        filename: Option<String>,
301    },
302    /// Displays the state of all vessels as raw json data.
303    Debug,
304    /// Exits parade, or stops programming a vessel if the user is doing so.
305    Exit,
306    /// Displays general information on Parade, or information on a specific action.
307    Learn {
308        /// An optional preposition before the help topic being specified.
309        preposition: Option<AboutPrep>,
310        /// The optional help topic, to have its [Verb::help_text] displayed.
311        topic: Option<Verb>,
312    },
313}
314impl Action {
315    /// Wraps a [VesselRequest] in an [Action], if the [Verb] refers to an [Action] that wraps a [VesselRequest].
316    ///
317    /// # Errors
318    ///
319    /// This method fails if the [Verb] refers to an [Action] which does not exclusively wrap a [VesselRequest].
320    /// The viable [Verb]s are:
321    /// - [Verb::Create]
322    /// - [Verb::Become]
323    /// - [Verb::Enter]
324    /// - [Verb::Take]
325    /// - [Verb::Drop]
326    /// - [Verb::Program]
327    /// - [Verb::Use]
328    pub fn from_vessel_request(verb: Verb, basic: VesselRequest) -> Result<Self, Error> {
329        match verb {
330            Verb::Create => Ok(Action::Create(basic)),
331            Verb::Become => Ok(Action::Become(basic)),
332            Verb::Enter => Ok(Action::Enter(basic)),
333            Verb::Take => Ok(Action::Take(basic)),
334            Verb::Drop => Ok(Action::Drop(basic)),
335            Verb::Program => Ok(Action::Program(Some(basic))),
336            Verb::Use => Ok(Action::Use(basic)),
337            Verb::Learn
338            | Verb::Leave
339            | Verb::Move
340            | Verb::Look
341            | Verb::Transform
342            | Verb::Note
343            | Verb::Warp
344            | Verb::Cast
345            | Verb::Exit
346            | Verb::Save
347            | Verb::Load
348            | Verb::Debug => Err(Error::NonBasicAction(verb)),
349        }
350    }
351
352    /// Parse a user-provided action string into an [Action] (or none, if the user entered nothing).
353    ///
354    /// # Errors
355    ///
356    /// This method fails if the string does not match the requirements of the given [Action], or specifies a [Verb] which does not exist.
357    pub fn parse(action_string: &str) -> Result<Option<Action>, Error> {
358        let mut words = action_string.split_whitespace();
359        match words.next() {
360            Some(first_word) => match Verb::from_str(first_word) {
361                Ok(verb) => match verb {
362                    Verb::Learn => match words.next() {
363                        Some(second_word) => {
364                            let preposition = AboutPrep::from_str(second_word);
365                            let topic = if preposition.is_ok() {
366                                match words.next() {
367                                    Some(word) => Verb::from_str(word),
368                                    None => {
369                                        return Ok(Some(Action::Learn {
370                                            preposition: preposition.ok(),
371                                            topic: None,
372                                        }));
373                                    }
374                                }
375                            } else {
376                                Verb::from_str(second_word)
377                            };
378                            match topic {
379                                Ok(help_topic) => Ok(Some(Action::Learn {
380                                    preposition: preposition.ok(),
381                                    topic: Some(help_topic),
382                                })),
383                                Err(_) => Err(Error::UnknownVerb(second_word.to_string())),
384                            }
385                        }
386                        None => Ok(Some(Action::Learn {
387                            preposition: None,
388                            topic: None,
389                        })),
390                    },
391                    Verb::Create
392                    | Verb::Become
393                    | Verb::Enter
394                    | Verb::Take
395                    | Verb::Drop
396                    | Verb::Use => Ok(Some(Action::from_vessel_request(
397                        verb,
398                        VesselRequest::parse(words.next().ok_or(Error::MissingNoun)?, &mut words)?,
399                    )?)),
400                    Verb::Leave => Ok(Some(Action::Leave)),
401                    Verb::Move => match words.next() {
402                        Some(second_word) => {
403                            let VesselRequest {
404                                article: object_article,
405                                name: object_name,
406                            } = VesselRequest::parse(
407                                second_word,
408                                &mut words.take_while_ref(|word| IntoPrep::from_str(word).is_err()),
409                            )?;
410
411                            let preposition = words
412                                .next()
413                                .and_then(|prep_word| IntoPrep::from_str(prep_word).ok())
414                                .ok_or(Error::MissingSecondNoun)?;
415
416                            let VesselRequest {
417                                article: target_article,
418                                name: target_name,
419                            } = VesselRequest::parse(
420                                words.next().ok_or(Error::MissingSecondNoun)?,
421                                &mut words,
422                            )?;
423
424                            Ok(Some(Action::Move {
425                                object_article,
426                                object_name,
427                                preposition,
428                                target_article,
429                                target_name,
430                            }))
431                        }
432                        None => Err(Error::MissingNoun),
433                    },
434                    Verb::Look => match words.next() {
435                        Some(second_word) => {
436                            let (preposition, basic) = VesselRequest::parse_with_prep::<AtPrep, _>(
437                                second_word,
438                                &mut words,
439                            )?;
440                            Ok(Some(Action::Look {
441                                preposition,
442                                basic: Some(basic),
443                            }))
444                        }
445                        None => Ok(Some(Action::Look {
446                            preposition: None,
447                            basic: None,
448                        })),
449                    },
450                    Verb::Transform => {
451                        let (preposition, basic) = VesselRequest::parse_with_prep::<IntoPrep, _>(
452                            words.next().ok_or(Error::MissingNoun)?,
453                            &mut words,
454                        )?;
455                        Ok(Some(Action::Transform { preposition, basic }))
456                    }
457                    Verb::Note => Ok(Some(Action::Note {
458                        text: join_or_none(&mut words, " "),
459                    })),
460                    Verb::Warp => {
461                        let (preposition, basic) = VesselRequest::parse_with_prep::<IntoPrep, _>(
462                            words.next().ok_or(Error::MissingNoun)?,
463                            &mut words,
464                        )?;
465                        Ok(Some(Action::Warp { preposition, basic }))
466                    }
467                    Verb::Program => {
468                        match words
469                            .next()
470                            .ok_or(Error::MissingNoun)
471                            .and_then(|second_word| VesselRequest::parse(second_word, &mut words))
472                        {
473                            Ok(vessel_request) => {
474                                Ok(Some(Action::from_vessel_request(verb, vessel_request)?))
475                            }
476                            Err(Error::MissingNoun) => Ok(Some(Action::Program(None))),
477                            Err(e) => Err(e),
478                        }
479                    }
480                    Verb::Cast => match words.next() {
481                        Some(second_word) => {
482                            let spell = VesselRequest::parse(
483                                second_word,
484                                &mut words.take_while_ref(|word| OnPrep::from_str(word).is_err()),
485                            )?;
486
487                            let preposition = words
488                                .next()
489                                .and_then(|prep_word| OnPrep::from_str(prep_word).ok())
490                                .ok_or(Error::MissingSecondNoun)?;
491
492                            let target = VesselRequest::parse(
493                                words.next().ok_or(Error::MissingSecondNoun)?,
494                                &mut words,
495                            )?;
496
497                            Ok(Some(Action::Cast {
498                                spell,
499                                preposition,
500                                target,
501                            }))
502                        }
503                        None => Err(Error::MissingNoun),
504                    },
505                    Verb::Exit => Ok(Some(Action::Exit)),
506                    Verb::Save => Ok(Some(Action::Save {
507                        filename: join_or_none(&mut words, " "),
508                    })),
509                    Verb::Load => Ok(Some(Action::Load {
510                        filename: join_or_none(&mut words, " "),
511                    })),
512                    Verb::Debug => Ok(Some(Action::Debug)),
513                },
514                Err(_) => Err(Error::UnknownVerb(first_word.to_string())),
515            },
516            None => Ok(None),
517        }
518    }
519}
520
521/// The errors that can be encountered when parsing an [Action].
522#[derive(Debug)]
523pub enum Error {
524    /// Occurs when the [Action] requires a noun, but none is provided.
525    MissingNoun,
526    /// Occurs when the [Action] requires two nouns, but only one is provided.
527    MissingSecondNoun,
528    /// Occurs when the input does not match any [Verb].
529    UnknownVerb(String),
530    /// Occurs when the system attempts to wrap a [VesselRequest] with an [Action] variant that does not support it.
531    NonBasicAction(Verb),
532}
533impl Display for Error {
534    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
535        match self {
536            Self::MissingNoun => write!(f, "This action requires a noun."),
537            Self::MissingSecondNoun => write!(
538                f,
539                "This action requires two nouns. Did you remember to delimit them?"
540            ),
541            Self::UnknownVerb(verb_string) => {
542                write!(f, "Unrecognized action: \"{verb_string}\".")
543            }
544            Self::NonBasicAction(verb) => write!(
545                f,
546                "Program tried to parse non-basic action \"{verb}\" as basic."
547            ),
548        }
549    }
550}
551impl std::error::Error for Error {}
552
553/// Signifies that the given enum is a grammatical preposition.
554pub trait Preposition: FromStr {}
555
556/// The prepositions for moving, transforming, or warping to/into something.
557#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, EnumString)]
558#[strum(ascii_case_insensitive)]
559pub enum IntoPrep {
560    /// The English word "Into".
561    Into,
562    /// The English word "To".
563    To,
564}
565impl Preposition for IntoPrep {}
566
567/// The prepositions for looking at/in something.
568#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, EnumString)]
569#[strum(ascii_case_insensitive)]
570pub enum AtPrep {
571    /// The English word "At".
572    At,
573    /// The English word "In".
574    In,
575}
576impl Preposition for AtPrep {}
577
578/// The prepositions for casting something at/on something.
579#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, EnumString)]
580#[strum(ascii_case_insensitive)]
581pub enum OnPrep {
582    /// The English word "At".
583    At,
584    /// The English word "On".
585    On,
586}
587impl Preposition for OnPrep {}
588
589/// The preposition "About".
590#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, EnumString)]
591#[strum(ascii_case_insensitive)]
592pub enum AboutPrep {
593    /// The English word "About".
594    About,
595}
596impl Preposition for AboutPrep {}
597
598/// A basic set of [Action] parameters, indicating a [crate::vessel::Vessel] by name and (optionally) [Article].
599#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
600pub struct VesselRequest {
601    /// The [Article] with which to refer to the [crate::vessel::Vessel].
602    pub article: Option<Article>,
603    /// The name/key of the [crate::vessel::Vessel] being indicated.
604    pub name: String,
605}
606impl VesselRequest {
607    /// Parses a [VesselRequest] from the given second and following words of an action string.
608    /// (The first word is always the [Verb].)
609    pub fn parse<'a, T: Iterator<Item = &'a str>>(
610        second_word: &'a str,
611        words: &mut T,
612    ) -> Result<Self, Error> {
613        let article = Article::from_str(second_word).ok();
614        let name = join_or_none(
615            &mut if article.is_some() {
616                None
617            } else {
618                Some(second_word)
619            }
620            .into_iter()
621            .chain(words),
622            " ",
623        )
624        .ok_or(Error::MissingNoun)?;
625        Ok(Self { article, name })
626    }
627    /// Parses a [VesselRequest] and optional [Preposition] from the given second and following words of an action string.
628    /// (The first word is always the [Verb].)
629    pub fn parse_with_prep<'a, P: Preposition, T: Iterator<Item = &'a str>>(
630        second_word: &'a str,
631        words: &mut T,
632    ) -> Result<(Option<P>, Self), Error> {
633        let preposition = P::from_str(second_word).ok();
634        let third_word = if preposition.is_none() {
635            second_word
636        } else {
637            words.next().ok_or(Error::MissingNoun)?
638        };
639        Ok((preposition, Self::parse(third_word, words)?))
640    }
641}
642
643fn join_or_none<'a, T: Iterator<Item = &'a str>>(words: &mut T, sep: &str) -> Option<String> {
644    let string = words.join(sep);
645    if string.is_empty() {
646        None
647    } else {
648        Some(string)
649    }
650}