Skip to main content

scirs2_ndimage/
documentation.rs

1//! Documentation Generation System
2//!
3//! This module provides a comprehensive documentation generation system for SciRS2 NDImage,
4//! including HTML generation, tutorials, examples, API documentation, and styling.
5//!
6//! This module has been refactored into focused components for better maintainability.
7//! See the submodules for specific functionality.
8//!
9//! # Features
10//!
11//! - **Comprehensive API Documentation**: Auto-generated documentation for all modules and functions
12//! - **Interactive Tutorials**: Step-by-step guides with executable examples
13//! - **Code Examples**: Real-world examples for different domains (medical, satellite, scientific)
14//! - **Modern Web Interface**: Responsive design with search functionality
15//! - **Syntax Highlighting**: Code syntax highlighting with Prism.js
16//! - **Mobile-Friendly**: Responsive design that works on all devices
17//!
18//! # Usage
19//!
20//! ```no_run
21//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
22//! use scirs2_ndimage::documentation::DocumentationSite;
23//!
24//! let mut site = DocumentationSite::new();
25//! site.build_comprehensive_documentation()?;
26//! site.generate_html_documentation("./docs")?;
27//! # Ok(()) }
28//! ```
29
30// Re-export all module components for backward compatibility
31pub use self::{html_generation::*, modules::*, styling::*, tutorials::*, types::*};
32
33// Module declarations
34pub mod html_generation;
35pub mod modules;
36pub mod styling;
37pub mod tutorials;
38pub mod types;
39
40// Import for conditional compilation
41#[cfg(feature = "serde")]
42use serde::{Deserialize, Serialize};
43
44/// Quick builder function for creating a complete documentation site
45pub fn create_documentation_site() -> types::Result<DocumentationSite> {
46    let mut site = DocumentationSite::new();
47    site.build_comprehensive_documentation()?;
48    Ok(site)
49}
50
51/// Generate complete HTML documentation to a directory
52pub fn generate_complete_documentation(output_dir: &str) -> types::Result<()> {
53    let site = create_documentation_site()?;
54    site.generate_html_documentation(output_dir)?;
55    Ok(())
56}
57
58/// Builder pattern for customizing documentation generation
59pub struct DocumentationBuilder {
60    site: DocumentationSite,
61    include_tutorials: bool,
62    include_examples: bool,
63    include_search: bool,
64    custom_css: Option<String>,
65    custom_js: Option<String>,
66}
67
68impl DocumentationBuilder {
69    /// Create a new documentation builder
70    pub fn new() -> Self {
71        Self {
72            site: DocumentationSite::new(),
73            include_tutorials: true,
74            include_examples: true,
75            include_search: true,
76            custom_css: None,
77            custom_js: None,
78        }
79    }
80
81    /// Set the site title
82    pub fn title(mut self, title: impl Into<String>) -> Self {
83        self.site.title = title.into();
84        self
85    }
86
87    /// Set the site description
88    pub fn description(mut self, description: impl Into<String>) -> Self {
89        self.site.description = description.into();
90        self
91    }
92
93    /// Set the site version
94    pub fn version(mut self, version: impl Into<String>) -> Self {
95        self.site.version = version.into();
96        self
97    }
98
99    /// Set the base URL
100    pub fn base_url(mut self, url: impl Into<String>) -> Self {
101        self.site.base_url = url.into();
102        self
103    }
104
105    /// Include or exclude tutorials
106    pub fn with_tutorials(mut self, include: bool) -> Self {
107        self.include_tutorials = include;
108        self
109    }
110
111    /// Include or exclude examples
112    pub fn with_examples(mut self, include: bool) -> Self {
113        self.include_examples = include;
114        self
115    }
116
117    /// Include or exclude search functionality
118    pub fn with_search(mut self, include: bool) -> Self {
119        self.include_search = include;
120        self
121    }
122
123    /// Add custom CSS
124    pub fn custom_css(mut self, css: impl Into<String>) -> Self {
125        self.custom_css = Some(css.into());
126        self
127    }
128
129    /// Add custom JavaScript
130    pub fn custom_js(mut self, js: impl Into<String>) -> Self {
131        self.custom_js = Some(js.into());
132        self
133    }
134
135    /// Build the documentation site
136    pub fn build(mut self) -> types::Result<DocumentationSite> {
137        // Build modules (always included)
138        self.site.build_module_documentation()?;
139
140        // Build tutorials if requested
141        if self.include_tutorials {
142            self.site.build_tutorials()?;
143        }
144
145        // Build examples if requested
146        if self.include_examples {
147            self.site.build_examples()?;
148        }
149
150        Ok(self.site)
151    }
152
153    /// Build and generate HTML documentation
154    pub fn generate(self, output_dir: &str) -> types::Result<()> {
155        let site = self.build()?;
156        site.generate_html_documentation(output_dir)?;
157        Ok(())
158    }
159}
160
161impl Default for DocumentationBuilder {
162    fn default() -> Self {
163        Self::new()
164    }
165}
166
167/// Utility functions for common documentation tasks
168pub mod utils {
169    use super::*;
170
171    /// Extract function signatures from Rust code
172    pub fn extract_function_signatures(rust_code: &str) -> Vec<String> {
173        let mut signatures = Vec::new();
174
175        // Simple regex-based extraction (in practice, you'd want a proper parser)
176        if let Ok(re) = regex::Regex::new(r"pub fn (\w+)[^{]*\{") {
177            for cap in re.captures_iter(rust_code) {
178                if let Some(sig) = cap.get(0) {
179                    signatures.push(sig.as_str().to_string());
180                }
181            }
182        }
183
184        signatures
185    }
186
187    /// Generate API documentation from source files
188    pub fn generate_api_from_source(source_dir: &str) -> types::Result<Vec<types::ModuleDoc>> {
189        use std::fs;
190        use std::path::Path;
191
192        let mut modules = Vec::new();
193        let source_path = Path::new(source_dir);
194
195        if source_path.exists() {
196            for entry in fs::read_dir(source_path)? {
197                let entry = entry?;
198                let path = entry.path();
199
200                if path.extension().and_then(|s| s.to_str()) == Some("rs") {
201                    let content = fs::read_to_string(&path)?;
202                    let module_name = path
203                        .file_stem()
204                        .and_then(|s| s.to_str())
205                        .unwrap_or("unknown")
206                        .to_string();
207
208                    let mut module =
209                        types::ModuleDoc::new(module_name, "Auto-generated documentation");
210
211                    // Extract functions (simplified)
212                    let signatures = extract_function_signatures(&content);
213                    for sig in signatures {
214                        let func = types::FunctionDoc::new(
215                            "extracted_function",
216                            sig,
217                            "Auto-extracted function",
218                            "Return type",
219                        );
220                        module.add_function(func);
221                    }
222
223                    modules.push(module);
224                }
225            }
226        }
227
228        Ok(modules)
229    }
230
231    /// Validate HTML output for common issues
232    pub fn validate_html_output(html: &str) -> Vec<String> {
233        let mut issues = Vec::new();
234
235        // Check for unclosed tags (simplified).
236        // `close_open` counts explicit closing tags (</foo>).
237        // `self_close` counts self-closing tags (<br/>).
238        // Opening tags = all `<` minus the ones that start a `</foo>` closer.
239        let close_open = html.matches("</").count();
240        let self_close = html.matches("/>").count();
241        let opening = html.matches('<').count() - close_open;
242        let closing = close_open + self_close;
243
244        if opening != closing {
245            issues.push("Potential unclosed HTML tags detected".to_string());
246        }
247
248        // Check for missing alt attributes on images
249        if html.contains("<img") && !html.contains("alt=") {
250            issues.push("Images missing alt attributes".to_string());
251        }
252
253        // Check for missing title
254        if !html.contains("<title>") {
255            issues.push("HTML missing title tag".to_string());
256        }
257
258        issues
259    }
260
261    /// Generate sitemap.xml for the documentation
262    pub fn generate_sitemap(base_url: &str, pages: &[&str]) -> String {
263        let mut sitemap = String::from(
264            r#"<?xml version="1.0" encoding="UTF-8"?>
265<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">"#,
266        );
267
268        for page in pages {
269            sitemap.push_str(&format!(
270                r#"
271  <url>
272    <loc>{}{}</loc>
273    <changefreq>weekly</changefreq>
274    <priority>0.8</priority>
275  </url>"#,
276                base_url.trim_end_matches('/'),
277                page
278            ));
279        }
280
281        sitemap.push_str("\n</urlset>");
282        sitemap
283    }
284}
285
286/// Configuration for documentation themes and styling
287#[derive(Debug, Clone)]
288pub struct DocumentationTheme {
289    /// Primary color (hex)
290    pub primary_color: String,
291    /// Secondary color (hex)
292    pub secondary_color: String,
293    /// Background color (hex)
294    pub background_color: String,
295    /// Text color (hex)
296    pub text_color: String,
297    /// Font family
298    pub font_family: String,
299    /// Code font family
300    pub code_font_family: String,
301}
302
303impl Default for DocumentationTheme {
304    fn default() -> Self {
305        Self {
306            primary_color: "#3498db".to_string(),
307            secondary_color: "#2c3e50".to_string(),
308            background_color: "#f8f9fa".to_string(),
309            text_color: "#333333".to_string(),
310            font_family: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
311                .to_string(),
312            code_font_family: "Consolas, Monaco, 'Andale Mono', monospace".to_string(),
313        }
314    }
315}
316
317impl DocumentationTheme {
318    /// Create a dark theme variant
319    pub fn dark() -> Self {
320        Self {
321            primary_color: "#61dafb".to_string(),
322            secondary_color: "#282c34".to_string(),
323            background_color: "#20232a".to_string(),
324            text_color: "#ffffff".to_string(),
325            ..Default::default()
326        }
327    }
328
329    /// Create a minimal theme variant
330    pub fn minimal() -> Self {
331        Self {
332            primary_color: "#000000".to_string(),
333            secondary_color: "#666666".to_string(),
334            background_color: "#ffffff".to_string(),
335            text_color: "#333333".to_string(),
336            ..Default::default()
337        }
338    }
339
340    /// Generate CSS variables for this theme
341    pub fn to_css_variables(&self) -> String {
342        format!(
343            r#":root {{
344    --primary-color: {};
345    --secondary-color: {};
346    --background-color: {};
347    --text-color: {};
348    --font-family: {};
349    --code-font-family: {};
350}}"#,
351            self.primary_color,
352            self.secondary_color,
353            self.background_color,
354            self.text_color,
355            self.font_family,
356            self.code_font_family
357        )
358    }
359}
360
361#[cfg(test)]
362mod tests {
363    use super::*;
364
365    #[test]
366    fn test_create_documentation_site() {
367        let result = create_documentation_site();
368        assert!(result.is_ok());
369
370        let site = result.expect("Operation failed");
371        assert_eq!(site.title, "SciRS2 NDImage Documentation");
372        assert!(!site.modules.is_empty());
373        assert!(!site.tutorials.is_empty());
374        assert!(!site.examples.is_empty());
375    }
376
377    #[test]
378    fn test_documentation_builder() {
379        let site = DocumentationBuilder::new()
380            .title("Custom Documentation")
381            .description("Custom description")
382            .version("1.0.0")
383            .with_tutorials(true)
384            .with_examples(false)
385            .build()
386            .expect("Operation failed");
387
388        assert_eq!(site.title, "Custom Documentation");
389        assert_eq!(site.description, "Custom description");
390        assert_eq!(site.version, "1.0.0");
391        assert!(!site.tutorials.is_empty());
392        assert!(site.examples.is_empty()); // Examples disabled
393    }
394
395    #[test]
396    fn test_documentation_theme() {
397        let default_theme = DocumentationTheme::default();
398        assert_eq!(default_theme.primary_color, "#3498db");
399
400        let dark_theme = DocumentationTheme::dark();
401        assert_eq!(dark_theme.background_color, "#20232a");
402
403        let css_vars = default_theme.to_css_variables();
404        assert!(css_vars.contains("--primary-color"));
405        assert!(css_vars.contains("--text-color"));
406    }
407
408    #[test]
409    fn test_utils_extract_functions() {
410        let rust_code = r#"
411            pub fn example_function() {
412                // Some code
413            }
414
415            pub fn another_function(param: i32) -> String {
416                // More code
417            }
418        "#;
419
420        let signatures = utils::extract_function_signatures(rust_code);
421        assert_eq!(signatures.len(), 2);
422        assert!(signatures[0].contains("example_function"));
423        assert!(signatures[1].contains("another_function"));
424    }
425
426    #[test]
427    fn test_utils_validate_html() {
428        let good_html = "<html><head><title>Test</title></head><body><img src='test.png' alt='test'/></body></html>";
429        let issues = utils::validate_html_output(good_html);
430        assert!(issues.is_empty());
431
432        let bad_html = "<html><head></head><body><img src='test.png'/></body>";
433        let issues = utils::validate_html_output(bad_html);
434        assert!(!issues.is_empty());
435    }
436
437    #[test]
438    fn test_utils_generate_sitemap() {
439        let pages = vec![
440            "/index.html",
441            "/api/index.html",
442            "/tutorials/getting-started.html",
443        ];
444        let sitemap = utils::generate_sitemap("https://example.com", &pages);
445
446        assert!(sitemap.contains("<?xml version"));
447        assert!(sitemap.contains("https://example.com/index.html"));
448        assert!(sitemap.contains("https://example.com/api/index.html"));
449        assert!(sitemap.contains("https://example.com/tutorials/getting-started.html"));
450    }
451
452    #[test]
453    fn test_module_integration() {
454        let mut site = DocumentationSite::new();
455
456        // Test that all modules integrate properly
457        assert!(site.build_module_documentation().is_ok());
458        assert!(site.build_tutorials().is_ok());
459        assert!(site.build_examples().is_ok());
460
461        // Verify content was built
462        assert!(!site.modules.is_empty());
463        assert!(!site.tutorials.is_empty());
464        assert!(!site.examples.is_empty());
465
466        // Test HTML generation methods exist
467        let _ = site.generate_module_cards();
468        let _ = site.generate_api_module_list();
469        let _ = site.generate_tutorial_cards();
470        let _ = site.generate_default_css();
471        let _ = site.generate_default_js();
472    }
473}