pub use self::{
html_generation::*, modules::*, styling::*, tutorials::*, types::*,
};
pub mod html_generation;
pub mod modules;
pub mod styling;
pub mod tutorials;
pub mod types;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub fn create_documentation_site() -> types::Result<DocumentationSite> {
let mut site = DocumentationSite::new();
site.build_comprehensive_documentation()?;
Ok(site)
}
pub fn generate_complete_documentation(output_dir: &str) -> types::Result<()> {
let site = create_documentation_site()?;
site.generate_html_documentation(output_dir)?;
Ok(())
}
pub struct DocumentationBuilder {
site: DocumentationSite,
include_tutorials: bool,
include_examples: bool,
include_search: bool,
custom_css: Option<String>,
custom_js: Option<String>,
}
impl DocumentationBuilder {
pub fn new() -> Self {
Self {
site: DocumentationSite::new(),
include_tutorials: true,
include_examples: true,
include_search: true,
custom_css: None,
custom_js: None,
}
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.site.title = title.into();
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.site.description = description.into();
self
}
pub fn version(mut self, version: impl Into<String>) -> Self {
self.site.version = version.into();
self
}
pub fn base_url(mut self, url: impl Into<String>) -> Self {
self.site.base_url = url.into();
self
}
pub fn with_tutorials(mut self, include: bool) -> Self {
self.include_tutorials = include;
self
}
pub fn with_examples(mut self, include: bool) -> Self {
self.include_examples = include;
self
}
pub fn with_search(mut self, include: bool) -> Self {
self.include_search = include;
self
}
pub fn custom_css(mut self, css: impl Into<String>) -> Self {
self.custom_css = Some(css.into());
self
}
pub fn custom_js(mut self, js: impl Into<String>) -> Self {
self.custom_js = Some(js.into());
self
}
pub fn build(mut self) -> types::Result<DocumentationSite> {
self.site.build_module_documentation()?;
if self.include_tutorials {
self.site.build_tutorials()?;
}
if self.include_examples {
self.site.build_examples()?;
}
Ok(self.site)
}
pub fn generate(self, output_dir: &str) -> types::Result<()> {
let site = self.build()?;
site.generate_html_documentation(output_dir)?;
Ok(())
}
}
impl Default for DocumentationBuilder {
fn default() -> Self {
Self::new()
}
}
pub mod utils {
use super::*;
pub fn extract_function_signatures(rust_code: &str) -> Vec<String> {
let mut signatures = Vec::new();
if let Ok(re) = regex::Regex::new(r"pub fn (\w+).*?{") {
for cap in re.captures_iter(rust_code) {
if let Some(sig) = cap.get(0) {
signatures.push(sig.as_str().to_string());
}
}
}
signatures
}
pub fn generate_api_from_source(source_dir: &str) -> types::Result<Vec<types::ModuleDoc>> {
use std::fs;
use std::path::Path;
let mut modules = Vec::new();
let source_path = Path::new(source_dir);
if source_path.exists() {
for entry in fs::read_dir(source_path)? {
let entry = entry?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("rs") {
let content = fs::read_to_string(&path)?;
let module_name = path.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("unknown")
.to_string();
let mut module = types::ModuleDoc::new(
module_name,
"Auto-generated documentation"
);
let signatures = extract_function_signatures(&content);
for sig in signatures {
let func = types::FunctionDoc::new(
"extracted_function",
sig,
"Auto-extracted function",
"Return type"
);
module.add_function(func);
}
modules.push(module);
}
}
}
Ok(modules)
}
pub fn validate_html_output(html: &str) -> Vec<String> {
let mut issues = Vec::new();
let open_tags = html.matches('<').count();
let close_tags = html.matches("</").count() + html.matches("/>").count();
if open_tags != close_tags {
issues.push("Potential unclosed HTML tags detected".to_string());
}
if html.contains("<img") && !html.contains("alt=") {
issues.push("Images missing alt attributes".to_string());
}
if !html.contains("<title>") {
issues.push("HTML missing title tag".to_string());
}
issues
}
pub fn generate_sitemap(base_url: &str, pages: &[&str]) -> String {
let mut sitemap = String::from(r#"<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">"#);
for page in pages {
sitemap.push_str(&format!(
r#"
<url>
<loc>{}{}</loc>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>"#,
base_url.trim_end_matches('/'),
page
));
}
sitemap.push_str("\n</urlset>");
sitemap
}
}
#[derive(Debug, Clone)]
pub struct DocumentationTheme {
pub primary_color: String,
pub secondary_color: String,
pub background_color: String,
pub text_color: String,
pub font_family: String,
pub code_font_family: String,
}
impl Default for DocumentationTheme {
fn default() -> Self {
Self {
primary_color: "#3498db".to_string(),
secondary_color: "#2c3e50".to_string(),
background_color: "#f8f9fa".to_string(),
text_color: "#333333".to_string(),
font_family: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif".to_string(),
code_font_family: "Consolas, Monaco, 'Andale Mono', monospace".to_string(),
}
}
}
impl DocumentationTheme {
pub fn dark() -> Self {
Self {
primary_color: "#61dafb".to_string(),
secondary_color: "#282c34".to_string(),
background_color: "#20232a".to_string(),
text_color: "#ffffff".to_string(),
..Default::default()
}
}
pub fn minimal() -> Self {
Self {
primary_color: "#000000".to_string(),
secondary_color: "#666666".to_string(),
background_color: "#ffffff".to_string(),
text_color: "#333333".to_string(),
..Default::default()
}
}
pub fn to_css_variables(&self) -> String {
format!(
r#":root {{
--primary-color: {};
--secondary-color: {};
--background-color: {};
--text-color: {};
--font-family: {};
--code-font-family: {};
}}"#,
self.primary_color,
self.secondary_color,
self.background_color,
self.text_color,
self.font_family,
self.code_font_family
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_documentation_site() {
let result = create_documentation_site();
assert!(result.is_ok());
let site = result.expect("Operation failed");
assert_eq!(site.title, "SciRS2 NDImage Documentation");
assert!(!site.modules.is_empty());
assert!(!site.tutorials.is_empty());
assert!(!site.examples.is_empty());
}
#[test]
fn test_documentation_builder() {
let site = DocumentationBuilder::new()
.title("Custom Documentation")
.description("Custom description")
.version("1.0.0")
.with_tutorials(true)
.with_examples(false)
.build()
.expect("Operation failed");
assert_eq!(site.title, "Custom Documentation");
assert_eq!(site.description, "Custom description");
assert_eq!(site.version, "1.0.0");
assert!(!site.tutorials.is_empty());
assert!(site.examples.is_empty()); }
#[test]
fn test_documentation_theme() {
let default_theme = DocumentationTheme::default();
assert_eq!(default_theme.primary_color, "#3498db");
let dark_theme = DocumentationTheme::dark();
assert_eq!(dark_theme.background_color, "#20232a");
let css_vars = default_theme.to_css_variables();
assert!(css_vars.contains("--primary-color"));
assert!(css_vars.contains("--text-color"));
}
#[test]
fn test_utils_extract_functions() {
let rust_code = r#"
pub fn example_function() {
// Some code
}
pub fn another_function(param: i32) -> String {
// More code
}
"#;
let signatures = utils::extract_function_signatures(rust_code);
assert_eq!(signatures.len(), 2);
assert!(signatures[0].contains("example_function"));
assert!(signatures[1].contains("another_function"));
}
#[test]
fn test_utils_validate_html() {
let good_html = "<html><head><title>Test</title></head><body><img src='test.png' alt='test'/></body></html>";
let issues = utils::validate_html_output(good_html);
assert!(issues.is_empty());
let bad_html = "<html><head></head><body><img src='test.png'/></body>";
let issues = utils::validate_html_output(bad_html);
assert!(!issues.is_empty());
}
#[test]
fn test_utils_generate_sitemap() {
let pages = vec!["/index.html", "/api/index.html", "/tutorials/getting-started.html"];
let sitemap = utils::generate_sitemap("https://example.com", &pages);
assert!(sitemap.contains("<?xml version"));
assert!(sitemap.contains("https://example.com/index.html"));
assert!(sitemap.contains("https://example.com/api/index.html"));
assert!(sitemap.contains("https://example.com/tutorials/getting-started.html"));
}
#[test]
fn test_module_integration() {
let mut site = DocumentationSite::new();
assert!(site.build_module_documentation().is_ok());
assert!(site.build_tutorials().is_ok());
assert!(site.build_examples().is_ok());
assert!(!site.modules.is_empty());
assert!(!site.tutorials.is_empty());
assert!(!site.examples.is_empty());
let _ = site.generate_module_cards();
let _ = site.generate_api_module_list();
let _ = site.generate_tutorial_cards();
let _ = site.generate_default_css();
let _ = site.generate_default_js();
}
}