Skip to content

Instantly share code, notes, and snippets.

@raspberrypisig
Created April 18, 2025 07:31
Show Gist options
  • Save raspberrypisig/8fa5f940ffa9907c14b5781aa29f0b04 to your computer and use it in GitHub Desktop.
Save raspberrypisig/8fa5f940ffa9907c14b5781aa29f0b04 to your computer and use it in GitHub Desktop.
const std = @import("std");
// Import the zf library module we added in build.zig
const zf = @import("zf");
// Helper function to recursively find all file paths relative to the start_dir
// It appends duplicated paths to the provided ArrayList.
fn getAllFilePathsRecursive(
allocator: std.mem.Allocator,
base_dir: std.fs.Dir, // The directory we are currently iterating in
current_rel_path: []const u8, // Path relative to the *initial* start directory (e.g., ".")
paths_list: *std.ArrayList([]const u8), // List to append found file paths to
) !void {
// Use iterate for potentially better performance on some systems
var dir_iter = base_dir.iterate();
// Iterate through entries in the current directory
while (try dir_iter.next()) |entry| {
// Construct the path relative to the *initial* starting directory
// Avoid joining if we are in the root (".") to prevent paths like "./filename"
const entry_rel_path = if (std.mem.eql(u8, current_rel_path, "."))
entry.name
else // Otherwise, join the current relative path with the new entry name
try std.fs.path.join(allocator, &.{ current_rel_path, entry.name });
// If std.fs.path.join allocated memory, ensure it's freed after use
const free_entry_rel_path = !std.mem.eql(u8, current_rel_path, ".");
if (free_entry_rel_path) defer allocator.free(entry_rel_path);
switch (entry.kind) {
.File => {
// If it's a file, duplicate its relative path and add it to our list
try paths_list.append(try allocator.dupe(u8, entry_rel_path));
},
.Directory => {
// If it's a directory, try to open it and recurse
var sub_dir = base_dir.openDir(entry.name, .{ .iterate = true }) catch |err| {
// Handle errors like permission denied gracefully
std.debug.print("Warning: Could not open directory '{s}': {s}. Skipping.\n", .{ entry_rel_path, @errorName(err) });
// Skip this directory if not accessible
continue;
};
// Ensure the subdirectory handle is closed even if recursion fails
defer sub_dir.close();
// Recurse into the subdirectory, passing the *new relative path*
try getAllFilePathsRecursive(allocator, sub_dir, entry_rel_path, paths_list);
},
else => {
// Ignore other entry types like symlinks, block devices, etc. for this example
},
}
}
}
pub fn main() !void {
// Standard allocator setup
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit(); // Ensure cleanup
const allocator = gpa.allocator();
// --- Configuration ---
// Define the prefix to search for. This should be relative to the current working directory.
// Example uses the structure provided in the prompt.
const file_prefix = "src/zf";
// IMPORTANT: Assumes Unix-style path separators ('/') based on your example.
// For cross-platform compatibility, consider normalizing separators.
const cwd_path = try std.fs.cwd().realpathAlloc(allocator, ".");
defer allocator.free(cwd_path);
std.debug.print("Searching from CWD: {s}\n", .{cwd_path});
std.debug.print("Looking for files starting with relative path: \"{s}\"\n", .{file_prefix});
// --- Step 1: Get all file paths recursively from CWD ---
var all_paths = std.ArrayList([]const u8).init(allocator);
defer {
// Free the duplicated path strings stored in the list
for (all_paths.items) |p| allocator.free(p);
all_paths.deinit(); // Free the list itself
}
const cwd = std.fs.cwd();
// Start the recursive search from the current working directory (".")
try getAllFilePathsRecursive(allocator, cwd, ".", &all_paths);
std.debug.print("\nFound {d} total files recursively.\n", .{all_paths.items.len});
// --- Method 1: Standard Zig Prefix Matching (Exact Match) ---
std.debug.print("\n--- Method 1: Files matching prefix (std.mem.startsWith + path check) ---\n", .{});
var found_count_prefix: usize = 0;
for (all_paths.items) |path| {
// Check if the path string starts with the defined prefix
if (std.mem.startsWith(u8, path, file_prefix)) {
// Additionally, ensure it's either an exact match for the prefix,
// or the character immediately following the prefix is a path separator.
// This prevents matching "src/zfolder_extra" if the prefix is "src/zf".
if (path.len == file_prefix.len or path[file_prefix.len] == std.fs.path.sep) {
std.debug.print(" [Prefix Match] {s}\n", .{path});
found_count_prefix += 1;
}
}
}
if (found_count_prefix == 0) {
std.debug.print(" (No files found with this exact prefix structure)\n", .{});
}
// --- Method 2: Using zf.rank for Filtering (Fuzzy Match) ---
// This uses the zf library as requested, but provides fuzzy matching.
std.debug.print("\n--- Method 2: Files ranked by zf against prefix query (zf.rank, fuzzy) ---\n", .{});
std.debug.print(" (Lower rank score is better. Shows all files that match the query fuzzily.)\n", .{});
// Treat the prefix as a single query token for zf
const query_tokens = [_][]const u8{file_prefix};
var ranked_paths = std.ArrayList(struct {
path: []const u8, // Points to memory owned by all_paths
rank: f64,
}).init(allocator);
// No need to free path strings here, as they are owned by `all_paths`
defer ranked_paths.deinit();
for (all_paths.items) |path| {
// Configure zf.rank:
// .plain = false -> Use path-specific ranking logic (favors filename matches, etc.)
// .to_lower = true -> Case-insensitive matching (typical for fuzzy finders)
const rank_options = zf.RankOptions{ .plain = false, .to_lower = true };
// Call zf.rank. It returns the rank score (f64) if matched, or null otherwise.
if (zf.rank(path, &query_tokens, rank_options)) |rank_value| {
// A match was found (rank_value is not null)
try ranked_paths.append(.{ .path = path, .rank = rank_value });
}
}
// Sort the fuzzy matches by rank (lower score is better) to show the best matches first
std.sort.block(struct { path: []const u8, rank: f64 }, ranked_paths.items, {}, struct {
fn lessThan(_: void, a: anytype, b: anytype) bool {
// Sort primarily by rank (ascending)
if (a.rank < b.rank) return true;
if (a.rank > b.rank) return false;
// As a tie-breaker, sort by path alphabetically
return std.mem.lessThan(u8, a.path, b.path);
}
}.lessThan);
if (ranked_paths.items.len == 0) {
std.debug.print(" (No files found matching the query '{s}' using zf.rank)\n", .{file_prefix});
} else {
std.debug.print(" (Showing top matches ranked by zf):\n", .{});
// Limit output for demonstration purposes if many files match fuzzily
const limit = @min(ranked_paths.items.len, 20);
for (ranked_paths.items[0..limit]) |ranked| {
// Print the rank score and the path
std.debug.print(" [zf Rank: {d:.2f}] {s}\n", .{ ranked.rank, ranked.path });
}
if (ranked_paths.items.len > limit) {
std.debug.print(" (... and {d} more)\n", .{ranked_paths.items.len - limit});
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment