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() -> Result<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  Ok(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().unwrap();
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
103        .register(Box::new(MockPlugin::new("timing", "timing")))
104        .unwrap();
105      registry.register(Box::new(MockPlugin::new("doing", "doing"))).unwrap();
106
107      let formats = registry.available_formats();
108
109      assert_eq!(formats, vec!["doing", "timing"]);
110    }
111  }
112
113  mod registry_register {
114    use pretty_assertions::assert_eq;
115
116    use super::*;
117
118    #[test]
119    fn it_adds_plugin_to_registry() {
120      let mut registry = Registry::<dyn ImportPlugin>::new();
121
122      registry.register(Box::new(MockPlugin::new("doing", "doing"))).unwrap();
123
124      assert_eq!(registry.available_formats(), vec!["doing"]);
125    }
126
127    #[test]
128    fn it_returns_error_on_invalid_trigger_pattern() {
129      let mut registry = Registry::<dyn ImportPlugin>::new();
130
131      let result = registry.register(Box::new(MockPlugin::new("bad", "(?invalid")));
132
133      assert!(result.is_err());
134      assert!(result.unwrap_err().to_string().contains("invalid trigger pattern"));
135    }
136  }
137
138  mod registry_resolve {
139    use pretty_assertions::assert_eq;
140
141    use super::*;
142
143    #[test]
144    fn it_matches_exact_format_name() {
145      let mut registry = Registry::<dyn ImportPlugin>::new();
146      registry.register(Box::new(MockPlugin::new("doing", "doing"))).unwrap();
147
148      let plugin = registry.resolve("doing").unwrap();
149
150      assert_eq!(plugin.name(), "doing");
151    }
152
153    #[test]
154    fn it_matches_case_insensitively() {
155      let mut registry = Registry::<dyn ImportPlugin>::new();
156      registry.register(Box::new(MockPlugin::new("doing", "doing"))).unwrap();
157
158      assert!(registry.resolve("DOING").is_some());
159      assert!(registry.resolve("Doing").is_some());
160    }
161
162    #[test]
163    fn it_returns_none_for_unknown_format() {
164      let mut registry = Registry::<dyn ImportPlugin>::new();
165      registry.register(Box::new(MockPlugin::new("doing", "doing"))).unwrap();
166
167      assert!(registry.resolve("csv").is_none());
168    }
169
170    #[test]
171    fn it_does_not_match_partial_strings() {
172      let mut registry = Registry::<dyn ImportPlugin>::new();
173      registry.register(Box::new(MockPlugin::new("doing", "doing"))).unwrap();
174
175      assert!(registry.resolve("doingx").is_none());
176      assert!(registry.resolve("xdoing").is_none());
177    }
178  }
179}