1use anyhow::{anyhow, Context};
38use serde::{Deserialize, Deserializer, Serialize, Serializer};
39use std::path::Path;
40
41#[derive(Debug, Clone)]
43pub struct FilterPattern {
44 pub original: String,
46 matcher: globset::GlobMatcher,
48 pub dir_only: bool,
50 pub anchored: bool,
52}
53
54impl FilterPattern {
55 pub fn parse(pattern: &str) -> Result<Self, anyhow::Error> {
57 if pattern.is_empty() {
58 return Err(anyhow!("empty pattern is not allowed"));
59 }
60 let original = pattern.to_string();
61 let dir_only = pattern.ends_with('/');
62 let anchored = pattern.starts_with('/');
63 let pattern_str = pattern.trim_start_matches('/').trim_end_matches('/');
65 if pattern_str.is_empty() {
66 return Err(anyhow!(
67 "pattern '{}' results in empty glob after stripping / markers",
68 pattern
69 ));
70 }
71 let glob = globset::GlobBuilder::new(pattern_str)
73 .literal_separator(true) .build()
75 .with_context(|| format!("invalid glob pattern: {}", pattern))?;
76 let matcher = glob.compile_matcher();
77 Ok(Self {
78 original,
79 matcher,
80 dir_only,
81 anchored,
82 })
83 }
84 fn is_path_pattern(&self) -> bool {
87 let core = self.original.trim_start_matches('/').trim_end_matches('/');
89 core.contains('/')
90 }
91 pub fn matches(&self, relative_path: &Path, is_dir: bool) -> bool {
93 if self.dir_only && !is_dir {
95 return false;
96 }
97 if self.anchored {
98 self.matcher.is_match(relative_path)
100 } else {
101 if self.matcher.is_match(relative_path) {
104 return true;
105 }
106 if !self.is_path_pattern() {
109 if let Some(file_name) = relative_path.file_name() {
110 if self.matcher.is_match(Path::new(file_name)) {
111 return true;
112 }
113 }
114 }
115 false
116 }
117 }
118}
119
120#[derive(Debug, Clone)]
122pub enum FilterResult {
123 Included,
125 ExcludedByDefault,
127 ExcludedByPattern(String),
129}
130
131#[derive(Debug, Clone, Default)]
133pub struct FilterSettings {
134 pub includes: Vec<FilterPattern>,
136 pub excludes: Vec<FilterPattern>,
138}
139
140impl FilterSettings {
141 pub fn new() -> Self {
143 Self::default()
144 }
145 pub fn add_include(&mut self, pattern: &str) -> Result<(), anyhow::Error> {
147 self.includes.push(FilterPattern::parse(pattern)?);
148 Ok(())
149 }
150 pub fn add_exclude(&mut self, pattern: &str) -> Result<(), anyhow::Error> {
152 self.excludes.push(FilterPattern::parse(pattern)?);
153 Ok(())
154 }
155 pub fn is_empty(&self) -> bool {
157 self.includes.is_empty() && self.excludes.is_empty()
158 }
159 pub fn has_includes(&self) -> bool {
161 !self.includes.is_empty()
162 }
163 pub fn should_include_root_item(&self, name: &Path, is_dir: bool) -> FilterResult {
175 for pattern in &self.excludes {
179 if !pattern.anchored
180 && !Self::is_path_pattern(&pattern.original)
181 && pattern.matches(name, is_dir)
182 {
183 return FilterResult::ExcludedByPattern(pattern.original.clone());
184 }
185 }
186 if !self.includes.is_empty() {
188 if !is_dir {
190 for pattern in &self.includes {
191 if !pattern.anchored
192 && !Self::is_path_pattern(&pattern.original)
193 && pattern.matches(name, false)
194 {
195 return FilterResult::Included;
196 }
197 }
198 return FilterResult::ExcludedByDefault;
200 }
201 return FilterResult::Included;
205 }
206 FilterResult::Included
208 }
209 fn is_path_pattern(original: &str) -> bool {
211 let trimmed = original.trim_start_matches('/').trim_end_matches('/');
212 trimmed.contains('/')
213 }
214 pub fn should_include(&self, relative_path: &Path, is_dir: bool) -> FilterResult {
226 for pattern in &self.excludes {
228 if pattern.matches(relative_path, is_dir) {
229 return FilterResult::ExcludedByPattern(pattern.original.clone());
230 }
231 }
232 if !self.includes.is_empty() {
234 for pattern in &self.includes {
236 if pattern.matches(relative_path, is_dir) {
237 return FilterResult::Included;
238 }
239 }
240 if is_dir {
242 for pattern in &self.includes {
243 if self.could_contain_matches(relative_path, pattern) {
244 return FilterResult::Included;
245 }
246 }
247 }
248 return FilterResult::ExcludedByDefault;
249 }
250 FilterResult::Included
252 }
253 pub fn directly_matches_include(&self, relative_path: &std::path::Path, is_dir: bool) -> bool {
263 if self.includes.is_empty() {
264 return true; }
266 for pattern in &self.includes {
267 if pattern.matches(relative_path, is_dir) {
268 return true;
269 }
270 }
271 false
272 }
273 pub fn could_contain_matches(&self, dir_path: &Path, pattern: &FilterPattern) -> bool {
275 if !pattern.anchored && !pattern.is_path_pattern() {
277 return true;
278 }
279 let pattern_path = pattern
282 .original
283 .trim_start_matches('/')
284 .trim_end_matches('/');
285 let prefix = Self::extract_literal_prefix(pattern_path);
286 let dir_str = dir_path.to_string_lossy();
287 if prefix.is_empty() {
290 return true;
291 }
292 if dir_str.is_empty() {
294 return true;
295 }
296 if prefix.starts_with(&*dir_str) {
302 let after_dir = &prefix[dir_str.len()..];
303 if after_dir.is_empty() || after_dir.starts_with('/') {
305 return true;
306 }
307 }
308 if let Some(after_prefix) = dir_str.strip_prefix(prefix) {
310 if after_prefix.is_empty() || after_prefix.starts_with('/') {
311 return true;
312 }
313 }
314 false
315 }
316 fn extract_literal_prefix(pattern: &str) -> &str {
326 let wildcard_pos = pattern.find(['*', '?', '[']).unwrap_or(pattern.len());
328 if wildcard_pos == pattern.len() {
330 return pattern;
331 }
332 if wildcard_pos == 0 {
334 return "";
335 }
336 let prefix = &pattern[..wildcard_pos];
338 match prefix.rfind('/') {
339 Some(pos) => &pattern[..pos],
340 None => {
341 ""
343 }
344 }
345 }
346 pub fn from_file(path: &Path) -> Result<Self, anyhow::Error> {
357 let content = std::fs::read_to_string(path)
358 .with_context(|| format!("failed to read filter file: {:?}", path))?;
359 Self::parse_content(&content)
360 }
361 pub fn parse_content(content: &str) -> Result<Self, anyhow::Error> {
363 let mut settings = Self::new();
364 for (line_num, line) in content.lines().enumerate() {
365 let line = line.trim();
366 if line.is_empty() || line.starts_with('#') {
368 continue;
369 }
370 let line_num = line_num + 1; if let Some(pattern) = line.strip_prefix("--include ") {
372 let pattern = pattern.trim();
373 settings
374 .add_include(pattern)
375 .with_context(|| format!("line {}: invalid include pattern", line_num))?;
376 } else if let Some(pattern) = line.strip_prefix("--exclude ") {
377 let pattern = pattern.trim();
378 settings
379 .add_exclude(pattern)
380 .with_context(|| format!("line {}: invalid exclude pattern", line_num))?;
381 } else {
382 return Err(anyhow!(
383 "line {}: invalid syntax '{}', expected '--include PATTERN' or '--exclude PATTERN'",
384 line_num, line
385 ));
386 }
387 }
388 Ok(settings)
389 }
390}
391
392#[derive(Serialize, Deserialize)]
395struct FilterSettingsDto {
396 includes: Vec<String>,
397 excludes: Vec<String>,
398}
399
400impl Serialize for FilterSettings {
401 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
402 let dto = FilterSettingsDto {
403 includes: self.includes.iter().map(|p| p.original.clone()).collect(),
404 excludes: self.excludes.iter().map(|p| p.original.clone()).collect(),
405 };
406 dto.serialize(serializer)
407 }
408}
409
410impl<'de> Deserialize<'de> for FilterSettings {
411 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
412 let dto = FilterSettingsDto::deserialize(deserializer)?;
413 let mut settings = FilterSettings::new();
414 for pattern in dto.includes {
415 settings
416 .add_include(&pattern)
417 .map_err(serde::de::Error::custom)?;
418 }
419 for pattern in dto.excludes {
420 settings
421 .add_exclude(&pattern)
422 .map_err(serde::de::Error::custom)?;
423 }
424 Ok(settings)
425 }
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431 #[test]
432 fn test_pattern_basic_glob() {
433 let pattern = FilterPattern::parse("*.rs").unwrap();
434 assert!(pattern.matches(Path::new("foo.rs"), false));
435 assert!(pattern.matches(Path::new("main.rs"), false));
436 assert!(!pattern.matches(Path::new("foo.txt"), false));
437 assert!(pattern.matches(Path::new("src/foo.rs"), false));
439 }
440 #[test]
441 fn test_pattern_double_star() {
442 let pattern = FilterPattern::parse("**/*.rs").unwrap();
443 assert!(pattern.matches(Path::new("src/foo.rs"), false));
444 assert!(pattern.matches(Path::new("a/b/c/d.rs"), false));
445 assert!(pattern.matches(Path::new("foo.rs"), false));
447 }
448 #[test]
449 fn test_pattern_question_mark() {
450 let pattern = FilterPattern::parse("file?.txt").unwrap();
451 assert!(pattern.matches(Path::new("file1.txt"), false));
452 assert!(pattern.matches(Path::new("fileA.txt"), false));
453 assert!(!pattern.matches(Path::new("file12.txt"), false));
454 assert!(!pattern.matches(Path::new("file.txt"), false));
455 }
456 #[test]
457 fn test_pattern_character_class() {
458 let pattern = FilterPattern::parse("[abc].txt").unwrap();
459 assert!(pattern.matches(Path::new("a.txt"), false));
460 assert!(pattern.matches(Path::new("b.txt"), false));
461 assert!(pattern.matches(Path::new("c.txt"), false));
462 assert!(!pattern.matches(Path::new("d.txt"), false));
463 }
464 #[test]
465 fn test_pattern_anchored() {
466 let pattern = FilterPattern::parse("/src").unwrap();
467 assert!(pattern.anchored);
468 assert!(pattern.matches(Path::new("src"), true));
470 assert!(!pattern.matches(Path::new("foo/src"), true));
471 }
472 #[test]
473 fn test_pattern_dir_only() {
474 let pattern = FilterPattern::parse("build/").unwrap();
475 assert!(pattern.dir_only);
476 assert!(pattern.matches(Path::new("build"), true));
478 assert!(!pattern.matches(Path::new("build"), false)); }
480 #[test]
481 fn test_include_only_mode() {
482 let mut settings = FilterSettings::new();
483 settings.add_include("*.rs").unwrap();
484 settings.add_include("Cargo.toml").unwrap();
485 assert!(matches!(
486 settings.should_include(Path::new("main.rs"), false),
487 FilterResult::Included
488 ));
489 assert!(matches!(
490 settings.should_include(Path::new("Cargo.toml"), false),
491 FilterResult::Included
492 ));
493 assert!(matches!(
494 settings.should_include(Path::new("README.md"), false),
495 FilterResult::ExcludedByDefault
496 ));
497 }
498 #[test]
499 fn test_exclude_only_mode() {
500 let mut settings = FilterSettings::new();
501 settings.add_exclude("*.log").unwrap();
502 settings.add_exclude("target/").unwrap();
503 assert!(matches!(
504 settings.should_include(Path::new("main.rs"), false),
505 FilterResult::Included
506 ));
507 match settings.should_include(Path::new("debug.log"), false) {
508 FilterResult::ExcludedByPattern(p) => assert_eq!(p, "*.log"),
509 other => panic!("expected ExcludedByPattern, got {:?}", other),
510 }
511 match settings.should_include(Path::new("target"), true) {
512 FilterResult::ExcludedByPattern(p) => assert_eq!(p, "target/"),
513 other => panic!("expected ExcludedByPattern, got {:?}", other),
514 }
515 }
516 #[test]
517 fn test_include_then_exclude() {
518 let mut settings = FilterSettings::new();
519 settings.add_include("*.rs").unwrap();
520 settings.add_exclude("test_*.rs").unwrap();
521 assert!(matches!(
523 settings.should_include(Path::new("main.rs"), false),
524 FilterResult::Included
525 ));
526 match settings.should_include(Path::new("test_foo.rs"), false) {
528 FilterResult::ExcludedByPattern(p) => assert_eq!(p, "test_*.rs"),
529 other => panic!("expected ExcludedByPattern, got {:?}", other),
530 }
531 assert!(matches!(
533 settings.should_include(Path::new("README.md"), false),
534 FilterResult::ExcludedByDefault
535 ));
536 }
537 #[test]
538 fn test_filter_file_basic() {
539 let content = r#"
540# this is a comment
541--include *.rs
542--include Cargo.toml
543
544--exclude target/
545--exclude *.log
546"#;
547 let settings = FilterSettings::parse_content(content).unwrap();
548 assert_eq!(settings.includes.len(), 2);
549 assert_eq!(settings.excludes.len(), 2);
550 }
551 #[test]
552 fn test_filter_file_comments() {
553 let content = "# only comments\n# and empty lines\n\n";
554 let settings = FilterSettings::parse_content(content).unwrap();
555 assert!(settings.is_empty());
556 }
557 #[test]
558 fn test_filter_file_syntax_error() {
559 let content = "invalid line without prefix";
560 let result = FilterSettings::parse_content(content);
561 assert!(result.is_err());
562 let err = result.unwrap_err().to_string();
563 assert!(err.contains("line 1"));
564 assert!(err.contains("invalid syntax"));
565 }
566 #[test]
567 fn test_empty_pattern_error() {
568 let result = FilterPattern::parse("");
569 assert!(result.is_err());
570 }
571 #[test]
572 fn test_is_empty() {
573 let empty = FilterSettings::new();
574 assert!(empty.is_empty());
575 let mut with_include = FilterSettings::new();
576 with_include.add_include("*.rs").unwrap();
577 assert!(!with_include.is_empty());
578 let mut with_exclude = FilterSettings::new();
579 with_exclude.add_exclude("*.log").unwrap();
580 assert!(!with_exclude.is_empty());
581 }
582 #[test]
583 fn test_has_includes() {
584 let empty = FilterSettings::new();
585 assert!(!empty.has_includes());
586 let mut with_include = FilterSettings::new();
587 with_include.add_include("*.rs").unwrap();
588 assert!(with_include.has_includes());
589 let mut with_exclude = FilterSettings::new();
590 with_exclude.add_exclude("*.log").unwrap();
591 assert!(!with_exclude.has_includes());
592 let mut with_both = FilterSettings::new();
593 with_both.add_include("*.rs").unwrap();
594 with_both.add_exclude("*.log").unwrap();
595 assert!(with_both.has_includes());
596 }
597 #[test]
598 fn test_filename_match_for_simple_patterns() {
599 let pattern = FilterPattern::parse("*.rs").unwrap();
601 assert!(pattern.matches(Path::new("foo.rs"), false));
602 assert!(pattern.matches(Path::new("src/foo.rs"), false)); assert!(pattern.matches(Path::new("a/b/c/foo.rs"), false));
605 }
606 #[test]
607 fn test_path_pattern_requires_full_match() {
608 let pattern = FilterPattern::parse("src/*.rs").unwrap();
610 assert!(pattern.matches(Path::new("src/foo.rs"), false));
611 assert!(!pattern.matches(Path::new("foo.rs"), false));
612 assert!(!pattern.matches(Path::new("other/src/foo.rs"), false));
613 }
614 #[test]
615 fn test_double_star_matches_nested_paths() {
616 let pattern = FilterPattern::parse("**/*.rs").unwrap();
618 assert!(pattern.matches(Path::new("foo.rs"), false));
619 assert!(pattern.matches(Path::new("src/foo.rs"), false));
620 assert!(pattern.matches(Path::new("src/lib/foo.rs"), false));
621 assert!(pattern.matches(Path::new("a/b/c/d/e.rs"), false));
622 }
623 #[test]
624 fn test_anchored_pattern_matches_only_at_root() {
625 let pattern = FilterPattern::parse("/src").unwrap();
627 assert!(pattern.matches(Path::new("src"), true));
628 assert!(!pattern.matches(Path::new("foo/src"), true));
629 assert!(!pattern.matches(Path::new("a/b/src"), true));
630 }
631 #[test]
632 fn test_nested_directory_pattern() {
633 let pattern = FilterPattern::parse("src/lib/").unwrap();
635 assert!(pattern.matches(Path::new("src/lib"), true));
636 assert!(!pattern.matches(Path::new("lib"), true));
637 assert!(!pattern.matches(Path::new("other/src/lib"), true));
638 }
639 #[test]
640 fn test_dir_only_simple_pattern_matches_at_any_level() {
641 let pattern = FilterPattern::parse("target/").unwrap();
644 assert!(pattern.dir_only);
645 assert!(!pattern.anchored);
646 assert!(pattern.matches(Path::new("target"), true));
648 assert!(pattern.matches(Path::new("foo/target"), true));
650 assert!(pattern.matches(Path::new("a/b/target"), true));
651 assert!(!pattern.matches(Path::new("target"), false));
653 assert!(!pattern.matches(Path::new("foo/target"), false));
654 }
655 #[test]
656 fn test_dir_only_pattern_could_contain_matches() {
657 let mut settings = FilterSettings::new();
659 settings.add_include("target/").unwrap();
660 let pattern = &settings.includes[0];
661 assert!(settings.could_contain_matches(Path::new("foo"), pattern));
663 assert!(settings.could_contain_matches(Path::new("a/b"), pattern));
664 assert!(settings.could_contain_matches(Path::new("src"), pattern));
665 }
666 #[test]
667 fn test_precedence_exclude_overrides_include() {
668 let mut settings = FilterSettings::new();
670 settings.add_include("*.rs").unwrap();
671 settings.add_exclude("test_*.rs").unwrap();
672 match settings.should_include(Path::new("test_main.rs"), false) {
674 FilterResult::ExcludedByPattern(p) => assert_eq!(p, "test_*.rs"),
675 other => panic!("expected ExcludedByPattern, got {:?}", other),
676 }
677 assert!(matches!(
679 settings.should_include(Path::new("main.rs"), false),
680 FilterResult::Included
681 ));
682 }
683 #[test]
684 fn test_should_include_root_item_non_anchored_exclude() {
685 let mut settings = FilterSettings::new();
687 settings.add_exclude("*.log").unwrap();
688 match settings.should_include_root_item(Path::new("debug.log"), false) {
690 FilterResult::ExcludedByPattern(p) => assert_eq!(p, "*.log"),
691 other => panic!("expected ExcludedByPattern, got {:?}", other),
692 }
693 assert!(matches!(
695 settings.should_include_root_item(Path::new("main.rs"), false),
696 FilterResult::Included
697 ));
698 }
699 #[test]
700 fn test_should_include_root_item_anchored_exclude_skipped() {
701 let mut settings = FilterSettings::new();
703 settings.add_exclude("/target/").unwrap();
704 assert!(matches!(
706 settings.should_include_root_item(Path::new("target"), true),
707 FilterResult::Included
708 ));
709 }
710 #[test]
711 fn test_should_include_root_item_non_anchored_include() {
712 let mut settings = FilterSettings::new();
714 settings.add_include("*.rs").unwrap();
715 assert!(matches!(
717 settings.should_include_root_item(Path::new("main.rs"), false),
718 FilterResult::Included
719 ));
720 assert!(matches!(
722 settings.should_include_root_item(Path::new("readme.md"), false),
723 FilterResult::ExcludedByDefault
724 ));
725 }
726 #[test]
727 fn test_should_include_root_item_anchored_include_skipped() {
728 let mut settings = FilterSettings::new();
731 settings.add_include("/bar").unwrap();
732 assert!(matches!(
734 settings.should_include_root_item(Path::new("foo"), true),
735 FilterResult::Included
736 ));
737 assert!(matches!(
739 settings.should_include_root_item(Path::new("baz"), false),
740 FilterResult::ExcludedByDefault
741 ));
742 }
743 #[test]
744 fn test_should_include_root_item_mixed_patterns() {
745 let mut settings = FilterSettings::new();
747 settings.add_include("*.rs").unwrap();
748 settings.add_include("/bar").unwrap();
749 settings.add_exclude("test_*.rs").unwrap();
750 assert!(matches!(
752 settings.should_include_root_item(Path::new("main.rs"), false),
753 FilterResult::Included
754 ));
755 match settings.should_include_root_item(Path::new("test_foo.rs"), false) {
757 FilterResult::ExcludedByPattern(p) => assert_eq!(p, "test_*.rs"),
758 other => panic!("expected ExcludedByPattern, got {:?}", other),
759 }
760 assert!(matches!(
762 settings.should_include_root_item(Path::new("foo"), true),
763 FilterResult::Included
764 ));
765 }
766 #[test]
767 fn test_could_contain_matches_anchored_double_star() {
768 let mut settings = FilterSettings::new();
770 settings.add_include("/src/**").unwrap();
771 let pattern = &settings.includes[0];
772 assert!(settings.could_contain_matches(Path::new(""), pattern));
774 assert!(settings.could_contain_matches(Path::new("src"), pattern));
776 assert!(settings.could_contain_matches(Path::new("src/foo"), pattern));
778 assert!(settings.could_contain_matches(Path::new("src/foo/bar"), pattern));
779 assert!(!settings.could_contain_matches(Path::new("build"), pattern));
781 assert!(!settings.could_contain_matches(Path::new("target"), pattern));
782 assert!(!settings.could_contain_matches(Path::new("build/src"), pattern));
783 }
784 #[test]
785 fn test_could_contain_matches_non_anchored_double_star() {
786 let mut settings = FilterSettings::new();
788 settings.add_include("**/*.rs").unwrap();
789 let pattern = &settings.includes[0];
790 assert!(settings.could_contain_matches(Path::new("src"), pattern));
792 assert!(settings.could_contain_matches(Path::new("build"), pattern));
793 assert!(settings.could_contain_matches(Path::new("any/path"), pattern));
794 }
795 #[test]
796 fn test_could_contain_matches_nested_prefix() {
797 let mut settings = FilterSettings::new();
799 settings.add_include("/src/foo/**").unwrap();
800 let pattern = &settings.includes[0];
801 assert!(settings.could_contain_matches(Path::new(""), pattern));
803 assert!(settings.could_contain_matches(Path::new("src"), pattern));
804 assert!(settings.could_contain_matches(Path::new("src/foo"), pattern));
806 assert!(settings.could_contain_matches(Path::new("src/foo/bar"), pattern));
808 assert!(!settings.could_contain_matches(Path::new("build"), pattern));
810 assert!(!settings.could_contain_matches(Path::new("src/bar"), pattern));
811 }
812 #[test]
813 fn test_extract_literal_prefix() {
814 assert_eq!(FilterSettings::extract_literal_prefix("src/**"), "src");
816 assert_eq!(
817 FilterSettings::extract_literal_prefix("src/foo/**"),
818 "src/foo"
819 );
820 assert_eq!(FilterSettings::extract_literal_prefix("**/*.rs"), "");
821 assert_eq!(FilterSettings::extract_literal_prefix("*.rs"), "");
822 assert_eq!(FilterSettings::extract_literal_prefix("src/*.rs"), "src");
823 assert_eq!(
825 FilterSettings::extract_literal_prefix("src/foo/bar"),
826 "src/foo/bar"
827 );
828 assert_eq!(FilterSettings::extract_literal_prefix("bar"), "bar");
829 assert_eq!(FilterSettings::extract_literal_prefix("src[0-9]/*.rs"), "");
830 }
831 #[test]
832 fn test_directly_matches_include_simple_pattern() {
833 let mut settings = FilterSettings::new();
835 settings.add_include("*.txt").unwrap();
836 assert!(settings.directly_matches_include(Path::new("foo.txt"), false));
838 assert!(settings.directly_matches_include(Path::new("bar/foo.txt"), false));
839 assert!(!settings.directly_matches_include(Path::new("foo.rs"), false));
841 assert!(!settings.directly_matches_include(Path::new("txt"), true));
843 }
844 #[test]
845 fn test_directly_matches_include_anchored_pattern() {
846 let mut settings = FilterSettings::new();
848 settings.add_include("/foo").unwrap();
849 assert!(settings.directly_matches_include(Path::new("foo"), true));
851 assert!(settings.directly_matches_include(Path::new("foo"), false));
852 assert!(!settings.directly_matches_include(Path::new("bar/foo"), true));
854 }
855 #[test]
856 fn test_directly_matches_include_empty_includes() {
857 let settings = FilterSettings::new();
859 assert!(settings.directly_matches_include(Path::new("anything"), true));
860 assert!(settings.directly_matches_include(Path::new("foo/bar"), false));
861 }
862 #[test]
863 fn test_directly_matches_include_path_pattern() {
864 let mut settings = FilterSettings::new();
866 settings.add_include("src/*.rs").unwrap();
867 assert!(settings.directly_matches_include(Path::new("src/foo.rs"), false));
869 assert!(!settings.directly_matches_include(Path::new("foo.rs"), false));
871 assert!(!settings.directly_matches_include(Path::new("other/foo.rs"), false));
872 }
873 #[test]
874 fn test_directly_matches_include_dir_only_pattern() {
875 let mut settings = FilterSettings::new();
877 settings.add_include("target/").unwrap();
878 assert!(settings.directly_matches_include(Path::new("target"), true));
880 assert!(settings.directly_matches_include(Path::new("foo/target"), true));
881 assert!(!settings.directly_matches_include(Path::new("target"), false));
883 }
884}