zlob 1.3.3

SIMD optimized glob pattern matching library faster than glob crate
Documentation
//! SIMD-accelerated glob pattern matching library
//!
//! This library provides high-performance file pattern matching (globbing) using
//! SIMD optimizations for improved performance on pattern matching operations.
//!
//! This is the pure Zig API. For C-compatible API, see c_lib.zig which provides
//! POSIX glob() and globfree() functions and C header (include/zlob.h).

const std = @import("std");
const zlob = @import("zlob");

// Re-export the flags module as a namespace
// Consumers access constants via zlob.flags.ZLOB_MARK, zlob.flags.ZlobFlags, etc.
pub const flags = @import("zlob_flags");

// Re-export modules through zlob_core to avoid module conflicts
pub const fnmatch = zlob.fnmatch;
pub const pattern_context = zlob.pattern_context;
pub const suffix_match = zlob.suffix_match;
pub const PatternContext = zlob.PatternContext;

pub const ZlobResults = zlob.ZlobResults;
pub const ZlobError = zlob.ZlobError;
pub const zlob_t = zlob.zlob_t;
pub const analyzePattern = zlob.analyzePattern;
pub const simdFindChar = zlob.simdFindChar;
pub const hasWildcardsWithFlags = zlob.hasWildcards;
pub const hasWildcardsBasic = zlob.hasWildcardsBasic;
pub const ZlobFlags = zlob.ZlobFlags;

/// Check if a pattern contains any glob special characters.
/// Detects all glob syntax: basic wildcards (*, ?, [), braces ({), and extglob patterns.
/// For fine-grained control, use `hasWildcardsWithFlags(pattern, flags)`.
pub fn hasWildcards(s: []const u8) bool {
    return zlob.hasWildcards(s, .{ .brace = true, .extglob = true });
}

/// Perform file system walking and collect matching results to ZlobResults
///
/// Example with ZlobFlags (recommended):
/// ```zig
/// const flags = zlob.ZlobFlags{ .brace = true, .gitignore = true };
/// if (try zlob.match(allocator, "**/*.zig", flags)) |*result| {
///     defer result.deinit();
///     var it = result.iterator();
///     while (it.next()) |path| {
///         std.debug.print("{s}\n", .{path});
///     }
/// }
/// ```
///
/// You can also pass any integer type build from the ZLOB_* flags if you prefer
/// or even struct literals like .{ .mark = true } for convenience
pub fn match(allocator: std.mem.Allocator, io: std.Io, pattern: []const u8, flags_param: anytype) !?ZlobResults {
    const zflags = flagsToZlobFlags(flags_param);

    var pzlob: zlob_t = undefined;
    const opt_result = try zlob.globSlice(allocator, io, pattern, zflags.toInt(), null, &pzlob);

    if (opt_result) |_| {
        return ZlobResults{
            .source = .{ .zlob = pzlob },
            .allocator = allocator,
        };
    } else {
        if (zflags.nocheck) {
            var paths = try allocator.alloc([]const u8, 1);
            paths[0] = try allocator.dupe(u8, pattern);
            return ZlobResults{
                .source = .{ .paths = .{ .items = paths, .owns_strings = true } },
                .allocator = allocator,
            };
        }
        return null;
    }
}

/// Match glob pattern against array of paths with full ** recursive support
///
/// This function provides in-memory pattern matching against an array of path strings
/// WITHOUT any filesystem I/O. It properly handles recursive ** patterns that match
/// zero or more directory components.
///
/// Pattern examples:
/// - `**/*.c` - All .c files at any depth
/// - `/users/**/code/*.zig` - All .zig files in any 'code' directory under /users
/// - `src/**/test_*.zig` - All test files under src/
///
/// Example with ZlobFlags (recommended):
/// ```zig
/// const paths = [_][]const u8{
///     "/users/alice/code/main.c",
///     "/users/alice/code/src/utils.c",
///     "/users/bob/docs/readme.md",
/// };
///
/// var result = try zlob.matchPaths(allocator, "/users/**/code/*.c", &paths, .{});
/// defer result.deinit();
///
/// var it = result.iterator();
/// while (it.next()) |path| {
///     std.debug.print("Match: {s}\n", .{path});
/// }
/// ```
///
/// Supported flags:
/// - .nosort: Don't sort results
/// - .nocheck: Return pattern itself if no matches
/// - .period: Allow wildcards to match hidden files (starting with '.')
/// - .noescape: Treat backslashes as literal characters
///
/// Requirements:
/// - Input paths MUST be normalized (no consecutive slashes like //)
/// - Paths from filesystem operations are typically already normalized
pub fn matchPaths(allocator: std.mem.Allocator, pattern: []const u8, paths: []const []const u8, flags_param: anytype) !ZlobResults {
    const zflags = flagsToZlobFlags(flags_param);
    return zlob.path_matcher.matchPaths(allocator, pattern, paths, zflags);
}

