Skip to main content

doing_plugins/
import.rs

1mod calendar;
2mod doing;
3mod json;
4mod timing;
5
6use std::path::Path;
7
8use doing_error::Result;
9use doing_taskpaper::Entry;
10
11use crate::{Plugin, Registry};
12
13/// The interface that import format plugins must implement.
14///
15/// Each plugin provides a trigger pattern used to match `--type FORMAT` values
16/// and an import method that reads entries from a file path.
17pub trait ImportPlugin: Plugin {
18  /// Import entries from the file at `path`.
19  fn import(&self, path: &Path) -> Result<Vec<Entry>>;
20}
21
22/// Build the default import registry with all built-in import plugins.
23pub fn default_registry() -> Registry<dyn ImportPlugin> {
24  let mut registry: Registry<dyn ImportPlugin> = Registry::new();
25  registry.register(Box::new(calendar::CalendarImport));
26  registry.register(Box::new(doing::DoingImport));
27  registry.register(Box::new(json::JsonImport));
28  registry.register(Box::new(timing::TimingImport));
29  registry
30}
31
32#[cfg(test)]
33mod test {
34  use std::path::Path;
35
36  use super::*;
37  use crate::PluginSettings;
38
39  struct MockPlugin {
40    name: String,
41    trigger: String,
42  }
43
44  impl MockPlugin {
45    fn new(name: &str, trigger: &str) -> Self {
46      Self {
47        name: name.into(),
48        trigger: trigger.into(),
49      }
50    }
51  }
52
53  impl Plugin for MockPlugin {
54    fn name(&self) -> &str {
55      &self.name
56    }
57
58    fn settings(&self) -> PluginSettings {
59      PluginSettings {
60        trigger: self.trigger.clone(),
61      }
62    }
63  }
64
65  impl ImportPlugin for MockPlugin {
66    fn import(&self, _path: &Path) -> Result<Vec<Entry>> {
67      Ok(Vec::new())
68    }
69  }
70
71  mod default_registry {
72    use pretty_assertions::assert_eq;
73
74    use super::*;
75
76    #[test]
77    fn it_registers_all_built_in_plugins() {
78      let registry = default_registry();
79
80      assert_eq!(
81        registry.available_formats(),
82        vec!["calendar", "doing", "json", "timing"]
83      );
84    }
85  }
86
87  mod registry_available_formats {
88    use pretty_assertions::assert_eq;
89
90    use super::*;
91
92    #[test]
93    fn it_returns_empty_for_new_registry() {
94      let registry = Registry::<dyn ImportPlugin>::new();
95
96      assert!(registry.available_formats().is_empty());
97    }
98
99    #[test]
100    fn it_returns_sorted_format_names() {
101      let mut registry = Registry::<dyn ImportPlugin>::new();
102      registry.register(Box::new(MockPlugin::new("timing", "timing")));
103      registry.register(Box::new(MockPlugin::new("doing", "doing")));
104
105      let formats = registry.available_formats();
106
107      assert_eq!(formats, vec!["doing", "timing"]);
108    }
109  }
110
111  mod registry_register {
112    use pretty_assertions::assert_eq;
113
114    use super::*;
115
116    #[test]
117    fn it_adds_plugin_to_registry() {
118      let mut registry = Registry::<dyn ImportPlugin>::new();
119
120      registry.register(Box::new(MockPlugin::new("doing", "doing")));
121
122      assert_eq!(registry.available_formats(), vec!["doing"]);
123    }
124
125    #[test]
126    #[should_panic(expected = "invalid trigger pattern")]
127    fn it_panics_on_invalid_trigger_pattern() {
128      let mut registry = Registry::<dyn ImportPlugin>::new();
129
130      registry.register(Box::new(MockPlugin::new("bad", "(?invalid")));
131    }
132  }
133
134  mod registry_resolve {
135    use pretty_assertions::assert_eq;
136
137    use super::*;
138
139    #[test]
140    fn it_matches_exact_format_name() {
141      let mut registry = Registry::<dyn ImportPlugin>::new();
142      registry.register(Box::new(MockPlugin::new("doing", "doing")));
143
144      let plugin = registry.resolve("doing").unwrap();
145
146      assert_eq!(plugin.name(), "doing");
147    }
148
149    #[test]
150    fn it_matches_case_insensitively() {
151      let mut registry = Registry::<dyn ImportPlugin>::new();
152      registry.register(Box::new(MockPlugin::new("doing", "doing")));
153
154      assert!(registry.resolve("DOING").is_some());
155      assert!(registry.resolve("Doing").is_some());
156    }
157
158    #[test]
159    fn it_returns_none_for_unknown_format() {
160      let mut registry = Registry::<dyn ImportPlugin>::new();
161      registry.register(Box::new(MockPlugin::new("doing", "doing")));
162
163      assert!(registry.resolve("csv").is_none());
164    }
165
166    #[test]
167    fn it_does_not_match_partial_strings() {
168      let mut registry = Registry::<dyn ImportPlugin>::new();
169      registry.register(Box::new(MockPlugin::new("doing", "doing")));
170
171      assert!(registry.resolve("doingx").is_none());
172      assert!(registry.resolve("xdoing").is_none());
173    }
174  }
175}