@@ -25,7 +25,7 @@ pub fn run_scan(path: Option<PathBuf>, recipes_path: Option<PathBuf>, max_depth:
2525 let recipes = RecipeFile :: load ( recipes_path) ?;
2626 let globset = recipes. compile_globset ( ) ?;
2727
28- let ( agg, total) = scan_dir ( & root, & globset, max_depth) ?;
28+ let ( agg, total) = scan_dir ( & root, & globset, max_depth, & recipes ) ?;
2929 println ! ( "Scanning {}" , root. display( ) ) ;
3030 println ! ( "Found total {} across {} matched groups" , format_size( total, DECIMAL ) , agg. len( ) ) ;
3131 for ( pattern_group, summary) in agg {
@@ -41,7 +41,7 @@ pub fn run_suggest(path: Option<PathBuf>, recipes_path: Option<PathBuf>) -> Resu
4141 let root = path. unwrap_or ( std:: env:: current_dir ( ) ?) ;
4242 let recipes = RecipeFile :: load ( recipes_path) ?;
4343 let globset = recipes. compile_globset ( ) ?;
44- let ( agg, _total) = scan_dir ( & root, & globset, 8 ) ?;
44+ let ( agg, _total) = scan_dir ( & root, & globset, 8 , & recipes ) ?;
4545
4646 println ! ( "Suggestions for {}" , root. display( ) ) ;
4747 let mut ranked: Vec < ( String , RuleAggregate ) > = agg. into_iter ( ) . collect ( ) ;
@@ -67,11 +67,11 @@ pub fn run_suggest(path: Option<PathBuf>, recipes_path: Option<PathBuf>) -> Resu
6767 Ok ( ( ) )
6868}
6969
70- pub fn run_gen_ignore ( path : Option < PathBuf > , recipes_path : Option < PathBuf > , dry_run : bool ) -> Result < ( ) > {
70+ pub fn run_gen_ignore ( path : Option < PathBuf > , recipes_path : Option < PathBuf > , dry_run : Option < bool > ) -> Result < ( ) > {
7171 let root = path. unwrap_or ( std:: env:: current_dir ( ) ?) ;
7272 let recipes = RecipeFile :: load ( recipes_path) ?;
7373 let globset = recipes. compile_globset ( ) ?;
74- let ( agg, _total) = scan_dir ( & root, & globset, 8 ) ?;
74+ let ( agg, _total) = scan_dir ( & root, & globset, 8 , & recipes ) ?;
7575
7676 let mut ranked: Vec < ( String , RuleAggregate ) > = agg. into_iter ( ) . collect ( ) ;
7777 ranked. sort_by_key ( |( _, v) | std:: cmp:: Reverse ( v. total_bytes ) ) ;
@@ -86,7 +86,8 @@ pub fn run_gen_ignore(path: Option<PathBuf>, recipes_path: Option<PathBuf>, dry_
8686 return Ok ( ( ) ) ;
8787 }
8888 let gi_path = root. join ( ".gitignore" ) ;
89- if dry_run {
89+ let dry = dry_run. unwrap_or ( false ) ;
90+ if dry {
9091 println ! ( "Would append to {}:\n " , gi_path. display( ) ) ;
9192 for h in & hints { println ! ( "{}" , h) ; }
9293 return Ok ( ( ) ) ;
@@ -132,7 +133,7 @@ fn gitignore_hint_for_group(group: &str) -> Option<String> {
132133 }
133134}
134135
135- fn scan_dir ( root : & Path , globset : & globset:: GlobSet , max_depth : usize ) -> Result < ( BTreeMap < String , RuleAggregate > , u64 ) > {
136+ fn scan_dir ( root : & Path , globset : & globset:: GlobSet , max_depth : usize , recipes : & RecipeFile ) -> Result < ( BTreeMap < String , RuleAggregate > , u64 ) > {
136137 let mut totals: BTreeMap < String , RuleAggregate > = BTreeMap :: new ( ) ;
137138 let mut seen_dirs: HashSet < PathBuf > = HashSet :: new ( ) ;
138139 let mut total_bytes: u64 = 0 ;
@@ -156,7 +157,7 @@ fn scan_dir(root: &Path, globset: &globset::GlobSet, max_depth: usize) -> Result
156157 seen_dirs. insert ( path. to_path_buf ( ) ) ;
157158 size = dir_size ( path) ?;
158159 }
159- let group_key = group_from_path ( rel) ;
160+ let group_key = group_from_globs ( rel, recipes ) ;
160161 let entry = totals. entry ( group_key) . or_default ( ) ;
161162 entry. total_bytes = entry. total_bytes . saturating_add ( size) ;
162163 entry. count += 1 ;
@@ -166,6 +167,27 @@ fn scan_dir(root: &Path, globset: &globset::GlobSet, max_depth: usize) -> Result
166167 Ok ( ( totals, total_bytes) )
167168}
168169
170+ fn group_from_globs ( path : & Path , recipes : & RecipeFile ) -> String {
171+ let rel_str = path. to_string_lossy ( ) ;
172+ // Try to find a sensible token from the recipe globs (e.g., node_modules, target, __pycache__)
173+ for rule in & recipes. rules {
174+ for g in & rule. globs {
175+ // split the glob into components and pick the first non-wildcard segment
176+ for seg in g. split ( '/' ) {
177+ let seg = seg. trim ( ) ;
178+ if seg. is_empty ( ) { continue ; }
179+ if seg. contains ( '*' ) || seg. contains ( '?' ) || seg. contains ( '[' ) { continue ; }
180+ // Found a literal segment; check if the path contains it
181+ if rel_str. contains ( seg) {
182+ return seg. to_string ( ) ;
183+ }
184+ }
185+ }
186+ }
187+ // Fallback to original behavior (last path component)
188+ group_from_path ( path)
189+ }
190+
169191fn group_from_path ( path : & Path ) -> String {
170192 let comps: Vec < _ > = path. components ( ) . map ( |c| c. as_os_str ( ) . to_string_lossy ( ) . to_string ( ) ) . collect ( ) ;
171193 if comps. is_empty ( ) { return String :: from ( "root" ) ; }
0 commit comments