Skip to content

Commit a394af0

Browse files
committed
refactor: centralize nested-glob traversal
Move nested-glob walk and filtering into a shared helper so sync and clean stay aligned on exclusion handling, subtree pruning, and verbose traversal diagnostics.
1 parent 0e21606 commit a394af0

1 file changed

Lines changed: 84 additions & 110 deletions

File tree

src/linker.rs

Lines changed: 84 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -606,54 +606,97 @@ impl Linker {
606606
return Ok(result);
607607
}
608608

609+
self.for_each_nested_glob_match(
610+
search_root,
611+
glob_pattern,
612+
excludes,
613+
options,
614+
|full_path, rel_path| {
615+
let dest_str = Self::expand_destination_template(dest_template, rel_path);
616+
if dest_str.is_empty() {
617+
if options.verbose {
618+
println!(
619+
" {} Destination template produced empty path for: {}",
620+
"!".yellow(),
621+
full_path.display()
622+
);
623+
}
624+
result.skipped += 1;
625+
return Ok(());
626+
}
627+
628+
let dest = self.project_root.join(&dest_str);
629+
630+
let resolved = ResolvedSource {
631+
path: full_path.to_path_buf(),
632+
exists: true,
633+
};
634+
635+
let item_result = self.create_symlink(&resolved, &dest, options)?;
636+
result.created += item_result.created;
637+
result.updated += item_result.updated;
638+
result.skipped += item_result.skipped;
639+
640+
Ok(())
641+
},
642+
)?;
643+
644+
Ok(result)
645+
}
646+
647+
fn for_each_nested_glob_match<F>(
648+
&self,
649+
search_root: &Path,
650+
glob_pattern: &str,
651+
excludes: &[String],
652+
options: &SyncOptions,
653+
mut on_match: F,
654+
) -> Result<()>
655+
where
656+
F: FnMut(&Path, &Path) -> Result<()>,
657+
{
609658
let mut it = WalkDir::new(search_root).follow_links(false).into_iter();
610659

611660
while let Some(entry) = it.next() {
612661
let entry = match entry {
613-
Ok(e) => e,
662+
Ok(entry) => entry,
614663
Err(err) => {
615664
if options.verbose {
616665
let path = err
617666
.path()
618667
.map(|p| p.display().to_string())
619668
.unwrap_or_else(|| "<unknown path>".to_string());
620669
println!(
621-
" {} WalkDir error while scanning {}: {}",
670+
" {} WalkDir error while traversing {}: {}",
622671
"!".yellow(),
623672
path,
624673
err
625674
);
626675
}
627-
tracing::debug!(error = %err, path = ?err.path(), "WalkDir entry skipped during nested-glob sync");
676+
tracing::debug!(error = %err, path = ?err.path(), "WalkDir entry skipped during nested-glob traversal");
628677
continue;
629678
}
630679
};
631680

632-
// Compute path relative to search root
633681
let full_path = entry.path();
634682
let rel_path = match full_path.strip_prefix(search_root) {
635-
Ok(p) => p,
683+
Ok(path) => path,
636684
Err(_) => continue,
637685
};
638686

639-
// Convert to forward-slash string for pattern matching
640687
let rel_str = rel_path
641688
.components()
642-
.map(|c| c.as_os_str().to_string_lossy().into_owned())
689+
.map(|component| component.as_os_str().to_string_lossy().into_owned())
643690
.collect::<Vec<_>>()
644691
.join("/");
645692

646693
if rel_str.is_empty() {
647-
continue; // Skip root itself
694+
continue;
648695
}
649696

650-
// Check exclusion patterns.
651-
// Uses `find()` instead of `any()` to:
652-
// 1. Skip iteration when excludes list is empty (short-circuit)
653-
// 2. Determine which pattern matched (for verbose logging)
654697
if let Some(matched_exclude) = excludes
655698
.iter()
656-
.find(|excl| matches_path_glob(&rel_str, excl))
699+
.find(|exclude| matches_path_glob(&rel_str, exclude))
657700
{
658701
if options.verbose {
659702
println!(
@@ -663,52 +706,25 @@ impl Linker {
663706
full_path.display()
664707
);
665708
}
666-
// Optimization: if this is a directory and it matches an exclude pattern,
667-
// skip its entire subtree to avoid unnecessary I/O.
709+
668710
if entry.file_type().is_dir() {
669711
it.skip_current_dir();
670712
}
671713
continue;
672714
}
673715

674-
// Only process files
675716
if !entry.file_type().is_file() {
676717
continue;
677718
}
678719

679-
// Match against the glob pattern
680720
if !matches_path_glob(&rel_str, glob_pattern) {
681721
continue;
682722
}
683723

684-
// Expand the destination template
685-
let dest_str = Self::expand_destination_template(dest_template, rel_path);
686-
if dest_str.is_empty() {
687-
if options.verbose {
688-
println!(
689-
" {} Destination template produced empty path for: {}",
690-
"!".yellow(),
691-
full_path.display()
692-
);
693-
}
694-
result.skipped += 1;
695-
continue;
696-
}
697-
698-
let dest = self.project_root.join(&dest_str);
699-
700-
let resolved = ResolvedSource {
701-
path: full_path.to_path_buf(),
702-
exists: true,
703-
};
704-
705-
let item_result = self.create_symlink(&resolved, &dest, options)?;
706-
result.created += item_result.created;
707-
result.updated += item_result.updated;
708-
result.skipped += item_result.skipped;
724+
on_match(full_path, rel_path)?;
709725
}
710726

711-
Ok(result)
727+
Ok(())
712728
}
713729

714730
/// Process a `module-map` target: iterate mappings and create a symlink
@@ -840,78 +856,36 @@ impl Linker {
840856
let dest_template = &target_config.destination;
841857
let excludes = &target_config.exclude;
842858

843-
let mut it = WalkDir::new(&search_root).follow_links(false).into_iter();
844-
845-
while let Some(entry) = it.next() {
846-
let entry = match entry {
847-
Ok(e) => e,
848-
Err(err) => {
849-
if options.verbose {
850-
let path = err
851-
.path()
852-
.map(|p| p.display().to_string())
853-
.unwrap_or_else(|| "<unknown path>".to_string());
859+
self.for_each_nested_glob_match(
860+
&search_root,
861+
glob_pattern,
862+
excludes,
863+
options,
864+
|_, rel_path| {
865+
let dest_str =
866+
Self::expand_destination_template(dest_template, rel_path);
867+
if dest_str.is_empty() {
868+
return Ok(());
869+
}
870+
871+
let dest = self.project_root.join(&dest_str);
872+
if dest.is_symlink() {
873+
if options.dry_run {
854874
println!(
855-
" {} WalkDir error while cleaning {}: {}",
856-
"!".yellow(),
857-
path,
858-
err
875+
" {} Would remove: {}",
876+
"→".cyan(),
877+
dest.display()
859878
);
879+
} else {
880+
fs::remove_file(&dest)?;
881+
println!(" {} Removed: {}", "✔".green(), dest.display());
860882
}
861-
tracing::debug!(error = %err, path = ?err.path(), "WalkDir entry skipped during nested-glob clean");
862-
continue;
863-
}
864-
};
865-
866-
let rel_path = match entry.path().strip_prefix(&search_root) {
867-
Ok(p) => p,
868-
Err(_) => continue,
869-
};
870-
let rel_str = rel_path
871-
.components()
872-
.map(|c| c.as_os_str().to_string_lossy().into_owned())
873-
.collect::<Vec<_>>()
874-
.join("/");
875-
876-
if rel_str.is_empty() {
877-
continue;
878-
}
879-
880-
if excludes
881-
.iter()
882-
.any(|excl| matches_path_glob(&rel_str, excl))
883-
{
884-
// Optimization: if this is a directory and it matches an exclude pattern,
885-
// skip its entire subtree to avoid unnecessary I/O.
886-
if entry.file_type().is_dir() {
887-
it.skip_current_dir();
883+
result.removed += 1;
888884
}
889-
continue;
890-
}
891885

892-
if !entry.file_type().is_file() {
893-
continue;
894-
}
895-
896-
if !matches_path_glob(&rel_str, glob_pattern) {
897-
continue;
898-
}
899-
let dest_str =
900-
Self::expand_destination_template(dest_template, rel_path);
901-
if dest_str.is_empty() {
902-
continue;
903-
}
904-
let dest = self.project_root.join(&dest_str);
905-
if dest.is_symlink() {
906-
if options.dry_run {
907-
println!(" {} Would remove: {}", "→".cyan(), dest.display());
908-
} else {
909-
fs::remove_file(&dest)?;
910-
println!(" {} Removed: {}", "✔".green(), dest.display());
911-
}
912-
result.removed += 1;
913-
}
914-
}
886+
Ok(())
887+
},
888+
)?;
915889
}
916890
SyncType::SymlinkContents => {
917891
let dest = self.project_root.join(&target_config.destination);

0 commit comments

Comments
 (0)