Glob.zig - Fast and reliable glob pattern matching in pure zig

Built this for a side project im working on.

glob v0.1.0 it would be great if we can have std.utils with things like glob and such

I am taking contributions to enhance this with more features.

:books: API

match(pattern: []const u8, text: []const u8) bool

Matches text against a glob pattern.

Supported wildcards:

  • * β€” matches any number of characters

  • ? β€” matches any single character

  • [abc] β€” matches one character from the set

  • [a-z] β€” matches one character from the range

  • ! β€” negates the pattern (must be first character)

validate(pattern: []const u8) !void

Validates a pattern for syntax errors.

matchAny(patterns: []const []const u8, text: []const u8) bool

Returns true if text matches any of the patterns.

matchAll(patterns: []const []const u8, text: []const u8) bool

Returns true if text matches all of the patterns.

7 Likes

I wrote something similar but much more limited (only supporting *) for a project I was working on a while ago, I don’t know if its particularly efficient but I find it interesting to see the different approaches.

pub fn wildcardMatch(pattern: []const u8, value: []const u8) bool {
    if (pattern.len == 0 or pattern.ptr == value.ptr) return true;

    var has_carded = false;
    var partial_match = false;
    var currently_carding = false;
    var i: usize = 0;
    outer: for (pattern) |e| {
        if (e == '*') {
            partial_match = false;
            currently_carding = true;
            has_carded = true;
            continue;
        }
        for (value[i..]) |v| {
            i += 1;
            if (e == v) {
                partial_match = currently_carding;
                currently_carding = false;
                continue :outer;
            }
            if (currently_carding) continue;
            if (partial_match) {
                partial_match = false;
                currently_carding = true;
                has_carded = true;
                continue;
            }
            return false;
        }
        return false;
    }

    return has_carded or pattern.len == value.len;
}

test "wildcardMatch" {
    try std.testing.expect(!wildcardMatch("te", "test"));
    try std.testing.expect(!wildcardMatch("te", "t"));
    try std.testing.expect(wildcardMatch("", "test"));
    try std.testing.expect(wildcardMatch("te*", "test"));
    try std.testing.expect(wildcardMatch("*st", "test"));
    try std.testing.expect(wildcardMatch("t*t", "test"));

    try std.testing.expect(!wildcardMatch("te*", "west"));
    try std.testing.expect(!wildcardMatch("*st", "tesw"));
    try std.testing.expect(!wildcardMatch("t*t", "west"));
    try std.testing.expect(!wildcardMatch("*st", "tewt"));
    try std.testing.expect(!wildcardMatch("t*t", "tesw"));

    try std.testing.expect(wildcardMatch("*test*", "ingtestingtesting"));
    try std.testing.expect(wildcardMatch("*test*", "test"));
    try std.testing.expect(!wildcardMatch("*test*", "inginging"));
    try std.testing.expect(wildcardMatch("da*da*da*", "daaadabadmanda"));

    try std.testing.expect(!wildcardMatch("test.ing", "tester.ing"));
}