-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwatch.zig
More file actions
201 lines (164 loc) · 6.42 KB
/
watch.zig
File metadata and controls
201 lines (164 loc) · 6.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
const std = @import("std");
const discovery = @import("discovery.zig");
const test_loader = @import("test_loader.zig");
const compat = @import("compat.zig");
/// Watch mode options
pub const WatchOptions = struct {
/// Directory to watch
watch_dir: []const u8 = ".",
/// Test file pattern
pattern: []const u8 = "*.test.zig",
/// Whether to watch recursively
recursive: bool = true,
/// Debounce delay in milliseconds
debounce_ms: u64 = 300,
/// Clear screen between runs
clear_screen: bool = true,
/// Verbose output
verbose: bool = false,
};
/// File watcher for test files
pub const TestWatcher = struct {
allocator: std.mem.Allocator,
options: WatchOptions,
running: *std.atomic.Value(bool),
last_run_time: std.atomic.Value(i64) = std.atomic.Value(i64).init(0),
const Self = @This();
pub fn init(allocator: std.mem.Allocator, options: WatchOptions, running: *std.atomic.Value(bool)) Self {
return .{
.allocator = allocator,
.options = options,
.running = running,
};
}
/// Start watching for file changes
pub fn watch(self: *Self, loader_options: test_loader.LoaderOptions) !void {
if (self.options.verbose) {
std.debug.print("Watching for changes in '{s}' (pattern: {s})...\n", .{ self.options.watch_dir, self.options.pattern });
std.debug.print("Press Ctrl+C to stop.\n\n", .{});
}
// Run tests initially
try self.runTests(loader_options);
// Watch for file changes
while (self.running.load(.monotonic)) {
compat.sleep(self.options.debounce_ms * std.time.ns_per_ms);
if (try self.checkForChanges()) {
const current_time = compat.milliTimestamp();
const last_run = self.last_run_time.load(.monotonic);
// Debounce: only run if enough time has passed
if (current_time - last_run >= self.options.debounce_ms) {
if (self.options.clear_screen) {
self.clearScreen();
}
std.debug.print("Change detected, re-running tests...\n\n", .{});
try self.runTests(loader_options);
self.last_run_time.store(current_time, .monotonic);
}
}
}
}
/// Check if any test files have changed
fn checkForChanges(self: *Self) !bool {
// TODO: Re-implement with std.Io.Dir when Io is available in Zig 0.16
_ = self;
return false;
}
/// Run tests with discovery
fn runTests(self: *Self, loader_options: test_loader.LoaderOptions) !void {
const discovery_options = discovery.DiscoveryOptions{
.root_path = self.options.watch_dir,
.pattern = self.options.pattern,
.recursive = self.options.recursive,
};
var discovered = try discovery.discoverTests(self.allocator, discovery_options);
defer discovered.deinit();
const passed = try test_loader.runDiscoveredTests(self.allocator, &discovered, loader_options);
if (passed) {
std.debug.print("\n✓ All tests passed! Watching for changes...\n", .{});
} else {
std.debug.print("\n✗ Some tests failed. Fix them and save to re-run.\n", .{});
}
}
/// Check if filename matches pattern
fn matchesPattern(self: *Self, filename: []const u8, pattern: []const u8) bool {
_ = self;
// Simple wildcard matching
if (std.mem.indexOf(u8, pattern, "*")) |star_pos| {
const prefix = pattern[0..star_pos];
const suffix = pattern[star_pos + 1 ..];
if (prefix.len > 0 and !std.mem.startsWith(u8, filename, prefix)) {
return false;
}
if (suffix.len > 0 and !std.mem.endsWith(u8, filename, suffix)) {
return false;
}
return true;
} else {
return std.mem.eql(u8, filename, pattern);
}
}
/// Clear terminal screen
fn clearScreen(self: *Self) void {
_ = self;
// ANSI escape code to clear screen
std.debug.print("\x1b[2J\x1b[H", .{});
}
};
/// Simple file watcher using polling
/// Note: In production, use std.fs.Watch or platform-specific APIs (inotify, kqueue, etc.)
pub const FileWatcher = struct {
allocator: std.mem.Allocator,
watch_paths: std.ArrayList([]const u8),
file_times: std.StringHashMap(i64),
const Self = @This();
pub fn init(allocator: std.mem.Allocator) Self {
return .{
.allocator = allocator,
.watch_paths = std.ArrayList([]const u8).empty,
.file_times = std.StringHashMap(i64).init(allocator),
};
}
pub fn deinit(self: *Self) void {
for (self.watch_paths.items) |path| {
self.allocator.free(path);
}
self.watch_paths.deinit(self.allocator);
self.file_times.deinit();
}
/// Add a path to watch
pub fn addPath(self: *Self, path: []const u8) !void {
const path_copy = try self.allocator.dupe(u8, path);
try self.watch_paths.append(self.allocator, path_copy);
// Initialize modification time
// TODO: statFile needs Io in Zig 0.16
try self.file_times.put(path_copy, 0);
}
/// Check if any watched files have changed
pub fn hasChanges(self: *Self) !bool {
for (self.watch_paths.items) |path| {
// TODO: statFile needs Io in Zig 0.16
const mtime_ms: i64 = 0;
const last_mtime = self.file_times.get(path) orelse 0;
if (mtime_ms > last_mtime) {
try self.file_times.put(path, mtime_ms);
return true;
}
}
return false;
}
};
// Tests
test "WatchOptions default values" {
const options = WatchOptions{};
try std.testing.expectEqualStrings(".", options.watch_dir);
try std.testing.expectEqualStrings("*.test.zig", options.pattern);
try std.testing.expectEqual(true, options.recursive);
try std.testing.expectEqual(@as(u64, 300), options.debounce_ms);
try std.testing.expectEqual(true, options.clear_screen);
}
test "FileWatcher initialization" {
const allocator = std.testing.allocator;
var watcher = FileWatcher.init(allocator);
defer watcher.deinit();
try std.testing.expectEqual(@as(usize, 0), watcher.watch_paths.items.len);
}