Skip to content

Commit f20918e

Browse files
feat(cli): add cli
- iterate and compile exercise files - ascii art - event loop
1 parent ea0cc32 commit f20918e

1 file changed

Lines changed: 263 additions & 0 deletions

File tree

src/main.zig

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
const std = @import("std");
2+
3+
var stdin_buffer: [1024]u8 = undefined;
4+
var stdin_reader = std.fs.File.stdin().reader(&stdin_buffer);
5+
const stdin = &stdin_reader.interface;
6+
7+
var stdout_buffer: [1024]u8 = undefined;
8+
var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
9+
const stdout = &stdout_writer.interface;
10+
11+
pub const ExerciseList = struct {
12+
allocator: std.mem.Allocator,
13+
filenames: std.ArrayList([]const u8),
14+
15+
pub fn init(allocator: std.mem.Allocator) !*ExerciseList {
16+
var self = try allocator.create(ExerciseList);
17+
18+
self.* = .{ .allocator = allocator, .filenames = .empty };
19+
20+
try self.fill();
21+
self.sort();
22+
23+
return self;
24+
}
25+
26+
pub fn deinit(self: *ExerciseList) void {
27+
self.filenames.deinit(self.allocator);
28+
self.allocator.destroy(self);
29+
}
30+
31+
fn fill(self: *ExerciseList) !void {
32+
const exercises_dir_path = "exercises/";
33+
34+
const exercises_dir = try std.fs.cwd().openDir(exercises_dir_path, .{ .iterate = true });
35+
var chapters = exercises_dir.iterate();
36+
37+
while (try chapters.next()) |chapter| {
38+
const chapter_dir_path = try std.fs.path.join(self.allocator, &.{ exercises_dir_path, chapter.name });
39+
40+
const chapter_dir = try std.fs.cwd().openDir(chapter_dir_path, .{ .iterate = true });
41+
var exercises = chapter_dir.iterate();
42+
43+
while (try exercises.next()) |exercise| {
44+
const exercise_file_path = try std.fs.path.join(self.allocator, &.{ exercises_dir_path, chapter.name, exercise.name });
45+
46+
try self.filenames.append(self.allocator, exercise_file_path);
47+
}
48+
}
49+
}
50+
51+
fn sort(self: @This()) void {
52+
std.sort.insertion(
53+
[]const u8,
54+
self.filenames.items,
55+
{},
56+
struct {
57+
fn lessThan(_: void, a: []const u8, b: []const u8) bool {
58+
return std.mem.lessThan(u8, a, b);
59+
}
60+
}.lessThan,
61+
);
62+
}
63+
64+
fn print(self: @This()) void {
65+
for (self.filenames.items) |item| {
66+
std.debug.print("{s}\n", .{item});
67+
}
68+
}
69+
};
70+
71+
const ASCII_STYLES = struct {
72+
pub const bold = "\x1B[1m";
73+
pub const underline = "\x1B[4m";
74+
pub const clear_style = "\x1B[0m";
75+
pub const clear_prompt = "\x1b[2J\x1b[H";
76+
77+
pub const red = "\x1b[31m";
78+
pub const green = "\x1b[32m";
79+
};
80+
81+
pub const Cli = struct {
82+
exercise_list: std.ArrayList([]const u8),
83+
allocator: std.mem.Allocator,
84+
85+
current_exercise: []const u8 = "",
86+
current_exercise_index: u8 = 0,
87+
did_current_exercise_compile: bool = false,
88+
completed_exercises: std.ArrayList([]const u8) = .empty,
89+
current_exercise_stdout: std.ArrayList(u8) = .empty,
90+
current_exercise_stderr: std.ArrayList(u8) = .empty,
91+
92+
pub const Components = struct {
93+
const ASCII_ART =
94+
\\ _ _
95+
\\ | (_)
96+
\\ ___ _ __ _ __ | |_ _ __ __ _ ___
97+
\\ / __| '_ \| '_ \| | | '_ \ / _` / __|
98+
\\ | (__| |_) | |_) | | | | | | (_| \__ \
99+
\\ \___| .__/| .__/|_|_|_| |_|\__, |___/
100+
\\ | | | | __/ |
101+
\\ |_| |_| |___/
102+
\\
103+
;
104+
105+
const ProgressBar = struct {
106+
var completed_exercises_status: std.ArrayList(u8) = .empty;
107+
var incomplete_exercises_status: std.ArrayList(u8) = .empty;
108+
109+
fn output(cli: *Cli) !void {
110+
for (cli.completed_exercises.items) |exercise| {
111+
_ = exercise;
112+
try completed_exercises_status.append(cli.allocator, '#');
113+
}
114+
115+
for (cli.exercise_list.items[cli.completed_exercises.items.len..]) |exercise| {
116+
_ = exercise;
117+
try incomplete_exercises_status.append(cli.allocator, '-');
118+
}
119+
120+
try stdout.print("\nProgress: [{s}>{s}] {d}/{d}\n", .{ completed_exercises_status.items, incomplete_exercises_status.items, cli.current_exercise_index + 1, cli.exercise_list.items.len });
121+
122+
incomplete_exercises_status.clearAndFree(cli.allocator);
123+
completed_exercises_status.clearAndFree(cli.allocator);
124+
}
125+
};
126+
};
127+
128+
pub fn iterateExercises(self: *Cli) !void {
129+
for (self.exercise_list.items, 0..) |exercise, index| {
130+
self.current_exercise = exercise;
131+
self.current_exercise_index = @intCast(index);
132+
self.did_current_exercise_compile = false;
133+
134+
try self.compileCurrentExercise();
135+
136+
if (!(self.did_current_exercise_compile)) {
137+
break;
138+
}
139+
140+
try self.completed_exercises.append(self.allocator, exercise);
141+
}
142+
}
143+
144+
pub fn iterateNextExercise(self: *Cli) !void {
145+
try self.compileCurrentExercise();
146+
147+
if (!(self.did_current_exercise_compile)) {
148+
return;
149+
}
150+
151+
try self.completed_exercises.append(self.allocator, self.current_exercise);
152+
153+
const next_exercise_index = self.current_exercise_index + 1;
154+
155+
self.current_exercise = self.exercise_list.items[next_exercise_index];
156+
self.current_exercise_index = @intCast(next_exercise_index);
157+
158+
try self.compileCurrentExercise();
159+
}
160+
161+
pub fn compileCurrentExercise(self: *Cli) !void {
162+
self.current_exercise_stdout.clearAndFree(self.allocator);
163+
self.current_exercise_stderr.clearAndFree(self.allocator);
164+
165+
var process = std.process.Child.init(
166+
&[_][]const u8{ "zig", "build", "exercises", "--", self.current_exercise },
167+
self.allocator,
168+
);
169+
170+
process.stderr_behavior = .Pipe;
171+
process.stdout_behavior = .Pipe;
172+
173+
process.spawn() catch
174+
{
175+
try stdout.print("\n{s}Error compiling exercise...{s}\n", .{
176+
ASCII_STYLES.red,
177+
ASCII_STYLES.clear_style,
178+
});
179+
return;
180+
};
181+
182+
try process.collectOutput(self.allocator, &self.current_exercise_stdout, &self.current_exercise_stderr, 4096);
183+
const process_status = try process.wait();
184+
185+
if (process_status.Exited == 0) {
186+
self.did_current_exercise_compile = true;
187+
return;
188+
}
189+
190+
self.did_current_exercise_compile = false;
191+
}
192+
193+
pub fn run(self: *Cli) !void {
194+
try self.iterateExercises();
195+
196+
while (true) {
197+
try stdout.print("{s}", .{ASCII_STYLES.clear_prompt});
198+
199+
try stdout.print("{s}\n", .{Components.ASCII_ART});
200+
201+
try stdout.print("{s}", .{self.current_exercise_stdout.items});
202+
203+
if (!(self.did_current_exercise_compile)) {
204+
try stdout.print("\n{s}", .{self.current_exercise_stderr.items});
205+
}
206+
207+
try stdout.print("{s}", .{ASCII_STYLES.bold});
208+
if (self.did_current_exercise_compile) {
209+
try stdout.print("\nExercise completed , move on to the next...\n", .{});
210+
} else {
211+
try stdout.print("\nExercise failed to compile . Keep trying, we believe in you.\n", .{});
212+
}
213+
try stdout.print("{s}", .{ASCII_STYLES.clear_style});
214+
215+
try Components.ProgressBar.output(self);
216+
217+
try stdout.print("Current exercise: {s}{s}{s}{s}\n", .{ ASCII_STYLES.bold, ASCII_STYLES.underline, self.current_exercise, ASCII_STYLES.clear_style });
218+
219+
try stdout.print("\n", .{});
220+
try stdout.print("{s}n{s}: next / ", .{ ASCII_STYLES.bold, ASCII_STYLES.clear_style });
221+
try stdout.print("{s}c{s}: check all / ", .{ ASCII_STYLES.bold, ASCII_STYLES.clear_style });
222+
try stdout.print("{s}r{s}: refresh / ", .{ ASCII_STYLES.bold, ASCII_STYLES.clear_style });
223+
try stdout.print("{s}x{s}: reset / ", .{ ASCII_STYLES.bold, ASCII_STYLES.clear_style });
224+
try stdout.print("{s}q{s}: quit ", .{ ASCII_STYLES.bold, ASCII_STYLES.clear_style });
225+
try stdout.print("-> ", .{});
226+
try stdout.flush();
227+
228+
const input = try stdin.takeDelimiterExclusive('\n');
229+
230+
if (std.mem.eql(u8, input, "n")) {
231+
try self.iterateNextExercise();
232+
}
233+
234+
if (std.mem.eql(u8, input, "c")) {
235+
try self.iterateExercises();
236+
}
237+
238+
if (std.mem.eql(u8, input, "r")) {
239+
try self.compileCurrentExercise();
240+
}
241+
242+
// TODO: reset using diff and patches
243+
if (std.mem.eql(u8, input, "x")) {}
244+
245+
if (std.mem.eql(u8, input, "q")) {
246+
break;
247+
}
248+
}
249+
}
250+
};
251+
252+
pub fn main() !void {
253+
var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
254+
defer mem_arena.deinit();
255+
const mem_allocator = mem_arena.allocator();
256+
257+
const exercise_list: *ExerciseList = try ExerciseList.init(mem_allocator);
258+
defer exercise_list.deinit();
259+
260+
var cpplings: Cli = .{ .allocator = mem_allocator, .exercise_list = exercise_list.filenames };
261+
262+
try cpplings.run();
263+
}

0 commit comments

Comments
 (0)