rubydex 0.2.8 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/rust/Cargo.toml CHANGED
@@ -1,7 +1,6 @@
1
1
  [workspace]
2
2
  members = [
3
3
  "rubydex",
4
- "rubydex-mcp",
5
4
  "rubydex-sys",
6
5
  ]
7
6
 
@@ -21,9 +21,9 @@ pub const DEFAULT_EXCLUDED_DIRECTORIES: &[&str] = &[
21
21
  /// Configuration coming from a config file
22
22
  #[derive(Debug, Default, Deserialize)]
23
23
  struct ConfigFile {
24
- /// Paths to exclude from file discovery during indexing.
24
+ /// Patterns to exclude from file discovery during indexing.
25
25
  #[serde(default)]
26
- exclude: Vec<PathBuf>,
26
+ exclude: Vec<Box<str>>,
27
27
  }
28
28
 
29
29
  /// Project configuration
@@ -31,8 +31,8 @@ struct ConfigFile {
31
31
  pub struct Config {
32
32
  /// Path to the workspace being analyzed
33
33
  workspace_path: Box<Path>,
34
- /// Paths to exclude from file discovery during indexing.
35
- excluded_paths: HashSet<PathBuf>,
34
+ /// Patterns to exclude from file discovery during indexing.
35
+ excluded_patterns: HashSet<Box<str>>,
36
36
  }
37
37
  assert_mem_size!(Config, 64);
38
38
 
@@ -50,7 +50,7 @@ impl Config {
50
50
  workspace_path: std::env::current_dir()
51
51
  .unwrap_or_else(|_| PathBuf::from("."))
52
52
  .into_boxed_path(),
53
- excluded_paths: DEFAULT_EXCLUDED_DIRECTORIES.iter().map(PathBuf::from).collect(),
53
+ excluded_patterns: DEFAULT_EXCLUDED_DIRECTORIES.iter().map(|&dir| Box::from(dir)).collect(),
54
54
  }
55
55
  }
56
56
 
@@ -65,18 +65,24 @@ impl Config {
65
65
  self.workspace_path = workspace_path.into_boxed_path();
66
66
  }
67
67
 
68
- /// Adds paths to exclude from file discovery during indexing. Excluded directories will be skipped entirely during
68
+ /// Adds patterns to exclude from file discovery during indexing. Excluded directories will be skipped entirely during
69
69
  /// directory traversal.
70
- pub fn exclude_paths(&mut self, paths: impl IntoIterator<Item = PathBuf>) {
71
- self.excluded_paths.extend(paths);
70
+ pub fn exclude_patterns(&mut self, patterns: impl IntoIterator<Item = Box<str>>) {
71
+ self.excluded_patterns.extend(patterns);
72
72
  }
73
73
 
74
- /// Returns the set of paths excluded from file discovery
74
+ /// Returns the set of exclusion patterns resolved against the workspace path.
75
75
  #[must_use]
76
- pub fn excluded_paths(&self) -> HashSet<PathBuf> {
77
- self.excluded_paths
76
+ pub fn excluded_patterns(&self) -> HashSet<Box<str>> {
77
+ self.excluded_patterns
78
78
  .iter()
79
- .map(|path| self.workspace_path.join(path))
79
+ .map(|entry| {
80
+ self.workspace_path
81
+ .join(&**entry)
82
+ .to_string_lossy()
83
+ .into_owned()
84
+ .into_boxed_str()
85
+ })
80
86
  .collect()
81
87
  }
82
88
 
@@ -124,7 +130,7 @@ impl Config {
124
130
  Errors::ConfigError(format!("Invalid config file `{}`: {error}", config_path.display()))
125
131
  })?;
126
132
 
127
- self.excluded_paths.extend(parsed.exclude);
133
+ self.excluded_patterns.extend(parsed.exclude);
128
134
  Ok(())
129
135
  }
130
136
 
@@ -139,19 +145,32 @@ mod tests {
139
145
  use super::*;
140
146
  use std::path::Path;
141
147
 
148
+ fn workspace_exclusion(entry: &str) -> String {
149
+ PathBuf::from("/workspace").join(entry).to_string_lossy().into_owned()
150
+ }
151
+
142
152
  #[test]
143
- fn excluded_paths_are_resolved_against_the_workspace_path() {
153
+ fn excluded_patterns_resolves_patterns_against_the_workspace_path() {
144
154
  let mut config = Config::new();
145
155
  config.set_workspace_path(PathBuf::from("/workspace"));
146
- config.exclude_paths([PathBuf::from("vendor"), PathBuf::from("/absolute/path")]);
147
-
148
- let excluded = config.excluded_paths();
149
-
150
- // Relative entries (including the defaults) are joined with the workspace path.
151
- assert!(excluded.contains(Path::new("/workspace/vendor")));
152
- assert!(excluded.contains(Path::new("/workspace/.git")));
153
- // Absolute entries pass through unchanged.
154
- assert!(excluded.contains(Path::new("/absolute/path")));
156
+ config.exclude_patterns([
157
+ Box::from("vendor"),
158
+ Box::from("**/fixtures"),
159
+ Box::from("/absolute/path"),
160
+ ]);
161
+
162
+ let excluded = config.excluded_patterns();
163
+
164
+ let vendor = workspace_exclusion("vendor");
165
+ let fixtures = workspace_exclusion("**/fixtures");
166
+ let absolute = PathBuf::from("/absolute/path").to_string_lossy().into_owned();
167
+ let git = workspace_exclusion(".git");
168
+
169
+ assert!(excluded.contains(vendor.as_str()));
170
+ assert!(excluded.contains(fixtures.as_str()));
171
+ assert!(excluded.contains(absolute.as_str()));
172
+ // Defaults are included and resolved as well.
173
+ assert!(excluded.contains(git.as_str()));
155
174
  }
156
175
 
157
176
  #[test]
@@ -160,14 +179,14 @@ mod tests {
160
179
 
161
180
  for default in DEFAULT_EXCLUDED_DIRECTORIES {
162
181
  assert!(
163
- config.excluded_paths.contains(Path::new(default)),
182
+ config.excluded_patterns.contains(*default),
164
183
  "expected `{default}` to be excluded by default"
165
184
  );
166
185
  }
167
186
  }
168
187
 
169
188
  #[test]
170
- fn load_file_merges_excluded_paths_and_leaves_the_workspace_path_untouched() {
189
+ fn load_file_merges_excluded_patterns_and_leaves_the_workspace_path_untouched() {
171
190
  let dir = tempfile::tempdir().expect("failed to create temp dir");
172
191
  let config_path = dir.path().join("rubydex.toml");
173
192
  fs::write(&config_path, "exclude = [\"vendor\", \"generated\"]\n").unwrap();
@@ -179,12 +198,12 @@ mod tests {
179
198
  .load_file(&config_path)
180
199
  .expect("expected the config file to load");
181
200
 
182
- let excluded = config.excluded_paths();
201
+ let excluded = config.excluded_patterns();
183
202
  // Entries from the file are merged in and resolved against the workspace path.
184
- assert!(excluded.contains(Path::new("/workspace/vendor")));
185
- assert!(excluded.contains(Path::new("/workspace/generated")));
203
+ assert!(excluded.contains(workspace_exclusion("vendor").as_str()));
204
+ assert!(excluded.contains(workspace_exclusion("generated").as_str()));
186
205
  // Defaults seeded at construction survive the merge.
187
- assert!(excluded.contains(Path::new("/workspace/node_modules")));
206
+ assert!(excluded.contains(workspace_exclusion("node_modules").as_str()));
188
207
  // A config file cannot override the programmatically-set workspace path.
189
208
  assert_eq!(config.workspace_path(), Path::new("/workspace"));
190
209
  }
@@ -204,9 +223,9 @@ mod tests {
204
223
  .load_file(&dir.path().join("b.toml"))
205
224
  .expect("expected the second file to load");
206
225
 
207
- let excluded = config.excluded_paths();
208
- assert!(excluded.contains(Path::new("/workspace/vendor")));
209
- assert!(excluded.contains(Path::new("/workspace/generated")));
226
+ let excluded = config.excluded_patterns();
227
+ assert!(excluded.contains(workspace_exclusion("vendor").as_str()));
228
+ assert!(excluded.contains(workspace_exclusion("generated").as_str()));
210
229
  }
211
230
 
212
231
  #[test]
@@ -244,7 +263,8 @@ mod tests {
244
263
  config.set_workspace_path(dir.path().to_path_buf());
245
264
  config.load_default().expect("expected rubydex.toml to load");
246
265
 
247
- assert!(config.excluded_paths().contains(&dir.path().join("vendor")));
266
+ let expected = dir.path().join("vendor").to_string_lossy().into_owned();
267
+ assert!(config.excluded_patterns().contains(expected.as_str()));
248
268
  }
249
269
 
250
270
  #[test]
@@ -263,7 +283,7 @@ mod tests {
263
283
  }
264
284
 
265
285
  #[test]
266
- fn parse_defaults_the_excluded_paths_to_empty_when_the_key_is_absent() {
286
+ fn parse_defaults_the_excluded_patterns_to_empty_when_the_key_is_absent() {
267
287
  let file = Config::parse("").expect("an empty config is valid");
268
288
  assert!(file.exclude.is_empty());
269
289
  }
@@ -3,9 +3,9 @@ use crate::{
3
3
  job_queue::{Job, JobQueue},
4
4
  };
5
5
  use crossbeam_channel::{Sender, unbounded};
6
+ use glob::Pattern;
6
7
  use std::{
7
8
  collections::HashSet,
8
- fs,
9
9
  hash::BuildHasher,
10
10
  path::{Path, PathBuf},
11
11
  sync::Arc,
@@ -16,7 +16,7 @@ pub struct FileDiscoveryJob {
16
16
  queue: Arc<JobQueue>,
17
17
  paths_tx: Sender<PathBuf>,
18
18
  errors_tx: Sender<Errors>,
19
- excluded_paths: Arc<HashSet<PathBuf>>,
19
+ excluded_patterns: Arc<Vec<Pattern>>,
20
20
  }
21
21
 
22
22
  impl FileDiscoveryJob {
@@ -26,24 +26,17 @@ impl FileDiscoveryJob {
26
26
  queue: Arc<JobQueue>,
27
27
  paths_tx: Sender<PathBuf>,
28
28
  errors_tx: Sender<Errors>,
29
- excluded_paths: Arc<HashSet<PathBuf>>,
29
+ excluded_patterns: Arc<Vec<Pattern>>,
30
30
  ) -> Self {
31
31
  Self {
32
32
  path,
33
33
  queue,
34
34
  paths_tx,
35
35
  errors_tx,
36
- excluded_paths,
36
+ excluded_patterns,
37
37
  }
38
38
  }
39
- }
40
-
41
- fn is_indexable_file(path: &Path) -> bool {
42
- path.extension()
43
- .is_some_and(|ext| ext == "rb" || ext == "rake" || ext == "rbs" || ext == "ru")
44
- }
45
39
 
46
- impl FileDiscoveryJob {
47
40
  fn handle_file(&self, path: &Path) {
48
41
  if is_indexable_file(path) {
49
42
  self.paths_tx
@@ -52,29 +45,6 @@ impl FileDiscoveryJob {
52
45
  }
53
46
  }
54
47
 
55
- fn handle_symlink(&self, path: &PathBuf) {
56
- let Ok(canonicalized) = fs::canonicalize(path) else {
57
- self.send_error(Errors::FileError(format!(
58
- "Failed to canonicalize symlink: `{}`",
59
- path.display(),
60
- )));
61
-
62
- return;
63
- };
64
-
65
- if self.excluded_paths.contains(&canonicalized) {
66
- return;
67
- }
68
-
69
- self.queue.push(Box::new(FileDiscoveryJob::new(
70
- canonicalized,
71
- Arc::clone(&self.queue),
72
- self.paths_tx.clone(),
73
- self.errors_tx.clone(),
74
- Arc::clone(&self.excluded_paths),
75
- )));
76
- }
77
-
78
48
  fn send_error(&self, error: Errors) {
79
49
  self.errors_tx
80
50
  .send(error)
@@ -84,64 +54,59 @@ impl FileDiscoveryJob {
84
54
 
85
55
  impl Job for FileDiscoveryJob {
86
56
  fn run(&self) {
87
- if self.path.is_dir() {
88
- let Ok(read_dir) = self.path.read_dir() else {
57
+ let Ok(read_dir) = self.path.read_dir() else {
58
+ if self.path.is_file() {
59
+ self.handle_file(&self.path);
60
+ } else {
89
61
  self.send_error(Errors::FileError(format!(
90
62
  "Failed to read directory `{}`",
63
+ self.path.display()
64
+ )));
65
+ }
66
+
67
+ return;
68
+ };
69
+
70
+ for result in read_dir {
71
+ let Ok(entry) = result else {
72
+ self.send_error(Errors::FileError(format!(
73
+ "Failed to read directory `{}`: {result:?}",
91
74
  self.path.display(),
92
75
  )));
93
76
 
94
- return;
77
+ continue;
95
78
  };
96
79
 
97
- for result in read_dir {
98
- let Ok(entry) = result else {
99
- self.send_error(Errors::FileError(format!(
100
- "Failed to read directory `{}`: {result:?}",
101
- self.path.display(),
102
- )));
103
-
104
- continue;
105
- };
106
-
107
- let kind = entry.file_type().unwrap();
108
-
109
- if kind.is_dir() {
110
- if self.excluded_paths.contains(&entry.path()) {
111
- continue;
112
- }
113
-
114
- self.queue.push(Box::new(FileDiscoveryJob::new(
115
- entry.path(),
116
- Arc::clone(&self.queue),
117
- self.paths_tx.clone(),
118
- self.errors_tx.clone(),
119
- Arc::clone(&self.excluded_paths),
120
- )));
121
- } else if kind.is_file() {
122
- self.handle_file(&entry.path());
123
- } else if kind.is_symlink() {
124
- self.handle_symlink(&entry.path());
125
- } else {
126
- self.send_error(Errors::FileError(format!(
127
- "Path `{}` is not a file or directory",
128
- entry.path().display()
129
- )));
130
- }
80
+ let path = entry.path();
81
+
82
+ if is_excluded(&self.excluded_patterns, &path) {
83
+ continue;
84
+ }
85
+
86
+ if entry.file_type().unwrap().is_dir() {
87
+ self.queue.push(Box::new(FileDiscoveryJob::new(
88
+ path,
89
+ Arc::clone(&self.queue),
90
+ self.paths_tx.clone(),
91
+ self.errors_tx.clone(),
92
+ Arc::clone(&self.excluded_patterns),
93
+ )));
94
+ } else {
95
+ self.handle_file(&path);
131
96
  }
132
- } else if self.path.is_file() {
133
- self.handle_file(&self.path);
134
- } else if self.path.is_symlink() {
135
- self.handle_symlink(&self.path);
136
- } else {
137
- self.send_error(Errors::FileError(format!(
138
- "Path `{}` is not a file or directory",
139
- self.path.display()
140
- )));
141
97
  }
142
98
  }
143
99
  }
144
100
 
101
+ fn is_indexable_file(path: &Path) -> bool {
102
+ path.extension()
103
+ .is_some_and(|ext| ext == "rb" || ext == "rake" || ext == "rbs" || ext == "ru")
104
+ }
105
+
106
+ fn is_excluded(excluded_patterns: &[Pattern], path: &Path) -> bool {
107
+ excluded_patterns.iter().any(|pattern| pattern.matches_path(path))
108
+ }
109
+
145
110
  /// Recursively collects all Ruby files for the given workspace and dependencies, returning a vector of document instances
146
111
  ///
147
112
  /// # Errors
@@ -154,34 +119,42 @@ impl Job for FileDiscoveryJob {
154
119
  #[must_use]
155
120
  pub fn collect_file_paths<S: BuildHasher>(
156
121
  paths: Vec<String>,
157
- excluded: &HashSet<PathBuf, S>,
122
+ excluded: &HashSet<Box<str>, S>,
158
123
  ) -> (Vec<PathBuf>, Vec<Errors>) {
159
124
  let queue = Arc::new(JobQueue::new());
160
125
  let (files_tx, files_rx) = unbounded();
161
126
  let (errors_tx, errors_rx) = unbounded();
162
127
 
163
- // Canonicalize the excluded paths since they may be symlinks
164
- let excluded: Arc<HashSet<PathBuf>> = Arc::new(excluded.iter().filter_map(|p| fs::canonicalize(p).ok()).collect());
128
+ let excluded_patterns: Arc<Vec<Pattern>> =
129
+ Arc::new(excluded.iter().filter_map(|entry| Pattern::new(entry).ok()).collect());
165
130
 
166
131
  for path in paths {
167
- let Ok(canonicalized) = fs::canonicalize(&path) else {
132
+ let Ok(path) = std::path::absolute(&path) else {
168
133
  errors_tx
169
- .send(Errors::FileError(format!("Path `{path}` does not exist")))
134
+ .send(Errors::FileError(format!("Failed to resolve path `{path}`")))
170
135
  .expect("errors receiver dropped before run completion");
171
136
 
172
137
  continue;
173
138
  };
174
139
 
175
- if excluded.contains(&canonicalized) {
140
+ if !path.exists() {
141
+ errors_tx
142
+ .send(Errors::FileError(format!("Path `{}` does not exist", path.display())))
143
+ .expect("errors receiver dropped before run completion");
144
+
145
+ continue;
146
+ }
147
+
148
+ if is_excluded(&excluded_patterns, &path) {
176
149
  continue;
177
150
  }
178
151
 
179
152
  queue.push(Box::new(FileDiscoveryJob::new(
180
- canonicalized,
153
+ path,
181
154
  Arc::clone(&queue),
182
155
  files_tx.clone(),
183
156
  errors_tx.clone(),
184
- Arc::clone(&excluded),
157
+ Arc::clone(&excluded_patterns),
185
158
  )));
186
159
  }
187
160
 
@@ -205,7 +178,7 @@ mod tests {
205
178
  fn collect_document_paths_with_exclusions(
206
179
  context: &Context,
207
180
  paths: &[&str],
208
- excluded: &HashSet<PathBuf>,
181
+ excluded: &HashSet<Box<str>>,
209
182
  ) -> (Vec<String>, Vec<Errors>) {
210
183
  let (files, errors) = collect_file_paths(
211
184
  paths
@@ -324,6 +297,31 @@ mod tests {
324
297
  );
325
298
  }
326
299
 
300
+ #[cfg(unix)]
301
+ #[test]
302
+ fn collect_files_emits_absolute_paths_for_relative_roots() {
303
+ let context = Context::new();
304
+ context.touch(PathBuf::from("project").join("foo.rb"));
305
+
306
+ // Express the project directory as a path relative to the process working directory.
307
+ let working_directory = std::env::current_dir().unwrap();
308
+ let mut relative_root = PathBuf::new();
309
+ for _ in 0..working_directory.components().count() - 1 {
310
+ relative_root.push("..");
311
+ }
312
+ let project = context.absolute_path_to("project");
313
+ let relative_root = relative_root.join(project.strip_prefix("/").unwrap());
314
+
315
+ let (files, errors) = collect_file_paths(vec![relative_root.to_string_lossy().into_owned()], &HashSet::new());
316
+
317
+ assert!(errors.is_empty());
318
+ assert!(!files.is_empty());
319
+ assert!(
320
+ files.iter().all(|path| path.is_absolute()),
321
+ "expected only absolute paths, got {files:?}"
322
+ );
323
+ }
324
+
327
325
  #[test]
328
326
  fn collect_files_excludes_directories() {
329
327
  let context = Context::new();
@@ -333,7 +331,7 @@ mod tests {
333
331
  context.touch(&excluded_file);
334
332
 
335
333
  let mut excluded = HashSet::new();
336
- excluded.insert(context.absolute_path_to("excluded"));
334
+ excluded.insert(context.absolute_path_to("excluded").to_string_lossy().into());
337
335
 
338
336
  let (files, errors) = collect_document_paths_with_exclusions(&context, &["included", "excluded"], &excluded);
339
337
 
@@ -350,7 +348,7 @@ mod tests {
350
348
  context.touch(&nested);
351
349
 
352
350
  let mut excluded = HashSet::new();
353
- excluded.insert(context.absolute_path_to("root/skip"));
351
+ excluded.insert("**/skip".into());
354
352
 
355
353
  let (files, errors) = collect_document_paths_with_exclusions(&context, &["root"], &excluded);
356
354
 
@@ -360,23 +358,82 @@ mod tests {
360
358
 
361
359
  #[cfg(unix)]
362
360
  #[test]
363
- fn collect_files_excludes_symlinked_directories() {
361
+ fn collect_files_indexes_symlinked_files_at_their_own_path() {
364
362
  let context = Context::new();
365
- let included = PathBuf::from("included").join("foo.rb");
366
- let excluded_file = PathBuf::from("real_dir").join("bar.rb");
367
- context.touch(&included);
368
- context.touch(&excluded_file);
363
+ let target = PathBuf::from("outside").join("real.rb");
364
+ context.touch(&target);
365
+ context.mkdir("project");
366
+
367
+ // Create a symlink to a file outside the traversed tree: project/alias.rb -> outside/real.rb
368
+ std::os::unix::fs::symlink(
369
+ context.absolute_path_to("outside/real.rb"),
370
+ context.absolute_path_to("project/alias.rb"),
371
+ )
372
+ .unwrap();
373
+
374
+ let (files, errors) = collect_document_paths(&context, &["project"]);
375
+
376
+ assert!(errors.is_empty());
377
+ // The symlink is indexed at its own path, not resolved to the target.
378
+ let alias = PathBuf::from("project").join("alias.rb");
379
+ assert_eq!(files, [alias.to_str().unwrap().to_string()]);
380
+ }
381
+
382
+ #[cfg(unix)]
383
+ #[test]
384
+ fn collect_files_does_not_follow_symlinked_directories() {
385
+ let context = Context::new();
386
+ let kept = PathBuf::from("project").join("foo.rb");
387
+ let outside = PathBuf::from("outside").join("bar.rb");
388
+ context.touch(&kept);
389
+ context.touch(&outside);
369
390
 
370
- // Create a symlink: link -> real_dir
371
- std::os::unix::fs::symlink(context.absolute_path_to("real_dir"), context.absolute_path_to("link")).unwrap();
391
+ // Create a symlink inside the traversed tree: project/link -> outside
392
+ std::os::unix::fs::symlink(
393
+ context.absolute_path_to("outside"),
394
+ context.absolute_path_to("project/link"),
395
+ )
396
+ .unwrap();
397
+
398
+ let (files, errors) = collect_document_paths(&context, &["project"]);
399
+
400
+ assert!(errors.is_empty());
401
+ // The symlinked directory is not followed, so `outside/bar.rb` is never reached.
402
+ assert_eq!(files, [kept.to_str().unwrap().to_string()]);
403
+ }
404
+
405
+ #[cfg(unix)]
406
+ #[test]
407
+ fn collect_files_indexes_symlinked_directory_roots() {
408
+ let context = Context::new();
409
+ let target = PathBuf::from("real").join("foo.rb");
410
+ context.touch(&target);
411
+
412
+ // A symlink to a directory passed as an explicit root, as `Graph#workspace_paths` does via `File.directory?`.
413
+ std::os::unix::fs::symlink(context.absolute_path_to("real"), context.absolute_path_to("link")).unwrap();
414
+
415
+ let (files, errors) = collect_document_paths(&context, &["link"]);
416
+
417
+ assert!(errors.is_empty());
418
+ // The requested root is traversed; files are indexed under the requested (symlink) path.
419
+ let foo = PathBuf::from("link").join("foo.rb");
420
+ assert_eq!(files, [foo.to_str().unwrap().to_string()]);
421
+ }
422
+
423
+ #[test]
424
+ fn collect_files_excludes_nested_files_matching_globs() {
425
+ let context = Context::new();
426
+ let kept = PathBuf::from("lib").join("foo.rb");
427
+ let excluded_file = PathBuf::from("lib").join("version.rb");
428
+ context.touch(&kept);
429
+ context.touch(&excluded_file);
372
430
 
373
- // Excluding the real directory while requesting to index the symlink should properly exclude the link
374
431
  let mut excluded = HashSet::new();
375
- excluded.insert(context.absolute_path_to("real_dir"));
432
+ excluded.insert("**/version.rb".into());
376
433
 
377
- let (files, errors) = collect_document_paths_with_exclusions(&context, &["included", "link"], &excluded);
434
+ let (files, errors) = collect_document_paths_with_exclusions(&context, &["lib"], &excluded);
378
435
 
379
436
  assert!(errors.is_empty());
380
- assert_eq!(files, [included.to_str().unwrap().to_string()]);
437
+ assert_eq!(files, [kept.to_str().unwrap().to_string()]);
381
438
  }
382
439
  }
@@ -134,7 +134,7 @@ fn main() {
134
134
  // Listing
135
135
 
136
136
  let (file_paths, errors) = time_it!(listing, {
137
- listing::collect_file_paths(args.paths, &graph.excluded_paths())
137
+ listing::collect_file_paths(args.paths, &graph.excluded_patterns())
138
138
  });
139
139
 
140
140
  for error in errors {
@@ -128,16 +128,16 @@ impl Graph {
128
128
  &mut self.declarations
129
129
  }
130
130
 
131
- /// Adds paths to exclude from file discovery during indexing. Excluded directories will be skipped entirely during
132
- /// directory traversal.
133
- pub fn exclude_paths(&mut self, paths: Vec<PathBuf>) {
134
- self.config.exclude_paths(paths);
131
+ /// Adds glob patterns to exclude from file discovery during indexing. Excluded directories will be skipped entirely
132
+ /// during directory traversal.
133
+ pub fn exclude_patterns(&mut self, patterns: Vec<Box<str>>) {
134
+ self.config.exclude_patterns(patterns);
135
135
  }
136
136
 
137
- /// Returns the set of paths excluded from file discovery.
137
+ /// Returns the set of exclusion patterns.
138
138
  #[must_use]
139
- pub fn excluded_paths(&self) -> HashSet<PathBuf> {
140
- self.config.excluded_paths()
139
+ pub fn excluded_patterns(&self) -> HashSet<Box<str>> {
140
+ self.config.excluded_patterns()
141
141
  }
142
142
 
143
143
  /// Returns the root directory of the workspace being indexed.
@@ -116,9 +116,7 @@ impl OperationApplier {
116
116
  _ => {}
117
117
  }
118
118
  }
119
- }
120
119
 
121
- impl OperationApplier {
122
120
  fn apply_operation(&mut self, op: Operation) {
123
121
  match op {
124
122
  Operation::EnterClass(op) => self.apply_enter_class(op),