sequent_repl/commands/
save.rs

1//! Saving of the current scenario to a YAML document.
2
3use crate::commands::prompt::YesNo;
4use crate::Context;
5use sequent::persistence::yaml;
6use sequent::SimulationError;
7use revolver::command::{
8    ApplyCommandError, ApplyOutcome, Command, Description, Example, NamedCommandParser,
9    ParseCommandError,
10};
11use revolver::looper::Looper;
12use revolver::terminal::Terminal;
13use serde::ser::Serialize;
14use std::borrow::Cow;
15use std::marker::PhantomData;
16use std::path::PathBuf;
17
18/// Command to save the scenario to a user-specified output file. If the file exists, a yes/no prompt
19/// will be presented before overwriting it.
20pub struct Save<S, C> {
21    path: String,
22    __phantom_data: PhantomData<(S, C)>
23}
24
25impl<S, C> Save<S, C> {
26    pub fn new(path: String) -> Self {
27        Self {
28            path,
29            __phantom_data: PhantomData::default()
30        }
31    }
32}
33
34impl<S: Clone + Serialize, C: Context<State = S>, T: Terminal> Command<T> for Save<S, C> {
35    type Context = C;
36    type Error = SimulationError<S>;
37
38    fn apply(
39        &mut self,
40        looper: &mut Looper<C, SimulationError<S>, T>,
41    ) -> Result<ApplyOutcome, ApplyCommandError<SimulationError<S>>> {
42        let path = PathBuf::from(&self.path);
43        if path.exists() {
44            let response = looper
45                .terminal()
46                .read_from_str_default("Output file exists. Overwrite? [y/N]: ")?;
47
48            if let YesNo::No = response {
49                return Ok(ApplyOutcome::Skipped);
50            }
51        }
52        yaml::write_to_file(looper.context().sim().scenario(), path)
53            .map_err(SimulationError::from)
54            .map_err(ApplyCommandError::Application)?;
55
56        looper
57            .terminal()
58            .print_line(&format!("Saved scenario to '{}'.", self.path))?;
59        Ok(ApplyOutcome::Applied)
60    }
61}
62
63/// Parser for [`Save`].
64pub struct Parser<S, C> {
65    __phantom_data: PhantomData<(S, C)>
66}
67
68impl<S, C> Default for Parser<S, C> {
69    fn default() -> Self {
70        Self {
71            __phantom_data: PhantomData::default()
72        }
73    }
74}
75
76impl<S: Clone + Serialize + 'static, C: Context<State = S> + 'static, T: Terminal> NamedCommandParser<T>
77    for Parser<S, C>
78{
79    type Context = C;
80    type Error = SimulationError<S>;
81
82    fn parse(
83        &self,
84        s: &str,
85    ) -> Result<Box<dyn Command<T, Context = C, Error = SimulationError<S>>>, ParseCommandError> {
86        if s.is_empty() {
87            return Err(ParseCommandError("empty arguments to 'save'".into()));
88        }
89        let path = s.into();
90        Ok(Box::new(Save::new(path)))
91    }
92
93    fn shorthand(&self) -> Option<Cow<'static, str>> {
94        None
95    }
96
97    fn name(&self) -> Cow<'static, str> {
98        "save".into()
99    }
100
101    fn description(&self) -> Description {
102        Description {
103            purpose: "Saves the current scenario to a file.".into(),
104            usage: "<path>".into(),
105            examples: vec![Example {
106                scenario: "save to a file named 'trixie.yaml' in the working directory".into(),
107                command: "trixie.yaml".into(),
108            }],
109        }
110    }
111}
112
113#[cfg(test)]
114mod tests;