1use crate::core::{EditorDocument, EditorError};
7use std::collections::HashMap;
8use std::fmt;
9use std::io::{Read, Write};
10use std::path::Path;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct FormatInfo {
15 pub name: String,
17 pub extensions: Vec<String>,
19 pub mime_type: String,
21 pub description: String,
23 pub supports_styling: bool,
25 pub supports_positioning: bool,
27}
28
29#[derive(Debug, Clone)]
31pub struct FormatOptions {
32 pub encoding: String,
34 pub preserve_formatting: bool,
36 pub custom_options: HashMap<String, String>,
38}
39
40impl Default for FormatOptions {
41 fn default() -> Self {
42 Self {
43 encoding: "UTF-8".to_string(),
44 preserve_formatting: true,
45 custom_options: HashMap::new(),
46 }
47 }
48}
49
50#[derive(Debug)]
52pub struct FormatResult {
53 pub success: bool,
55 pub lines_processed: usize,
57 pub warnings: Vec<String>,
59 pub metadata: HashMap<String, String>,
61}
62
63impl FormatResult {
64 pub fn success(lines_processed: usize) -> Self {
65 Self {
66 success: true,
67 lines_processed,
68 warnings: Vec::new(),
69 metadata: HashMap::new(),
70 }
71 }
72
73 pub fn with_warnings(mut self, warnings: Vec<String>) -> Self {
74 self.warnings = warnings;
75 self
76 }
77
78 pub fn with_metadata(mut self, key: String, value: String) -> Self {
79 self.metadata.insert(key, value);
80 self
81 }
82}
83
84pub trait FormatImporter: fmt::Debug + Send + Sync {
86 fn format_info(&self) -> &FormatInfo;
88
89 fn can_import(&self, extension: &str) -> bool {
91 self.format_info()
92 .extensions
93 .iter()
94 .any(|ext| ext.eq_ignore_ascii_case(extension))
95 }
96
97 fn import_from_reader(
99 &self,
100 reader: &mut dyn Read,
101 options: &FormatOptions,
102 ) -> Result<(EditorDocument, FormatResult), EditorError>;
103
104 fn import_from_path(
106 &self,
107 path: &Path,
108 options: &FormatOptions,
109 ) -> Result<(EditorDocument, FormatResult), EditorError> {
110 let mut file = std::fs::File::open(path)
111 .map_err(|e| EditorError::IoError(format!("Failed to open file: {e}")))?;
112 self.import_from_reader(&mut file, options)
113 }
114
115 fn import_from_string(
117 &self,
118 content: &str,
119 options: &FormatOptions,
120 ) -> Result<(EditorDocument, FormatResult), EditorError> {
121 let mut cursor = std::io::Cursor::new(content.as_bytes());
122 self.import_from_reader(&mut cursor, options)
123 }
124}
125
126pub trait FormatExporter: fmt::Debug + Send + Sync {
128 fn format_info(&self) -> &FormatInfo;
130
131 fn can_export(&self, extension: &str) -> bool {
133 self.format_info()
134 .extensions
135 .iter()
136 .any(|ext| ext.eq_ignore_ascii_case(extension))
137 }
138
139 fn export_to_writer(
141 &self,
142 document: &EditorDocument,
143 writer: &mut dyn Write,
144 options: &FormatOptions,
145 ) -> Result<FormatResult, EditorError>;
146
147 fn export_to_path(
149 &self,
150 document: &EditorDocument,
151 path: &Path,
152 options: &FormatOptions,
153 ) -> Result<FormatResult, EditorError> {
154 let mut file = std::fs::File::create(path)
155 .map_err(|e| EditorError::IoError(format!("Failed to create file: {e}")))?;
156 self.export_to_writer(document, &mut file, options)
157 }
158
159 fn export_to_string(
161 &self,
162 document: &EditorDocument,
163 options: &FormatOptions,
164 ) -> Result<(String, FormatResult), EditorError> {
165 let mut buffer = Vec::new();
166 let result = self.export_to_writer(document, &mut buffer, options)?;
167 let content = String::from_utf8(buffer)
168 .map_err(|e| EditorError::InvalidFormat(format!("Invalid UTF-8 output: {e}")))?;
169 Ok((content, result))
170 }
171}
172
173pub trait Format: FormatImporter + FormatExporter {
175 fn name(&self) -> &str {
177 &FormatImporter::format_info(self).name
178 }
179
180 fn supports_extension(&self, extension: &str) -> bool {
182 self.can_import(extension) || self.can_export(extension)
183 }
184
185 fn as_importer(&self) -> &dyn FormatImporter;
187
188 fn as_exporter(&self) -> &dyn FormatExporter;
190}
191
192#[derive(Debug, Default)]
194pub struct FormatRegistry {
195 importers: HashMap<String, Box<dyn FormatImporter>>,
196 exporters: HashMap<String, Box<dyn FormatExporter>>,
197 formats: HashMap<String, Box<dyn Format>>,
198}
199
200impl FormatRegistry {
201 pub fn new() -> Self {
203 Self::default()
204 }
205
206 pub fn register_format(&mut self, format: Box<dyn Format>) {
208 let name = format.name().to_string();
209 self.formats.insert(name, format);
210 }
211
212 pub fn register_importer(&mut self, importer: Box<dyn FormatImporter>) {
214 let name = importer.format_info().name.clone();
215 self.importers.insert(name, importer);
216 }
217
218 pub fn register_exporter(&mut self, exporter: Box<dyn FormatExporter>) {
220 let name = exporter.format_info().name.clone();
221 self.exporters.insert(name, exporter);
222 }
223
224 pub fn find_importer(&self, extension: &str) -> Option<&dyn FormatImporter> {
226 for format in self.formats.values() {
228 if format.can_import(extension) {
229 return Some(format.as_importer());
230 }
231 }
232
233 for importer in self.importers.values() {
235 if importer.can_import(extension) {
236 return Some(importer.as_ref());
237 }
238 }
239
240 None
241 }
242
243 pub fn find_exporter(&self, extension: &str) -> Option<&dyn FormatExporter> {
245 for format in self.formats.values() {
247 if format.can_export(extension) {
248 return Some(format.as_exporter());
249 }
250 }
251
252 for exporter in self.exporters.values() {
254 if exporter.can_export(extension) {
255 return Some(exporter.as_ref());
256 }
257 }
258
259 None
260 }
261
262 pub fn supported_import_extensions(&self) -> Vec<String> {
264 let mut extensions = Vec::new();
265
266 for format in self.formats.values() {
267 extensions.extend(
268 FormatImporter::format_info(format.as_ref())
269 .extensions
270 .clone(),
271 );
272 }
273
274 for importer in self.importers.values() {
275 extensions.extend(importer.format_info().extensions.clone());
276 }
277
278 extensions.sort();
279 extensions.dedup();
280 extensions
281 }
282
283 pub fn supported_export_extensions(&self) -> Vec<String> {
285 let mut extensions = Vec::new();
286
287 for format in self.formats.values() {
288 extensions.extend(
289 FormatExporter::format_info(format.as_ref())
290 .extensions
291 .clone(),
292 );
293 }
294
295 for exporter in self.exporters.values() {
296 extensions.extend(exporter.format_info().extensions.clone());
297 }
298
299 extensions.sort();
300 extensions.dedup();
301 extensions
302 }
303
304 pub fn import_file(
306 &self,
307 path: &Path,
308 options: Option<&FormatOptions>,
309 ) -> Result<(EditorDocument, FormatResult), EditorError> {
310 let extension = path
311 .extension()
312 .and_then(|ext| ext.to_str())
313 .ok_or_else(|| EditorError::InvalidFormat("No file extension found".to_string()))?;
314
315 let importer = self
316 .find_importer(extension)
317 .ok_or_else(|| EditorError::UnsupportedFormat(extension.to_string()))?;
318
319 let default_options = FormatOptions::default();
320 let options = options.unwrap_or(&default_options);
321 importer.import_from_path(path, options)
322 }
323
324 pub fn export_file(
326 &self,
327 document: &EditorDocument,
328 path: &Path,
329 options: Option<&FormatOptions>,
330 ) -> Result<FormatResult, EditorError> {
331 let extension = path
332 .extension()
333 .and_then(|ext| ext.to_str())
334 .ok_or_else(|| EditorError::InvalidFormat("No file extension found".to_string()))?;
335
336 let exporter = self
337 .find_exporter(extension)
338 .ok_or_else(|| EditorError::UnsupportedFormat(extension.to_string()))?;
339
340 let default_options = FormatOptions::default();
341 let options = options.unwrap_or(&default_options);
342 exporter.export_to_path(document, path, options)
343 }
344}
345
346pub mod ass;
348pub mod srt;
349pub mod webvtt;
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354 #[cfg(not(feature = "std"))]
355 use alloc::string::ToString;
356 #[cfg(not(feature = "std"))]
357 use alloc::{format, string::String, vec};
358
359 #[test]
360 fn test_format_info_creation() {
361 let info = FormatInfo {
362 name: "Test Format".to_string(),
363 extensions: vec!["test".to_string(), "tst".to_string()],
364 mime_type: "text/test".to_string(),
365 description: "A test format".to_string(),
366 supports_styling: true,
367 supports_positioning: false,
368 };
369
370 assert_eq!(info.name, "Test Format");
371 assert_eq!(info.extensions.len(), 2);
372 assert!(info.supports_styling);
373 assert!(!info.supports_positioning);
374 }
375
376 #[test]
377 fn test_format_options_default() {
378 let options = FormatOptions::default();
379 assert_eq!(options.encoding, "UTF-8");
380 assert!(options.preserve_formatting);
381 assert!(options.custom_options.is_empty());
382 }
383
384 #[test]
385 fn test_format_result_creation() {
386 let result = FormatResult::success(42)
387 .with_warnings(vec!["Warning 1".to_string()])
388 .with_metadata("key".to_string(), "value".to_string());
389
390 assert!(result.success);
391 assert_eq!(result.lines_processed, 42);
392 assert_eq!(result.warnings.len(), 1);
393 assert_eq!(result.metadata.get("key"), Some(&"value".to_string()));
394 }
395
396 #[test]
397 fn test_format_registry_creation() {
398 let registry = FormatRegistry::new();
399 assert!(registry.formats.is_empty());
400 assert!(registry.importers.is_empty());
401 assert!(registry.exporters.is_empty());
402 }
403
404 #[test]
405 fn test_format_registry_extensions() {
406 let registry = FormatRegistry::new();
407 let import_exts = registry.supported_import_extensions();
408 let export_exts = registry.supported_export_extensions();
409
410 assert!(import_exts.is_empty());
411 assert!(export_exts.is_empty());
412 }
413}