scirs2_ndimage/
documentation.rs1pub use self::{html_generation::*, modules::*, styling::*, tutorials::*, types::*};
30
31pub mod html_generation;
33pub mod modules;
34pub mod styling;
35pub mod tutorials;
36pub mod types;
37
38#[cfg(feature = "serde")]
40use serde::{Deserialize, Serialize};
41
42pub fn create_documentation_site() -> types::Result<DocumentationSite> {
44 let mut site = DocumentationSite::new();
45 site.build_comprehensive_documentation()?;
46 Ok(site)
47}
48
49pub fn generate_complete_documentation(output_dir: &str) -> types::Result<()> {
51 let site = create_documentation_site()?;
52 site.generate_html_documentation(output_dir)?;
53 Ok(())
54}
55
56pub struct DocumentationBuilder {
58 site: DocumentationSite,
59 include_tutorials: bool,
60 include_examples: bool,
61 include_search: bool,
62 custom_css: Option<String>,
63 custom_js: Option<String>,
64}
65
66impl DocumentationBuilder {
67 pub fn new() -> Self {
69 Self {
70 site: DocumentationSite::new(),
71 include_tutorials: true,
72 include_examples: true,
73 include_search: true,
74 custom_css: None,
75 custom_js: None,
76 }
77 }
78
79 pub fn title(mut self, title: impl Into<String>) -> Self {
81 self.site.title = title.into();
82 self
83 }
84
85 pub fn description(mut self, description: impl Into<String>) -> Self {
87 self.site.description = description.into();
88 self
89 }
90
91 pub fn version(mut self, version: impl Into<String>) -> Self {
93 self.site.version = version.into();
94 self
95 }
96
97 pub fn base_url(mut self, url: impl Into<String>) -> Self {
99 self.site.base_url = url.into();
100 self
101 }
102
103 pub fn with_tutorials(mut self, include: bool) -> Self {
105 self.include_tutorials = include;
106 self
107 }
108
109 pub fn with_examples(mut self, include: bool) -> Self {
111 self.include_examples = include;
112 self
113 }
114
115 pub fn with_search(mut self, include: bool) -> Self {
117 self.include_search = include;
118 self
119 }
120
121 pub fn custom_css(mut self, css: impl Into<String>) -> Self {
123 self.custom_css = Some(css.into());
124 self
125 }
126
127 pub fn custom_js(mut self, js: impl Into<String>) -> Self {
129 self.custom_js = Some(js.into());
130 self
131 }
132
133 pub fn build(mut self) -> types::Result<DocumentationSite> {
135 self.site.build_module_documentation()?;
137
138 if self.include_tutorials {
140 self.site.build_tutorials()?;
141 }
142
143 if self.include_examples {
145 self.site.build_examples()?;
146 }
147
148 Ok(self.site)
149 }
150
151 pub fn generate(self, output_dir: &str) -> types::Result<()> {
153 let site = self.build()?;
154 site.generate_html_documentation(output_dir)?;
155 Ok(())
156 }
157}
158
159impl Default for DocumentationBuilder {
160 fn default() -> Self {
161 Self::new()
162 }
163}
164
165pub mod utils {
167 use super::*;
168
169 pub fn extract_function_signatures(rust_code: &str) -> Vec<String> {
171 let mut signatures = Vec::new();
172
173 if let Ok(re) = regex::Regex::new(r"pub fn (\w+)[^{]*\{") {
175 for cap in re.captures_iter(rust_code) {
176 if let Some(sig) = cap.get(0) {
177 signatures.push(sig.as_str().to_string());
178 }
179 }
180 }
181
182 signatures
183 }
184
185 pub fn generate_api_from_source(source_dir: &str) -> types::Result<Vec<types::ModuleDoc>> {
187 use std::fs;
188 use std::path::Path;
189
190 let mut modules = Vec::new();
191 let source_path = Path::new(source_dir);
192
193 if source_path.exists() {
194 for entry in fs::read_dir(source_path)? {
195 let entry = entry?;
196 let path = entry.path();
197
198 if path.extension().and_then(|s| s.to_str()) == Some("rs") {
199 let content = fs::read_to_string(&path)?;
200 let module_name = path
201 .file_stem()
202 .and_then(|s| s.to_str())
203 .unwrap_or("unknown")
204 .to_string();
205
206 let mut module =
207 types::ModuleDoc::new(module_name, "Auto-generated documentation");
208
209 let signatures = extract_function_signatures(&content);
211 for sig in signatures {
212 let func = types::FunctionDoc::new(
213 "extracted_function",
214 sig,
215 "Auto-extracted function",
216 "Return type",
217 );
218 module.add_function(func);
219 }
220
221 modules.push(module);
222 }
223 }
224 }
225
226 Ok(modules)
227 }
228
229 pub fn validate_html_output(html: &str) -> Vec<String> {
231 let mut issues = Vec::new();
232
233 let close_open = html.matches("</").count();
238 let self_close = html.matches("/>").count();
239 let opening = html.matches('<').count() - close_open;
240 let closing = close_open + self_close;
241
242 if opening != closing {
243 issues.push("Potential unclosed HTML tags detected".to_string());
244 }
245
246 if html.contains("<img") && !html.contains("alt=") {
248 issues.push("Images missing alt attributes".to_string());
249 }
250
251 if !html.contains("<title>") {
253 issues.push("HTML missing title tag".to_string());
254 }
255
256 issues
257 }
258
259 pub fn generate_sitemap(base_url: &str, pages: &[&str]) -> String {
261 let mut sitemap = String::from(
262 r#"<?xml version="1.0" encoding="UTF-8"?>
263<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">"#,
264 );
265
266 for page in pages {
267 sitemap.push_str(&format!(
268 r#"
269 <url>
270 <loc>{}{}</loc>
271 <changefreq>weekly</changefreq>
272 <priority>0.8</priority>
273 </url>"#,
274 base_url.trim_end_matches('/'),
275 page
276 ));
277 }
278
279 sitemap.push_str("\n</urlset>");
280 sitemap
281 }
282}
283
284#[derive(Debug, Clone)]
286pub struct DocumentationTheme {
287 pub primary_color: String,
289 pub secondary_color: String,
291 pub background_color: String,
293 pub text_color: String,
295 pub font_family: String,
297 pub code_font_family: String,
299}
300
301impl Default for DocumentationTheme {
302 fn default() -> Self {
303 Self {
304 primary_color: "#3498db".to_string(),
305 secondary_color: "#2c3e50".to_string(),
306 background_color: "#f8f9fa".to_string(),
307 text_color: "#333333".to_string(),
308 font_family: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
309 .to_string(),
310 code_font_family: "Consolas, Monaco, 'Andale Mono', monospace".to_string(),
311 }
312 }
313}
314
315impl DocumentationTheme {
316 pub fn dark() -> Self {
318 Self {
319 primary_color: "#61dafb".to_string(),
320 secondary_color: "#282c34".to_string(),
321 background_color: "#20232a".to_string(),
322 text_color: "#ffffff".to_string(),
323 ..Default::default()
324 }
325 }
326
327 pub fn minimal() -> Self {
329 Self {
330 primary_color: "#000000".to_string(),
331 secondary_color: "#666666".to_string(),
332 background_color: "#ffffff".to_string(),
333 text_color: "#333333".to_string(),
334 ..Default::default()
335 }
336 }
337
338 pub fn to_css_variables(&self) -> String {
340 format!(
341 r#":root {{
342 --primary-color: {};
343 --secondary-color: {};
344 --background-color: {};
345 --text-color: {};
346 --font-family: {};
347 --code-font-family: {};
348}}"#,
349 self.primary_color,
350 self.secondary_color,
351 self.background_color,
352 self.text_color,
353 self.font_family,
354 self.code_font_family
355 )
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362
363 #[test]
364 fn test_create_documentation_site() {
365 let result = create_documentation_site();
366 assert!(result.is_ok());
367
368 let site = result.expect("Operation failed");
369 assert_eq!(site.title, "SciRS2 NDImage Documentation");
370 assert!(!site.modules.is_empty());
371 assert!(!site.tutorials.is_empty());
372 assert!(!site.examples.is_empty());
373 }
374
375 #[test]
376 fn test_documentation_builder() {
377 let site = DocumentationBuilder::new()
378 .title("Custom Documentation")
379 .description("Custom description")
380 .version("1.0.0")
381 .with_tutorials(true)
382 .with_examples(false)
383 .build()
384 .expect("Operation failed");
385
386 assert_eq!(site.title, "Custom Documentation");
387 assert_eq!(site.description, "Custom description");
388 assert_eq!(site.version, "1.0.0");
389 assert!(!site.tutorials.is_empty());
390 assert!(site.examples.is_empty()); }
392
393 #[test]
394 fn test_documentation_theme() {
395 let default_theme = DocumentationTheme::default();
396 assert_eq!(default_theme.primary_color, "#3498db");
397
398 let dark_theme = DocumentationTheme::dark();
399 assert_eq!(dark_theme.background_color, "#20232a");
400
401 let css_vars = default_theme.to_css_variables();
402 assert!(css_vars.contains("--primary-color"));
403 assert!(css_vars.contains("--text-color"));
404 }
405
406 #[test]
407 fn test_utils_extract_functions() {
408 let rust_code = r#"
409 pub fn example_function() {
410 // Some code
411 }
412
413 pub fn another_function(param: i32) -> String {
414 // More code
415 }
416 "#;
417
418 let signatures = utils::extract_function_signatures(rust_code);
419 assert_eq!(signatures.len(), 2);
420 assert!(signatures[0].contains("example_function"));
421 assert!(signatures[1].contains("another_function"));
422 }
423
424 #[test]
425 fn test_utils_validate_html() {
426 let good_html = "<html><head><title>Test</title></head><body><img src='test.png' alt='test'/></body></html>";
427 let issues = utils::validate_html_output(good_html);
428 assert!(issues.is_empty());
429
430 let bad_html = "<html><head></head><body><img src='test.png'/></body>";
431 let issues = utils::validate_html_output(bad_html);
432 assert!(!issues.is_empty());
433 }
434
435 #[test]
436 fn test_utils_generate_sitemap() {
437 let pages = vec![
438 "/index.html",
439 "/api/index.html",
440 "/tutorials/getting-started.html",
441 ];
442 let sitemap = utils::generate_sitemap("https://example.com", &pages);
443
444 assert!(sitemap.contains("<?xml version"));
445 assert!(sitemap.contains("https://example.com/index.html"));
446 assert!(sitemap.contains("https://example.com/api/index.html"));
447 assert!(sitemap.contains("https://example.com/tutorials/getting-started.html"));
448 }
449
450 #[test]
451 fn test_module_integration() {
452 let mut site = DocumentationSite::new();
453
454 assert!(site.build_module_documentation().is_ok());
456 assert!(site.build_tutorials().is_ok());
457 assert!(site.build_examples().is_ok());
458
459 assert!(!site.modules.is_empty());
461 assert!(!site.tutorials.is_empty());
462 assert!(!site.examples.is_empty());
463
464 let _ = site.generate_module_cards();
466 let _ = site.generate_api_module_list();
467 let _ = site.generate_tutorial_cards();
468 let _ = site.generate_default_css();
469 let _ = site.generate_default_js();
470 }
471}