waddling-errors 0.7.3

Structured, secure-by-default diagnostic codes for distributed systems with no_std and role-based documentation
Documentation
//! HTML customization options for documentation generation

#[cfg(feature = "std")]
use std::{
    format,
    string::{String, ToString},
};

/// Customization options for HTML documentation
#[derive(Clone, Debug, Default)]
pub struct HtmlCustomization {
    /// Custom CSS to append after the default styles
    /// This allows users to override any default styling
    pub custom_css: Option<String>,

    /// Path to a custom logo image (can be data URI or URL)
    /// Example: "data:image/svg+xml;base64,..." or "https://example.com/logo.svg"
    pub logo_url: Option<String>,

    /// Alternative text for the logo (accessibility)
    pub logo_alt: Option<String>,

    /// Custom JavaScript to append after the default script
    /// Useful for adding analytics, custom interactions, etc.
    pub custom_js: Option<String>,

    /// Replace the entire CSS instead of using defaults
    /// When set, custom_css is ignored and only this CSS is used
    pub replace_css: Option<String>,

    /// Replace the entire JavaScript instead of using defaults
    /// When set, custom_js is ignored and only this JS is used
    pub replace_js: Option<String>,

    /// Custom footer HTML (centered at bottom of page)
    pub footer_html: Option<String>,

    /// Custom header HTML (shown above the search bar)
    pub header_html: Option<String>,

    /// Theme color (affects accent colors throughout)
    /// Example: "#FF5722" or "rgb(255, 87, 34)"
    pub accent_color: Option<String>,

    /// Dark mode accent color (used when dark mode is active)
    pub accent_color_dark: Option<String>,
}

impl HtmlCustomization {
    /// Create a new customization with defaults
    pub fn new() -> Self {
        Self::default()
    }

    /// Set custom CSS to append to defaults
    ///
    /// # Example
    /// ```
    /// use waddling_errors::doc_generator::HtmlCustomization;
    ///
    /// let custom = HtmlCustomization::new()
    ///     .with_custom_css(r#"
    ///         .error-card { border-radius: 16px; }
    ///         .severity-error { background: linear-gradient(135deg, #ff6b6b, #ff5252); }
    ///     "#);
    /// ```
    pub fn with_custom_css(mut self, css: impl Into<String>) -> Self {
        self.custom_css = Some(css.into());
        self
    }

    /// Set a custom logo
    ///
    /// # Example
    /// ```
    /// use waddling_errors::doc_generator::HtmlCustomization;
    ///
    /// let custom = HtmlCustomization::new()
    ///     .with_logo("https://example.com/logo.svg", "My Project");
    /// ```
    pub fn with_logo(mut self, url: impl Into<String>, alt: impl Into<String>) -> Self {
        self.logo_url = Some(url.into());
        self.logo_alt = Some(alt.into());
        self
    }

    /// Set accent colors (light and dark mode)
    ///
    /// # Example
    /// ```
    /// use waddling_errors::doc_generator::HtmlCustomization;
    ///
    /// let custom = HtmlCustomization::new()
    ///     .with_accent_color("#FF5722", "#FF8A65");
    /// ```
    pub fn with_accent_color(mut self, light: impl Into<String>, dark: impl Into<String>) -> Self {
        self.accent_color = Some(light.into());
        self.accent_color_dark = Some(dark.into());
        self
    }

    /// Add custom JavaScript for analytics, interactions, etc.
    ///
    /// # Example
    /// ```
    /// use waddling_errors::doc_generator::HtmlCustomization;
    ///
    /// let custom = HtmlCustomization::new()
    ///     .with_custom_js(r#"
    ///         console.log('Error docs loaded');
    ///         // Add your analytics code here
    ///     "#);
    /// ```
    pub fn with_custom_js(mut self, js: impl Into<String>) -> Self {
        self.custom_js = Some(js.into());
        self
    }

    /// Set a custom footer
    ///
    /// # Example
    /// ```
    /// use waddling_errors::doc_generator::HtmlCustomization;
    ///
    /// let custom = HtmlCustomization::new()
    ///     .with_footer(r#"
    ///         <p>© 2025 MyProject | <a href="/docs">Documentation</a></p>
    ///     "#);
    /// ```
    pub fn with_footer(mut self, html: impl Into<String>) -> Self {
        self.footer_html = Some(html.into());
        self
    }

    /// Set a custom header
    pub fn with_header(mut self, html: impl Into<String>) -> Self {
        self.header_html = Some(html.into());
        self
    }

    /// Completely replace the default CSS
    ///
    /// ⚠️ **Warning**: This disables all default styling. You must provide complete CSS.
    pub fn replace_css(mut self, css: impl Into<String>) -> Self {
        self.replace_css = Some(css.into());
        self
    }

    /// Completely replace the default JavaScript
    ///
    /// ⚠️ **Warning**: This disables all default functionality. You must reimplement everything.
    pub fn replace_js(mut self, js: impl Into<String>) -> Self {
        self.replace_js = Some(js.into());
        self
    }

    /// Generate the CSS variables section for theme customization
    pub(crate) fn generate_css_variables(&self) -> String {
        if let (Some(light), Some(dark)) = (&self.accent_color, &self.accent_color_dark) {
            format!(
                r#"
        :root {{
            --accent-color: {};
            --accent-hover: {};
        }}
        
        @media (prefers-color-scheme: dark) {{
            :root {{
                --accent-color: {};
                --accent-hover: {};
            }}
        }}
        "#,
                light,
                Self::adjust_brightness(light, 0.9),
                dark,
                Self::adjust_brightness(dark, 1.1)
            )
        } else if let Some(light) = &self.accent_color {
            format!(
                r#"
        :root {{
            --accent-color: {};
            --accent-hover: {};
        }}
        "#,
                light,
                Self::adjust_brightness(light, 0.9)
            )
        } else {
            String::new()
        }
    }

    /// Simple brightness adjustment (placeholder - can be enhanced)
    fn adjust_brightness(color: &str, _factor: f32) -> String {
        // For now, return the same color
        // TODO: Implement proper color brightness adjustment
        color.to_string()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_builder_pattern() {
        let custom = HtmlCustomization::new()
            .with_logo("logo.svg", "My Logo")
            .with_accent_color("#FF5722", "#FF8A65")
            .with_custom_css(".custom { color: red; }");

        assert!(custom.logo_url.is_some());
        assert!(custom.accent_color.is_some());
        assert!(custom.custom_css.is_some());
    }

    #[test]
    fn test_css_variables_generation() {
        let custom = HtmlCustomization::new().with_accent_color("#FF5722", "#FF8A65");

        let css = custom.generate_css_variables();
        assert!(css.contains("--accent-color: #FF5722"));
        assert!(css.contains("--accent-color: #FF8A65"));
    }
}