weavatrix-rust 2.7.1

Protocol-independent Rust repository intelligence: typed evidence graphs for impact, architecture, APIs, Git, search, and memory
Documentation
use super::PathPattern;

fn matches(pattern: &str, path: &str) -> bool {
    PathPattern::compile(pattern)
        .unwrap()
        .matches(path)
        .is_some()
}

fn captures(pattern: &str, path: &str) -> Vec<Option<String>> {
    PathPattern::compile(pattern)
        .unwrap()
        .matches(path)
        .unwrap()
}

#[test]
fn anchored_literals_match_exactly() {
    assert!(matches(r"^src/data/store\.js$", "src/data/store.js"));
    assert!(!matches(r"^src/data/store\.js$", "src/data/store.jsx"));
    assert!(!matches(r"^src/data/store\.js$", "lib/src/data/store.js"));
    assert!(matches("^src/presentation/", "src/presentation/entry.js"));
    assert!(!matches("^src/presentation/", "src/presentationX/entry.js"));
}

#[test]
fn unanchored_patterns_match_anywhere() {
    assert!(matches(
        r"-controller\.js$",
        "src/controllers/order-controller.js"
    ));
    assert!(!matches(
        r"-controller\.js$",
        "src/controllers/order-controller.jsx"
    ));
    assert!(matches("data", "src/data/store.js"));
}

#[test]
fn character_classes_match_sets_ranges_and_negation() {
    assert!(matches(r"^src/a[.]js$", "src/a.js"));
    assert!(!matches(r"^src/a[.]js$", "src/aXjs"));
    assert!(matches("^[a-z]+/", "src/store.js"));
    assert!(!matches("^[a-z]+$", "src/store.js"));
    assert!(matches("^[^/]+$", "store.js"));
    assert!(!matches("^[^/]+$", "src/store.js"));
}

#[test]
fn groups_alternate_and_capture() {
    assert!(matches("^src/(auth|data)/", "src/auth/token.js"));
    assert!(matches("^src/(auth|data)/", "src/data/store.js"));
    assert!(!matches("^src/(auth|data)/", "src/api/router.js"));
    assert_eq!(
        captures("^src/([^/]+)/", "src/presentation/entry.js"),
        vec![Some("presentation".to_owned())]
    );
    assert_eq!(
        captures("^(?:src|lib)/([^/]+)/", "lib/data/store.js"),
        vec![Some("data".to_owned())]
    );
}

#[test]
fn greedy_repetition_backtracks_for_the_suffix() {
    assert_eq!(
        captures("^(.*)/file$", "a/b/file"),
        vec![Some("a/b".to_owned())]
    );
    assert!(matches("^a.*b$", "axxb"));
    assert!(matches("^a.*b$", "ab"));
}

#[test]
fn plus_requires_at_least_one_occurrence() {
    assert!(!matches("^ax+$", "a"));
    assert!(matches("^ax+$", "ax"));
    assert!(matches("^ax+$", "axxx"));
    assert!(matches("^ax*$", "a"));
    assert!(!matches("^ax?$", "axx"));
}

#[test]
fn an_unmatched_optional_group_captures_nothing() {
    assert_eq!(captures("^a(b)?c?$", "ac"), vec![None]);
    assert_eq!(captures("^a(b)?$", "ab"), vec![Some("b".to_owned())]);
}

#[test]
fn unsupported_syntax_is_rejected_not_ignored() {
    for pattern in [
        r"^src/\d+$",
        r"^(a)\1$",
        "^a{2,3}$",
        "(?=lookahead)",
        "(?<name>x)",
        "a**",
        "a*+",
        "[]x",
        "[z-a]",
        "unclosed(",
        "unclosed[",
        "closed)extra",
        "mid$dollar",
        "not^start",
        "trailing\\",
        "*leading",
    ] {
        assert!(
            PathPattern::compile(pattern).is_err(),
            "`{pattern}` must be rejected"
        );
    }
}

#[test]
fn group_count_reflects_capturing_groups_only() {
    assert_eq!(
        PathPattern::compile("^(a)(?:b)(c)$").unwrap().group_count(),
        2
    );
    assert_eq!(PathPattern::compile("^plain$").unwrap().group_count(), 0);
}