829 lines
27 KiB
Rust
829 lines
27 KiB
Rust
|
|
use std::collections::HashMap;
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::{Arc, Mutex};
|
|
use std::fs;
|
|
use serde::Deserialize;
|
|
|
|
use crate::collection::Collection;
|
|
use crate::error::{DocTreeError, Result};
|
|
use crate::storage::RedisStorage;
|
|
use crate::include::process_includes;
|
|
use crate::utils::name_fix;
|
|
|
|
/// Configuration for a collection from a .collection file
|
|
#[derive(Deserialize, Default, Debug)]
|
|
struct CollectionConfig {
|
|
/// Optional name of the collection
|
|
name: Option<String>,
|
|
// Add other configuration options as needed
|
|
}
|
|
|
|
// 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,
|
|
|
|
/// Name of the doctree (used as prefix for Redis keys)
|
|
pub doctree_name: String,
|
|
|
|
/// 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>,
|
|
|
|
/// Name of the doctree (used as prefix for Redis keys)
|
|
doctree_name: Option<String>,
|
|
|
|
/// 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,
|
|
doctree_name: Some("default".to_string()),
|
|
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);
|
|
// Clone the storage and set the doctree name
|
|
let storage = self.storage.clone();
|
|
storage.set_doctree_name(&self.doctree_name);
|
|
|
|
let collection = Collection::builder(path, &namefixed)
|
|
.with_storage(storage)
|
|
.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(())
|
|
}
|
|
|
|
/// Delete all collections from the DocTree and Redis
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Ok(()) on success or an error
|
|
pub fn delete_all_collections(&mut self) -> Result<()> {
|
|
// Delete all collections from Redis
|
|
self.storage.delete_all_collections()?;
|
|
|
|
// Clear the collections map
|
|
self.collections.clear();
|
|
|
|
// Reset the default collection
|
|
self.default_collection = None;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// List all collections
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A vector of collection names
|
|
pub fn list_collections(&self) -> Vec<String> {
|
|
// First, try to get collections from the in-memory map
|
|
let mut collections = self.collections.keys().cloned().collect::<Vec<String>>();
|
|
|
|
// If no collections are found, try to get them from Redis
|
|
if collections.is_empty() {
|
|
// Get all collection keys from Redis
|
|
if let Ok(keys) = self.storage.list_all_collections() {
|
|
collections = keys;
|
|
}
|
|
}
|
|
|
|
collections
|
|
}
|
|
|
|
/// Load a collection from Redis
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `name` - Name of the collection
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Ok(()) on success or an error
|
|
pub fn load_collection(&mut self, name: &str) -> Result<()> {
|
|
// Check if the collection exists in Redis
|
|
if !self.storage.collection_exists(name)? {
|
|
return Err(DocTreeError::CollectionNotFound(name.to_string()));
|
|
}
|
|
|
|
// Try to get the collection's path from Redis
|
|
let path = match self.storage.get_collection_path(name) {
|
|
Ok(path_str) => {
|
|
println!("DEBUG: Found collection path in Redis: {}", path_str);
|
|
PathBuf::from(path_str)
|
|
},
|
|
Err(e) => {
|
|
println!("DEBUG: Could not retrieve collection path from Redis: {}", e);
|
|
PathBuf::new() // Fallback to empty path if not found
|
|
}
|
|
};
|
|
|
|
// Create a new collection
|
|
let collection = Collection {
|
|
path,
|
|
name: name.to_string(),
|
|
storage: self.storage.clone(),
|
|
};
|
|
|
|
// Add to the collections map
|
|
self.collections.insert(name.to_string(), collection);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Load all collections from Redis
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Ok(()) on success or an error
|
|
pub fn load_collections_from_redis(&mut self) -> Result<()> {
|
|
// Get all collection names from Redis
|
|
let collections = self.storage.list_all_collections()?;
|
|
|
|
// Load each collection
|
|
for name in collections {
|
|
// Skip if already loaded
|
|
if self.collections.contains_key(&name) {
|
|
continue;
|
|
}
|
|
|
|
// Try to get the collection's path from Redis
|
|
let path = match self.storage.get_collection_path(&name) {
|
|
Ok(path_str) => {
|
|
println!("DEBUG: Found collection path in Redis: {}", path_str);
|
|
PathBuf::from(path_str)
|
|
},
|
|
Err(e) => {
|
|
println!("DEBUG: Could not retrieve collection path from Redis: {}", e);
|
|
PathBuf::new() // Fallback to empty path if not found
|
|
}
|
|
};
|
|
|
|
// Create a new collection
|
|
let collection = Collection {
|
|
path,
|
|
name: name.clone(),
|
|
storage: self.storage.clone(),
|
|
};
|
|
|
|
// Add to the collections map
|
|
self.collections.insert(name, collection);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// 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(&mut 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))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Recursively scan directories for .collection files and add them as collections
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `root_path` - The root path to start scanning from
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Ok(()) on success or an error
|
|
pub fn scan_collections<P: AsRef<Path>>(&mut self, root_path: P) -> Result<()> {
|
|
let root_path = root_path.as_ref();
|
|
|
|
println!("DEBUG: Scanning for collections in directory: {:?}", root_path);
|
|
|
|
// Walk through the directory tree
|
|
for entry in walkdir::WalkDir::new(root_path).follow_links(true) {
|
|
let entry = match entry {
|
|
Ok(entry) => entry,
|
|
Err(e) => {
|
|
eprintln!("Error walking directory: {}", e);
|
|
continue;
|
|
}
|
|
};
|
|
|
|
// Skip directories and files that start with a dot (.)
|
|
let file_name = entry.file_name().to_string_lossy();
|
|
if file_name.starts_with(".") {
|
|
continue;
|
|
}
|
|
|
|
// Skip non-directories
|
|
if !entry.file_type().is_dir() {
|
|
continue;
|
|
}
|
|
|
|
// Check if this directory contains a .collection file
|
|
let collection_file_path = entry.path().join(".collection");
|
|
if collection_file_path.exists() {
|
|
// Found a collection directory
|
|
println!("DEBUG: Found .collection file at: {:?}", collection_file_path);
|
|
let dir_path = entry.path();
|
|
|
|
// Get the directory name as a fallback collection name
|
|
let dir_name = dir_path.file_name()
|
|
.and_then(|name| name.to_str())
|
|
.unwrap_or("unnamed");
|
|
|
|
// Try to read and parse the .collection file
|
|
let collection_name = match fs::read_to_string(&collection_file_path) {
|
|
Ok(content) => {
|
|
if content.trim().is_empty() {
|
|
// Empty file, use directory name (name_fixed)
|
|
dir_name.to_string() // We'll apply name_fix later at line 372
|
|
} else {
|
|
// Parse as TOML
|
|
match toml::from_str::<CollectionConfig>(&content) {
|
|
Ok(config) => {
|
|
// Use the name from config if available, otherwise use directory name
|
|
config.name.unwrap_or_else(|| dir_name.to_string())
|
|
},
|
|
Err(e) => {
|
|
eprintln!("Error parsing .collection file at {:?}: {}", collection_file_path, e);
|
|
dir_name.to_string()
|
|
}
|
|
}
|
|
}
|
|
},
|
|
Err(e) => {
|
|
eprintln!("Error reading .collection file at {:?}: {}", collection_file_path, e);
|
|
dir_name.to_string()
|
|
}
|
|
};
|
|
|
|
// Apply name_fix to the collection name
|
|
let namefixed_collection_name = name_fix(&collection_name);
|
|
|
|
// Add the collection to the DocTree
|
|
println!("DEBUG: Adding collection '{}' from directory {:?}", namefixed_collection_name, dir_path);
|
|
match self.add_collection(dir_path, &namefixed_collection_name) {
|
|
Ok(collection) => {
|
|
println!("DEBUG: Successfully added collection '{}' from {:?}", namefixed_collection_name, dir_path);
|
|
println!("DEBUG: Collection stored in Redis key 'collections:{}'", collection.name);
|
|
|
|
// Count documents and images
|
|
let docs = collection.page_list().unwrap_or_default();
|
|
let files = collection.file_list().unwrap_or_default();
|
|
let images = files.iter().filter(|f|
|
|
f.ends_with(".png") || f.ends_with(".jpg") ||
|
|
f.ends_with(".jpeg") || f.ends_with(".gif") ||
|
|
f.ends_with(".svg")
|
|
).count();
|
|
|
|
println!("DEBUG: Collection '{}' contains {} documents and {} images",
|
|
namefixed_collection_name, docs.len(), images);
|
|
},
|
|
Err(e) => {
|
|
eprintln!("Error adding collection '{}' from {:?}: {}", namefixed_collection_name, dir_path, e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Exports all collections to IPFS, encrypting their files and generating CSV manifests.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `output_dir` - The directory to save the output CSV files.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Ok(()) on success or an error.
|
|
pub async fn export_collections_to_ipfs<P: AsRef<Path>>(&self, output_dir: P) -> Result<()> {
|
|
use tokio::fs;
|
|
|
|
let output_dir = output_dir.as_ref();
|
|
|
|
// Create the output directory if it doesn't exist
|
|
fs::create_dir_all(output_dir).await.map_err(DocTreeError::IoError)?;
|
|
|
|
for (name, collection) in &self.collections {
|
|
let csv_file_path = output_dir.join(format!("{}.csv", name));
|
|
println!("DEBUG: Exporting collection '{}' to IPFS and generating CSV at {:?}", name, csv_file_path);
|
|
if let Err(e) = collection.export_to_ipfs(&csv_file_path) {
|
|
eprintln!("Error exporting collection '{}': {}", name, e);
|
|
// Continue with the next collection
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Exports a specific collection to IPFS synchronously, encrypting its files and generating a CSV manifest.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `collection_name` - The name of the collection to export.
|
|
/// * `output_csv_path` - The path to save the output CSV file.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Ok(()) on success or an error.
|
|
pub fn export_collection_to_ipfs(&self, collection_name: &str, output_csv_path: &Path) -> Result<()> {
|
|
// Get the collection
|
|
let collection = self.get_collection(collection_name)?;
|
|
|
|
// Create a new tokio runtime and block on the async export function
|
|
let csv_file_path = output_csv_path.join(format!("{}.csv", collection_name));
|
|
collection.export_to_ipfs(&csv_file_path)?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl DocTreeBuilder {
|
|
/// Set the storage backend
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `storage` - Redis storage backend
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Self for method chaining
|
|
/// Set the doctree name
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `name` - Name of the doctree
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Self for method chaining
|
|
pub fn with_doctree_name(mut self, name: &str) -> Self {
|
|
self.doctree_name = Some(name.to_string());
|
|
self
|
|
}
|
|
|
|
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())
|
|
})?;
|
|
|
|
// Get the doctree name
|
|
let doctree_name = self.doctree_name.clone().unwrap_or_else(|| "default".to_string());
|
|
|
|
// Create a new collection
|
|
let namefixed = name_fix(name);
|
|
|
|
// Clone the storage and set the doctree name
|
|
let storage_clone = storage.clone();
|
|
storage_clone.set_doctree_name(&doctree_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
|
|
}
|
|
|
|
/// Scan for collections in the given root path
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `root_path` - The root path to scan for collections
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Self for method chaining or an error
|
|
pub fn scan_collections<P: AsRef<Path>>(self, root_path: P) -> Result<Self> {
|
|
// Ensure storage is set
|
|
let storage = self.storage.as_ref().ok_or_else(|| {
|
|
DocTreeError::MissingParameter("storage".to_string())
|
|
})?;
|
|
|
|
// Get the doctree name
|
|
let doctree_name = self.doctree_name.clone().unwrap_or_else(|| "default".to_string());
|
|
|
|
// Clone the storage and set the doctree name
|
|
let storage_clone = storage.clone();
|
|
storage_clone.set_doctree_name(&doctree_name);
|
|
|
|
// Create a temporary DocTree to scan collections
|
|
let mut temp_doctree = DocTree {
|
|
collections: HashMap::new(),
|
|
default_collection: None,
|
|
storage: storage_clone,
|
|
doctree_name: doctree_name,
|
|
name: self.name.clone().unwrap_or_default(),
|
|
path: self.path.clone().unwrap_or_else(|| PathBuf::from("")),
|
|
};
|
|
|
|
// Scan for collections
|
|
temp_doctree.scan_collections(root_path)?;
|
|
|
|
// Create a new builder with the scanned collections
|
|
let mut new_builder = self;
|
|
for (name, collection) in temp_doctree.collections {
|
|
new_builder.collections.insert(name.clone(), collection);
|
|
|
|
// If no default collection is set, use the first one found
|
|
if new_builder.default_collection.is_none() {
|
|
new_builder.default_collection = Some(name);
|
|
}
|
|
}
|
|
|
|
Ok(new_builder)
|
|
}
|
|
|
|
/// 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())
|
|
})?;
|
|
|
|
// Get the doctree name
|
|
let doctree_name = self.doctree_name.unwrap_or_else(|| "default".to_string());
|
|
|
|
// Set the doctree name in the storage
|
|
let storage_clone = storage.clone();
|
|
storage_clone.set_doctree_name(&doctree_name);
|
|
|
|
// Create the DocTree
|
|
let mut doctree = DocTree {
|
|
collections: self.collections,
|
|
default_collection: self.default_collection,
|
|
storage: storage_clone,
|
|
doctree_name,
|
|
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());
|
|
}
|
|
|
|
// Load all collections from Redis
|
|
doctree.load_collections_from_redis()?;
|
|
|
|
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);
|
|
|
|
// If the first argument is a doctree name, use it
|
|
if args.len() >= 1 && args[0].starts_with("--doctree=") {
|
|
let doctree_name = args[0].trim_start_matches("--doctree=");
|
|
builder = builder.with_doctree_name(doctree_name);
|
|
}
|
|
|
|
// 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()
|
|
}
|
|
|
|
/// Create a new DocTree by scanning a directory for collections
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `root_path` - The root path to scan for collections
|
|
/// * `doctree_name` - Optional name for the doctree (default: "default")
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A new DocTree or an error
|
|
pub fn from_directory<P: AsRef<Path>>(root_path: P, doctree_name: Option<&str>) -> Result<DocTree> {
|
|
let storage = RedisStorage::new("redis://localhost:6379")?;
|
|
|
|
let mut builder = DocTree::builder().with_storage(storage);
|
|
|
|
// Set the doctree name if provided
|
|
if let Some(name) = doctree_name {
|
|
builder = builder.with_doctree_name(name);
|
|
}
|
|
|
|
builder.scan_collections(root_path)?.build()
|
|
} |