diff --git a/src/modules/html.rs b/src/modules/html.rs index bf850e13..a2f938b8 100644 --- a/src/modules/html.rs +++ b/src/modules/html.rs @@ -1,12 +1,48 @@ // Copyright © 2024 Shokunin Static Site Generator. All rights reserved. // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::{modules::{markdown::convert_markdown_to_html, postprocessor::post_process_html}, utilities::directory::{ - create_comrak_options, extract_front_matter, - format_header_with_id_class, update_class_attributes, -}}; +use crate::{ + modules::{ + markdown::convert_markdown_to_html, + postprocessor::post_process_html, + }, + utilities::directory::{ + extract_front_matter, + format_header_with_id_class, update_class_attributes, + }, +}; use regex::Regex; -use std::error::Error; + +/// Error enum for HTML generation. +#[derive(Debug)] +pub enum HtmlGenerationError { + /// Title cannot be empty + EmptyTitle, + /// Description cannot be empty + EmptyDescription, + /// Regex compilation error + RegexCompilationError(regex::Error), +} + +impl std::fmt::Display for HtmlGenerationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + Self::EmptyTitle => write!(f, "Title cannot be empty."), + Self::EmptyDescription => write!(f, "Description cannot be empty."), + Self::RegexCompilationError(ref err) => { + write!(f, "Regex compilation error: {}", err) + } + } + } +} + +impl std::error::Error for HtmlGenerationError {} + +impl From for HtmlGenerationError { + fn from(err: regex::Error) -> Self { + Self::RegexCompilationError(err) + } +} /// Generates an HTML page from Markdown content, title, and description. /// @@ -19,13 +55,12 @@ use std::error::Error; /// /// # Returns /// -/// A `Result` containing a `String` representing the generated HTML page if successful, or a `Box` if an error occurs. +/// A `Result` containing a `String` representing the generated HTML page if successful, or a `HtmlGenerationError` if an error occurs. /// /// # Examples /// /// ```rust /// use ssg::modules::html::generate_html; -/// use ssg::modules::postprocessor::post_process_html; /// /// let content = "## Hello, world!\n\nThis is a test."; /// let title = "My Page"; @@ -40,7 +75,16 @@ pub fn generate_html( title: &str, description: &str, json_content: Option<&str>, -) -> Result> { +) -> Result { + // Validate arguments + if title.is_empty() { + return Err(HtmlGenerationError::EmptyTitle); + } + + if description.is_empty() { + return Err(HtmlGenerationError::EmptyDescription); + } + // Regex patterns for ID, class, and image tags let id_regex = Regex::new(r"[^a-zA-Z0-9]+")?; let class_regex = Regex::new(r#"\.class="([^&"]+)""#)?; @@ -48,21 +92,27 @@ pub fn generate_html( // Extract front matter from content let markdown_content = extract_front_matter(content); + // Preprocess content to update class attributes and image tags let processed_content = preprocess_content(markdown_content, &class_regex, &img_regex)?; // Convert Markdown to HTML - let options = create_comrak_options(); let markdown_html = - convert_markdown_to_html(&processed_content, &options)?; + convert_markdown_to_html(&processed_content, &Default::default()); + + // Unwrap the Result to get the String + let markdown_html = markdown_html.unwrap(); // Post-process HTML content let processed_html = - post_process_html(&markdown_html, &class_regex, &img_regex)?; + post_process_html(&markdown_html, &class_regex, &img_regex); + + // Unwrap the Result to get the String + let processed_html = processed_html.unwrap(); - // Generate header and description - let header = generate_header(title, &id_regex); + // Generate page header and description + let header = generate_page_header(title, &id_regex); let desc = generate_description(description); // Process headers in HTML @@ -78,6 +128,45 @@ pub fn generate_html( Ok(format!("{}{}{}{}", header, desc, json_html, html_string)) } +/// Preprocesses the HTML content to update class attributes and image tags. +/// +/// # Arguments +/// +/// * `content` - A string containing the HTML content to be processed. +/// * `class_regex` - A reference to a `Regex` object for matching class attributes. +/// * `img_regex` - A reference to a `Regex` object for matching image tags. +/// +/// # Returns +/// +/// A `Result` containing a `String` with the processed HTML content, or a `HtmlGenerationError` if an error occurs. +/// +/// # Example +/// +/// ```rust +/// use regex::Regex; +/// use ssg::modules::html::preprocess_content; +/// +/// let content = "
...
"; +/// let class_regex = Regex::new(r#".class="([^"]+)""#).unwrap(); +/// let img_regex = Regex::new(r#"]+)>"#).unwrap(); +/// +/// let processed_content = preprocess_content(content, &class_regex, &img_regex).unwrap(); +/// println!("{}", processed_content); +/// ``` +pub fn preprocess_content( + content: &str, + class_regex: &Regex, + img_regex: &Regex, +) -> Result { + let processed_content: Vec = content + .lines() + .map(|line| { + update_class_attributes(line, class_regex, img_regex) + }) + .collect(); + Ok(processed_content.join("\n")) +} + fn process_headers( html: &str, header_tags: &[&str], @@ -101,7 +190,7 @@ fn process_headers( html_string } -/// Generate header HTML string based on title +/// Generate page header HTML string based on title /// /// # Arguments /// @@ -116,69 +205,17 @@ fn process_headers( /// /// ```rust /// use regex::Regex; -/// use ssg::modules::html::generate_header; +/// use ssg::modules::html::generate_page_header; /// let id_regex = Regex::new(r"[^a-zA-Z0-9]+").unwrap(); -/// let header_html = generate_header("My Page Title", &id_regex); +/// let header_html = generate_page_header("My Page Title", &id_regex); /// assert_eq!(header_html, "

