This commit is contained in:
2025-04-09 07:54:37 +02:00
parent 5e4dcbf77c
commit 44cbf20d7b
13 changed files with 1073 additions and 518 deletions

View File

@@ -1,6 +1,8 @@
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::fs;
use serde::Deserialize;
use crate::collection::{Collection, CollectionBuilder};
use crate::error::{DocTreeError, Result};
@@ -8,6 +10,14 @@ use crate::storage::RedisStorage;
use crate::include::process_includes;
use crate::utils::{name_fix, ensure_md_extension};
/// 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! {
@@ -144,13 +154,100 @@ impl DocTree {
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> {
self.collections.keys().cloned().collect()
// 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()));
}
// Create a new collection
let collection = Collection {
path: PathBuf::new(), // We don't have the path, but it's not needed for Redis operations
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;
}
// Create a new collection
let collection = Collection {
path: PathBuf::new(), // We don't have the path, but it's not needed for Redis operations
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
@@ -163,7 +260,7 @@ impl DocTree {
/// # Returns
///
/// The page content or an error
pub fn page_get(&self, collection_name: Option<&str>, page_name: &str) -> Result<String> {
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
@@ -293,6 +390,111 @@ impl DocTree {
}
}
}
/// 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(())
}
}
impl DocTreeBuilder {
@@ -363,6 +565,47 @@ impl DocTreeBuilder {
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())
})?;
// Create a temporary DocTree to scan collections
let mut temp_doctree = DocTree {
collections: HashMap::new(),
default_collection: None,
storage: storage.clone(),
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
@@ -375,7 +618,7 @@ impl DocTreeBuilder {
})?;
// Create the DocTree
let doctree = DocTree {
let mut doctree = DocTree {
collections: self.collections,
default_collection: self.default_collection,
storage: storage.clone(),
@@ -389,6 +632,9 @@ impl DocTreeBuilder {
*current_collection_name = Some(default_collection.clone());
}
// Load all collections from Redis
doctree.load_collections_from_redis()?;
Ok(doctree)
}
}
@@ -430,4 +676,22 @@ pub fn new<P: AsRef<Path>>(args: &[&str]) -> Result<DocTree> {
}
builder.build()
}
/// Create a new DocTree by scanning a directory for collections
///
/// # Arguments
///
/// * `root_path` - The root path to scan for collections
///
/// # Returns
///
/// A new DocTree or an error
pub fn from_directory<P: AsRef<Path>>(root_path: P) -> Result<DocTree> {
let storage = RedisStorage::new("redis://localhost:6379")?;
DocTree::builder()
.with_storage(storage)
.scan_collections(root_path)?
.build()
}