178 lines
6.2 KiB
Rust
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)
|
|
} |