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        #[cfg(feature = "native-export")]
139        registry.register(crate::formats::pdf::PdfFormat::default());
140        #[cfg(feature = "native-export")]
141        registry.register(crate::formats::png::PngFormat::default());
142        registry.register(crate::formats::markdown::MarkdownFormat);
143        registry.register(crate::formats::rfc_xml::RfcXmlFormat);
144        registry.register(crate::formats::tag::TagFormat);
145        registry.register(crate::formats::treeviz::TreevizFormat);
146        registry.register(crate::formats::linetreeviz::LinetreevizFormat);
147
148        registry
149    }
150}
151
152impl Default for FormatRegistry {
153    fn default() -> Self {
154        Self::with_defaults()
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use crate::format::Format;
162    use lex_core::lex::ast::{ContentItem, Document, Paragraph};
163
164    // Test format
165    struct TestFormat;
166    impl Format for TestFormat {
167        fn name(&self) -> &str {
168            "test"
169        }
170        fn description(&self) -> &str {
171            "Test format"
172        }
173        fn supports_parsing(&self) -> bool {
174            true
175        }
176        fn supports_serialization(&self) -> bool {
177            true
178        }
179        fn parse(&self, _source: &str) -> Result<Document, FormatError> {
180            Ok(Document::with_content(vec![ContentItem::Paragraph(
181                Paragraph::from_line("test".to_string()),
182            )]))
183        }
184        fn serialize(&self, _doc: &Document) -> Result<String, FormatError> {
185            Ok("test output".to_string())
186        }
187    }
188
189    #[test]
190    fn test_registry_creation() {
191        let registry = FormatRegistry::new();
192        assert_eq!(registry.formats.len(), 0);
193    }
194
195    #[test]
196    fn test_registry_register() {
197        let mut registry = FormatRegistry::new();
198        registry.register(TestFormat);
199
200        assert!(registry.has("test"));
201        assert_eq!(registry.list_formats(), vec!["test"]);
202    }
203
204    #[test]
205    fn test_registry_get() {
206        let mut registry = FormatRegistry::new();
207        registry.register(TestFormat);
208
209        let format = registry.get("test");
210        assert!(format.is_ok());
211        assert_eq!(format.unwrap().name(), "test");
212    }
213
214    #[test]
215    fn test_registry_get_nonexistent() {
216        let registry = FormatRegistry::new();
217        let result = registry.get("nonexistent");
218        assert!(result.is_err());
219    }
220
221    #[test]
222    fn test_registry_has() {
223        let mut registry = FormatRegistry::new();
224        registry.register(TestFormat);
225
226        assert!(registry.has("test"));
227        assert!(!registry.has("nonexistent"));
228    }
229
230    #[test]
231    fn test_registry_parse() {
232        let mut registry = FormatRegistry::new();
233        registry.register(TestFormat);
234
235        let result = registry.parse("input", "test");
236        assert!(result.is_ok());
237    }
238
239    #[test]
240    fn test_registry_parse_not_found() {
241        let registry = FormatRegistry::new();
242
243        let result = registry.parse("input", "nonexistent");
244        assert!(result.is_err());
245        match result.unwrap_err() {
246            FormatError::FormatNotFound(name) => assert_eq!(name, "nonexistent"),
247            _ => panic!("Expected FormatNotFound error"),
248        }
249    }
250
251    #[test]
252    fn test_registry_serialize() {
253        let mut registry = FormatRegistry::new();
254        registry.register(TestFormat);
255
256        let doc = Document::with_content(vec![ContentItem::Paragraph(Paragraph::from_line(
257            "Hello".to_string(),
258        ))]);
259
260        let result = registry.serialize(&doc, "test");
261        assert!(result.is_ok());
262        assert_eq!(result.unwrap(), "test output");
263    }
264
265    #[test]
266    fn test_registry_serialize_not_found() {
267        let registry = FormatRegistry::new();
268        let doc = Document::with_content(vec![]);
269
270        let result = registry.serialize(&doc, "nonexistent");
271        assert!(result.is_err());
272        match result.unwrap_err() {
273            FormatError::FormatNotFound(name) => assert_eq!(name, "nonexistent"),
274            _ => panic!("Expected FormatNotFound error"),
275        }
276    }
277
278    #[test]
279    fn test_registry_serialize_with_options_default_behavior() {
280        let mut registry = FormatRegistry::new();
281        registry.register(TestFormat);
282
283        let doc = Document::with_content(vec![ContentItem::Paragraph(Paragraph::from_line(
284            "Hello".to_string(),
285        ))]);
286        let mut options = HashMap::new();
287        options.insert("unused".to_string(), "true".to_string());
288
289        let result = registry.serialize_with_options(&doc, "test", &options);
290        assert!(result.is_err());
291    }
292
293    #[test]
294    fn test_registry_list_formats() {
295        let mut registry = FormatRegistry::new();
296        registry.register(TestFormat);
297
298        let formats = registry.list_formats();
299        assert_eq!(formats.len(), 1);
300        assert_eq!(formats[0], "test");
301    }
302
303    #[test]
304    fn test_registry_with_defaults() {
305        let registry = FormatRegistry::with_defaults();
306        assert!(registry.has("lex"));
307        assert!(registry.has("markdown"));
308        assert!(registry.has("tag"));
309        assert!(registry.has("treeviz"));
310    }
311
312    #[test]
313    fn test_registry_default_trait() {
314        let registry = FormatRegistry::default();
315        assert!(registry.has("lex"));
316        assert!(registry.has("markdown"));
317        assert!(registry.has("tag"));
318        assert!(registry.has("treeviz"));
319    }
320
321    #[test]
322    fn test_registry_replace_format() {
323        let mut registry = FormatRegistry::new();
324        registry.register(TestFormat);
325        registry.register(TestFormat); // Replace
326
327        assert_eq!(registry.list_formats().len(), 1);
328    }
329
330    #[test]
331    fn test_detect_format_from_filename() {
332        let registry = FormatRegistry::with_defaults();
333
334        // Test lex extension
335        assert_eq!(
336            registry.detect_format_from_filename("doc.lex"),
337            Some("lex".to_string())
338        );
339        assert_eq!(
340            registry.detect_format_from_filename("/path/to/file.lex"),
341            Some("lex".to_string())
342        );
343
344        // Test tag extensions
345        assert_eq!(
346            registry.detect_format_from_filename("doc.tag"),
347            Some("tag".to_string())
348        );
349        assert_eq!(
350            registry.detect_format_from_filename("doc.xml"),
351            Some("tag".to_string())
352        );
353
354        // Test treeviz extensions
355        assert_eq!(
356            registry.detect_format_from_filename("doc.tree"),
357            Some("treeviz".to_string())
358        );
359        assert_eq!(
360            registry.detect_format_from_filename("doc.treeviz"),
361            Some("treeviz".to_string())
362        );
363
364        // Test unknown extension
365        assert_eq!(registry.detect_format_from_filename("doc.unknown"), None);
366
367        // Test no extension
368        assert_eq!(registry.detect_format_from_filename("doc"), None);
369    }
370
371    #[test]
372    fn test_detect_format_case_sensitive() {
373        let registry = FormatRegistry::with_defaults();
374
375        // Extensions are case-sensitive by default (as returned by Path::extension())
376        assert_eq!(
377            registry.detect_format_from_filename("doc.lex"),
378            Some("lex".to_string())
379        );
380        // Note: On some systems, extensions might be case-insensitive
381        // but we treat them as case-sensitive for consistency
382    }
383}