use crate::doctree::DocTree; use crate::error::{DocTreeError, Result}; use crate::utils::trim_spaces_and_quotes; /// Process includes in markdown content /// /// # Arguments /// /// * `content` - The markdown content to process /// * `current_collection_name` - The name of the current collection /// * `doctree` - The DocTree instance /// /// # Returns /// /// The processed content or an error pub fn process_includes(content: &str, current_collection_name: &str, doctree: &DocTree) -> Result { // Find all include directives let lines: Vec<&str> = content.split('\n').collect(); let mut result = Vec::with_capacity(lines.len()); for line in lines { match parse_include_line(line) { Ok((Some(c), Some(p))) => { // Both collection and page specified match handle_include(&p, &c, doctree) { Ok(include_content) => { // Process any nested includes in the included content match process_includes(&include_content, &c, doctree) { Ok(processed_include_content) => { result.push(processed_include_content); }, Err(e) => { result.push(format!(">>ERROR: Failed to process nested includes: {}", e)); } } }, Err(e) => { result.push(format!(">>ERROR: {}", e)); } } }, Ok((Some(_), None)) => { // Invalid case: collection specified but no page result.push(format!(">>ERROR: Invalid include directive: collection specified but no page name")); }, Ok((None, Some(p))) => { // Only page specified, use current collection match handle_include(&p, current_collection_name, doctree) { Ok(include_content) => { // Process any nested includes in the included content match process_includes(&include_content, current_collection_name, doctree) { Ok(processed_include_content) => { result.push(processed_include_content); }, Err(e) => { result.push(format!(">>ERROR: Failed to process nested includes: {}", e)); } } }, Err(e) => { result.push(format!(">>ERROR: {}", e)); } } }, Ok((None, None)) => { // Not an include directive, keep the line result.push(line.to_string()); }, Err(e) => { // Error parsing include directive result.push(format!(">>ERROR: Failed to process include directive: {}", e)); } } } Ok(result.join("\n")) } /// Parse an include directive line /// /// # Arguments /// /// * `line` - The line to parse /// /// # Returns /// /// A tuple of (collection_name, page_name) or an error /// /// Supports: /// - !!include collectionname:'pagename' /// - !!include collectionname:'pagename.md' /// - !!include 'pagename' /// - !!include collectionname:pagename /// - !!include collectionname:pagename.md /// - !!include name:'pagename' /// - !!include pagename fn parse_include_line(line: &str) -> Result<(Option, Option)> { // Check if the line contains an include directive if !line.contains("!!include") { return Ok((None, None)); } // Extract the part after !!include let parts: Vec<&str> = line.splitn(2, "!!include").collect(); if parts.len() != 2 { return Err(DocTreeError::InvalidIncludeDirective(line.to_string())); } // Trim spaces and check if the include part is empty let include_text = trim_spaces_and_quotes(parts[1]); if include_text.is_empty() { return Err(DocTreeError::InvalidIncludeDirective(line.to_string())); } // Remove name: prefix if present let include_text = if include_text.starts_with("name:") { let text = include_text.trim_start_matches("name:").trim(); if text.is_empty() { return Err(DocTreeError::InvalidIncludeDirective( format!("empty page name after 'name:' prefix: {}", line) )); } text.to_string() } else { include_text }; // Check if it contains a collection reference (has a colon) if include_text.contains(':') { let parts: Vec<&str> = include_text.splitn(2, ':').collect(); if parts.len() != 2 { return Err(DocTreeError::InvalidIncludeDirective( format!("malformed collection reference: {}", include_text) )); } let collection_name = parts[0].trim(); let page_name = trim_spaces_and_quotes(parts[1]); if collection_name.is_empty() { return Err(DocTreeError::InvalidIncludeDirective( format!("empty collection name in include directive: {}", line) )); } if page_name.is_empty() { return Err(DocTreeError::InvalidIncludeDirective( format!("empty page name in include directive: {}", line) )); } Ok((Some(collection_name.to_string()), Some(page_name))) } else { // No collection specified, just a page name Ok((None, Some(include_text))) } } /// Handle an include directive /// /// # Arguments /// /// * `page_name` - The name of the page to include /// * `collection_name` - The name of the collection /// * `doctree` - The DocTree instance /// /// # Returns /// /// The included content or an error fn handle_include(page_name: &str, collection_name: &str, doctree: &DocTree) -> Result { // Get the collection let collection = doctree.get_collection(collection_name)?; // Get the page content let content = collection.page_get(page_name)?; Ok(content) }