doc: add missing README file; lint: correct reported typo
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 1m2s
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 1m2s
This commit is contained in:
123
README.md
Normal file
123
README.md
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
# fuzzig
|
||||||
|
|
||||||
|
Fuzzy matcher based on the matching algorithm implementation of [ms-edit](https://github.com/microsoft/edit/blob/main/src/fuzzy.rs) as the editor is MIT-Licensed, this project is also under the MIT-License.
|
||||||
|
|
||||||
|
> [!caution]
|
||||||
|
> Only builds using the zig master version are tested to work.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Add or update this library as a dependency in your zig project run the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
zig fetch --save git+https://gitea.yves-biener.de/yves-biener/fuzzig
|
||||||
|
```
|
||||||
|
|
||||||
|
Afterwards add the library as a dependency to any module in your *build.zig*:
|
||||||
|
|
||||||
|
```zig
|
||||||
|
const fuzzig_dependency = b.dependency("fuzzig", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The following snippet shows a test case to illustrate usage of `fuzzig` to run fuzzy matches:
|
||||||
|
|
||||||
|
```zig
|
||||||
|
test "matching `s` on local files" {
|
||||||
|
var gpa = testing.allocator;
|
||||||
|
|
||||||
|
// files to fuzzy match against
|
||||||
|
var files: std.ArrayList([]const u8) = .empty;
|
||||||
|
defer {
|
||||||
|
for (files.items) |file| gpa.free(file);
|
||||||
|
files.deinit(gpa);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fuzzy matching results (containing only the scores)
|
||||||
|
var results: std.ArrayList(Result) = .empty;
|
||||||
|
defer {
|
||||||
|
for (results.items) |*result| result.deinit(gpa);
|
||||||
|
results.deinit(gpa);
|
||||||
|
}
|
||||||
|
|
||||||
|
// arrange
|
||||||
|
var dir = try std.fs.cwd().openDir(".", .{ .iterate = true });
|
||||||
|
defer dir.close();
|
||||||
|
|
||||||
|
var iter = try dir.walk(gpa);
|
||||||
|
defer iter.deinit();
|
||||||
|
|
||||||
|
while (try iter.next()) |entry| {
|
||||||
|
switch (entry.kind) {
|
||||||
|
.file => {
|
||||||
|
if (std.mem.startsWith(u8, entry.path, ".git/")) continue;
|
||||||
|
if (std.mem.startsWith(u8, entry.path, ".zig-cache")) continue;
|
||||||
|
const path = try gpa.dupe(u8, entry.path[0..entry.path.len]);
|
||||||
|
try files.append(gpa, path);
|
||||||
|
},
|
||||||
|
else => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try results.ensureTotalCapacity(gpa, files.items.len);
|
||||||
|
|
||||||
|
// act
|
||||||
|
const search = "s";
|
||||||
|
|
||||||
|
// create fuzzy score for each file entry
|
||||||
|
for (0.., files.items) |idx, entry| {
|
||||||
|
const result = try match(gpa, entry, search, idx) orelse continue;
|
||||||
|
try results.append(gpa, result);
|
||||||
|
}
|
||||||
|
// sort scores by their received score descending
|
||||||
|
std.sort.heap(Result, results.items, {}, greaterThan);
|
||||||
|
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
var buffer = std.fs.File.stderr().writer(&buf);
|
||||||
|
var writer = &buffer.interface;
|
||||||
|
defer writer.flush() catch unreachable;
|
||||||
|
|
||||||
|
std.debug.lockStdErr();
|
||||||
|
defer std.debug.unlockStdErr();
|
||||||
|
|
||||||
|
// assert
|
||||||
|
var scored_entries: usize = 0;
|
||||||
|
var unscored_entries: usize = 0;
|
||||||
|
for (results.items) |result| {
|
||||||
|
if (result.score > 0) scored_entries += 1 else unscored_entries += 1;
|
||||||
|
if (result.score == 0) continue; // do not print results that are unmatched
|
||||||
|
|
||||||
|
const item = files.items[result.index];
|
||||||
|
var match_highlights: []u8 = try gpa.alloc(u8, item.len);
|
||||||
|
defer gpa.free(match_highlights);
|
||||||
|
|
||||||
|
@memset(match_highlights, ' ');
|
||||||
|
// highlight what caused this search result
|
||||||
|
for (result.positions.items) |pos| match_highlights[pos] = '^';
|
||||||
|
// print item and its highlighted positions
|
||||||
|
// NOTE uncomment the print for the writer to show matches and their highlights of what matched
|
||||||
|
// -> as the writer prints to *stderr* writing will cause the test to fail, hence it is commented out by default
|
||||||
|
try writer.print("{s}\n{s}\n", .{ item, match_highlights });
|
||||||
|
}
|
||||||
|
try testing.expectEqual(5, scored_entries);
|
||||||
|
try testing.expectEqual(results.items.len - 5, unscored_entries);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Resulting in the output of the found (and ordered) matches:
|
||||||
|
|
||||||
|
```
|
||||||
|
src/root.zig
|
||||||
|
^
|
||||||
|
LICENSE
|
||||||
|
^
|
||||||
|
.gitea/workflows/test.yaml
|
||||||
|
^
|
||||||
|
.gitea/workflows/release.yaml
|
||||||
|
^
|
||||||
|
.typos-config
|
||||||
|
^
|
||||||
|
```
|
||||||
@@ -174,7 +174,7 @@ fn compute_char_score(query: u8, query_lower: u8, target_prev: ?u8, target_curr:
|
|||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scoring for separator characters. Slightly prefering path separators over other separators.
|
/// Scoring for separator characters. Slightly preferring path separators over other separators.
|
||||||
fn score_separator_at_pos(prev: u8) u32 {
|
fn score_separator_at_pos(prev: u8) u32 {
|
||||||
return switch (prev) {
|
return switch (prev) {
|
||||||
'/', '\\' => 5, // prefer path separators...
|
'/', '\\' => 5, // prefer path separators...
|
||||||
|
|||||||
Reference in New Issue
Block a user