Skip to content

Commit

Permalink
Merge pull request #69 from evaneliasyoung/feat/regex-search
Browse files Browse the repository at this point in the history
Support regex search queries
  • Loading branch information
shilangyu authored Jan 15, 2025
2 parents c19911c + ec69c2b commit c1a78c7
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 7 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
*.zig text eol=lf
*.zig.zon text eol=lf
6 changes: 6 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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);

Expand Down
36 changes: 36 additions & 0 deletions build.zig.zon
Original file line number Diff line number Diff line change
@@ -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 <url>`, 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",
},
}
6 changes: 5 additions & 1 deletion src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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", .{});
Expand Down Expand Up @@ -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;
};
Expand Down
14 changes: 8 additions & 6 deletions src/search.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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});
Expand Down Expand Up @@ -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";
Expand All @@ -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
Expand Down

0 comments on commit c1a78c7

Please sign in to comment.