433 lines
12 KiB
Rust
433 lines
12 KiB
Rust
use std::collections::HashMap;
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
use crate::collection::{Collection, CollectionBuilder};
|
|
use crate::error::{DocTreeError, Result};
|
|
use crate::storage::RedisStorage;
|
|
use crate::include::process_includes;
|
|
use crate::utils::{name_fix, ensure_md_extension};
|
|
|
|
// Global variable to track the current collection name
|
|
// This is for compatibility with the Go implementation
|
|
lazy_static::lazy_static! {
|
|
static ref CURRENT_COLLECTION_NAME: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
|
|
}
|
|
|
|
// Global variable to track the current Collection
|
|
// This is for compatibility with the Go implementation
|
|
|
|
/// DocTree represents a manager for multiple collections
|
|
pub struct DocTree {
|
|
/// Map of collections by name
|
|
pub collections: HashMap<String, Collection>,
|
|
|
|
/// Default collection name
|
|
pub default_collection: Option<String>,
|
|
|
|
/// Redis storage backend
|
|
storage: RedisStorage,
|
|
|
|
/// For backward compatibility
|
|
pub name: String,
|
|
|
|
/// For backward compatibility
|
|
pub path: PathBuf,
|
|
}
|
|
|
|
/// Builder for DocTree
|
|
pub struct DocTreeBuilder {
|
|
/// Map of collections by name
|
|
collections: HashMap<String, Collection>,
|
|
|
|
/// Default collection name
|
|
default_collection: Option<String>,
|
|
|
|
/// Redis storage backend
|
|
storage: Option<RedisStorage>,
|
|
|
|
/// For backward compatibility
|
|
name: Option<String>,
|
|
|
|
/// For backward compatibility
|
|
path: Option<PathBuf>,
|
|
}
|
|
|
|
impl DocTree {
|
|
/// Create a new DocTreeBuilder
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A new DocTreeBuilder
|
|
pub fn builder() -> DocTreeBuilder {
|
|
DocTreeBuilder {
|
|
collections: HashMap::new(),
|
|
default_collection: None,
|
|
storage: None,
|
|
name: None,
|
|
path: None,
|
|
}
|
|
}
|
|
|
|
/// Add a collection to the DocTree
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `path` - Base path of the collection
|
|
/// * `name` - Name of the collection
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The added collection or an error
|
|
pub fn add_collection<P: AsRef<Path>>(&mut self, path: P, name: &str) -> Result<&Collection> {
|
|
// Create a new collection
|
|
let namefixed = name_fix(name);
|
|
let collection = Collection::builder(path, &namefixed)
|
|
.with_storage(self.storage.clone())
|
|
.build()?;
|
|
|
|
// Scan the collection
|
|
collection.scan()?;
|
|
|
|
// Add to the collections map
|
|
self.collections.insert(collection.name.clone(), collection);
|
|
|
|
// Return a reference to the added collection
|
|
self.collections.get(&namefixed).ok_or_else(|| {
|
|
DocTreeError::CollectionNotFound(namefixed.clone())
|
|
})
|
|
}
|
|
|
|
/// Get a collection by name
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `name` - Name of the collection
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The collection or an error
|
|
pub fn get_collection(&self, name: &str) -> Result<&Collection> {
|
|
// For compatibility with tests, apply namefix
|
|
let namefixed = name_fix(name);
|
|
|
|
// Check if the collection exists
|
|
self.collections.get(&namefixed).ok_or_else(|| {
|
|
DocTreeError::CollectionNotFound(name.to_string())
|
|
})
|
|
}
|
|
|
|
/// Delete a collection from the DocTree
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `name` - Name of the collection
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Ok(()) on success or an error
|
|
pub fn delete_collection(&mut self, name: &str) -> Result<()> {
|
|
// For compatibility with tests, apply namefix
|
|
let namefixed = name_fix(name);
|
|
|
|
// Check if the collection exists
|
|
if !self.collections.contains_key(&namefixed) {
|
|
return Err(DocTreeError::CollectionNotFound(name.to_string()));
|
|
}
|
|
|
|
// Delete from Redis
|
|
self.storage.delete_collection(&namefixed)?;
|
|
|
|
// Remove from the collections map
|
|
self.collections.remove(&namefixed);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// List all collections
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A vector of collection names
|
|
pub fn list_collections(&self) -> Vec<String> {
|
|
self.collections.keys().cloned().collect()
|
|
}
|
|
|
|
/// Get a page by name from a specific collection
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `collection_name` - Name of the collection (optional)
|
|
/// * `page_name` - Name of the page
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The page content or an error
|
|
pub fn page_get(&self, collection_name: Option<&str>, page_name: &str) -> Result<String> {
|
|
let (collection_name, page_name) = self.resolve_collection_and_page(collection_name, page_name)?;
|
|
|
|
// Get the collection
|
|
let collection = self.get_collection(&collection_name)?;
|
|
|
|
// Get the page content
|
|
let content = collection.page_get(page_name)?;
|
|
|
|
// Process includes
|
|
let processed_content = process_includes(&content, &collection_name, self)?;
|
|
|
|
Ok(processed_content)
|
|
}
|
|
|
|
/// Get a page by name from a specific collection and return its HTML content
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `collection_name` - Name of the collection (optional)
|
|
/// * `page_name` - Name of the page
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The HTML content or an error
|
|
pub fn page_get_html(&self, collection_name: Option<&str>, page_name: &str) -> Result<String> {
|
|
let (collection_name, page_name) = self.resolve_collection_and_page(collection_name, page_name)?;
|
|
|
|
// Get the collection
|
|
let collection = self.get_collection(&collection_name)?;
|
|
|
|
// Get the HTML
|
|
collection.page_get_html(page_name, Some(self))
|
|
}
|
|
|
|
/// Get the URL for a file in a specific collection
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `collection_name` - Name of the collection (optional)
|
|
/// * `file_name` - Name of the file
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The URL for the file or an error
|
|
pub fn file_get_url(&self, collection_name: Option<&str>, file_name: &str) -> Result<String> {
|
|
let (collection_name, file_name) = self.resolve_collection_and_page(collection_name, file_name)?;
|
|
|
|
// Get the collection
|
|
let collection = self.get_collection(&collection_name)?;
|
|
|
|
// Get the URL
|
|
collection.file_get_url(file_name)
|
|
}
|
|
|
|
/// Get the path to a page in the default collection
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `page_name` - Name of the page
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The path to the page or an error
|
|
pub fn page_get_path(&self, page_name: &str) -> Result<String> {
|
|
// Check if a default collection is set
|
|
let default_collection = self.default_collection.as_ref().ok_or_else(|| {
|
|
DocTreeError::NoDefaultCollection
|
|
})?;
|
|
|
|
// Get the collection
|
|
let collection = self.get_collection(default_collection)?;
|
|
|
|
// Get the path
|
|
collection.page_get_path(page_name)
|
|
}
|
|
|
|
/// Get information about the DocTree
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A map of information
|
|
pub fn info(&self) -> HashMap<String, String> {
|
|
let mut info = HashMap::new();
|
|
info.insert("name".to_string(), self.name.clone());
|
|
info.insert("path".to_string(), self.path.to_string_lossy().to_string());
|
|
info.insert("collections".to_string(), self.collections.len().to_string());
|
|
info
|
|
}
|
|
|
|
/// Scan the default collection
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Ok(()) on success or an error
|
|
pub fn scan(&self) -> Result<()> {
|
|
// Check if a default collection is set
|
|
let default_collection = self.default_collection.as_ref().ok_or_else(|| {
|
|
DocTreeError::NoDefaultCollection
|
|
})?;
|
|
|
|
// Get the collection
|
|
let collection = self.get_collection(default_collection)?;
|
|
|
|
// Scan the collection
|
|
collection.scan()
|
|
}
|
|
|
|
/// Resolve collection and page names
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `collection_name` - Name of the collection (optional)
|
|
/// * `page_name` - Name of the page
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A tuple of (collection_name, page_name) or an error
|
|
fn resolve_collection_and_page<'a>(&self, collection_name: Option<&'a str>, page_name: &'a str) -> Result<(String, &'a str)> {
|
|
match collection_name {
|
|
Some(name) => Ok((name_fix(name), page_name)),
|
|
None => {
|
|
// Use the default collection
|
|
let default_collection = self.default_collection.as_ref().ok_or_else(|| {
|
|
DocTreeError::NoDefaultCollection
|
|
})?;
|
|
Ok((default_collection.clone(), page_name))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DocTreeBuilder {
|
|
/// Set the storage backend
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `storage` - Redis storage backend
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Self for method chaining
|
|
pub fn with_storage(mut self, storage: RedisStorage) -> Self {
|
|
self.storage = Some(storage);
|
|
self
|
|
}
|
|
|
|
/// Add a collection
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `path` - Base path of the collection
|
|
/// * `name` - Name of the collection
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Self for method chaining or an error
|
|
pub fn with_collection<P: AsRef<Path>>(mut self, path: P, name: &str) -> Result<Self> {
|
|
// Ensure storage is set
|
|
let storage = self.storage.as_ref().ok_or_else(|| {
|
|
DocTreeError::MissingParameter("storage".to_string())
|
|
})?;
|
|
|
|
// Create a new collection
|
|
let namefixed = name_fix(name);
|
|
let collection = Collection::builder(path.as_ref(), &namefixed)
|
|
.with_storage(storage.clone())
|
|
.build()?;
|
|
|
|
// Scan the collection
|
|
collection.scan()?;
|
|
|
|
// Add to the collections map
|
|
self.collections.insert(collection.name.clone(), collection);
|
|
|
|
// For backward compatibility
|
|
if self.name.is_none() {
|
|
self.name = Some(namefixed.clone());
|
|
}
|
|
if self.path.is_none() {
|
|
self.path = Some(path.as_ref().to_path_buf());
|
|
}
|
|
|
|
Ok(self)
|
|
}
|
|
|
|
/// Set the default collection
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `name` - Name of the default collection
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Self for method chaining
|
|
pub fn with_default_collection(mut self, name: &str) -> Self {
|
|
self.default_collection = Some(name_fix(name));
|
|
self
|
|
}
|
|
|
|
/// Build the DocTree
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A new DocTree or an error
|
|
pub fn build(self) -> Result<DocTree> {
|
|
// Ensure storage is set
|
|
let storage = self.storage.ok_or_else(|| {
|
|
DocTreeError::MissingParameter("storage".to_string())
|
|
})?;
|
|
|
|
// Create the DocTree
|
|
let doctree = DocTree {
|
|
collections: self.collections,
|
|
default_collection: self.default_collection,
|
|
storage: storage.clone(),
|
|
name: self.name.unwrap_or_default(),
|
|
path: self.path.unwrap_or_else(|| PathBuf::from("")),
|
|
};
|
|
|
|
// Set the global current collection name if a default collection is set
|
|
if let Some(default_collection) = &doctree.default_collection {
|
|
let mut current_collection_name = CURRENT_COLLECTION_NAME.lock().unwrap();
|
|
*current_collection_name = Some(default_collection.clone());
|
|
}
|
|
|
|
Ok(doctree)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Create a new DocTree instance
|
|
///
|
|
/// For backward compatibility, it also accepts path and name parameters
|
|
/// to create a DocTree with a single collection
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `args` - Optional path and name for backward compatibility
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A new DocTree or an error
|
|
pub fn new<P: AsRef<Path>>(args: &[&str]) -> Result<DocTree> {
|
|
let storage = RedisStorage::new("redis://localhost:6379")?;
|
|
|
|
let mut builder = DocTree::builder().with_storage(storage);
|
|
|
|
// For backward compatibility with existing code
|
|
if args.len() == 2 {
|
|
let path = args[0];
|
|
let name = args[1];
|
|
|
|
// Apply namefix for compatibility with tests
|
|
let namefixed = name_fix(name);
|
|
|
|
// Add the collection
|
|
builder = builder.with_collection(path, &namefixed)?;
|
|
|
|
// Set the default collection
|
|
builder = builder.with_default_collection(&namefixed);
|
|
}
|
|
|
|
builder.build()
|
|
} |