Skip to main content
Glama
by 8b-is
function_markdown.rs13.6 kB
//! Function Markdown Formatter - Visual function documentation as you work! //! //! This formatter creates a beautiful markdown visualization of functions //! in your codebase, perfect for real-time documentation while coding. //! //! Trisha says: "It's like having a living blueprint of your code!" 📐 use crate::formatters::Formatter; use crate::scanner::{FileNode, TreeStats}; use anyhow::Result; use std::collections::{HashMap, HashSet}; use std::io::Write; use std::path::Path; /// Function information extracted from code #[derive(Debug, Clone)] struct FunctionInfo { name: String, file: String, line_start: usize, line_end: usize, signature: String, doc_comment: Option<String>, calls: Vec<String>, visibility: String, complexity: usize, language: String, } /// Function markdown formatter pub struct FunctionMarkdownFormatter { no_emoji: bool, show_private: bool, show_complexity: bool, show_call_graph: bool, group_by_file: bool, } impl FunctionMarkdownFormatter { pub fn new(show_private: bool, show_complexity: bool, show_call_graph: bool) -> Self { Self { no_emoji: false, show_private, show_complexity, show_call_graph, group_by_file: true, } } /// Extract functions from code files fn extract_functions(&self, nodes: &[FileNode]) -> Vec<FunctionInfo> { let mut functions = Vec::new(); for node in nodes { if node.is_dir || node.permission_denied { continue; } // Check if it's a code file let ext = node.path.extension().and_then(|e| e.to_str()).unwrap_or(""); if !is_code_extension(ext) { continue; } // For now, create placeholder functions based on file content // In a real implementation, we would use tree-sitter or similar if let Ok(content) = std::fs::read_to_string(&node.path) { let file_functions = extract_functions_from_content(&content, ext); for (idx, func_name) in file_functions.into_iter().enumerate() { functions.push(FunctionInfo { name: func_name.clone(), file: node.path.to_string_lossy().to_string(), line_start: idx * 10 + 1, line_end: idx * 10 + 10, signature: format!("{}(...)", func_name), doc_comment: None, calls: Vec::new(), visibility: "public".to_string(), complexity: 5, language: ext.to_string(), }); } } } functions } /// Generate the markdown output fn generate_markdown(&self, functions: &[FunctionInfo], _stats: &TreeStats) -> String { let mut output = String::new(); // Header let doc_title = if self.no_emoji { "# Function Documentation\n\n" } else { "# 📚 Function Documentation\n\n" }; output.push_str(doc_title); output.push_str(&format!( "*Generated by Smart Tree - {} functions found*\n\n", functions.len() )); // Summary stats let summary_title = if self.no_emoji { "## Summary\n\n" } else { "## 📊 Summary\n\n" }; output.push_str(summary_title); output.push_str(&format!("- **Total Functions**: {}\n", functions.len())); output.push_str(&format!( "- **Public Functions**: {}\n", functions .iter() .filter(|f| f.visibility == "public") .count() )); output.push_str(&format!( "- **Private Functions**: {}\n", functions .iter() .filter(|f| f.visibility != "public") .count() )); // Language breakdown let mut lang_counts: HashMap<String, usize> = HashMap::new(); for func in functions { *lang_counts.entry(func.language.clone()).or_insert(0) += 1; } let lang_title = if self.no_emoji { "\n### Languages\n" } else { "\n### 🗣️ Languages\n" }; output.push_str(lang_title); for (lang, count) in lang_counts.iter() { if self.no_emoji { output.push_str(&format!("- {}: {} functions\n", lang, count)); } else { let emoji = get_language_emoji(lang); output.push_str(&format!("- {} {}: {} functions\n", emoji, lang, count)); } } // Table of contents let toc_title = if self.no_emoji { "\n## Table of Contents\n\n" } else { "\n## 📑 Table of Contents\n\n" }; output.push_str(toc_title); if self.group_by_file { let mut files: HashSet<String> = HashSet::new(); for func in functions { files.insert(func.file.clone()); } let mut sorted_files: Vec<String> = files.into_iter().collect(); sorted_files.sort(); for file in &sorted_files { let file_funcs: Vec<&FunctionInfo> = functions.iter().filter(|f| &f.file == file).collect(); output.push_str(&format!( "- [{}](#{})\n", file, file.replace(['/', '.'], "-").to_lowercase() )); for func in &file_funcs { if !self.show_private && func.visibility != "public" { continue; } output.push_str(&format!( " - [{}()](#{})\n", func.name, format!("{}-{}", file, func.name) .replace('/', "-") .replace('.', "-") .to_lowercase() )); } } } // Function details let functions_title = if self.no_emoji { "\n## Functions\n\n" } else { "\n## 🔧 Functions\n\n" }; output.push_str(functions_title); if self.group_by_file { let mut files: HashSet<String> = HashSet::new(); for func in functions { files.insert(func.file.clone()); } let mut sorted_files: Vec<String> = files.into_iter().collect(); sorted_files.sort(); for file in sorted_files { let file_funcs: Vec<&FunctionInfo> = functions.iter().filter(|f| f.file == file).collect(); if file_funcs.is_empty() { continue; } output.push_str(&format!("### 📄 {}\n\n", file)); for func in file_funcs { if !self.show_private && func.visibility != "public" { continue; } self.format_function(&mut output, func); } } } else { // Sort functions by name let mut sorted_funcs = functions.to_vec(); sorted_funcs.sort_by(|a, b| a.name.cmp(&b.name)); for func in &sorted_funcs { if !self.show_private && func.visibility != "public" { continue; } self.format_function(&mut output, func); } } // Call graph if self.show_call_graph { output.push_str("\n## 🕸️ Call Graph\n\n"); output.push_str("```mermaid\ngraph TD\n"); for func in functions { for call in &func.calls { output.push_str(&format!( " {}[{}] --> {}[{}]\n", func.name, func.name, call, call )); } } output.push_str("```\n"); } // Footer output.push_str("\n---\n"); output.push_str("*Generated by Smart Tree Function Markdown Formatter*\n"); output.push_str("*\"It's like having a living blueprint of your code!\" - Trisha* 📐\n"); output } /// Format a single function fn format_function(&self, output: &mut String, func: &FunctionInfo) { let visibility_emoji = if func.visibility == "public" { "🔓" } else { "🔒" }; output.push_str(&format!( "#### {} {} `{}`\n\n", visibility_emoji, func.name, func.visibility )); // Location output.push_str(&format!( "📍 **Location**: `{}:{}-{}`\n\n", func.file, func.line_start, func.line_end )); // Signature output.push_str("**Signature**:\n```"); output.push_str(&func.language); output.push('\n'); output.push_str(&func.signature); output.push_str("\n```\n\n"); // Documentation if let Some(doc) = &func.doc_comment { output.push_str("**Documentation**:\n"); output.push_str(doc); output.push_str("\n\n"); } // Complexity if self.show_complexity { let complexity_emoji = match func.complexity { 0..=5 => "🟢", 6..=10 => "🟡", 11..=20 => "🟠", _ => "🔴", }; output.push_str(&format!( "**Complexity**: {} {}\n\n", complexity_emoji, func.complexity )); } // Calls if !func.calls.is_empty() { output.push_str("**Calls**:\n"); for call in &func.calls { output.push_str(&format!("- `{}`\n", call)); } output.push('\n'); } output.push_str("---\n\n"); } } impl Formatter for FunctionMarkdownFormatter { fn format( &self, writer: &mut dyn Write, nodes: &[FileNode], stats: &TreeStats, _root_path: &Path, ) -> Result<()> { // Extract functions from all code files let functions = self.extract_functions(nodes); // Generate markdown let markdown = self.generate_markdown(&functions, stats); // Write output writer.write_all(markdown.as_bytes())?; Ok(()) } } // Helper functions fn is_code_extension(ext: &str) -> bool { matches!( ext, "rs" | "py" | "js" | "ts" | "jsx" | "tsx" | "java" | "cpp" | "c" | "h" | "hpp" | "go" | "rb" | "php" | "swift" | "kt" | "scala" | "r" | "jl" | "cs" | "vb" | "lua" | "pl" | "sh" | "bash" | "zsh" | "ps1" | "dart" | "elm" | "ex" | "exs" | "clj" | "cljs" | "ml" | "mli" ) } /// Simple function extraction using regex patterns fn extract_functions_from_content(content: &str, ext: &str) -> Vec<String> { let mut functions = Vec::new(); let patterns = match ext { "rs" => vec![r"fn\s+(\w+)\s*[<(]", r"pub\s+fn\s+(\w+)\s*[<(]"], "py" => vec![r"def\s+(\w+)\s*\(", r"async\s+def\s+(\w+)\s*\("], "js" | "ts" | "jsx" | "tsx" => vec![ r"function\s+(\w+)\s*\(", r"const\s+(\w+)\s*=\s*\(", r"(\w+)\s*:\s*function\s*\(", r"(\w+)\s*\(.*\)\s*\{", ], "java" => vec![ r"(?:public|private|protected)\s+\w+\s+(\w+)\s*\(", r"\b(\w+)\s*\(.*\)\s*\{", ], "go" => vec![r"func\s+(\w+)\s*\(", r"func\s+\(.*\)\s+(\w+)\s*\("], "cpp" | "c" | "hpp" | "h" => vec![r"\b(\w+)\s*\(.*\)\s*\{", r"\b(\w+)\s*\(.*\);$"], _ => vec![], }; for pattern in patterns { if let Ok(re) = regex::Regex::new(pattern) { for cap in re.captures_iter(content) { if let Some(name) = cap.get(1) { let func_name = name.as_str().to_string(); if !functions.contains(&func_name) && is_valid_function_name(&func_name) { functions.push(func_name); } } } } } functions } /// Check if a name is likely a function name fn is_valid_function_name(name: &str) -> bool { // Filter out common false positives !matches!( name, "if" | "for" | "while" | "switch" | "catch" | "return" | "import" | "export" ) && name.len() > 1 && !name.chars().all(|c| c.is_uppercase()) } fn get_language_emoji(lang: &str) -> &'static str { match lang { "rs" => "🦀", "py" => "🐍", "js" | "ts" | "jsx" | "tsx" => "📜", "java" => "☕", "go" => "🐹", "rb" => "💎", "php" => "🐘", "swift" => "🦉", "cpp" | "c" | "h" | "hpp" => "⚙️", _ => "📄", } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/8b-is/smart-tree'

If you have feedback or need assistance with the MCP directory API, please join our Discord server