mirror of
https://codeberg.org/ziglings/exercises.git
synced 2026-07-28 18:25:16 +00:00
Update color setup using new IO/Terminal API
The new API add support for NO_COLOR and CLICOLOR_FORCE. Move color variables to a namespace. Add a message in case colors are not available un unix systems. Add a message in case on Windows colors are available (on old versions).
This commit is contained in:
+65
-46
@@ -19,12 +19,28 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
const Io = std.Io;
|
||||||
|
const File = Io.File;
|
||||||
const Process = std.process;
|
const Process = std.process;
|
||||||
|
const Terminal = std.Io.Terminal;
|
||||||
const print = std.debug.print;
|
const print = std.debug.print;
|
||||||
const cutPrefix = std.mem.cutPrefix;
|
const cutPrefix = std.mem.cutPrefix;
|
||||||
|
|
||||||
const progress_filename = ".progress.txt";
|
const progress_filename = ".progress.txt";
|
||||||
|
|
||||||
|
const no_colors_help =
|
||||||
|
\\Colors are not available. Check the NO_COLOR environ variable.
|
||||||
|
\\
|
||||||
|
\\
|
||||||
|
;
|
||||||
|
|
||||||
|
const windows_colors_help =
|
||||||
|
\\Windows colors are not supported.
|
||||||
|
\\Install Windows Terminal or update to a more recent Windows version.
|
||||||
|
\\
|
||||||
|
\\
|
||||||
|
;
|
||||||
|
|
||||||
pub const logo =
|
pub const logo =
|
||||||
\\ _ _ _
|
\\ _ _ _
|
||||||
\\ ___(_) __ _| (_)_ __ __ _ ___
|
\\ ___(_) __ _| (_)_ __ __ _ ___
|
||||||
@@ -114,37 +130,18 @@ const Context = struct {
|
|||||||
|
|
||||||
const Error = error{Failed};
|
const Error = error{Failed};
|
||||||
|
|
||||||
// Ansi colors.
|
var stderr_term_mode: Terminal.Mode = .no_color;
|
||||||
var use_color_escapes = false;
|
|
||||||
var red_text: []const u8 = "";
|
|
||||||
var red_bold_text: []const u8 = "";
|
|
||||||
var red_dim_text: []const u8 = "";
|
|
||||||
var green_text: []const u8 = "";
|
|
||||||
var yellow_text: []const u8 = "";
|
|
||||||
var bold_text: []const u8 = "";
|
|
||||||
var reset_text: []const u8 = "";
|
|
||||||
|
|
||||||
fn setupColors(io: std.Io) void {
|
// Ansi colors.
|
||||||
use_color_escapes = false;
|
const C = struct {
|
||||||
const stderr = std.Io.File.stderr();
|
var red: []const u8 = "\x1b[31m";
|
||||||
if (stderr.supportsAnsiEscapeCodes(io)) |ok| {
|
var red_bold: []const u8 = "\x1b[31;1m";
|
||||||
if (ok) use_color_escapes = true;
|
var red_dim: []const u8 = "\x1b[31;2m";
|
||||||
} else |_| {}
|
var green: []const u8 = "\x1b[32m";
|
||||||
if (!use_color_escapes and builtin.os.tag == .windows) {
|
var yellow: []const u8 = "\x1b[33m";
|
||||||
if (stderr.enableAnsiEscapeCodes(io)) {
|
var bold: []const u8 = "\x1b[1m";
|
||||||
use_color_escapes = true;
|
var reset: []const u8 = "\x1b[0m";
|
||||||
} else |_| {}
|
};
|
||||||
}
|
|
||||||
if (use_color_escapes) {
|
|
||||||
red_text = "\x1b[31m";
|
|
||||||
red_bold_text = "\x1b[31;1m";
|
|
||||||
red_dim_text = "\x1b[31;2m";
|
|
||||||
green_text = "\x1b[32m";
|
|
||||||
yellow_text = "\x1b[33m";
|
|
||||||
bold_text = "\x1b[1m";
|
|
||||||
reset_text = "\x1b[0m";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn main(init: std.process.Init) !void {
|
pub fn main(init: std.process.Init) !void {
|
||||||
const io = init.io;
|
const io = init.io;
|
||||||
@@ -153,7 +150,31 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
var args_it = try init.minimal.args.iterateAllocator(arena);
|
var args_it = try init.minimal.args.iterateAllocator(arena);
|
||||||
if (!args_it.skip()) @panic("expected self arg");
|
if (!args_it.skip()) @panic("expected self arg");
|
||||||
|
|
||||||
setupColors(io);
|
const stderr = File.stderr();
|
||||||
|
|
||||||
|
// Setup colors.
|
||||||
|
const NO_COLOR = try init.minimal.environ.containsUnempty(arena, "NO_COLOR");
|
||||||
|
const CLICOLOR_FORCE = try init.minimal.environ.containsUnempty(arena, "CLICOLOR_FORCE");
|
||||||
|
|
||||||
|
const term_mode = try Terminal.Mode.detect(io, stderr, NO_COLOR, CLICOLOR_FORCE);
|
||||||
|
stderr_term_mode = term_mode;
|
||||||
|
|
||||||
|
// Show help in case colors are not available or not supported.
|
||||||
|
switch (term_mode) {
|
||||||
|
.no_color => print(no_colors_help, .{}),
|
||||||
|
.escape_codes => {},
|
||||||
|
.windows_api => print(windows_colors_help, .{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (term_mode == .no_color) {
|
||||||
|
C.red = "";
|
||||||
|
C.red_bold = "";
|
||||||
|
C.red_dim = "";
|
||||||
|
C.green = "";
|
||||||
|
C.yellow = "";
|
||||||
|
C.bold = "";
|
||||||
|
C.reset = "";
|
||||||
|
}
|
||||||
|
|
||||||
if (!validateExercises()) std.process.exit(1);
|
if (!validateExercises()) std.process.exit(1);
|
||||||
|
|
||||||
@@ -166,7 +187,7 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
|
|
||||||
while (args_it.next()) |arg| {
|
while (args_it.next()) |arg| {
|
||||||
if (std.mem.eql(u8, arg, "--logo")) {
|
if (std.mem.eql(u8, arg, "--logo")) {
|
||||||
print("{s}{s}{s}", .{ yellow_text, logo, reset_text });
|
print("{s}{s}{s}", .{ C.yellow, logo, C.reset });
|
||||||
return;
|
return;
|
||||||
} else if (cutPrefix(u8, arg, "--zig=")) |v| {
|
} else if (cutPrefix(u8, arg, "--zig=")) |v| {
|
||||||
zig_exe = v;
|
zig_exe = v;
|
||||||
@@ -243,7 +264,7 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// All solved.
|
// All solved.
|
||||||
print("{s}All exercises completed!{s}\n", .{ green_text, reset_text });
|
print("{s}All exercises completed!{s}\n", .{ C.green, C.reset });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
iterateFrom(ctx, start_index) catch std.process.exit(1);
|
iterateFrom(ctx, start_index) catch std.process.exit(1);
|
||||||
@@ -265,7 +286,7 @@ fn runOne(ctx: Context, ex: Exercise, mode: Mode) Error!void {
|
|||||||
if (ex.skip) {
|
if (ex.skip) {
|
||||||
print("Skipping {s}", .{ex.main_file});
|
print("Skipping {s}", .{ex.main_file});
|
||||||
if (ex.skip_hint) |hint|
|
if (ex.skip_hint) |hint|
|
||||||
print("\n{s}Reason: {s}{s}\n", .{ bold_text, hint, reset_text });
|
print("\n{s}Reason: {s}{s}\n", .{ C.bold, hint, C.reset });
|
||||||
print("\n\n", .{});
|
print("\n\n", .{});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -288,7 +309,7 @@ fn runOne(ctx: Context, ex: Exercise, mode: Mode) Error!void {
|
|||||||
|
|
||||||
fn hintAndHelp(ex: Exercise, mode: Mode) void {
|
fn hintAndHelp(ex: Exercise, mode: Mode) void {
|
||||||
if (ex.hint) |hint|
|
if (ex.hint) |hint|
|
||||||
print("\n{s}{s}Ziglings hint: {s}{s}", .{ bold_text, green_text, hint, reset_text });
|
print("\n{s}{s}Ziglings hint: {s}{s}", .{ C.bold, C.green, hint, C.reset });
|
||||||
help(ex, mode);
|
help(ex, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,7 +354,7 @@ fn runExe(ctx: Context, ex: Exercise) !void {
|
|||||||
.stderr_limit = .limited(1024 * 1024),
|
.stderr_limit = .limited(1024 * 1024),
|
||||||
}) catch |err| {
|
}) catch |err| {
|
||||||
print("{s}error:{s} unable to run {s}: {s}\n", .{
|
print("{s}error:{s} unable to run {s}: {s}\n", .{
|
||||||
red_bold_text, reset_text, ex.main_file, @errorName(err),
|
C.red_bold, C.reset, ex.main_file, @errorName(err),
|
||||||
});
|
});
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
@@ -367,7 +388,7 @@ fn runTest(ctx: Context, ex: Exercise) !void {
|
|||||||
.stderr_limit = .limited(1024 * 1024),
|
.stderr_limit = .limited(1024 * 1024),
|
||||||
}) catch |err| {
|
}) catch |err| {
|
||||||
print("{s}error:{s} unable to run test {s}: {s}\n", .{
|
print("{s}error:{s} unable to run test {s}: {s}\n", .{
|
||||||
red_bold_text, reset_text, ex.main_file, @errorName(err),
|
C.red_bold, C.reset, ex.main_file, @errorName(err),
|
||||||
});
|
});
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
@@ -389,7 +410,7 @@ fn checkOutput(io: std.Io, arena: std.mem.Allocator, ex: Exercise, result: Proce
|
|||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
print("{s}{s} terminated unexpectedly{s}\n", .{
|
print("{s}{s} terminated unexpectedly{s}\n", .{
|
||||||
red_bold_text, ex.main_file, reset_text,
|
C.red_bold, ex.main_file, C.reset,
|
||||||
});
|
});
|
||||||
return Error.Failed;
|
return Error.Failed;
|
||||||
},
|
},
|
||||||
@@ -424,8 +445,6 @@ fn checkOutput(io: std.Io, arena: std.mem.Allocator, ex: Exercise, result: Proce
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!std.mem.eql(u8, output, exercise_output)) {
|
if (!std.mem.eql(u8, output, exercise_output)) {
|
||||||
const red = red_bold_text;
|
|
||||||
const reset = reset_text;
|
|
||||||
print(
|
print(
|
||||||
\\
|
\\
|
||||||
\\{s}========= expected this output: =========={s}
|
\\{s}========= expected this output: =========={s}
|
||||||
@@ -433,11 +452,11 @@ fn checkOutput(io: std.Io, arena: std.mem.Allocator, ex: Exercise, result: Proce
|
|||||||
\\{s}========= but found: ====================={s}
|
\\{s}========= but found: ====================={s}
|
||||||
\\{s}
|
\\{s}
|
||||||
\\{s}=========================================={s}
|
\\{s}=========================================={s}
|
||||||
++ "\n", .{ red, reset, exercise_output, red, reset, output, red, reset });
|
++ "\n", .{ C.red_bold, C.reset, exercise_output, C.red_bold, C.reset, output, C.red_bold, C.reset });
|
||||||
return Error.Failed;
|
return Error.Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
print("{s}PASSED:\n{s}{s}\n\n", .{ green_text, output, reset_text });
|
print("{s}PASSED:\n{s}{s}\n\n", .{ C.green, output, C.reset });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn checkTest(ex: Exercise, result: Process.RunResult) !void {
|
fn checkTest(ex: Exercise, result: Process.RunResult) !void {
|
||||||
@@ -449,12 +468,12 @@ fn checkTest(ex: Exercise, result: Process.RunResult) !void {
|
|||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
print("{s}{s} terminated unexpectedly{s}\n", .{
|
print("{s}{s} terminated unexpectedly{s}\n", .{
|
||||||
red_bold_text, ex.main_file, reset_text,
|
C.red_bold, ex.main_file, C.reset,
|
||||||
});
|
});
|
||||||
return Error.Failed;
|
return Error.Failed;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
print("{s}PASSED{s}\n\n", .{ green_text, reset_text });
|
print("{s}PASSED{s}\n\n", .{ C.green, C.reset });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn help(ex: Exercise, mode: Mode) void {
|
fn help(ex: Exercise, mode: Mode) void {
|
||||||
@@ -464,12 +483,12 @@ fn help(ex: Exercise, mode: Mode) void {
|
|||||||
.random => "zig build -Drandom",
|
.random => "zig build -Drandom",
|
||||||
};
|
};
|
||||||
print("\n{s}Edit exercises/{s} and run '{s}' again.{s}\n", .{
|
print("\n{s}Edit exercises/{s} and run '{s}' again.{s}\n", .{
|
||||||
red_bold_text, ex.main_file, cmd, reset_text,
|
C.red_bold, ex.main_file, cmd, C.reset,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resetLine() void {
|
fn resetLine() void {
|
||||||
if (use_color_escapes) print("{s}", .{"\x1b[2K\r"});
|
if (stderr_term_mode == .escape_codes) print("{s}", .{"\x1b[2K\r"});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removes trailing whitespace per line and any trailing LF at the end.
|
// Removes trailing whitespace per line and any trailing LF at the end.
|
||||||
|
|||||||
Reference in New Issue
Block a user