scirs2_ndimage/
documentation.rs1pub use self::{html_generation::*, modules::*, styling::*, tutorials::*, types::*};
32
33pub mod html_generation;
35pub mod modules;
36pub mod styling;
37pub mod tutorials;
38pub mod types;
39
40#[cfg(feature = "serde")]
42use serde::{Deserialize, Serialize};
43
44pub fn create_documentation_site() -> types::Result<DocumentationSite> {
46 let mut site = DocumentationSite::new();
47 site.build_comprehensive_documentation()?;
48 Ok(site)
49}
50
51pub 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
58pub 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 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 pub fn title(mut self, title: impl Into<String>) -> Self {
83 self.site.title = title.into();
84 self
85 }
86
87 pub fn description(mut self, description: impl Into<String>) -> Self {
89 self.site.description = description.into();
90 self
91 }
92
93 pub fn version(mut self, version: impl Into<String>) -> Self {
95 self.site.version = version.into();
96 self
97 }
98
99 pub fn base_url(mut self, url: impl Into<String>) -> Self {
101 self.site.base_url = url.into();
102 self
103 }
104
105 pub fn with_tutorials(mut self, include: bool) -> Self {
107 self.include_tutorials = include;
108 self
109 }
110
111 pub fn with_examples(mut self, include: bool) -> Self {
113 self.include_examples = include;
114 self
115 }
116
117 pub fn with_search(mut self, include: bool) -> Self {
119 self.include_search = include;
120 self
121 }
122
123 pub fn custom_css(mut self, css: impl Into<String>) -> Self {
125 self.custom_css = Some(css.into());
126 self
127 }
128
129 pub fn custom_js(mut self, js: impl Into<String>) -> Self {
131 self.custom_js = Some(js.into());
132 self
133 }
134
135 pub fn build(mut self) -> types::Result<DocumentationSite> {
137 self.site.build_module_documentation()?;
139
140 if self.include_tutorials {
142 self.site.build_tutorials()?;
143 }
144
145 if self.include_examples {
147 self.site.build_examples()?;
148 }
149
150 Ok(self.site)
151 }
152
153 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
167pub mod utils {
169 use super::*;
170
171 pub fn extract_function_signatures(rust_code: &str) -> Vec<String> {
173 let mut signatures = Vec::new();
174
175 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 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 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 pub fn validate_html_output(html: &str) -> Vec<String> {
233 let mut issues = Vec::new();
234
235 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 if html.contains("<img") && !html.contains("alt=") {
250 issues.push("Images missing alt attributes".to_string());
251 }
252
253 if !html.contains("<title>") {
255 issues.push("HTML missing title tag".to_string());
256 }
257
258 issues
259 }
260
261 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#[derive(Debug, Clone)]
288pub struct DocumentationTheme {
289 pub primary_color: String,
291 pub secondary_color: String,
293 pub background_color: String,
295 pub text_color: String,
297 pub font_family: String,
299 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 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 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 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()); }
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 assert!(site.build_module_documentation().is_ok());
458 assert!(site.build_tutorials().is_ok());
459 assert!(site.build_examples().is_ok());
460
461 assert!(!site.modules.is_empty());
463 assert!(!site.tutorials.is_empty());
464 assert!(!site.examples.is_empty());
465
466 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}