Skip to content

Commit 32a57ab

Browse files
committed
perf: store glob exclude patterns instead of traversing glob(...)
Previously add_excludes_from_gitignore() would use glob() and recursively traverse the generated paths to add them to excludes. Now we store excludes as an enum - Path or Glob - and use the glob crate's `Pattern.matches_path()` as needed, instead of the preemptive traversal.
1 parent ea031d8 commit 32a57ab

File tree

2 files changed

+33
-16
lines changed

2 files changed

+33
-16
lines changed

src/main.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::path::PathBuf;
44
use clap::{Parser, ArgAction};
55
use crate::entries::Entry;
66
use crate::render::render_entries;
7-
use crate::scan::{Stats, scan_dir, scan_todo_file, scan_readme_file};
7+
use crate::scan::{Stats, scan_dir, scan_todo_file, scan_readme_file, Exclude};
88

99
pub mod scan;
1010
pub mod render;
@@ -43,7 +43,7 @@ fn main() {
4343
let root_dir: PathBuf = std::env::current_dir().unwrap();
4444

4545
let mut paths: Vec<PathBuf> = vec![];
46-
let mut excludes: Vec<PathBuf> = vec![];
46+
let mut excludes: Vec<Exclude> = vec![];
4747

4848
let mut entries: Vec<Entry> = vec![];
4949
let mut stats = Stats::new(args.verbose);
@@ -68,7 +68,7 @@ fn main() {
6868

6969
if path.exists() {
7070
if let Ok(realpath) = canonicalize(path) {
71-
excludes.push(realpath);
71+
excludes.push(Exclude::Path(realpath));
7272
}
7373
}
7474
}
@@ -80,13 +80,13 @@ fn main() {
8080
readme_path.push(&args.readme);
8181

8282
if todos_path.exists() {
83-
excludes.push(todos_path.clone());
83+
excludes.push(Exclude::Path(todos_path.clone()));
8484

8585
scan_todo_file(&todos_path, &mut entries).unwrap();
8686
}
8787

8888
if readme_path.exists() {
89-
excludes.push(readme_path.clone());
89+
excludes.push(Exclude::Path(readme_path.clone()));
9090

9191
scan_readme_file(&readme_path, &mut entries).unwrap();
9292
}

src/scan.rs

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
11
use std::io;
22
use std::fs::{self, canonicalize};
33
use std::path::{Path, PathBuf};
4-
use glob::glob;
4+
use glob::{Pattern};
55

66
const PRIORITY_CHARS: [char; 10] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
77

88
use crate::entries::{Entry, EntryData, Location};
99

10+
#[derive(Debug, PartialEq, Eq)]
11+
pub enum Exclude {
12+
Path(PathBuf),
13+
Glob(Pattern),
14+
}
15+
16+
impl Exclude {
17+
pub fn matches(&self, path: &Path) -> bool {
18+
match self {
19+
Exclude::Path(p) => p == path,
20+
Exclude::Glob(g) => g.matches_path(path),
21+
}
22+
}
23+
}
24+
1025
pub struct Stats {
1126
visited_folder_count: usize,
1227
visited_file_count: usize,
@@ -100,7 +115,7 @@ fn clean_line<'a>(line: &'a str, delimiter_word: &str) -> &'a str {
100115
.trim()
101116
}
102117

103-
pub fn add_excludes_from_gitignore(base_dir: &PathBuf, excludes: &mut Vec<PathBuf>) {
118+
pub fn add_excludes_from_gitignore(base_dir: &PathBuf, excludes: &mut Vec<Exclude>) {
104119
let mut gitignore = base_dir.clone();
105120
gitignore.push(".gitignore");
106121

@@ -115,7 +130,7 @@ pub fn add_excludes_from_gitignore(base_dir: &PathBuf, excludes: &mut Vec<PathBu
115130

116131
if line.trim() == "*" {
117132
if let Ok(realpath) = canonicalize(base_dir) {
118-
excludes.push(realpath);
133+
excludes.push(Exclude::Path(realpath));
119134
}
120135

121136
break;
@@ -132,12 +147,14 @@ pub fn add_excludes_from_gitignore(base_dir: &PathBuf, excludes: &mut Vec<PathBu
132147
let mut pattern = base_dir.clone();
133148
pattern.push(line.trim_end_matches("*/").trim_matches('/'));
134149

135-
if let Some(pattern_str) = pattern.to_str() {
136-
for path in glob(pattern_str).unwrap() {
137-
if let Ok(exclude) = canonicalize(path.unwrap()) {
138-
excludes.push(exclude);
150+
if pattern.to_str().unwrap().contains('*') {
151+
if let Some(str) = pattern.to_str() {
152+
if let Ok(p) = Pattern::new(str) {
153+
excludes.push(Exclude::Glob(p));
139154
}
140155
}
156+
} else {
157+
excludes.push(Exclude::Path(pattern));
141158
}
142159
}
143160
}
@@ -226,19 +243,19 @@ pub fn scan_file(path: &Path, entries: &mut Vec<Entry>) -> io::Result<()> {
226243
Ok(())
227244
}
228245

229-
pub fn scan_dir(dir: &Path, entries: &mut Vec<Entry>, excludes: &mut Vec<PathBuf>, stats: &mut Stats) -> io::Result<()> {
246+
pub fn scan_dir(dir: &Path, entries: &mut Vec<Entry>, excludes: &mut Vec<Exclude>, stats: &mut Stats) -> io::Result<()> {
230247
let mut gitignore = dir.to_path_buf().clone();
231248
gitignore.push(".gitignore");
232249

233250
if gitignore.exists() {
234251
add_excludes_from_gitignore(&dir.to_path_buf(), excludes);
235252

236253
// `add_excludes_from_gitignore` can add the *entire* directory being scanned here to excludes
237-
// e.g. if it contains a `*` line. Tthe directory is visited first, and gitignore is read second,
254+
// e.g. if it contains a `*` line. The directory is visited first, and gitignore is read second,
238255
// so the exclude would not affect anything inside the for loop. For that reason, we re-check if
239256
// `dir` hasn't become excluded after running `add_excludes_from_gitignore`.
240257
for exclude in &*excludes {
241-
if canonicalize(dir).unwrap() == *exclude {
258+
if Exclude::Path(canonicalize(dir).unwrap()) == *exclude {
242259
return Ok(());
243260
}
244261
}
@@ -255,7 +272,7 @@ pub fn scan_dir(dir: &Path, entries: &mut Vec<Entry>, excludes: &mut Vec<PathBuf
255272
}
256273

257274
for exclude in &*excludes {
258-
if canonicalize(&path).unwrap() == *exclude {
275+
if exclude.matches(&path) {
259276
continue 'entry;
260277
}
261278
}

0 commit comments

Comments
 (0)