Skip to main content

lex_babel/
registry.rs

1//! Format registry for format discovery and selection
2//!
3//! This module provides a centralized registry for all available formats.
4//! Formats can be registered and retrieved by name.
5
6use crate::error::FormatError;
7use crate::format::{Format, SerializedDocument};
8use lex_core::lex::ast::Document;
9use std::collections::HashMap;
10
11/// Registry of document formats
12///
13/// Provides a centralized registry for all available formats.
14/// Formats can be registered and retrieved by name.
15///
16/// # Examples
17///
18/// ```ignore
19/// let mut registry = FormatRegistry::new();
20/// registry.register(MyFormat);
21///
22/// let format = registry.get("my-format")?;
23/// let doc = format.parse("source text")?;
24/// ```
25pub struct FormatRegistry {
26    formats: HashMap<String, Box<dyn Format>>,
27}
28
29impl FormatRegistry {
30    /// Create a new empty registry
31    pub fn new() -> Self {
32        FormatRegistry {
33            formats: HashMap::new(),
34        }
35    }
36
37    /// Register a format
38    ///
39    /// If a format with the same name already exists, it will be replaced.
40    pub fn register<F: Format + 'static>(&mut self, format: F) {
41        self.formats
42            .insert(format.name().to_string(), Box::new(format));
43    }
44
45    /// Get a format by name
46    pub fn get(&self, name: &str) -> Result<&dyn Format, FormatError> {
47        self.formats
48            .get(name)
49            .map(|f| f.as_ref())
50            .ok_or_else(|| FormatError::FormatNotFound(name.to_string()))
51    }
52
53    /// Check if a format exists
54    pub fn has(&self, name: &str) -> bool {
55        self.formats.contains_key(name)
56    }
57
58    /// List all available format names (sorted)
59    pub fn list_formats(&self) -> Vec<String> {
60        let mut names: Vec<_> = self.formats.keys().cloned().collect();
61        names.sort();
62        names
63    }
64
65    /// Detect format from filename based on file extension
66    ///
67    /// Returns the format name if a matching extension is found, or None otherwise.
68    ///
69    /// # Examples
70    ///
71    /// ```ignore
72    /// let registry = FormatRegistry::default();
73    /// assert_eq!(registry.detect_format_from_filename("doc.lex"), Some("lex".to_string()));
74    /// assert_eq!(registry.detect_format_from_filename("doc.md"), Some("markdown".to_string()));
75    /// assert_eq!(registry.detect_format_from_filename("doc.unknown"), None);
76    /// ```
77    pub fn detect_format_from_filename(&self, filename: &str) -> Option<String> {
78        // Extract extension from filename
79        let extension = std::path::Path::new(filename)
80            .extension()
81            .and_then(|ext| ext.to_str())?;
82
83        // Search for a format that supports this extension
84        for format in self.formats.values() {
85            if format.file_extensions().contains(&extension) {
86                return Some(format.name().to_string());
87            }
88        }
89
90        None
91    }
92
93    /// Parse source text using the specified format
94    pub fn parse(&self, source: &str, format: &str) -> Result<Document, FormatError> {
95        let fmt = self.get(format)?;
96        if !fmt.supports_parsing() {
97            return Err(FormatError::NotSupported(format!(
98                "Format '{format}' does not support parsing"
99            )));
100        }
101        fmt.parse(source)
102    }
103
104    /// Serialize a document using the specified format
105    pub fn serialize(&self, doc: &Document, format: &str) -> Result<String, FormatError> {
106        let empty = HashMap::new();
107        match self.serialize_with_options(doc, format, &empty)? {
108            SerializedDocument::Text(text) => Ok(text),
109            SerializedDocument::Binary(_) => Err(FormatError::SerializationError(format!(
110                "Format '{format}' produced binary output when text was expected"
111            ))),
112        }
113    }
114
115    /// Serialize a document using the specified format and options
116    pub fn serialize_with_options(
117        &self,
118        doc: &Document,
119        format: &str,
120        options: &HashMap<String, String>,
121    ) -> Result<SerializedDocument, FormatError> {
122        let fmt = self.get(format)?;
123        if !fmt.supports_serialization() {
124            return Err(FormatError::NotSupported(format!(
125                "Format '{format}' does not support serialization"
126            )));
127        }
128        fmt.serialize_with_options(doc, options)
129    }
130
131    /// Create a registry with default formats
132    pub fn with_defaults() -> Self {
133        let mut registry = Self::new();
134
135        // Register built-in formats
136        registry.register(crate::formats::lex::LexFormat::default());
137        registry.register(crate::formats::html::HtmlFormat::default());
138        registry.register(crate::formats::pdf::PdfFormat::default());
139        registry.register(crate::formats::png::PngFormat::default());
140        registry.register(crate::formats::markdown::MarkdownFormat);
141        registry.register(crate::formats::tag::TagFormat);
142        registry.register(crate::formats::treeviz::TreevizFormat);
143        registry.register(crate::formats::linetreeviz::LinetreevizFormat);
144
145        registry
146    }
147}
148
149impl Default for FormatRegistry {
150    fn default() -> Self {
151        Self::with_defaults()
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158    use crate::format::Format;
159    use lex_core::lex::ast::{ContentItem, Document, Paragraph};
160
161    // Test format
162    struct TestFormat;
163    impl Format for TestFormat {
164        fn name(&self) -> &str {
165            "test"
166        }
167        fn description(&self) -> &str {
168            "Test format"
169        }
170        fn supports_parsing(&self) -> bool {
171            true
172        }
173        fn supports_serialization(&self) -> bool {
174            true
175        }
176        fn parse(&self, _source: &str) -> Result<Document, FormatError> {
177            Ok(Document::with_content(vec![ContentItem::Paragraph(
178                Paragraph::from_line("test".to_string()),
179            )]))
180        }
181        fn serialize(&self, _doc: &Document) -> Result<String, FormatError> {
182            Ok("test output".to_string())
183        }
184    }
185
186    #[test]
187    fn test_registry_creation() {
188        let registry = FormatRegistry::new();
189        assert_eq!(registry.formats.len(), 0);
190    }
191
192    #[test]
193    fn test_registry_register() {
194        let mut registry = FormatRegistry::new();
195        registry.register(TestFormat);
196
197        assert!(registry.has("test"));
198        assert_eq!(registry.list_formats(), vec!["test"]);
199    }
200
201    #[test]
202    fn test_registry_get() {
203        let mut registry = FormatRegistry::new();
204        registry.register(TestFormat);
205
206        let format = registry.get("test");
207        assert!(format.is_ok());
208        assert_eq!(format.unwrap().name(), "test");
209    }
210
211    #[test]
212    fn test_registry_get_nonexistent() {
213        let registry = FormatRegistry::new();
214        let result = registry.get("nonexistent");
215        assert!(result.is_err());
216    }
217
218    #[test]
219    fn test_registry_has() {
220        let mut registry = FormatRegistry::new();
221        registry.register(TestFormat);
222
223        assert!(registry.has("test"));
224        assert!(!registry.has("nonexistent"));
225    }
226
227    #[test]
228    fn test_registry_parse() {
229        let mut registry = FormatRegistry::new();
230        registry.register(TestFormat);
231
232        let result = registry.parse("input", "test");
233        assert!(result.is_ok());
234    }
235
236    #[test]
237    fn test_registry_parse_not_found() {
238        let registry = FormatRegistry::new();
239
240        let result = registry.parse("input", "nonexistent");
241        assert!(result.is_err());
242        match result.unwrap_err() {
243            FormatError::FormatNotFound(name) => assert_eq!(name, "nonexistent"),
244            _ => panic!("Expected FormatNotFound error"),
245        }
246    }
247
248    #[test]
249    fn test_registry_serialize() {
250        let mut registry = FormatRegistry::new();
251        registry.register(TestFormat);
252
253        let doc = Document::with_content(vec![ContentItem::Paragraph(Paragraph::from_line(
254            "Hello".to_string(),
255        ))]);
256
257        let result = registry.serialize(&doc, "test");
258        assert!(result.is_ok());
259        assert_eq!(result.unwrap(), "test output");
260    }
261
262    #[test]
263    fn test_registry_serialize_not_found() {
264        let registry = FormatRegistry::new();
265        let doc = Document::with_content(vec![]);
266
267        let result = registry.serialize(&doc, "nonexistent");
268        assert!(result.is_err());
269        match result.unwrap_err() {
270            FormatError::FormatNotFound(name) => assert_eq!(name, "nonexistent"),
271            _ => panic!("Expected FormatNotFound error"),
272        }
273    }
274
275    #[test]
276    fn test_registry_serialize_with_options_default_behavior() {
277        let mut registry = FormatRegistry::new();
278        registry.register(TestFormat);
279
280        let doc = Document::with_content(vec![ContentItem::Paragraph(Paragraph::from_line(
281            "Hello".to_string(),
282        ))]);
283        let mut options = HashMap::new();
284        options.insert("unused".to_string(), "true".to_string());
285
286        let result = registry.serialize_with_options(&doc, "test", &options);
287        assert!(result.is_err());
288    }
289
290    #[test]
291    fn test_registry_list_formats() {
292        let mut registry = FormatRegistry::new();
293        registry.register(TestFormat);
294
295        let formats = registry.list_formats();
296        assert_eq!(formats.len(), 1);
297        assert_eq!(formats[0], "test");
298    }
299
300    #[test]
301    fn test_registry_with_defaults() {
302        let registry = FormatRegistry::with_defaults();
303        assert!(registry.has("lex"));
304        assert!(registry.has("markdown"));
305        assert!(registry.has("tag"));
306        assert!(registry.has("treeviz"));
307    }
308
309    #[test]
310    fn test_registry_default_trait() {
311        let registry = FormatRegistry::default();
312        assert!(registry.has("lex"));
313        assert!(registry.has("markdown"));
314        assert!(registry.has("tag"));
315        assert!(registry.has("treeviz"));
316    }
317
318    #[test]
319    fn test_registry_replace_format() {
320        let mut registry = FormatRegistry::new();
321        registry.register(TestFormat);
322        registry.register(TestFormat); // Replace
323
324        assert_eq!(registry.list_formats().len(), 1);
325    }
326
327    #[test]
328    fn test_detect_format_from_filename() {
329        let registry = FormatRegistry::with_defaults();
330
331        // Test lex extension
332        assert_eq!(
333            registry.detect_format_from_filename("doc.lex"),
334            Some("lex".to_string())
335        );
336        assert_eq!(
337            registry.detect_format_from_filename("/path/to/file.lex"),
338            Some("lex".to_string())
339        );
340
341        // Test tag extensions
342        assert_eq!(
343            registry.detect_format_from_filename("doc.tag"),
344            Some("tag".to_string())
345        );
346        assert_eq!(
347            registry.detect_format_from_filename("doc.xml"),
348            Some("tag".to_string())
349        );
350
351        // Test treeviz extensions
352        assert_eq!(
353            registry.detect_format_from_filename("doc.tree"),
354            Some("treeviz".to_string())
355        );
356        assert_eq!(
357            registry.detect_format_from_filename("doc.treeviz"),
358            Some("treeviz".to_string())
359        );
360
361        // Test unknown extension
362        assert_eq!(registry.detect_format_from_filename("doc.unknown"), None);
363
364        // Test no extension
365        assert_eq!(registry.detect_format_from_filename("doc"), None);
366    }
367
368    #[test]
369    fn test_detect_format_case_sensitive() {
370        let registry = FormatRegistry::with_defaults();
371
372        // Extensions are case-sensitive by default (as returned by Path::extension())
373        assert_eq!(
374            registry.detect_format_from_filename("doc.lex"),
375            Some("lex".to_string())
376        );
377        // Note: On some systems, extensions might be case-insensitive
378        // but we treat them as case-sensitive for consistency
379    }
380}