/// Match glob pattern against an array of absolute paths, treating each path as relative
/// to the given base directory.
///
/// This is the "at" variant of `matchPaths` for use when input paths are absolute but the
/// pattern is relative to a known base directory. The `base_path` may or may not end with
/// a trailing `/` — the offset is computed automatically.
///
/// If the pattern starts with `./`, it is interpreted as relative to `base_path` and the
/// prefix is replaced accordingly (i.e. stripped, since matching already operates relative
/// to the base).
///
/// Matched results contain the **original full paths** as submitted by the caller.
///
/// Example:
/// ```zig
/// const paths = [_][]const u8{
///     "/home/user/project/src/main.c",
///     "/home/user/project/src/test/unit.c",
///     "/home/user/project/lib/utils.c",
///     "/home/user/project/docs/readme.md",
/// };
///
/// const result = try zlob.matchPathsAt(allocator, "/home/user/project", "**/*.c", &paths, .{});
/// defer result.deinit();
/// // Use result.get(i) or result.iterator() to access the 3 matching absolute paths
/// ```
///
/// Supported flags: same as `matchPaths`.
pub fn matchPathsAt(allocator: std.mem.Allocator, base_path: []const u8, pattern: []const u8, paths: []const []const u8, flags_param: anytype) !ZlobResults {
    const zflags = flagsToZlobFlags(flags_param);
    return zlob.path_matcher.matchPathsAt(allocator, base_path, pattern, paths, zflags);
}

/// Perform file system walking within a specified base directory and collect matching results.
///
/// This is similar to `match()` but operates relative to the given `base_path` instead of
/// the current working directory. The `base_path` must be an absolute path.
///
/// Example:
/// ```zig
/// // Find all .zig files under /home/user/project
/// if (try zlob.matchAt(allocator, "/home/user/project", "**/*.zig", .{ .brace = true })) |*result| {
///     defer result.deinit();
///     var it = result.iterator();
///     while (it.next()) |path| {
///         std.debug.print("{s}\n", .{path});
///     }
/// }
/// ```
///
/// Returns `error.Aborted` if `base_path` is not an absolute path (doesn't start with '/').
pub fn matchAt(allocator: std.mem.Allocator, io: std.Io, base_path: []const u8, pattern: []const u8, flags_param: anytype) !?ZlobResults {
    const zflags = flagsToZlobFlags(flags_param);
    const pattern_z = try allocator.dupeZ(u8, pattern);
    defer allocator.free(pattern_z);

    var pzlob: zlob_t = undefined;
    const opt_result = try zlob.globAt(allocator, io, base_path, pattern_z.ptr, zflags.toInt(), null, &pzlob);

    if (opt_result) |_| {
        return ZlobResults{
            .source = .{ .zlob = pzlob },
            .allocator = allocator,
        };
    } else {
        if (zflags.nocheck) {
            var paths = try allocator.alloc([]const u8, 1);
            paths[0] = try allocator.dupe(u8, pattern);
            return ZlobResults{
                .source = .{ .paths = .{ .items = paths, .owns_strings = true } },
                .allocator = allocator,
            };
        }
        return null;
    }
}

/// Convert any supported flags type to ZlobFlags.
/// Supports: ZlobFlags, u32, c_int, comptime_int, or struct literals like .{ .mark = true }
fn flagsToZlobFlags(flags_param: anytype) ZlobFlags {
    const T = @TypeOf(flags_param);
    if (T == ZlobFlags) {
        return flags_param;
    } else if (T == u32) {
        return ZlobFlags.fromU32(flags_param);
    } else if (T == c_int) {
        return ZlobFlags.fromInt(flags_param);
    } else if (T == comptime_int) {
        return ZlobFlags.fromU32(@intCast(flags_param));
    } else if (@typeInfo(T) == .@"struct") {
        // Handle anonymous struct literals like .{ .mark = true }
        const gf: ZlobFlags = flags_param;
        return gf;
    } else {
        @compileError("flags must be ZlobFlags, u32, c_int, or a struct literal");
    }
}