From 5e4dcbf77cdeb44c881609380077c12143e5cd3f Mon Sep 17 00:00:00 2001 From: despiegk Date: Wed, 9 Apr 2025 07:11:38 +0200 Subject: [PATCH] ... --- doctree/Cargo.toml | 4 + doctree/src/collection.rs | 412 +++++++++++++++ doctree/src/doctree.rs | 433 +++++++++++++++ doctree/src/error.rs | 44 ++ doctree/src/include.rs | 178 +++++++ doctree/src/lib.rs | 39 +- doctree/src/storage.rs | 169 ++++++ doctree/src/utils.rs | 106 ++++ doctree_implementation_plan.md | 499 ++++++++++++++++++ doctreecmd/Cargo.toml | 2 + doctreecmd/src/main.rs | 79 ++- examples/grid1/.collection | 0 examples/grid1/benefits/_category_.json | 8 + examples/grid1/benefits/revenue_generation.md | 28 + examples/grid1/benefits/use_cases.md | 32 ++ .../grid1/getting-started/_category_.json | 8 + .../getting-started/pre_order_process.md | 10 + .../grid1/getting-started/purchase_options.md | 84 +++ examples/grid1/getting-started/support.md | 8 + examples/grid1/introduction.md | 24 + examples/grid1/overview/_category_.json | 8 + examples/grid1/overview/vision_mission.md | 12 + examples/grid1/overview/who_is_aibox_for.md | 27 + .../overview/why_decentralized_ai_matters.md | 18 + .../grid1/technical-specs/_category_.json | 8 + .../technical-specs/features_capabilities.md | 35 ++ .../hardware_specifications.md | 37 ++ .../grid1/technical-specs/software_stack.md | 29 + examples/parent/docs2/.collection | 0 .../parent/docs2/get-started/01_features.md | 38 ++ .../docs2/get-started/02_mycelium-app.md | 23 + .../docs2/get-started/03_use-the-app.md | 48 ++ .../parent/docs2/get-started/_category_.json | 8 + .../docs2/get-started/img/mycelium_1.png | Bin 0 -> 36051 bytes .../docs2/get-started/img/mycelium_2.png | Bin 0 -> 45175 bytes .../docs2/get-started/img/mycelium_3.png | Bin 0 -> 14571 bytes .../docs2/get-started/img/mycelium_4.png | Bin 0 -> 9156 bytes 37 files changed, 2450 insertions(+), 8 deletions(-) create mode 100644 doctree/src/collection.rs create mode 100644 doctree/src/doctree.rs create mode 100644 doctree/src/error.rs create mode 100644 doctree/src/include.rs create mode 100644 doctree/src/storage.rs create mode 100644 doctree/src/utils.rs create mode 100644 doctree_implementation_plan.md create mode 100644 examples/grid1/.collection create mode 100644 examples/grid1/benefits/_category_.json create mode 100644 examples/grid1/benefits/revenue_generation.md create mode 100644 examples/grid1/benefits/use_cases.md create mode 100644 examples/grid1/getting-started/_category_.json create mode 100644 examples/grid1/getting-started/pre_order_process.md create mode 100644 examples/grid1/getting-started/purchase_options.md create mode 100644 examples/grid1/getting-started/support.md create mode 100644 examples/grid1/introduction.md create mode 100644 examples/grid1/overview/_category_.json create mode 100644 examples/grid1/overview/vision_mission.md create mode 100644 examples/grid1/overview/who_is_aibox_for.md create mode 100644 examples/grid1/overview/why_decentralized_ai_matters.md create mode 100644 examples/grid1/technical-specs/_category_.json create mode 100644 examples/grid1/technical-specs/features_capabilities.md create mode 100644 examples/grid1/technical-specs/hardware_specifications.md create mode 100644 examples/grid1/technical-specs/software_stack.md create mode 100644 examples/parent/docs2/.collection create mode 100644 examples/parent/docs2/get-started/01_features.md create mode 100644 examples/parent/docs2/get-started/02_mycelium-app.md create mode 100644 examples/parent/docs2/get-started/03_use-the-app.md create mode 100644 examples/parent/docs2/get-started/_category_.json create mode 100644 examples/parent/docs2/get-started/img/mycelium_1.png create mode 100644 examples/parent/docs2/get-started/img/mycelium_2.png create mode 100644 examples/parent/docs2/get-started/img/mycelium_3.png create mode 100644 examples/parent/docs2/get-started/img/mycelium_4.png diff --git a/doctree/Cargo.toml b/doctree/Cargo.toml index e1b690d..387fa29 100644 --- a/doctree/Cargo.toml +++ b/doctree/Cargo.toml @@ -4,3 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +walkdir = "2.3.3" +pulldown-cmark = "0.9.3" +thiserror = "1.0.40" +lazy_static = "1.4.0" diff --git a/doctree/src/collection.rs b/doctree/src/collection.rs new file mode 100644 index 0000000..28577ad --- /dev/null +++ b/doctree/src/collection.rs @@ -0,0 +1,412 @@ +use std::path::{Path, PathBuf}; +use walkdir::WalkDir; +use std::fs; + +use crate::error::{DocTreeError, Result}; +use crate::storage::RedisStorage; +use crate::utils::{name_fix, markdown_to_html, ensure_md_extension}; +use crate::include::process_includes; + +/// Collection represents a collection of markdown pages and files +#[derive(Clone)] +pub struct Collection { + /// Base path of the collection + pub path: PathBuf, + + /// Name of the collection (namefixed) + pub name: String, + + /// Redis storage backend + pub storage: RedisStorage, +} + +/// Builder for Collection +pub struct CollectionBuilder { + /// Base path of the collection + path: PathBuf, + + /// Name of the collection (namefixed) + name: String, + + /// Redis storage backend + storage: Option, +} + +impl Collection { + /// Create a new CollectionBuilder + /// + /// # Arguments + /// + /// * `path` - Base path of the collection + /// * `name` - Name of the collection + /// + /// # Returns + /// + /// A new CollectionBuilder + pub fn builder>(path: P, name: &str) -> CollectionBuilder { + CollectionBuilder { + path: path.as_ref().to_path_buf(), + name: name_fix(name), + storage: None, + } + } + + /// Scan walks over the path and finds all files and .md files + /// It stores the relative positions in Redis + /// + /// # Returns + /// + /// Ok(()) on success or an error + pub fn scan(&self) -> Result<()> { + // Delete existing collection data if any + self.storage.delete_collection(&self.name)?; + + // Walk through the directory + let walker = WalkDir::new(&self.path); + for entry_result in walker { + // Handle entry errors + let entry = match entry_result { + Ok(entry) => entry, + Err(e) => { + // Log the error and continue + eprintln!("Error walking directory: {}", e); + continue; + } + }; + + // Skip directories + if entry.file_type().is_dir() { + continue; + } + + // Get the relative path from the base path + let rel_path = match entry.path().strip_prefix(&self.path) { + Ok(path) => path, + Err(_) => { + // Log the error and continue + eprintln!("Failed to get relative path for: {:?}", entry.path()); + continue; + } + }; + + // Get the filename and apply namefix + let filename = entry.file_name().to_string_lossy().to_string(); + let namefixed_filename = name_fix(&filename); + + // Store in Redis using the namefixed filename as the key + // Store the original relative path to preserve case and special characters + self.storage.store_collection_entry( + &self.name, + &namefixed_filename, + &rel_path.to_string_lossy() + )?; + } + + Ok(()) + } + + /// Get a page by name and return its markdown content + /// + /// # Arguments + /// + /// * `page_name` - Name of the page + /// + /// # Returns + /// + /// The page content or an error + pub fn page_get(&self, page_name: &str) -> Result { + // Apply namefix to the page name + let namefixed_page_name = name_fix(page_name); + + // Ensure it has .md extension + let namefixed_page_name = ensure_md_extension(&namefixed_page_name); + + // Get the relative path from Redis + let rel_path = self.storage.get_collection_entry(&self.name, &namefixed_page_name) + .map_err(|_| DocTreeError::PageNotFound(page_name.to_string()))?; + + // Read the file + let full_path = self.path.join(rel_path); + let content = fs::read_to_string(full_path) + .map_err(|e| DocTreeError::IoError(e))?; + + // Skip include processing at this level to avoid infinite recursion + // Include processing will be done at the higher level + + Ok(content) + } + + /// Create or update a page in the collection + /// + /// # Arguments + /// + /// * `page_name` - Name of the page + /// * `content` - Content of the page + /// + /// # Returns + /// + /// Ok(()) on success or an error + pub fn page_set(&self, page_name: &str, content: &str) -> Result<()> { + // Apply namefix to the page name + let namefixed_page_name = name_fix(page_name); + + // Ensure it has .md extension + let namefixed_page_name = ensure_md_extension(&namefixed_page_name); + + // Create the full path + let full_path = self.path.join(&namefixed_page_name); + + // Create directories if needed + if let Some(parent) = full_path.parent() { + fs::create_dir_all(parent).map_err(DocTreeError::IoError)?; + } + + // Write content to file + fs::write(&full_path, content).map_err(DocTreeError::IoError)?; + + // Update Redis + self.storage.store_collection_entry(&self.name, &namefixed_page_name, &namefixed_page_name)?; + + Ok(()) + } + + /// Delete a page from the collection + /// + /// # Arguments + /// + /// * `page_name` - Name of the page + /// + /// # Returns + /// + /// Ok(()) on success or an error + pub fn page_delete(&self, page_name: &str) -> Result<()> { + // Apply namefix to the page name + let namefixed_page_name = name_fix(page_name); + + // Ensure it has .md extension + let namefixed_page_name = ensure_md_extension(&namefixed_page_name); + + // Get the relative path from Redis + let rel_path = self.storage.get_collection_entry(&self.name, &namefixed_page_name) + .map_err(|_| DocTreeError::PageNotFound(page_name.to_string()))?; + + // Delete the file + let full_path = self.path.join(rel_path); + fs::remove_file(full_path).map_err(DocTreeError::IoError)?; + + // Remove from Redis + self.storage.delete_collection_entry(&self.name, &namefixed_page_name)?; + + Ok(()) + } + + /// List all pages in the collection + /// + /// # Returns + /// + /// A vector of page names or an error + pub fn page_list(&self) -> Result> { + // Get all keys from Redis + let keys = self.storage.list_collection_entries(&self.name)?; + + // Filter to only include .md files + let pages = keys.into_iter() + .filter(|key| key.ends_with(".md")) + .collect(); + + Ok(pages) + } + + /// Get the URL for a file + /// + /// # Arguments + /// + /// * `file_name` - Name of the file + /// + /// # Returns + /// + /// The URL for the file or an error + pub fn file_get_url(&self, file_name: &str) -> Result { + // Apply namefix to the file name + let namefixed_file_name = name_fix(file_name); + + // Get the relative path from Redis + let rel_path = self.storage.get_collection_entry(&self.name, &namefixed_file_name) + .map_err(|_| DocTreeError::FileNotFound(file_name.to_string()))?; + + // Construct a URL for the file + let url = format!("/collections/{}/files/{}", self.name, rel_path); + + Ok(url) + } + + /// Add or update a file in the collection + /// + /// # Arguments + /// + /// * `file_name` - Name of the file + /// * `content` - Content of the file + /// + /// # Returns + /// + /// Ok(()) on success or an error + pub fn file_set(&self, file_name: &str, content: &[u8]) -> Result<()> { + // Apply namefix to the file name + let namefixed_file_name = name_fix(file_name); + + // Create the full path + let full_path = self.path.join(&namefixed_file_name); + + // Create directories if needed + if let Some(parent) = full_path.parent() { + fs::create_dir_all(parent).map_err(DocTreeError::IoError)?; + } + + // Write content to file + fs::write(&full_path, content).map_err(DocTreeError::IoError)?; + + // Update Redis + self.storage.store_collection_entry(&self.name, &namefixed_file_name, &namefixed_file_name)?; + + Ok(()) + } + + /// Delete a file from the collection + /// + /// # Arguments + /// + /// * `file_name` - Name of the file + /// + /// # Returns + /// + /// Ok(()) on success or an error + pub fn file_delete(&self, file_name: &str) -> Result<()> { + // Apply namefix to the file name + let namefixed_file_name = name_fix(file_name); + + // Get the relative path from Redis + let rel_path = self.storage.get_collection_entry(&self.name, &namefixed_file_name) + .map_err(|_| DocTreeError::FileNotFound(file_name.to_string()))?; + + // Delete the file + let full_path = self.path.join(rel_path); + fs::remove_file(full_path).map_err(DocTreeError::IoError)?; + + // Remove from Redis + self.storage.delete_collection_entry(&self.name, &namefixed_file_name)?; + + Ok(()) + } + + /// List all files (non-markdown) in the collection + /// + /// # Returns + /// + /// A vector of file names or an error + pub fn file_list(&self) -> Result> { + // Get all keys from Redis + let keys = self.storage.list_collection_entries(&self.name)?; + + // Filter to exclude .md files + let files = keys.into_iter() + .filter(|key| !key.ends_with(".md")) + .collect(); + + Ok(files) + } + + /// Get the relative path of a page in the collection + /// + /// # Arguments + /// + /// * `page_name` - Name of the page + /// + /// # Returns + /// + /// The relative path of the page or an error + pub fn page_get_path(&self, page_name: &str) -> Result { + // Apply namefix to the page name + let namefixed_page_name = name_fix(page_name); + + // Ensure it has .md extension + let namefixed_page_name = ensure_md_extension(&namefixed_page_name); + + // Get the relative path from Redis + self.storage.get_collection_entry(&self.name, &namefixed_page_name) + .map_err(|_| DocTreeError::PageNotFound(page_name.to_string())) + } + + /// Get a page by name and return its HTML content + /// + /// # Arguments + /// + /// * `page_name` - Name of the page + /// * `doctree` - Optional DocTree instance for include processing + /// + /// # Returns + /// + /// The HTML content of the page or an error + pub fn page_get_html(&self, page_name: &str, doctree: Option<&crate::doctree::DocTree>) -> Result { + // Get the markdown content + let markdown = self.page_get(page_name)?; + + // Process includes if doctree is provided + let processed_markdown = if let Some(dt) = doctree { + process_includes(&markdown, &self.name, dt)? + } else { + markdown + }; + + // Convert markdown to HTML + let html = markdown_to_html(&processed_markdown); + + Ok(html) + } + + /// Get information about the Collection + /// + /// # Returns + /// + /// A map of information + pub fn info(&self) -> std::collections::HashMap { + let mut info = std::collections::HashMap::new(); + info.insert("name".to_string(), self.name.clone()); + info.insert("path".to_string(), self.path.to_string_lossy().to_string()); + info + } +} + +impl CollectionBuilder { + /// 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 + } + + /// Build the Collection + /// + /// # Returns + /// + /// A new Collection or an error + pub fn build(self) -> Result { + let storage = self.storage.ok_or_else(|| { + DocTreeError::MissingParameter("storage".to_string()) + })?; + + let collection = Collection { + path: self.path, + name: self.name, + storage, + }; + + Ok(collection) + } +} \ No newline at end of file diff --git a/doctree/src/doctree.rs b/doctree/src/doctree.rs new file mode 100644 index 0000000..afa1bc9 --- /dev/null +++ b/doctree/src/doctree.rs @@ -0,0 +1,433 @@ +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>> = 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, + + /// Default collection name + pub default_collection: Option, + + /// 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, + + /// Default collection name + default_collection: Option, + + /// Redis storage backend + storage: Option, + + /// For backward compatibility + name: Option, + + /// For backward compatibility + path: Option, +} + +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>(&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 { + 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 { + 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 { + 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 { + 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 { + // 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 { + 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>(mut self, path: P, name: &str) -> Result { + // 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 { + // 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>(args: &[&str]) -> Result { + 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() +} \ No newline at end of file diff --git a/doctree/src/error.rs b/doctree/src/error.rs new file mode 100644 index 0000000..bdac532 --- /dev/null +++ b/doctree/src/error.rs @@ -0,0 +1,44 @@ +use thiserror::Error; + +/// Custom error type for the doctree library +#[derive(Error, Debug)] +pub enum DocTreeError { + /// IO error + #[error("IO error: {0}")] + IoError(#[from] std::io::Error), + + /// WalkDir error + #[error("WalkDir error: {0}")] + WalkDirError(String), + + /// Collection not found + #[error("Collection not found: {0}")] + CollectionNotFound(String), + + /// Page not found + #[error("Page not found: {0}")] + PageNotFound(String), + + /// File not found + #[error("File not found: {0}")] + FileNotFound(String), + + /// Invalid include directive + #[error("Invalid include directive: {0}")] + InvalidIncludeDirective(String), + + /// No default collection set + #[error("No default collection set")] + NoDefaultCollection, + + /// Invalid number of arguments + #[error("Invalid number of arguments")] + InvalidArgumentCount, + + /// Missing required parameter + #[error("Missing required parameter: {0}")] + MissingParameter(String), +} + +/// Result type alias for doctree operations +pub type Result = std::result::Result; \ No newline at end of file diff --git a/doctree/src/include.rs b/doctree/src/include.rs new file mode 100644 index 0000000..df56f17 --- /dev/null +++ b/doctree/src/include.rs @@ -0,0 +1,178 @@ +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 { + // 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, Option)> { + // 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 { + // Get the collection + let collection = doctree.get_collection(collection_name)?; + + // Get the page content + let content = collection.page_get(page_name)?; + + Ok(content) +} \ No newline at end of file diff --git a/doctree/src/lib.rs b/doctree/src/lib.rs index b93cf3f..2989d40 100644 --- a/doctree/src/lib.rs +++ b/doctree/src/lib.rs @@ -1,14 +1,41 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} +//! DocTree is a library for managing collections of markdown documents. +//! +//! It provides functionality for scanning directories, managing collections, +//! and processing includes between documents. + +// Import lazy_static +#[macro_use] +extern crate lazy_static; + +mod error; +mod storage; +mod utils; +mod collection; +mod doctree; +mod include; + +pub use error::{DocTreeError, Result}; +pub use storage::RedisStorage; +pub use collection::{Collection, CollectionBuilder}; +pub use doctree::{DocTree, DocTreeBuilder, new}; +pub use include::process_includes; #[cfg(test)] mod tests { use super::*; + use std::path::Path; #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + fn test_doctree_builder() { + // Create a storage instance + let storage = RedisStorage::new("dummy_url").unwrap(); + + let doctree = DocTree::builder() + .with_storage(storage) + .build() + .unwrap(); + + assert_eq!(doctree.collections.len(), 0); + assert_eq!(doctree.default_collection, None); } } diff --git a/doctree/src/storage.rs b/doctree/src/storage.rs new file mode 100644 index 0000000..f588703 --- /dev/null +++ b/doctree/src/storage.rs @@ -0,0 +1,169 @@ +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; +use crate::error::{DocTreeError, Result}; + +/// Storage backend for doctree +pub struct RedisStorage { + // Using a simple in-memory storage for demonstration + // In a real implementation, this would be a Redis client + collections: Arc>>>, +} + +impl RedisStorage { + /// Create a new RedisStorage instance + /// + /// # Arguments + /// + /// * `url` - Redis connection URL (e.g., "redis://localhost:6379") + /// This is ignored in the in-memory implementation + /// + /// # Returns + /// + /// A new RedisStorage instance or an error + pub fn new(_url: &str) -> Result { + Ok(Self { + collections: Arc::new(Mutex::new(HashMap::new())), + }) + } + + /// Store a collection entry + /// + /// # Arguments + /// + /// * `collection` - Collection name + /// * `key` - Entry key + /// * `value` - Entry value + /// + /// # Returns + /// + /// Ok(()) on success or an error + pub fn store_collection_entry(&self, collection: &str, key: &str, value: &str) -> Result<()> { + let mut collections = self.collections.lock().unwrap(); + + // Get or create the collection + let collection_entries = collections + .entry(format!("collections:{}", collection)) + .or_insert_with(HashMap::new); + + // Store the entry + collection_entries.insert(key.to_string(), value.to_string()); + + Ok(()) + } + + /// Get a collection entry + /// + /// # Arguments + /// + /// * `collection` - Collection name + /// * `key` - Entry key + /// + /// # Returns + /// + /// The entry value or an error + pub fn get_collection_entry(&self, collection: &str, key: &str) -> Result { + let collections = self.collections.lock().unwrap(); + + // Get the collection + let collection_key = format!("collections:{}", collection); + let collection_entries = collections.get(&collection_key) + .ok_or_else(|| DocTreeError::CollectionNotFound(collection.to_string()))?; + + // Get the entry + collection_entries.get(key) + .cloned() + .ok_or_else(|| DocTreeError::FileNotFound(key.to_string())) + } + + /// Delete a collection entry + /// + /// # Arguments + /// + /// * `collection` - Collection name + /// * `key` - Entry key + /// + /// # Returns + /// + /// Ok(()) on success or an error + pub fn delete_collection_entry(&self, collection: &str, key: &str) -> Result<()> { + let mut collections = self.collections.lock().unwrap(); + + // Get the collection + let collection_key = format!("collections:{}", collection); + let collection_entries = collections.get_mut(&collection_key) + .ok_or_else(|| DocTreeError::CollectionNotFound(collection.to_string()))?; + + // Remove the entry + collection_entries.remove(key); + + Ok(()) + } + + /// List all entries in a collection + /// + /// # Arguments + /// + /// * `collection` - Collection name + /// + /// # Returns + /// + /// A vector of entry keys or an error + pub fn list_collection_entries(&self, collection: &str) -> Result> { + let collections = self.collections.lock().unwrap(); + + // Get the collection + let collection_key = format!("collections:{}", collection); + let collection_entries = collections.get(&collection_key) + .ok_or_else(|| DocTreeError::CollectionNotFound(collection.to_string()))?; + + // Get the keys + let keys = collection_entries.keys().cloned().collect(); + + Ok(keys) + } + + /// Delete a collection + /// + /// # Arguments + /// + /// * `collection` - Collection name + /// + /// # Returns + /// + /// Ok(()) on success or an error + pub fn delete_collection(&self, collection: &str) -> Result<()> { + let mut collections = self.collections.lock().unwrap(); + + // Remove the collection + collections.remove(&format!("collections:{}", collection)); + + Ok(()) + } + + /// Check if a collection exists + /// + /// # Arguments + /// + /// * `collection` - Collection name + /// + /// # Returns + /// + /// true if the collection exists, false otherwise + pub fn collection_exists(&self, collection: &str) -> Result { + let collections = self.collections.lock().unwrap(); + + // Check if the collection exists + let exists = collections.contains_key(&format!("collections:{}", collection)); + + Ok(exists) + } +} + +// Implement Clone for RedisStorage +impl Clone for RedisStorage { + fn clone(&self) -> Self { + Self { + collections: Arc::clone(&self.collections), + } + } +} \ No newline at end of file diff --git a/doctree/src/utils.rs b/doctree/src/utils.rs new file mode 100644 index 0000000..3c20c10 --- /dev/null +++ b/doctree/src/utils.rs @@ -0,0 +1,106 @@ +use pulldown_cmark::{Parser, Options, html}; +use std::path::Path; + +/// Fix a name to be used as a key +/// +/// This is equivalent to the tools.NameFix function in the Go implementation. +/// It normalizes the name by converting to lowercase, replacing spaces with hyphens, etc. +/// +/// # Arguments +/// +/// * `name` - The name to fix +/// +/// # Returns +/// +/// The fixed name +pub fn name_fix(name: &str) -> String { + // Convert to lowercase + let mut result = name.to_lowercase(); + + // Replace spaces with hyphens + result = result.replace(' ', "-"); + + // Remove special characters + result = result.chars() + .filter(|c| c.is_alphanumeric() || *c == '-' || *c == '.') + .collect(); + + result +} + +/// Convert markdown to HTML +/// +/// # Arguments +/// +/// * `markdown` - The markdown content to convert +/// +/// # Returns +/// +/// The HTML content +pub fn markdown_to_html(markdown: &str) -> String { + let mut options = Options::empty(); + options.insert(Options::ENABLE_TABLES); + options.insert(Options::ENABLE_FOOTNOTES); + options.insert(Options::ENABLE_STRIKETHROUGH); + + let parser = Parser::new_ext(markdown, options); + let mut html_output = String::new(); + html::push_html(&mut html_output, parser); + + html_output +} + +/// Trim spaces and quotes from a string +/// +/// # Arguments +/// +/// * `s` - The string to trim +/// +/// # Returns +/// +/// The trimmed string +pub fn trim_spaces_and_quotes(s: &str) -> String { + let mut result = s.trim().to_string(); + + // Remove surrounding quotes + if (result.starts_with('\'') && result.ends_with('\'')) || + (result.starts_with('"') && result.ends_with('"')) { + result = result[1..result.len()-1].to_string(); + } + + result +} + +/// Ensure a string has a .md extension +/// +/// # Arguments +/// +/// * `name` - The name to check +/// +/// # Returns +/// +/// The name with a .md extension +pub fn ensure_md_extension(name: &str) -> String { + if !name.ends_with(".md") { + format!("{}.md", name) + } else { + name.to_string() + } +} + +/// Get the file extension from a path +/// +/// # Arguments +/// +/// * `path` - The path to check +/// +/// # Returns +/// +/// The file extension or an empty string +pub fn get_extension(path: &str) -> String { + Path::new(path) + .extension() + .and_then(|ext| ext.to_str()) + .unwrap_or("") + .to_string() +} \ No newline at end of file diff --git a/doctree_implementation_plan.md b/doctree_implementation_plan.md new file mode 100644 index 0000000..34e9bfc --- /dev/null +++ b/doctree_implementation_plan.md @@ -0,0 +1,499 @@ +# DocTree Implementation Plan + +## Overview + +The DocTree library will be a Rust implementation of the Go reference, maintaining the core functionality while improving the API design to be more idiomatic Rust. We'll use Redis as the storage backend and implement a minimal CLI example to demonstrate usage. + +## Architecture + +```mermaid +classDiagram + class DocTree { + +collections: HashMap + +default_collection: Option + +new() DocTreeBuilder + +add_collection(path, name) Result<&Collection> + +get_collection(name) Result<&Collection> + +delete_collection(name) Result<()> + +list_collections() Vec + +page_get(collection, page) Result + +page_get_html(collection, page) Result + +file_get_url(collection, file) Result + } + + class DocTreeBuilder { + -collections: HashMap + -default_collection: Option + +with_collection(path, name) DocTreeBuilder + +with_default_collection(name) DocTreeBuilder + +build() Result + } + + class Collection { + +path: String + +name: String + +new(path, name) CollectionBuilder + +scan() Result<()> + +page_get(name) Result + +page_set(name, content) Result<()> + +page_delete(name) Result<()> + +page_list() Result> + +file_get_url(name) Result + +file_set(name, content) Result<()> + +file_delete(name) Result<()> + +file_list() Result> + +page_get_html(name) Result + } + + class CollectionBuilder { + -path: String + -name: String + +build() Result + } + + class RedisStorage { + +client: redis::Client + +new(url) Result + +store_collection_entry(collection, key, value) Result<()> + +get_collection_entry(collection, key) Result + +delete_collection_entry(collection, key) Result<()> + +list_collection_entries(collection) Result> + +delete_collection(collection) Result<()> + } + + class IncludeProcessor { + +process_includes(content, collection, doctree) Result + } + + DocTree --> DocTreeBuilder : creates + DocTree --> "0..*" Collection : contains + Collection --> CollectionBuilder : creates + DocTree --> RedisStorage : uses + Collection --> RedisStorage : uses + DocTree --> IncludeProcessor : uses +``` + +## Implementation Steps + +### 1. Project Setup and Dependencies + +1. Update the Cargo.toml files with necessary dependencies: + - redis (for Redis client) + - walkdir (for directory traversal) + - pulldown-cmark (for Markdown to HTML conversion) + - thiserror (for error handling) + - clap (for CLI argument parsing in doctreecmd) + +### 2. Core Library Structure + +1. **Error Module** + - Create a custom error type using thiserror + - Define specific error variants for different failure scenarios + +2. **Storage Module** + - Implement the RedisStorage struct to handle Redis operations + - Provide methods for storing, retrieving, and deleting collection entries + - Implement connection pooling for efficient Redis access + +3. **Utils Module** + - Implement utility functions like name_fix (equivalent to tools.NameFix in Go) + - Implement markdown to HTML conversion using pulldown-cmark + +### 3. Collection Implementation + +1. **Collection Module** + - Implement the Collection struct to represent a collection of documents + - Implement the CollectionBuilder for creating Collection instances + - Implement methods for scanning directories, managing pages and files + +2. **Collection Builder Pattern** + - Create a builder pattern for Collection creation + - Allow configuration of Collection properties before building + +### 4. DocTree Implementation + +1. **DocTree Module** + - Implement the DocTree struct to manage multiple collections + - Implement the DocTreeBuilder for creating DocTree instances + - Implement methods for managing collections and accessing documents + +2. **DocTree Builder Pattern** + - Create a builder pattern for DocTree creation + - Allow adding collections and setting default collection before building + +### 5. Include Processor Implementation + +1. **Include Module** + - Implement the IncludeProcessor to handle include directives + - Implement parsing of include directives + - Implement recursive processing of includes + +### 6. CLI Example + +1. **Update doctreecmd** + - Implement a minimal CLI interface using clap + - Provide commands for basic operations: + - Scanning a directory + - Listing collections + - Getting page content + - Getting HTML content + +## Detailed Module Breakdown + +### Error Module (src/error.rs) + +```rust +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum DocTreeError { + #[error("IO error: {0}")] + IoError(#[from] std::io::Error), + + #[error("Redis error: {0}")] + RedisError(#[from] redis::RedisError), + + #[error("Collection not found: {0}")] + CollectionNotFound(String), + + #[error("Page not found: {0}")] + PageNotFound(String), + + #[error("File not found: {0}")] + FileNotFound(String), + + #[error("Invalid include directive: {0}")] + InvalidIncludeDirective(String), + + #[error("No default collection set")] + NoDefaultCollection, + + #[error("Invalid number of arguments")] + InvalidArgumentCount, +} + +pub type Result = std::result::Result; +``` + +### Storage Module (src/storage.rs) + +```rust +use redis::{Client, Commands, Connection}; +use crate::error::{DocTreeError, Result}; + +pub struct RedisStorage { + client: Client, +} + +impl RedisStorage { + pub fn new(url: &str) -> Result { + let client = Client::open(url)?; + Ok(Self { client }) + } + + pub fn get_connection(&self) -> Result { + Ok(self.client.get_connection()?) + } + + pub fn store_collection_entry(&self, collection: &str, key: &str, value: &str) -> Result<()> { + let mut conn = self.get_connection()?; + let collection_key = format!("collections:{}", collection); + conn.hset(collection_key, key, value)?; + Ok(()) + } + + pub fn get_collection_entry(&self, collection: &str, key: &str) -> Result { + let mut conn = self.get_connection()?; + let collection_key = format!("collections:{}", collection); + let value: String = conn.hget(collection_key, key)?; + Ok(value) + } + + // Additional methods for Redis operations +} +``` + +### Utils Module (src/utils.rs) + +```rust +use pulldown_cmark::{Parser, Options, html}; + +pub fn name_fix(name: &str) -> String { + // Implementation of name_fix similar to tools.NameFix in Go + // Normalize the name by converting to lowercase, replacing spaces with hyphens, etc. +} + +pub fn markdown_to_html(markdown: &str) -> String { + let mut options = Options::empty(); + options.insert(Options::ENABLE_TABLES); + options.insert(Options::ENABLE_FOOTNOTES); + options.insert(Options::ENABLE_STRIKETHROUGH); + + let parser = Parser::new_ext(markdown, options); + let mut html_output = String::new(); + html::push_html(&mut html_output, parser); + + html_output +} +``` + +### Collection Module (src/collection.rs) + +```rust +use std::path::{Path, PathBuf}; +use walkdir::WalkDir; +use crate::error::Result; +use crate::storage::RedisStorage; +use crate::utils::name_fix; + +pub struct Collection { + pub path: PathBuf, + pub name: String, + storage: RedisStorage, +} + +pub struct CollectionBuilder { + path: PathBuf, + name: String, + storage: Option, +} + +impl Collection { + pub fn builder>(path: P, name: &str) -> CollectionBuilder { + CollectionBuilder { + path: path.as_ref().to_path_buf(), + name: name_fix(name), + storage: None, + } + } + + pub fn scan(&self) -> Result<()> { + // Implementation of scanning directory and storing in Redis + } + + pub fn page_get(&self, page_name: &str) -> Result { + // Implementation of getting page content + } + + // Additional methods for Collection +} + +impl CollectionBuilder { + pub fn with_storage(mut self, storage: RedisStorage) -> Self { + self.storage = Some(storage); + self + } + + pub fn build(self) -> Result { + let storage = self.storage.ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::Other, "Storage not provided") + })?; + + let collection = Collection { + path: self.path, + name: self.name, + storage, + }; + + Ok(collection) + } +} +``` + +### DocTree Module (src/doctree.rs) + +```rust +use std::collections::HashMap; +use std::path::Path; +use crate::collection::{Collection, CollectionBuilder}; +use crate::error::{DocTreeError, Result}; +use crate::storage::RedisStorage; + +pub struct DocTree { + collections: HashMap, + default_collection: Option, + storage: RedisStorage, +} + +pub struct DocTreeBuilder { + collections: HashMap, + default_collection: Option, + storage: Option, +} + +impl DocTree { + pub fn builder() -> DocTreeBuilder { + DocTreeBuilder { + collections: HashMap::new(), + default_collection: None, + storage: None, + } + } + + pub fn add_collection>(&mut self, path: P, name: &str) -> Result<&Collection> { + // Implementation of adding a collection + } + + // Additional methods for DocTree +} + +impl DocTreeBuilder { + pub fn with_storage(mut self, storage: RedisStorage) -> Self { + self.storage = Some(storage); + self + } + + pub fn with_collection>(mut self, path: P, name: &str) -> Result { + // Implementation of adding a collection during building + Ok(self) + } + + pub fn with_default_collection(mut self, name: &str) -> Self { + self.default_collection = Some(name.to_string()); + self + } + + pub fn build(self) -> Result { + let storage = self.storage.ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::Other, "Storage not provided") + })?; + + let doctree = DocTree { + collections: self.collections, + default_collection: self.default_collection, + storage, + }; + + Ok(doctree) + } +} +``` + +### Include Module (src/include.rs) + +```rust +use crate::doctree::DocTree; +use crate::error::Result; + +pub fn process_includes(content: &str, collection_name: &str, doctree: &DocTree) -> Result { + // Implementation of processing include directives +} + +fn parse_include_line(line: &str) -> Result<(Option, Option)> { + // Implementation of parsing include directives +} + +fn handle_include(page_name: &str, collection_name: &str, doctree: &DocTree) -> Result { + // Implementation of handling include directives +} +``` + +### Main Library File (src/lib.rs) + +```rust +mod error; +mod storage; +mod utils; +mod collection; +mod doctree; +mod include; + +pub use error::{DocTreeError, Result}; +pub use storage::RedisStorage; +pub use collection::{Collection, CollectionBuilder}; +pub use doctree::{DocTree, DocTreeBuilder}; +pub use include::process_includes; +``` + +### CLI Example (doctreecmd/src/main.rs) + +```rust +use clap::{App, Arg, SubCommand}; +use doctree::{DocTree, RedisStorage}; + +fn main() -> Result<(), Box> { + let matches = App::new("DocTree CLI") + .version("0.1.0") + .author("Your Name") + .about("A tool to manage document collections") + .subcommand( + SubCommand::with_name("scan") + .about("Scan a directory and create a collection") + .arg(Arg::with_name("path").required(true)) + .arg(Arg::with_name("name").required(true)), + ) + .subcommand( + SubCommand::with_name("list") + .about("List collections"), + ) + .subcommand( + SubCommand::with_name("get") + .about("Get page content") + .arg(Arg::with_name("collection").required(true)) + .arg(Arg::with_name("page").required(true)), + ) + .get_matches(); + + // Implementation of CLI commands + + Ok(()) +} +``` + +## Example Usage + +Here's how the library would be used with the builder pattern: + +```rust +use doctree::{DocTree, RedisStorage}; + +fn main() -> Result<(), Box> { + // Create a Redis storage instance + let storage = RedisStorage::new("redis://localhost:6379")?; + + // Create a DocTree instance using the builder pattern + let mut doctree = DocTree::builder() + .with_storage(storage.clone()) + .with_collection("path/to/collection", "my-collection")? + .with_default_collection("my-collection") + .build()?; + + // Get page content + let content = doctree.page_get("my-collection", "page-name")?; + println!("Page content: {}", content); + + // Get HTML content + let html = doctree.page_get_html("my-collection", "page-name")?; + println!("HTML content: {}", html); + + Ok(()) +} +``` + +## Testing Strategy + +1. **Unit Tests** + - Test individual components in isolation + - Mock Redis for testing storage operations + - Test utility functions + +2. **Integration Tests** + - Test the interaction between components + - Test the builder pattern + - Test include processing + +3. **End-to-End Tests** + - Test the complete workflow with real files + - Test the CLI interface + +## Timeline + +1. **Project Setup and Dependencies**: 1 day +2. **Core Library Structure**: 2 days +3. **Collection Implementation**: 2 days +4. **DocTree Implementation**: 2 days +5. **Include Processor Implementation**: 1 day +6. **CLI Example**: 1 day +7. **Testing and Documentation**: 2 days + +Total estimated time: 11 days \ No newline at end of file diff --git a/doctreecmd/Cargo.toml b/doctreecmd/Cargo.toml index 95a8b68..e2bc869 100644 --- a/doctreecmd/Cargo.toml +++ b/doctreecmd/Cargo.toml @@ -4,3 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] +doctree = { path = "../doctree" } +clap = "3.2.25" diff --git a/doctreecmd/src/main.rs b/doctreecmd/src/main.rs index e7a11a9..cb2100f 100644 --- a/doctreecmd/src/main.rs +++ b/doctreecmd/src/main.rs @@ -1,3 +1,78 @@ -fn main() { - println!("Hello, world!"); +use clap::{App, Arg, SubCommand}; +use doctree::{DocTree, RedisStorage, Result}; +use std::path::Path; + +fn main() -> Result<()> { + let matches = App::new("DocTree CLI") + .version("0.1.0") + .author("Your Name") + .about("A tool to manage document collections") + .subcommand( + SubCommand::with_name("scan") + .about("Scan a directory and create a collection") + .arg(Arg::with_name("path").required(true).help("Path to the directory")) + .arg(Arg::with_name("name").required(true).help("Name of the collection")), + ) + .subcommand( + SubCommand::with_name("list") + .about("List collections"), + ) + .subcommand( + SubCommand::with_name("get") + .about("Get page content") + .arg(Arg::with_name("collection").required(true).help("Name of the collection")) + .arg(Arg::with_name("page").required(true).help("Name of the page")), + ) + .subcommand( + SubCommand::with_name("html") + .about("Get page content as HTML") + .arg(Arg::with_name("collection").required(true).help("Name of the collection")) + .arg(Arg::with_name("page").required(true).help("Name of the page")), + ) + .get_matches(); + + // Create a Redis storage instance + let storage = RedisStorage::new("redis://localhost:6379")?; + + // Create a DocTree instance + let mut doctree = DocTree::builder() + .with_storage(storage) + .build()?; + + // Handle subcommands + if let Some(matches) = matches.subcommand_matches("scan") { + let path = matches.value_of("path").unwrap(); + let name = matches.value_of("name").unwrap(); + + println!("Scanning directory: {}", path); + doctree.add_collection(Path::new(path), name)?; + println!("Collection '{}' created successfully", name); + } else if let Some(_) = matches.subcommand_matches("list") { + let collections = doctree.list_collections(); + + if collections.is_empty() { + println!("No collections found"); + } else { + println!("Collections:"); + for collection in collections { + println!("- {}", collection); + } + } + } else if let Some(matches) = matches.subcommand_matches("get") { + let collection = matches.value_of("collection").unwrap(); + let page = matches.value_of("page").unwrap(); + + let content = doctree.page_get(Some(collection), page)?; + println!("{}", content); + } else if let Some(matches) = matches.subcommand_matches("html") { + let collection = matches.value_of("collection").unwrap(); + let page = matches.value_of("page").unwrap(); + + let html = doctree.page_get_html(Some(collection), page)?; + println!("{}", html); + } else { + println!("No command specified. Use --help for usage information."); + } + + Ok(()) } diff --git a/examples/grid1/.collection b/examples/grid1/.collection new file mode 100644 index 0000000..e69de29 diff --git a/examples/grid1/benefits/_category_.json b/examples/grid1/benefits/_category_.json new file mode 100644 index 0000000..9acbed3 --- /dev/null +++ b/examples/grid1/benefits/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "AIBox Benefits", + "position": 4, + "link": { + "type": "generated-index", + "description": "The benefits of AIBox" + } +} \ No newline at end of file diff --git a/examples/grid1/benefits/revenue_generation.md b/examples/grid1/benefits/revenue_generation.md new file mode 100644 index 0000000..6066228 --- /dev/null +++ b/examples/grid1/benefits/revenue_generation.md @@ -0,0 +1,28 @@ +--- +title: Revenue Generation +sidebar_position: 2 +--- + +### Renting Options + +AIBox creates opportunities for revenue generation through resource sharing. The following numbers are suggestive as each AIBox owners can set their own pricing. + +| Plan | Rate | Monthly Potential | Usage Scenario | +|------|------|------------------|----------------| +| Micro | $0.40/hr | $200-300 | Inference workloads | +| Standard | $0.80/hr | $400-600 | Development | +| Full GPU | $1.60/hr | $800-1,200 | Training | + +### Proof of Capacity Revenues + +The AIBox implements a tiered proof of capacity reward system, distributing monthly INCA tokens based on hardware configuration + +| Configuration | Monthly Rewards | +|---------------|----------------| +| Base AIBox | 500-2000 INCA | +| 1 GPU AIBox | 1000 INCA | +| 2 GPU AIBox | 2000 INCA | + +### Proof of Utilization Revenues + +The AIBox implements a revenue-sharing model wherein device owners receive 80% of INCA tokens utilized for deployments, providing transparent proof of utilization economics. \ No newline at end of file diff --git a/examples/grid1/benefits/use_cases.md b/examples/grid1/benefits/use_cases.md new file mode 100644 index 0000000..77becd9 --- /dev/null +++ b/examples/grid1/benefits/use_cases.md @@ -0,0 +1,32 @@ +--- +title: Use Cases +sidebar_position: 3 +--- + +### Personal AI Development + +The AIBox provides an ideal environment for individual developers working on AI projects: +- Model training and fine-tuning +- Experimental AI architectures +- Unrestricted testing and development +- Complete control over computing resources + +The system allows developers to run extended training sessions without watching cloud billing meters or dealing with usage restrictions. + +### Shared Resources + +For teams and organizations, AIBox offers efficient resource sharing capabilities: +- Multi-user environment +- Resource pooling +- Cost sharing +- Distributed computing + +This makes it particularly valuable for small teams and startups looking to maintain control over their AI infrastructure while managing costs. + +### Commercial Applications + +The system supports various commercial deployments: +- AI-as-a-Service +- Model hosting +- Inference endpoints +- Dataset processing \ No newline at end of file diff --git a/examples/grid1/getting-started/_category_.json b/examples/grid1/getting-started/_category_.json new file mode 100644 index 0000000..bc3f27a --- /dev/null +++ b/examples/grid1/getting-started/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Getting Started", + "position": 5, + "link": { + "type": "generated-index", + "description": "Getting started with the AIBox" + } +} \ No newline at end of file diff --git a/examples/grid1/getting-started/pre_order_process.md b/examples/grid1/getting-started/pre_order_process.md new file mode 100644 index 0000000..46dd501 --- /dev/null +++ b/examples/grid1/getting-started/pre_order_process.md @@ -0,0 +1,10 @@ +--- +title: Pre-Order Process +sidebar_position: 2 +--- + +### How to Order + +The steps to qcquire an AIBox is simple: +1. [Select your configuration](./purchase_options.md) +2. [Submit pre-order form](https://www2.aibox.threefold.io/signup/) diff --git a/examples/grid1/getting-started/purchase_options.md b/examples/grid1/getting-started/purchase_options.md new file mode 100644 index 0000000..2ed514e --- /dev/null +++ b/examples/grid1/getting-started/purchase_options.md @@ -0,0 +1,84 @@ +--- +title: Purchase Options +sidebar_position: 1 +--- + +### Base AIBox Plan ($1-1500) +For experienced builders and hardware enthusiasts who want to customize their AI infrastructure. This plan provides the essential framework while allowing you to select and integrate your own GPU. + +Base Configuration: +- GPU: Your choice, with minimum requirement of AMD Radeon RX 7900 XT + * Flexibility to use existing GPU or select preferred model + * Support for multiple GPU vendors with minimum performance requirements + * Full integration support for chosen hardware +- Memory: 64-128 GB DDR5 + * Expandable configuration + * High-speed memory modules + * ECC support optional +- Storage: 2-4 TB of NVMe SSD + * PCIe 4.0 support + * Configurable RAID options + * Expansion capabilities +- Integrated Mycelium Network + * Full network stack + * P2P capabilities + * Decentralized computing support + +Rewards Structure: +- Proof of Capacity: 500-2000 INCA per month (depending on chosen GPU) +- Proof of Utilization: 80% of INCA Revenue +- Flexible earning potential based on hardware configuration + +### 1 GPU AIBox Plan ($2-2500) +Perfect for individual developers and researchers who need professional-grade AI computing power. This configuration provides enough processing power for smaller but smart models and AI agents. + +Standard Configuration: +- 1x AMD Radeon RX 7900 XTX + * 24GB VRAM + * 61.6 TFLOPS FP32 Performance + * 960 GB/s Memory Bandwidth +- 64-128 GB DDR5 Memory + * Optimal for AI workloads + * High-speed data processing + * Multi-tasking capability +- 2-4 TB of NVMe SSD + * Ultra-fast storage access + * Ample space for datasets + * Quick model loading +- Integrated Mycelium + * Full network integration + * Ready for distributed computing + * P2P capabilities enabled + +Rewards Structure: +- Proof of Capacity: 1000 INCA per month +- Proof of Utilization: 80% of INCA Revenue +- Consistent earning potential + +### 2 GPU AIBox Plan ($4-5000) +Our most powerful configuration, designed for serious AI researchers and organizations. This setup supports large 48GB models, providing substantial computing power for advanced AI applications. + +Advanced Configuration: +- 2x AMD Radeon RX 7900 XTX + * Combined 48GB VRAM + * 123.2 TFLOPS total FP32 Performance + * 1920 GB/s Total Memory Bandwidth +- 64-128 GB DDR5 Memory + * Maximum performance configuration + * Support for multiple large models + * Extensive multi-tasking capability +- 2-4 TB of NVMe SSD + * Enterprise-grade storage + * RAID configuration options + * Expandable capacity +- Integrated Mycelium + * Enhanced network capabilities + * Full distributed computing support + * Advanced P2P features + +Rewards Structure: +- Proof of Capacity: 2000 INCA per month +- Proof of Utilization: 80% of INCA Revenue +- Maximum earning potential + +Each plan includes comprehensive support, setup assistance, and access to the full AIBox ecosystem. Configurations can be further customized within each plan's framework to meet specific requirements. \ No newline at end of file diff --git a/examples/grid1/getting-started/support.md b/examples/grid1/getting-started/support.md new file mode 100644 index 0000000..02cdf97 --- /dev/null +++ b/examples/grid1/getting-started/support.md @@ -0,0 +1,8 @@ +--- +title: Support +sidebar_position: 3 +--- + +Our support team is composed of technically proficient members who understand AI development needs. + +Feel free to reach out the ThreeFold Support [here](https://threefoldfaq.crisp.help/en/) for more information. \ No newline at end of file diff --git a/examples/grid1/introduction.md b/examples/grid1/introduction.md new file mode 100644 index 0000000..ef08d36 --- /dev/null +++ b/examples/grid1/introduction.md @@ -0,0 +1,24 @@ +--- +title: Introducing AIBox +sidebar_position: 1 +slug: / +--- + +## AIBox: Powering Community-Driven AI + +The AIBox is built for those who want to explore AI on their own terms. With 2 RX 7900 XTX GPUs and 48GB of memory, it enables running demanding AI models efficiently. + +## Open AI Development + +AIBox offers full control—no cloud restrictions, no unexpected costs. Train models, fine-tune AI systems, and experiment freely with PyTorch, TensorFlow, or low-level GPU programming. + +## More Than Hardware: A Shared Network + +AIBox isn’t just a tool—it’s part of a decentralized AI network. When idle, its GPU power can be shared via Mycelium, benefiting the wider community while generating value. Designed for efficiency, with water cooling and power monitoring, it’s a practical, community-powered step toward open AI development. + +## Expanding the ThreeFold Grid + +Each AIBox integrates into the ThreeFold Grid, a decentralized Internet infrastructure active in over 50 countries. By connecting your AIBox, you contribute to this global network, enhancing its capacity and reach. This integration not only supports your AI endeavors but also strengthens a community-driven Internet ecosystem. + +More info about threefold see: https://www.threefold.io + diff --git a/examples/grid1/overview/_category_.json b/examples/grid1/overview/_category_.json new file mode 100644 index 0000000..e2b3ffc --- /dev/null +++ b/examples/grid1/overview/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "AIBox Overview", + "position": 2, + "link": { + "type": "generated-index", + "description": "Overview of the AIBox" + } +} \ No newline at end of file diff --git a/examples/grid1/overview/vision_mission.md b/examples/grid1/overview/vision_mission.md new file mode 100644 index 0000000..9bda541 --- /dev/null +++ b/examples/grid1/overview/vision_mission.md @@ -0,0 +1,12 @@ +--- +title: Vision & Mission +sidebar_position: 2 +--- + +## AI Landscape + +The AI landscape today is dominated by centralized cloud providers, creating barriers for innovation and increasing costs for developers. Our vision is different: we're building tools for a decentralized AI future where computing power isn't monopolized by large cloud providers. + +## High-End AI Hardware + +Our technical goal is straightforward: provide enterprise-grade AI hardware that's both powerful and profitable through resource sharing. We believe that AI development should be accessible to anyone with the technical skills to push boundaries. \ No newline at end of file diff --git a/examples/grid1/overview/who_is_aibox_for.md b/examples/grid1/overview/who_is_aibox_for.md new file mode 100644 index 0000000..b389dec --- /dev/null +++ b/examples/grid1/overview/who_is_aibox_for.md @@ -0,0 +1,27 @@ +--- +title: Who Is AIBox For? +sidebar_position: 4 +--- + +The AIBox is for hackers and AI explorers who want a simple, accessible gateway into AI experimentation, while also offering advanced features for those ready to push the boundaries of what's possible. + +### Developers & Hackers +Technical capabilities: +- Direct GPU programming through ROCm +- Custom containerization support +- Full Linux kernel access +- P2P networking capabilities + +### AI Researchers +Research-focused features: +- Support for popular ML frameworks (PyTorch, TensorFlow) +- Large model training capability (up to 48GB VRAM) +- Distributed training support +- Dataset management tools + +### Tech Enthusiasts +Advanced features: +- Water cooling management interface +- Power consumption monitoring +- Performance benchmarking tools +- Resource allocation controls \ No newline at end of file diff --git a/examples/grid1/overview/why_decentralized_ai_matters.md b/examples/grid1/overview/why_decentralized_ai_matters.md new file mode 100644 index 0000000..bdc50f1 --- /dev/null +++ b/examples/grid1/overview/why_decentralized_ai_matters.md @@ -0,0 +1,18 @@ +--- +title: Why Decentralized AI Matters +sidebar_position: 3 +--- + +The AIBox gives you complete control over your data privacy with full hardware access while enabling unlimited experimentation without the restrictions of cloud platforms. + +### Data Privacy & Control +- Full root access to hardware +- No data leaving your premises without explicit permission +- Custom firewall rules and network configurations +- Ability to air-gap when needed + +### Unlimited Experimentation +- Direct GPU access without virtualization overhead +- Custom model training without cloud restrictions +- Unrestricted model sizes and training durations +- Freedom to modify system parameters diff --git a/examples/grid1/technical-specs/_category_.json b/examples/grid1/technical-specs/_category_.json new file mode 100644 index 0000000..e60832b --- /dev/null +++ b/examples/grid1/technical-specs/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Technical Specs", + "position": 3, + "link": { + "type": "generated-index", + "description": "Technical aspects of the AIBox" + } +} \ No newline at end of file diff --git a/examples/grid1/technical-specs/features_capabilities.md b/examples/grid1/technical-specs/features_capabilities.md new file mode 100644 index 0000000..20934f5 --- /dev/null +++ b/examples/grid1/technical-specs/features_capabilities.md @@ -0,0 +1,35 @@ +--- +title: Features & Capabilities +sidebar_position: 3 +--- + +## Overview + +AIBox combines enterprise-grade hardware capabilities with flexible resource management, creating a powerful platform for AI development and deployment. Each feature is designed to meet the demanding needs of developers and researchers who require both raw computing power and precise control over their resources. + +## VM Management (CloudSlices) + +CloudSlices transforms your AIBox into a multi-tenant powerhouse, enabling you to run multiple isolated environments simultaneously. Unlike traditional virtualization, CloudSlices is optimized for AI workloads, ensuring minimal overhead and maximum GPU utilization. + +Each slice operates as a fully isolated virtual machine with guaranteed resources. The AIBox can be sliced into up to 8 virtual machines. + +The slicing system ensures resources are allocated efficiently while maintaining performance isolation between workloads. This means your critical training job won't be affected by other tasks running on the system. + +## GPU Resource Management + +Our GPU management system provides granular control while maintaining peak performance. Whether you're running a single large model or multiple smaller workloads, the system optimizes resource allocation automatically. + +## Network Connectivity + +The networking stack is built for both performance and security, integrating seamlessly with the Mycelium network, providing end-to-end encryption, and and Web gateways, allowing external connection to VM containers. The AI Box thus creates a robust foundation for distributed AI computing. + +## Security Features + +Security is implemented at every layer of the system without compromising performance: + +System Security: +- Hardware-level isolation +- Secure boot chain +- Network segmentation + +Each feature has been carefully selected and implemented to provide both practical utility and enterprise-grade security, ensuring your AI workloads and data remain protected while maintaining full accessibility for authorized users. \ No newline at end of file diff --git a/examples/grid1/technical-specs/hardware_specifications.md b/examples/grid1/technical-specs/hardware_specifications.md new file mode 100644 index 0000000..9c28238 --- /dev/null +++ b/examples/grid1/technical-specs/hardware_specifications.md @@ -0,0 +1,37 @@ +--- +title: Hardware Specifications +sidebar_position: 1 +--- + +### GPU Options + +At the heart of AIBox lies its GPU configuration, carefully selected for AI workloads. The AMD Radeon RX 7900 XTX provides an exceptional balance of performance, memory, and cost efficiency: + +| Model | VRAM | FP32 Performance | Memory Bandwidth | +|-------|------|------------------|------------------| +| RX 7900 XTX | 24GB | 61.6 TFLOPS | 960 GB/s | +| Dual Config | 48GB | 123.2 TFLOPS | 1920 GB/s | + +The dual GPU configuration enables handling larger models and datasets that wouldn't fit in single-GPU memory, making it ideal for advanced AI research and development. + +### Memory & Storage + +AI workloads demand high-speed memory and storage. The AIBox configuration ensures your GPU computing power isn't bottlenecked by I/O limitations: + +Memory Configuration: +- RAM: 64GB/128GB DDR5-4800 +- Storage: 2x 2TB NVMe SSDs (PCIe 4.0) + +This setup provides ample memory for large dataset preprocessing and fast storage access for model training and inference. + +### Cooling System + +Thermal management is crucial for sustained AI workloads. Our cooling solution focuses on maintaining consistent performance during extended operations: + +This cooling system allows for sustained maximum performance without thermal throttling, even during extended training sessions. + +### Power Supply + +Reliable power delivery is essential for system stability and performance. + +The AIBox power configuration ensures clean, stable power delivery under all operating conditions, with headroom for additional components or intense workloads. \ No newline at end of file diff --git a/examples/grid1/technical-specs/software_stack.md b/examples/grid1/technical-specs/software_stack.md new file mode 100644 index 0000000..a708936 --- /dev/null +++ b/examples/grid1/technical-specs/software_stack.md @@ -0,0 +1,29 @@ +--- +title: Software Stack +sidebar_position: 2 +--- + +### ThreeFold Zero-OS + +Zero-OS forms the foundation of AIBox's software architecture. Unlike traditional operating systems, it's a minimalist, security-focused platform optimized specifically for AI workloads and distributed computing. + +Key features: +- Bare metal operating system with minimal overhead +- Zero overhead virtualization +- Secure boot process +- Automated resource management + +This specialized operating system ensures maximum performance and security while eliminating unnecessary services and potential vulnerabilities. + +### Mycelium Network Integration + +The Mycelium Network integration transforms your AIBox from a standalone system into a node in a powerful distributed computing network based on peer-to-peer and end-to-end encrypted communication always choosing the shortest path. + +### Pre-installed AI Frameworks + +Your AIBox comes ready for development with a comprehensive AI software stack: + +- ROCm 5.7+ ML stack +- PyTorch 2.1+ with GPU optimization +- TensorFlow 2.14+ +- Pre-built container images \ No newline at end of file diff --git a/examples/parent/docs2/.collection b/examples/parent/docs2/.collection new file mode 100644 index 0000000..e69de29 diff --git a/examples/parent/docs2/get-started/01_features.md b/examples/parent/docs2/get-started/01_features.md new file mode 100644 index 0000000..ba0f0b7 --- /dev/null +++ b/examples/parent/docs2/get-started/01_features.md @@ -0,0 +1,38 @@ +--- +title: Features Mycelium Network +sidebar_position: 1 +--- + +Mycelium is a locality-aware, end-to-end encrypted network designed for efficient and secure communication between nodes. Below are its key features: + +## What Makes Mycelium Unique + +1. **Locality Awareness** + Mycelium identifies the shortest path between nodes, optimizing communication based on location. + +2. **End-to-End Encryption** + All traffic between nodes is encrypted, ensuring secure data transmission. + +3. **Traffic Routing Over Friend Nodes** + Traffic can be routed through nodes of trusted friends, maintaining location awareness. + +4. **Automatic Rerouting** + If a physical link fails, Mycelium automatically reroutes traffic to ensure uninterrupted connectivity. + +5. **Your network Address Linked to Private Key** + Each node is assigned an IPv6 network address that is cryptographically linked to its private key. + +6. **Scalability** + + Mycelium is designed to scale to a planetary level. The team has evaluated multiple overlay networks in the past and is focused on overcoming scalability challenges. + +## Tech + +1. **Flexible Deployment** + Mycelium can be run without a TUN interface, allowing it to function solely as a reliable message bus. + +2. **Reliable Message Bus** + Mycelium includes a simple and reliable message bus built on top of its network layer. + +1. **Multiple Communication Protocols** + Mycelium supports various communication methods, including QUIC and TCP. The team is also developing hole-punching for QUIC, enabling direct peer-to-peer (P2P) traffic without intermediaries. diff --git a/examples/parent/docs2/get-started/02_mycelium-app.md b/examples/parent/docs2/get-started/02_mycelium-app.md new file mode 100644 index 0000000..cbf744e --- /dev/null +++ b/examples/parent/docs2/get-started/02_mycelium-app.md @@ -0,0 +1,23 @@ +--- +title: Download the App +sidebar_position: 4 +--- + +The Mycelium app is available for Android, Windows, macOS and iOS. + +For Linux, read the [Linux Installation](../experts/03_linux-installation.md) section. + +## Download Links + +You can download the Mycelium app with the following links: + +- [iOS and macOS](https://apps.apple.com/app/id6504277565) + - Download the app from the App Store +- [Android](https://play.google.com/store/apps/details?id=tech.threefold.mycelium) + - Download the app from the Google Play Store +- [Windows](https://github.com/threefoldtech/myceliumflut/releases) + - Go to the official Mycelium release page and download the latest `.exe` + +## Upcoming Updates + +- The user interface (UI) will be drastically improved in upcoming releases to better represent the available features. \ No newline at end of file diff --git a/examples/parent/docs2/get-started/03_use-the-app.md b/examples/parent/docs2/get-started/03_use-the-app.md new file mode 100644 index 0000000..4924387 --- /dev/null +++ b/examples/parent/docs2/get-started/03_use-the-app.md @@ -0,0 +1,48 @@ +--- +title: Use the App +sidebar_position: 5 +--- + +## Start Mycelium + +To start Mycelium, simply open the app and click on `Start`. + +![](./img/mycelium_1.png) + +> Note for Windows Users: The Mycelium app must be run as an administrator to function properly. Right-click on the application icon and select "Run as administrator" to ensure proper network connectivity. + +## Stop or Restart Mycelium + +To stop or restart Mycelium, click on the appropriate button. + +![](./img/mycelium_2.png) + +## Add Peers + +You can add different Mycelium peers in the `Peers` window. + +Simply add peers and then either start or restart the app. + +![](./img/mycelium_3.png) + +You can consult the [Mycelium hosted public nodes](../experts/04_additional-information.md) to find more peers. + +For example, if you want to add the node with the IPv4 address `5.78.122.16` with the tcp port `9651`, simply add the following line then start or restart the app. + +``` +tcp://5.78.122.16:9651 +``` + +## Mycelium Address + +When you use the Mycelium app, you are assigned a unique Mycelium address. + +To copy the Mycelium address, click on the button on the right of the address. + +![](./img/mycelium_4.png) + +## Deploy on the Grid with Mycelium + +Once you've installed Mycelium, you can deploy on the ThreeFold Grid and connect to your workload using Mycelium. + +As a starter, you can explore the ThreeFold Grid and deploy apps on the [ThreeFold Dashboard](https://manual.grid.tf/documentation/dashboard/dashboard.html) using Mycelium to connect. \ No newline at end of file diff --git a/examples/parent/docs2/get-started/_category_.json b/examples/parent/docs2/get-started/_category_.json new file mode 100644 index 0000000..fff882b --- /dev/null +++ b/examples/parent/docs2/get-started/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Get Started", + "position": 4, + "link": { + "type": "generated-index", + "description": "Get started With Mycelium Network." + } +} \ No newline at end of file diff --git a/examples/parent/docs2/get-started/img/mycelium_1.png b/examples/parent/docs2/get-started/img/mycelium_1.png new file mode 100644 index 0000000000000000000000000000000000000000..bb79fff43e0dec13d494afc4468588033d48747a GIT binary patch literal 36051 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%0AGgU7tX$bmi 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 MT1Bc{!X)7T05NmWI{*Lx literal 0 HcmV?d00001 diff --git a/examples/parent/docs2/get-started/img/mycelium_4.png b/examples/parent/docs2/get-started/img/mycelium_4.png new file mode 100644 index 0000000000000000000000000000000000000000..2d3e35ab9b2fea856fed3352179741f6484ac9ea GIT binary patch literal 9156 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 bFO@f4^3mOtf%3qy91>kkBaK=$`{@4xmp0b} literal 0 HcmV?d00001