ricecoder_storage/config/
documents.rs

1//! Document format support for steering and specs
2//!
3//! This module provides loading and saving of documents in YAML and Markdown formats.
4
5use crate::error::{StorageError, StorageResult};
6use crate::types::DocumentFormat;
7use std::path::Path;
8
9/// Document content
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct Document {
12    /// Document content
13    pub content: String,
14    /// Document format
15    pub format: DocumentFormat,
16}
17
18/// Document loader for YAML and Markdown formats
19pub struct DocumentLoader;
20
21impl DocumentLoader {
22    /// Load a document from a file
23    ///
24    /// Automatically detects format based on file extension.
25    /// Supports YAML (.yaml, .yml) and Markdown (.md, .markdown) formats.
26    pub fn load_from_file<P: AsRef<Path>>(path: P) -> StorageResult<Document> {
27        let path = path.as_ref();
28        let content = std::fs::read_to_string(path).map_err(|e| {
29            StorageError::io_error(path.to_path_buf(), crate::error::IoOperation::Read, e)
30        })?;
31
32        let extension = path
33            .extension()
34            .and_then(|ext| ext.to_str())
35            .ok_or_else(|| {
36                StorageError::parse_error(path.to_path_buf(), "unknown", "File has no extension")
37            })?;
38
39        let format = DocumentFormat::from_extension(extension).ok_or_else(|| {
40            StorageError::parse_error(
41                path.to_path_buf(),
42                "unknown",
43                format!("Unsupported document format: {}", extension),
44            )
45        })?;
46
47        Ok(Document { content, format })
48    }
49
50    /// Load a document from a string with specified format
51    pub fn load_from_string(content: String, format: DocumentFormat) -> Document {
52        Document { content, format }
53    }
54
55    /// Save a document to a file
56    pub fn save_to_file<P: AsRef<Path>>(document: &Document, path: P) -> StorageResult<()> {
57        let path = path.as_ref();
58        std::fs::write(path, &document.content).map_err(|e| {
59            StorageError::io_error(path.to_path_buf(), crate::error::IoOperation::Write, e)
60        })
61    }
62
63    /// Get the file extension for a document format
64    pub fn extension_for_format(format: DocumentFormat) -> &'static str {
65        format.extension()
66    }
67
68    /// Detect format from file extension
69    pub fn detect_format<P: AsRef<Path>>(path: P) -> StorageResult<DocumentFormat> {
70        let path = path.as_ref();
71        let extension = path
72            .extension()
73            .and_then(|ext| ext.to_str())
74            .ok_or_else(|| {
75                StorageError::parse_error(path.to_path_buf(), "unknown", "File has no extension")
76            })?;
77
78        DocumentFormat::from_extension(extension).ok_or_else(|| {
79            StorageError::parse_error(
80                path.to_path_buf(),
81                "unknown",
82                format!("Unsupported document format: {}", extension),
83            )
84        })
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_load_yaml_document() {
94        let yaml_content = "# Steering Document\nkey: value\n";
95        let doc = DocumentLoader::load_from_string(yaml_content.to_string(), DocumentFormat::Yaml);
96        assert_eq!(doc.content, yaml_content);
97        assert_eq!(doc.format, DocumentFormat::Yaml);
98    }
99
100    #[test]
101    fn test_load_markdown_document() {
102        let md_content = "# Steering Document\n\nThis is a markdown document.\n";
103        let doc =
104            DocumentLoader::load_from_string(md_content.to_string(), DocumentFormat::Markdown);
105        assert_eq!(doc.content, md_content);
106        assert_eq!(doc.format, DocumentFormat::Markdown);
107    }
108
109    #[test]
110    fn test_save_and_load_yaml_document() {
111        let temp_dir = tempfile::TempDir::new().expect("Failed to create temp dir");
112        let file_path = temp_dir.path().join("steering.yaml");
113        let original = Document {
114            content: "# Steering\nkey: value\n".to_string(),
115            format: DocumentFormat::Yaml,
116        };
117
118        DocumentLoader::save_to_file(&original, &file_path).expect("Failed to save document");
119
120        let loaded = DocumentLoader::load_from_file(&file_path).expect("Failed to load document");
121
122        assert_eq!(original, loaded);
123    }
124
125    #[test]
126    fn test_save_and_load_markdown_document() {
127        let temp_dir = tempfile::TempDir::new().expect("Failed to create temp dir");
128        let file_path = temp_dir.path().join("steering.md");
129        let original = Document {
130            content: "# Steering\n\nThis is markdown.\n".to_string(),
131            format: DocumentFormat::Markdown,
132        };
133
134        DocumentLoader::save_to_file(&original, &file_path).expect("Failed to save document");
135
136        let loaded = DocumentLoader::load_from_file(&file_path).expect("Failed to load document");
137
138        assert_eq!(original, loaded);
139    }
140
141    #[test]
142    fn test_detect_yaml_format() {
143        let format = DocumentLoader::detect_format("test.yaml").expect("Failed to detect format");
144        assert_eq!(format, DocumentFormat::Yaml);
145
146        let format = DocumentLoader::detect_format("test.yml").expect("Failed to detect format");
147        assert_eq!(format, DocumentFormat::Yaml);
148    }
149
150    #[test]
151    fn test_detect_markdown_format() {
152        let format = DocumentLoader::detect_format("test.md").expect("Failed to detect format");
153        assert_eq!(format, DocumentFormat::Markdown);
154
155        let format =
156            DocumentLoader::detect_format("test.markdown").expect("Failed to detect format");
157        assert_eq!(format, DocumentFormat::Markdown);
158    }
159
160    #[test]
161    fn test_extension_for_format() {
162        assert_eq!(
163            DocumentLoader::extension_for_format(DocumentFormat::Yaml),
164            "yaml"
165        );
166        assert_eq!(
167            DocumentLoader::extension_for_format(DocumentFormat::Markdown),
168            "md"
169        );
170    }
171}