doctree_rust/doctree/src/include.rs
2025-04-09 07:11:38 +02:00

178 lines
6.2 KiB
Rust

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<String> {
// 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<String>, Option<String>)> {
// 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<String> {
// Get the collection
let collection = doctree.get_collection(collection_name)?;
// Get the page content
let content = collection.page_get(page_name)?;
Ok(content)
}