Skip to content

Commit 04e5c0e

Browse files
committed
add parser
1 parent dc67a02 commit 04e5c0e

File tree

1 file changed

+106
-8
lines changed

1 file changed

+106
-8
lines changed

src/walk_dir.zig

Lines changed: 106 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,118 @@
11
const std = @import("std");
22
const testing = std.testing;
33

4-
const Rule = struct {
4+
const State = union(enum) { anything: bool, exact: []const u8 };
5+
const StateMachine = std.ArrayList(State);
6+
7+
const IgnoreRule = struct {
58
is_dir: bool,
9+
is_exclude: bool,
10+
state_machine: StateMachine,
11+
12+
const Self = @This();
13+
14+
fn init(allocator: std.mem.Allocator) Self {
15+
return .{
16+
.is_dir = false,
17+
.is_exclude = false,
18+
.state_machine = StateMachine.init(allocator),
19+
};
20+
}
21+
22+
fn deinit(self: Self) void {
23+
self.state_machine.deinit();
24+
}
25+
26+
fn pushState(self: *Self, state: State) !void {
27+
try self.state_machine.append(state);
28+
}
29+
30+
fn printState(self: Self, buf: anytype) !void {
31+
try buf.writeAll("state: [");
32+
for (self.state_machine.items, 0..) |item, i| {
33+
if (i > 0) {
34+
try buf.writeAll(", ");
35+
}
36+
switch (item) {
37+
.anything => try buf.writeAll("any"),
38+
.exact => |exact| try buf.writeAll(exact),
39+
}
40+
}
41+
try buf.writeAll("]");
42+
}
43+
};
644

7-
const State = union(enum) { begin, end, anything, exact: []const u8 };
45+
const IgnoreParser = struct {
46+
allocator: std.mem.Allocator,
847

948
const Self = @This();
49+
fn init(allocator: std.mem.Allocator) Self {
50+
return .{ .allocator = allocator };
51+
}
52+
53+
fn parse(self: Self, input: []const u8) !?IgnoreRule {
54+
if (std.mem.startsWith(u8, input, "#") or std.mem.eql(u8, input, "")) {
55+
return null;
56+
}
57+
58+
var rule = IgnoreRule.init(self.allocator);
59+
var start: usize = 0;
60+
var end: usize = input.len;
61+
if (std.mem.startsWith(u8, input, "!")) {
62+
rule.is_exclude = true;
63+
start = 1;
64+
}
65+
if (std.mem.endsWith(u8, input, "/")) {
66+
rule.is_dir = true;
67+
end = end - 1;
68+
}
69+
70+
var it = std.mem.splitScalar(u8, input[start..end], '/');
71+
var first_item = it.first();
72+
if (!std.mem.eql(u8, "", first_item)) {
73+
try rule.pushState(State{ .anything = true });
74+
try rule.pushState(State{ .exact = first_item });
75+
}
76+
77+
while (it.next()) |item| {
78+
if (std.mem.eql(u8, "**", item)) {
79+
try rule.pushState(State{ .anything = true });
80+
} else {
81+
try rule.pushState(State{ .exact = item });
82+
}
83+
}
1084

11-
fn init(input: []const u8) Self {
12-
_ = input;
13-
return .{ .is_dir = true };
85+
return rule;
1486
}
1587
};
1688

17-
test "init rule" {
18-
const r = Rule.init("abc");
19-
try testing.expectEqual(true, r.is_dir);
89+
test "parser rule" {
90+
const parser = IgnoreParser.init(std.testing.allocator);
91+
92+
// https://www.atlassian.com/git/tutorials/saving-changes/gitignore#git-ignore-patterns
93+
// https://git-scm.com/docs/gitignore
94+
inline for (.{
95+
// (input, is_dir, is_exclude, state)
96+
.{ "/a/b/c", false, false, "state: [a, b, c]" },
97+
.{ "a/b/", true, false, "state: [any, a, b]" },
98+
.{ "/a/b/", true, false, "state: [a, b]" },
99+
.{ "!/a/b/", true, true, "state: [a, b]" },
100+
.{ "!/a/**/b/", true, true, "state: [a, any, b]" },
101+
}) |case| {
102+
const input = case.@"0";
103+
const is_dir = case.@"1";
104+
const is_exclude = case.@"2";
105+
const state = case.@"3";
106+
107+
const rule = parser.parse(input) catch unreachable orelse unreachable;
108+
defer rule.deinit();
109+
110+
try testing.expectEqual(is_dir, rule.is_dir);
111+
try testing.expectEqual(is_exclude, rule.is_exclude);
112+
113+
var collector = std.ArrayList(u8).init(std.testing.allocator);
114+
defer collector.deinit();
115+
try rule.printState(collector.writer());
116+
try testing.expectEqualStrings(state, collector.items);
117+
}
20118
}

0 commit comments

Comments
 (0)