From f17dff4934eced5630aa2f054887c8bc6a1f7097 Mon Sep 17 00:00:00 2001 From: Tyr Chen Date: Mon, 28 Apr 2025 22:44:31 -0700 Subject: [PATCH 1/3] feature: ignore directories --- src/bank.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/bank.rs b/src/bank.rs index 292a87d..71616a0 100644 --- a/src/bank.rs +++ b/src/bank.rs @@ -5,7 +5,7 @@ use crate::{ TypeScriptParser, formatter::Formatter, }, }; -use ignore::Walk; +use ignore::WalkBuilder; use regex::Regex; use std::cell::OnceCell; use std::fs; @@ -161,9 +161,29 @@ impl Bank for CodeBank { // Use a vector to collect all file units so we can sort them let mut file_units = Vec::new(); + // Build the directory walker, respecting ignored directories + let walker = WalkBuilder::new(root_dir); + // walker.hidden(false); // Optionally include hidden files/dirs + // walker.git_ignore(true); // Use .gitignore + // walker.ignore(true); // Use .ignore files + // Walk through all files in the directory - for entry in Walk::new(root_dir).filter_map(|e| e.ok()) { + for entry in walker.build().filter_map(|e| e.ok()) { let path = entry.path(); + + // Check if the path is within any ignored directory + let should_ignore = config.ignore_dirs.iter().any(|ignored_dir_name| { + path.ancestors().any(|ancestor| { + ancestor + .strip_prefix(root_dir) + .is_ok_and(|p| p.ends_with(ignored_dir_name)) + }) + }); + + if should_ignore { + continue; + } + if path.is_file() { // Try to parse the file with the appropriate parser if let Ok(Some(file_unit)) = code_bank.parse_file(path) { From 60dd6e5215bdbe21004eacd536dfc1f3c3c72a72 Mon Sep 17 00:00:00 2001 From: Tyr Chen Date: Mon, 28 Apr 2025 23:07:33 -0700 Subject: [PATCH 2/3] chore: make test pass --- src/bank.rs | 67 +++++++++++++------------- src/parser/formatter/mod.rs | 93 ++++++++++++++++++++++++------------- src/parser/mod.rs | 13 ++++++ 3 files changed, 105 insertions(+), 68 deletions(-) diff --git a/src/bank.rs b/src/bank.rs index 71616a0..faea078 100644 --- a/src/bank.rs +++ b/src/bank.rs @@ -49,19 +49,7 @@ impl CodeBank { Some("ts") | Some("tsx") | Some("js") | Some("jsx") => Some(LanguageType::TypeScript), Some("c") | Some("h") | Some("cpp") | Some("hpp") => Some(LanguageType::Cpp), Some("go") => Some(LanguageType::Go), - _ => None, - } - } - - /// Get the language name for markdown code blocks - fn get_language_name(&self, path: &Path) -> &str { - match path.extension().and_then(OsStr::to_str) { - Some("rs") => "rust", - Some("py") => "python", - Some("ts") | Some("tsx") | Some("js") | Some("jsx") => "typescript", - Some("c") | Some("h") | Some("cpp") | Some("hpp") => "cpp", - Some("go") => "go", - _ => "", + _ => Some(LanguageType::Unknown), } } @@ -204,23 +192,23 @@ impl Bank for CodeBank { .map(|p| p.display().to_string()) .unwrap_or_else(|_| file_unit.path.display().to_string()); - // Add the file header - output.push_str(&format!("## {}\n", relative_path)); + // Format the file unit using the Formatter trait + let lang = code_bank + .detect_language(&file_unit.path) + .unwrap_or(LanguageType::Unknown); + let formatted_content = file_unit.format(&config.strategy, lang)?; - // Add the code block with appropriate language - let lang = code_bank.get_language_name(&file_unit.path); - output.push_str(&format!("```{}\n", lang)); + if !formatted_content.is_empty() { + // Add the file header + output.push_str(&format!("## {}\n", relative_path)); - // Format the file unit using the Formatter trait - let formatted_content = file_unit.format( - &config.strategy, - code_bank - .detect_language(&file_unit.path) - .unwrap_or(LanguageType::Unknown), - )?; - output.push_str(&formatted_content); - - output.push_str("```\n\n"); + // Add the code block with appropriate language + output.push_str(&format!("```{}\n", lang.as_str())); + + output.push_str(&formatted_content); + + output.push_str("```\n\n"); + } } // remove all empty lines @@ -300,7 +288,10 @@ mod tests { // Test unsupported files let unsupported_path = PathBuf::from("test.txt"); - assert_eq!(code_bank.detect_language(&unsupported_path), None); + assert_eq!( + code_bank.detect_language(&unsupported_path), + Some(LanguageType::Unknown) + ); } #[test] @@ -309,26 +300,32 @@ mod tests { // Test Rust files let rust_path = PathBuf::from("test.rs"); - assert_eq!(code_bank.get_language_name(&rust_path), "rust"); + let lang = code_bank.detect_language(&rust_path).unwrap(); + assert_eq!(lang.as_str(), "rust"); // Test Python files let python_path = PathBuf::from("test.py"); - assert_eq!(code_bank.get_language_name(&python_path), "python"); + let lang = code_bank.detect_language(&python_path).unwrap(); + assert_eq!(lang.as_str(), "python"); // Test TypeScript files let ts_path = PathBuf::from("test.ts"); - assert_eq!(code_bank.get_language_name(&ts_path), "typescript"); + let lang = code_bank.detect_language(&ts_path).unwrap(); + assert_eq!(lang.as_str(), "ts"); // Test C files let c_path = PathBuf::from("test.c"); - assert_eq!(code_bank.get_language_name(&c_path), "cpp"); + let lang = code_bank.detect_language(&c_path).unwrap(); + assert_eq!(lang.as_str(), "cpp"); // Test Go files let go_path = PathBuf::from("test.go"); - assert_eq!(code_bank.get_language_name(&go_path), "go"); + let lang = code_bank.detect_language(&go_path).unwrap(); + assert_eq!(lang.as_str(), "go"); // Test unsupported files let unsupported_path = PathBuf::from("test.txt"); - assert_eq!(code_bank.get_language_name(&unsupported_path), ""); + let lang = code_bank.detect_language(&unsupported_path).unwrap(); + assert_eq!(lang.as_str(), "unknown"); } } diff --git a/src/parser/formatter/mod.rs b/src/parser/formatter/mod.rs index b74e4b3..1eac0f2 100644 --- a/src/parser/formatter/mod.rs +++ b/src/parser/formatter/mod.rs @@ -249,6 +249,41 @@ impl Formatter for ModuleUnit { BankStrategy::Summary => { // Public modules only if self.visibility == Visibility::Public { + let fns: Vec<&FunctionUnit> = self + .functions + .iter() + .filter(|f| f.visibility == Visibility::Public) + .collect(); + let structs: Vec<&StructUnit> = self + .structs + .iter() + .filter(|s| s.visibility == Visibility::Public) + .collect(); + let traits: Vec<&TraitUnit> = self + .traits + .iter() + .filter(|t| t.visibility == Visibility::Public) + .collect(); + let impls: Vec<&ImplUnit> = self + .impls + .iter() + .filter(|i| i.methods.iter().any(|m| m.visibility == Visibility::Public)) + .collect(); + let mods: Vec<&ModuleUnit> = self + .submodules + .iter() + .filter(|m| m.visibility == Visibility::Public) + .collect(); + + if fns.is_empty() + && structs.is_empty() + && traits.is_empty() + && impls.is_empty() + && mods.is_empty() + { + return Ok(String::new()); + } + // Add documentation if let Some(doc) = &self.doc { for line in doc.lines() { @@ -270,10 +305,8 @@ impl Formatter for ModuleUnit { } // Format public functions - for function in &self.functions { - if function.visibility == Visibility::Public - && !rules.is_test_function(&function.attributes) - { + for function in &fns { + if !rules.is_test_function(&function.attributes) { let function_formatted = function.format(strategy, language)?; if !function_formatted.is_empty() { output.push_str(&format!( @@ -285,33 +318,29 @@ impl Formatter for ModuleUnit { } // Format public structs - for struct_unit in &self.structs { - if struct_unit.visibility == Visibility::Public { - let struct_formatted = struct_unit.format(strategy, language)?; - if !struct_formatted.is_empty() { - output.push_str(&format!( - " {}\n\n", - struct_formatted.replace("\n", "\n ") - )); - } + for struct_unit in &structs { + let struct_formatted = struct_unit.format(strategy, language)?; + if !struct_formatted.is_empty() { + output.push_str(&format!( + " {}\n\n", + struct_formatted.replace("\n", "\n ") + )); } } // Format public traits - for trait_unit in &self.traits { - if trait_unit.visibility == Visibility::Public { - let trait_formatted = trait_unit.format(strategy, language)?; - if !trait_formatted.is_empty() { - output.push_str(&format!( - " {}\n\n", - trait_formatted.replace("\n", "\n ") - )); - } + for trait_unit in &traits { + let trait_formatted = trait_unit.format(strategy, language)?; + if !trait_formatted.is_empty() { + output.push_str(&format!( + " {}\n\n", + trait_formatted.replace("\n", "\n ") + )); } } // Format impls (showing public methods) - for impl_unit in &self.impls { + for impl_unit in &impls { let impl_formatted = impl_unit.format(strategy, language)?; if !impl_formatted.is_empty() { output.push_str(&format!( @@ -322,15 +351,13 @@ impl Formatter for ModuleUnit { } // Format public submodules - for submodule in &self.submodules { - if submodule.visibility == Visibility::Public { - let sub_formatted = submodule.format(strategy, language)?; - if !sub_formatted.is_empty() { - output.push_str(&format!( - " {}\n\n", - sub_formatted.replace("\n", "\n ") - )); - } + for submodule in &mods { + let sub_formatted = submodule.format(strategy, language)?; + if !sub_formatted.is_empty() { + output.push_str(&format!( + " {}\n\n", + sub_formatted.replace("\n", "\n ") + )); } } @@ -760,7 +787,7 @@ mod tests { let result = regular_module .format(&BankStrategy::Summary, LanguageType::Rust) .unwrap(); - assert!(result.contains("mod regular_module")); + assert!(!result.contains("mod regular_module")); } #[test] diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6ff3dbd..9e7c57a 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -411,3 +411,16 @@ impl Visibility { } } } + +impl LanguageType { + pub fn as_str(&self) -> &str { + match self { + LanguageType::Rust => "rust", + LanguageType::Python => "python", + LanguageType::TypeScript => "ts", + LanguageType::Cpp => "cpp", + LanguageType::Go => "go", + LanguageType::Unknown => "unknown", + } + } +} From 2fad60e24f50d2af48339b195e7abf43163d121d Mon Sep 17 00:00:00 2001 From: Tyr Chen Date: Mon, 28 Apr 2025 23:10:30 -0700 Subject: [PATCH 3/3] chore: bump version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d724107..e46e575 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "codebank" -version = "0.4.3" +version = "0.4.4" edition = "2024" description = """ A powerful code documentation generator that creates structured markdown documentation from your codebase.