My Page Title

"); /// ``` -pub fn generate_header(title: &str, id_regex: &Regex) -> String { - if title.is_empty() { - return String::new(); - } - +pub fn generate_page_header(title: &str, id_regex: &Regex) -> String { let header_str = format!("

{}

", title); format_header_with_id_class(&header_str, id_regex) } /// Generate description HTML string based on description fn generate_description(description: &str) -> String { - if description.is_empty() { - return String::new(); - } format!("

{}

", description) } - -/// Preprocesses the HTML content to update class attributes and image tags. -/// -/// # Arguments -/// -/// * `content` - A string containing the HTML content to be processed. -/// * `class_regex` - A reference to a `Regex` object for matching class attributes. -/// * `img_regex` - A reference to a `Regex` object for matching image tags. -/// -/// # Returns -/// -/// A `Result` containing a `String` with the processed HTML content, or a `Box` if an error occurs. -/// -/// # Example -/// -/// ```rust -/// use regex::Regex; -/// use std::error::Error; -/// use ssg::modules::html::preprocess_content; -/// -/// fn main() -> Result<(), Box> { -/// let content = "
...
"; -/// let class_regex = Regex::new(r#".class="([^"]+)""#)?; -/// let img_regex = Regex::new(r#"]+)>"#)?; -/// -/// let processed_content = preprocess_content(content, &class_regex, &img_regex)?; -/// println!("{}", processed_content); -/// -/// Ok(()) -/// } -/// ``` -pub fn preprocess_content( - content: &str, - class_regex: &Regex, - img_regex: &Regex, -) -> Result> { - let processed_content: Vec = content - .lines() - .map(|line| { - update_class_attributes(line, class_regex, img_regex) - }) - .collect(); - // println!("{}", processed_content.join("\n")); - Ok(processed_content.join("\n")) -} diff --git a/tests/test_html.rs b/tests/test_html.rs index 6f719641..c1446c98 100644 --- a/tests/test_html.rs +++ b/tests/test_html.rs @@ -6,6 +6,7 @@ mod tests { utilities::directory::format_header_with_id_class, }; use ssg::modules::postprocessor::post_process_html; + use ssg::modules::html::HtmlGenerationError; #[test] fn test_generate_html_with_front_matter() { @@ -40,10 +41,15 @@ mod tests { let title = ""; let description = "Say hi to the world!"; let result = generate_html(content, title, description, None); - let expected = "

Say hi to the world!

Welcome

"; match result { - Ok(res) => assert_eq!(res.trim(), expected), - Err(e) => panic!("Error: {:?}", e), + Ok(_) => panic!("Expected an error but got Ok"), + Err(e) => { + if let HtmlGenerationError::EmptyTitle = e { + // Test passed + } else { + panic!("Unexpected error: {:?}", e); + } + } } } @@ -53,10 +59,15 @@ mod tests { let title = "Welcome"; let description = ""; let result = generate_html(content, title, description, None); - let expected = "

Welcome

Welcome

"; match result { - Ok(res) => assert_eq!(res.trim(), expected), - Err(e) => panic!("Error: {:?}", e), + Ok(_) => panic!("Expected an error but got Ok"), + Err(e) => { + if let HtmlGenerationError::EmptyDescription = e { + // Test passed + } else { + panic!("Unexpected error: {:?}", e); + } + } } }