diff --git a/.gitattributes b/.gitattributes index ef01f61..8984289 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ *.zig text eol=lf +*.zig.zon text eol=lf diff --git a/build.zig b/build.zig index 41101a6..168f680 100644 --- a/build.zig +++ b/build.zig @@ -15,6 +15,10 @@ pub fn build(b: *std.Build) void { // set a preferred release mode, allowing the user to decide how to optimize. const optimize = b.standardOptimizeOption(.{}); + // Add mvzr dependency and create module. + const mvzr_dep = b.dependency("mvzr", .{ .target = target, .optimize = optimize }); + const mvzr_module = mvzr_dep.module("mvzr"); + const exe = b.addExecutable(.{ .name = "scoop-search", // In this case the main source file is merely a path, however, in more @@ -23,6 +27,7 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, }); + exe.root_module.addImport("mvzr", mvzr_module); // This declares intent for the executable to be installed into the // standard location when the user invokes the "install" step (the default @@ -59,6 +64,7 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, }); + unit_tests.root_module.addImport("mvzr", mvzr_module); const run_unit_tests = b.addRunArtifact(unit_tests); diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..71572fa --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,36 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = "scoop-search", + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "1.5.0", + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + .mvzr = .{ + .url = "https://github.com/mnemnion/mvzr/archive/refs/tags/v0.3.2.tar.gz", + .hash = "122084c73b4208fdfb02ee2c839e8e204d4b1d93421fecbf1463df96bb4e8a776491", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/src/main.zig b/src/main.zig index fb2f0c0..10c71ec 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5,6 +5,7 @@ const env = @import("env.zig"); const utils = @import("utils.zig"); const search = @import("search.zig"); const ThreadPool = @import("thread_pool.zig").ThreadPool; +const mvzr = @import("mvzr"); /// Stores results of a search in a single bucket. const SearchResult = struct { @@ -46,6 +47,9 @@ pub fn main() !void { const query = try std.ascii.allocLowerString(allocator, args.query orelse ""); defer allocator.free(query); + const regexQuery = mvzr.compile(query) orelse + return std.io.getStdErr().writer().print("Invalid regular expression: parsing \"{s}\".", .{query}); + const scoopHome = env.scoopHomeOwned(allocator, debug) catch |err| switch (err) { error.MissingHomeDir => { return std.io.getStdErr().writer().print("Could not establish scoop home directory. USERPROFILE environment variable is not defined.\n", .{}); @@ -79,7 +83,7 @@ pub fn main() !void { defer allocator.free(bucketBase); try debug.log("Found bucket: {s}\n", .{bucketBase}); - const result = search.searchBucket(allocator, query, bucketBase, debug) catch { + const result = search.searchBucket(allocator, regexQuery, bucketBase, debug) catch { try std.io.getStdErr().writer().print("Failed to search through the bucket: {s}.\n", .{f.name}); continue; }; diff --git a/src/search.zig b/src/search.zig index a926058..b7b1f84 100644 --- a/src/search.zig +++ b/src/search.zig @@ -2,6 +2,7 @@ const std = @import("std"); const utils = @import("utils.zig"); const Box = utils.Box; const DebugLogger = utils.DebugLogger; +const mvzr = @import("mvzr"); /// State associated with a worker thread. Stores thread local cache and matches. Has its own allocator. const ThreadPoolState = struct { @@ -119,7 +120,7 @@ fn getPackagesDir(allocator: std.mem.Allocator, bucketBase: []const u8) !std.fs. return packages; } -pub fn searchBucket(allocator: std.mem.Allocator, query: []const u8, bucketBase: []const u8, debug: DebugLogger) !SearchResult { +pub fn searchBucket(allocator: std.mem.Allocator, query: mvzr.Regex, bucketBase: []const u8, debug: DebugLogger) !SearchResult { var tp: ThreadPool = undefined; try tp.init(.{ .allocator = allocator }, ThreadPoolState.create); try debug.log("Worker count: {}\n", .{tp.threads.len}); @@ -153,25 +154,26 @@ pub fn searchBucket(allocator: std.mem.Allocator, query: []const u8, bucketBase: } /// If the given binary name matches the query, add it to the matches. -fn checkBin(allocator: std.mem.Allocator, bin: []const u8, query: []const u8, stem: []const u8, version: []const u8, matches: *std.ArrayList(SearchMatch)) !bool { +fn checkBin(allocator: std.mem.Allocator, bin: []const u8, query: mvzr.Regex, stem: []const u8, version: []const u8, matches: *std.ArrayList(SearchMatch)) !bool { const against = utils.basename(bin); const lowerBinStem = try std.ascii.allocLowerString(allocator, against.withoutExt); defer allocator.free(lowerBinStem); - if (std.mem.containsAtLeast(u8, lowerBinStem, 1, query)) { + if (query.isMatch(lowerBinStem)) { try matches.append(try SearchMatch.init(allocator, stem, version, against.withExt)); return true; } + return false; } -fn matchPackage(packagesDir: std.fs.Dir, query: []const u8, manifestName: []const u8, state: *ThreadPoolState) void { +fn matchPackage(packagesDir: std.fs.Dir, query: mvzr.Regex, manifestName: []const u8, state: *ThreadPoolState) void { // ignore failed match matchPackageAux(packagesDir, query, manifestName, state) catch return; } /// A worker function for checking if a given manifest matches the query. -fn matchPackageAux(packagesDir: std.fs.Dir, query: []const u8, manifestName: []const u8, state: *ThreadPoolState) !void { +fn matchPackageAux(packagesDir: std.fs.Dir, query: mvzr.Regex, manifestName: []const u8, state: *ThreadPoolState) !void { const allocator = state.allocator.ptr.allocator(); const extension = comptime ".json"; @@ -198,7 +200,7 @@ fn matchPackageAux(packagesDir: std.fs.Dir, query: []const u8, manifestName: []c defer allocator.free(lowerStem); // does the package name match? - if (query.len == 0 or std.mem.containsAtLeast(u8, lowerStem, 1, query)) { + if (query.isMatch(lowerStem)) { try state.matches.append(try SearchMatch.init(allocator, stem, version, null)); } else { // the name did not match, lets see if any binary files do