initiative_core/app/
mod.rs

1pub use command::{
2    AppCommand, Autocomplete, AutocompleteSuggestion, Command, CommandAlias, CommandMatches,
3    ContextAwareParse, Runnable,
4};
5pub use meta::AppMeta;
6
7mod command;
8mod meta;
9
10use crate::storage::backup::{import, BackupData};
11use crate::utils::CaseInsensitiveStr;
12use initiative_macros::motd;
13
14/// The application wrapper. Its inner [`AppMeta`] object holds metadata associated with the
15/// application, including ephemeral storage of journal entries and the object representing the
16/// underlying data storage.
17///
18/// The main methods of interest are [`App::command`], called when a command is run by the user,
19/// and [`App::autocomplete`], called to get a list of suggestions for a given input.
20#[derive(Debug)]
21pub struct App {
22    meta: AppMeta,
23}
24
25/// An event that can occur while the app is running that may require special handling by the UI.
26#[derive(Debug)]
27pub enum Event {
28    /// The user typed the `export` command and the journal backup is ready to download.
29    Export(BackupData),
30
31    /// The user typed the `import` command and should be prompted to select a file to import.
32    Import,
33}
34
35impl App {
36    pub fn new(meta: AppMeta) -> App {
37        App { meta }
38    }
39
40    /// Initialize a running application. This is done as a separate step from the constructor
41    /// because it runs asynchronously. Its purpose, in turn, is to trigger the underlying data
42    /// store to initialize, which may involve opening a database connection.
43    pub async fn init(&mut self) -> &'static str {
44        self.meta.repository.init().await;
45        let (motd, motd_len) = motd!("! Local storage is not available in your browser. You will be able to use initiative.sh, but anything you save will not persist beyond this session.");
46
47        if self.meta.repository.data_store_enabled() {
48            &motd[..motd_len]
49        } else {
50            motd
51        }
52    }
53
54    /// The user typed an input and pressed Enter. What happens?
55    ///
56    /// On success or failure, returns a String that can be displayed back to the user.
57    pub async fn command(&mut self, input: &str) -> Result<String, String> {
58        Command::parse_input_irrefutable(input, &self.meta)
59            .await
60            .run(input, &mut self.meta)
61            .await
62    }
63
64    /// The user has updated their input and a new set of suggestions should be populated. This
65    /// consists of a `Vec` of tuples; the first entry being the text that the user is suggested to
66    /// type, the second being a brief (1-3--word) description of what that input will do. `Cow` is
67    /// used here to allow either `String` or `&'static str`, whatever is appropriate to a given
68    /// case.
69    ///
70    /// Returns a maximum of 10 results.
71    pub async fn autocomplete(&self, input: &str) -> Vec<AutocompleteSuggestion> {
72        let mut suggestions: Vec<_> = Command::autocomplete(input, &self.meta).await;
73        suggestions.sort_by(|a, b| a.term.cmp_ci(&b.term));
74        suggestions.truncate(10);
75        suggestions
76    }
77
78    /// The part of the import flow that occurs after the user selects a file in response to the
79    /// [`Event::Import`].
80    pub async fn bulk_import(&mut self, data: BackupData) -> Result<String, String> {
81        import(&mut self.meta.repository, data)
82            .await
83            .map(|stats| stats.to_string())
84            .map_err(|_| "Failed to import.".to_string())
85    }
86}