From 96d97c366ef9a1c0e315b789a4c1fe47ec8a4656 Mon Sep 17 00:00:00 2001 From: hndrk <51416554+hendriknielaender@users.noreply.github.com> Date: Wed, 4 Dec 2024 00:14:29 +0100 Subject: [PATCH] feat: colorize output (#63) * feat: add colorized output * chore: use stdout instead of std.debug.print * chore: add runtime styles api --- build.zig | 8 +- src/alias.zig | 33 +++++--- src/command.zig | 192 +++++++++++++++++++++++++++++++----------- src/install.zig | 3 +- src/util/color.zig | 161 +++++++++++++++++++++++++++++++++++ src/util/minisign.zig | 18 ++-- 6 files changed, 340 insertions(+), 75 deletions(-) create mode 100644 src/util/color.zig diff --git a/build.zig b/build.zig index 7aa13e2..32b07d3 100644 --- a/build.zig +++ b/build.zig @@ -4,8 +4,8 @@ const builtin = @import("builtin"); const Build = std.Build; const min_zig_string = "0.13.0"; -// Semantic version of your application -const version = std.SemanticVersion{ .major = 0, .minor = 5, .patch = 0 }; +const semver = std.SemanticVersion{ .major = 0, .minor = 5, .patch = 0 }; +const semver_string = "0.5.0"; const CrossTargetInfo = struct { crossTarget: std.zig.CrossTarget, @@ -32,14 +32,14 @@ pub fn build(b: *Build) void { // Add a global option for versioning const options = b.addOptions(); options.addOption(std.log.Level, "log_level", b.option(std.log.Level, "log_level", "The Log Level to be used.") orelse .info); - options.addOption(std.SemanticVersion, "zvm_version", version); + options.addOption([]const u8, "version", semver_string); const exe = b.addExecutable(.{ .name = "zvm", .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, - .version = version, + .version = semver, }); const exe_options_module = options.createModule(); diff --git a/src/alias.zig b/src/alias.zig index 00769e1..fdb81fd 100644 --- a/src/alias.zig +++ b/src/alias.zig @@ -1,6 +1,6 @@ //! This file is used to create soft links and verify version -//! for Windows, we will use copy dir(when Windows create soft link it requires admin) -//! for set version +//! for Windows, we will use copy dir (creating symlinks requires admin privileges) +//! for setting versions. const std = @import("std"); const assert = std.debug.assert; const builtin = @import("builtin"); @@ -8,9 +8,9 @@ const config = @import("config.zig"); const util_data = @import("util/data.zig"); const util_tool = @import("util/tool.zig"); -/// try to set zig version -/// this will use system link on unix-like -/// for windows, this will use copy dir +/// Try to set the Zig version. +/// This will use a symlink on Unix-like systems. +/// For Windows, this will copy the directory. pub fn set_version(version: []const u8, is_zls: bool) !void { var arena = std.heap.ArenaAllocator.init(util_data.get_allocator()); defer arena.deinit(); @@ -33,7 +33,8 @@ pub fn set_version(version: []const u8, is_zls: bool) !void { if (err != error.FileNotFound) return err; - std.debug.print("zig version {s} is not installed. Please install it before proceeding.\n", .{version}); + const err_msg = "{s} version {s} is not installed. Please install it before proceeding.\n"; + try std.io.getStdErr().writer().print(err_msg, .{ if (is_zls) "zls" else "Zig", version }); std.process.exit(1); }; @@ -67,32 +68,38 @@ fn update_current(zig_path: []const u8, symlink_path: []const u8) !void { try std.posix.symlink(zig_path, symlink_path); } -/// Verify current zig version +/// Verify the current Zig version fn verify_zig_version(expected_version: []const u8) !void { const allocator = util_data.get_allocator(); const actual_version = try util_data.get_current_version(allocator, false); defer allocator.free(actual_version); + var stdout = std.io.getStdOut().writer(); + if (std.mem.eql(u8, expected_version, "master")) { - std.debug.print("Now using Zig version {s}\n", .{actual_version}); + try stdout.print("Now using Zig version {s}\n", .{actual_version}); } else if (!std.mem.eql(u8, expected_version, actual_version)) { - std.debug.print("Expected Zig version {s}, but currently using {s}. Please check.\n", .{ expected_version, actual_version }); + const err_msg = "Expected Zig version {s}, but currently using {s}. Please check.\n"; + try std.io.getStdErr().writer().print(err_msg, .{ expected_version, actual_version }); } else { - std.debug.print("Now using Zig version {s}\n", .{expected_version}); + try stdout.print("Now using Zig version {s}\n", .{expected_version}); } } -/// verify current zig version +/// Verify the current zls version fn verify_zls_version(expected_version: []const u8) !void { const allocator = util_data.get_allocator(); const actual_version = try util_data.get_current_version(allocator, true); defer allocator.free(actual_version); + var stdout = std.io.getStdOut().writer(); + if (!std.mem.eql(u8, expected_version, actual_version)) { - std.debug.print("Expected zls version {s}, but currently using {s}. Please check.\n", .{ expected_version, actual_version }); + const err_msg = "Expected zls version {s}, but currently using {s}. Please check.\n"; + try std.io.getStdErr().writer().print(err_msg, .{ expected_version, actual_version }); } else { - std.debug.print("Now using zls version {s}\n", .{expected_version}); + try stdout.print("Now using zls version {s}\n", .{expected_version}); } } diff --git a/src/command.zig b/src/command.zig index 76a2612..cea0c89 100644 --- a/src/command.zig +++ b/src/command.zig @@ -1,4 +1,4 @@ -//! This file stores command line parsing and processing +// command.zig const std = @import("std"); const builtin = @import("builtin"); const options = @import("options"); @@ -12,6 +12,7 @@ const config = @import("config.zig"); const util_data = @import("util/data.zig"); const util_tool = @import("util/tool.zig"); const util_http = @import("util/http.zig"); +const util_color = @import("util/color.zig"); // Command types pub const Command = enum { @@ -115,7 +116,7 @@ pub fn handle_alias(params: []const []const u8) !void { var arena = std.heap.ArenaAllocator.init(util_data.get_allocator()); defer arena.deinit(); - const allocator = arena.allocator(); + var allocator = arena.allocator(); const new_params = try allocator.dupe([]const u8, params); @@ -131,7 +132,13 @@ pub fn handle_alias(params: []const []const u8) !void { std.fs.accessAbsolute(current_path, .{}) catch |err| { if (err == std.fs.Dir.AccessError.FileNotFound) { - std.debug.print("{s} has not been installed yet, please install it fist!\n", .{if (is_zls) "zls" else "Zig"}); + var color = try util_color.Color.RuntimeStyle.init(allocator); + defer color.deinit(); + + try color.bold().red().printErr( + "{s} has not been installed yet, please install it first!\n", + .{if (is_zls) "zls" else "Zig"}, + ); std.process.exit(1); } return err; @@ -144,10 +151,14 @@ pub fn handle_alias(params: []const []const u8) !void { fn handle_list(param: ?[]const u8) !void { const allocator = util_data.get_allocator(); + var color = try util_color.Color.RuntimeStyle.init(allocator); + defer color.deinit(); + + var is_zls = false; const version_list: [][]const u8 = blk: { if (param) |p| { - // when zls if (util_tool.eql_str(p, "zls")) { + is_zls = true; const res = try util_http.http_get(allocator, config.zls_url); defer allocator.free(res); @@ -156,15 +167,16 @@ fn handle_list(param: ?[]const u8) !void { const version_list = try zls_meta.get_version_list(allocator); break :blk version_list; - } else - // when not zig - if (!util_tool.eql_str(p, "zig")) { - std.debug.print("Error param, you can specify zig or zls\n", .{}); + } else if (!util_tool.eql_str(p, "zig")) { + try color.bold().red().printErr( + "Invalid parameter '{s}'. You can specify 'zig' or 'zls'.\n", + .{p}, + ); return; } } - // when param is null + // When param is null or 'zig' const res = try util_http.http_get(allocator, config.zig_url); defer allocator.free(res); @@ -177,12 +189,32 @@ fn handle_list(param: ?[]const u8) !void { defer util_tool.free_str_array(version_list, allocator); + // Fetch the current version + var current_version: ?[]const u8 = null; + current_version = (if (is_zls) + util_data.get_zvm_current_zls(allocator) + else + util_data.get_zvm_current_zig(allocator)) catch null; + + // Ensure current_version is freed after use + defer if (current_version) |cv| { + allocator.free(cv); + }; + for (version_list) |version| { - std.debug.print("{s}\n", .{version}); + if (current_version != null and std.mem.eql(u8, version, current_version.?)) { + // Highlight the current version + try color.bold().cyan().print("* {s}\n", .{version}); + } else { + // Colorize other versions + try color.green().print(" {s}\n", .{version}); + } } } fn install_version(subcmd: ?[]const u8, param: ?[]const u8, root_node: std.Progress.Node) !void { + const allocator = util_data.get_allocator(); + if (subcmd) |scmd| { var is_zls: bool = undefined; @@ -191,12 +223,24 @@ fn install_version(subcmd: ?[]const u8, param: ?[]const u8, root_node: std.Progr } else if (util_tool.eql_str(scmd, "zls")) { is_zls = true; } else { - std.debug.print("Unknown subcommand '{s}'. Use 'install zig/zls '.\n", .{scmd}); + var color = try util_color.Color.RuntimeStyle.init(allocator); + defer color.deinit(); + + try color.bold().red().printErr( + "Unknown subcommand '{s}'. Use 'install zig/zls '.\n", + .{scmd}, + ); return; } const version = param orelse { - std.debug.print("Please specify a version to install: 'install zig/zls '.\n", .{}); + var color = try util_color.Color.RuntimeStyle.init(allocator); + defer color.deinit(); + + try color.bold().red().printErr( + "Please specify a version to install: 'install {s} '.\n", + .{scmd}, + ); return; }; @@ -205,11 +249,19 @@ fn install_version(subcmd: ?[]const u8, param: ?[]const u8, root_node: std.Progr // set zig version try install.install(version, false, root_node); } else { - std.debug.print("Please specify a version to install: 'install zig/zls ' or 'install '.\n", .{}); + var color = try util_color.Color.RuntimeStyle.init(allocator); + defer color.deinit(); + + try color.bold().red().printErr( + "Please specify a version to install: 'install zig/zls ' or 'install '.\n", + .{}, + ); } } fn use_version(subcmd: ?[]const u8, param: ?[]const u8) !void { + const allocator = util_data.get_allocator(); + if (subcmd) |scmd| { var is_zls: bool = undefined; @@ -218,12 +270,24 @@ fn use_version(subcmd: ?[]const u8, param: ?[]const u8) !void { } else if (util_tool.eql_str(scmd, "zls")) { is_zls = true; } else { - std.debug.print("Unknown subcommand '{s}'. Use 'use zig ' or 'use zls '.\n", .{scmd}); + var color = try util_color.Color.RuntimeStyle.init(allocator); + defer color.deinit(); + + try color.bold().red().printErr( + "Unknown subcommand '{s}'. Use 'use zig ' or 'use zls '.\n", + .{scmd}, + ); return; } const version = param orelse { - std.debug.print("Please specify a version to use: 'use zig/zls '.\n", .{}); + var color = try util_color.Color.RuntimeStyle.init(allocator); + defer color.deinit(); + + try color.bold().red().printErr( + "Please specify a version to use: 'use {s} '.\n", + .{scmd}, + ); return; }; @@ -231,14 +295,20 @@ fn use_version(subcmd: ?[]const u8, param: ?[]const u8) !void { } else if (param) |version| { // set zig version try alias.set_version(version, false); - // set zls version - // try alias.set_version(version, true); } else { - std.debug.print("Please specify a version to use: 'use zig/zls ' or 'use '.\n", .{}); + var color = try util_color.Color.RuntimeStyle.init(allocator); + defer color.deinit(); + + try color.bold().red().printErr( + "Please specify a version to use: 'use zig/zls ' or 'use '.\n", + .{}, + ); } } fn remove_version(subcmd: ?[]const u8, param: ?[]const u8) !void { + const allocator = util_data.get_allocator(); + if (subcmd) |scmd| { var is_zls: bool = undefined; @@ -247,12 +317,24 @@ fn remove_version(subcmd: ?[]const u8, param: ?[]const u8) !void { } else if (util_tool.eql_str(scmd, "zls")) { is_zls = true; } else { - std.debug.print("Unknown subcommand '{s}'. Use 'remove zig ' or 'remove zls '.\n", .{scmd}); + var color = try util_color.Color.RuntimeStyle.init(allocator); + defer color.deinit(); + + try color.bold().red().printErr( + "Unknown subcommand '{s}'. Use 'remove zig ' or 'remove zls '.\n", + .{scmd}, + ); return; } const version = param orelse { - std.debug.print("Please specify a version: 'remove zig ' or 'remove zls '.\n", .{}); + var color = try util_color.Color.RuntimeStyle.init(allocator); + defer color.deinit(); + + try color.bold().red().printErr( + "Please specify a version: 'remove {s} '.\n", + .{scmd}, + ); return; }; @@ -263,45 +345,53 @@ fn remove_version(subcmd: ?[]const u8, param: ?[]const u8) !void { // set zls version try remove.remove(version, true); } else { - std.debug.print("Please specify a version to use: 'remove zig/zls ' or 'remove '.\n", .{}); - } -} + var color = try util_color.Color.RuntimeStyle.init(allocator); + defer color.deinit(); -fn set_default() !void { - std.debug.print("Handling 'default' command.\n", .{}); - // Your default code here + try color.bold().red().printErr( + "Please specify a version to remove: 'remove zig/zls ' or 'remove '.\n", + .{}, + ); + } } fn get_version() !void { - std.debug.print("zvm {}\n", .{options.zvm_version}); + comptime var color = util_color.Color.ComptimeStyle.init(); + + const version_message = color.cyan().fmt("zvm " ++ options.version ++ "\n"); + try color.print("{s}", .{version_message}); } fn display_help() !void { - const help_message = - \\Usage: - \\ zvm [args] - \\ - \\Commands: - \\ ls, list List all available versions of Zig or zls. - \\ i, install Install the specified version of Zig or zls. - \\ use Use the specified version of Zig or zls. - \\ remove Remove the specified version of Zig or zls - \\ --version Display the current version of zvm. - \\ --help Show this help message. - \\ - \\Example: - \\ zvm install 0.12.0 Install Zig and zls version 0.12.0. - \\ zvm install zig 0.12.0 Install Zig version 0.12.0. - \\ zvm use 0.12.0 Switch to using Zig version 0.12.0. - \\ zvm use zig 0.12.0 Switch to using Zig version 0.12.0. - \\ zvm remove zig 0.12.0 Remove Zig version 0.12.0. - \\ - \\For additional information and contributions, please visit https://github.com/hendriknielaender/zvm - ; - - std.debug.print(help_message, .{}); + comptime var color = util_color.Color.ComptimeStyle.init(); + + const usage_title = color.bold().magenta().fmt("Usage:"); + const commands_title = color.bold().magenta().fmt("Commands:"); + const examples_title = color.bold().magenta().fmt("Examples:"); + const additional_info_title = color.bold().magenta().fmt("Additional Information:"); + + const help_message = usage_title ++ + "\n zvm [args]\n\n" ++ + commands_title ++ + "\n ls, list List all available versions of Zig or zls.\n" ++ + " i, install Install the specified version of Zig or zls.\n" ++ + " use Use the specified version of Zig or zls.\n" ++ + " remove Remove the specified version of Zig or zls.\n" ++ + " --version Display the current version of zvm.\n" ++ + " --help Show this help message.\n\n" ++ + examples_title ++ + "\n zvm install 0.12.0 Install Zig and zls version 0.12.0.\n" ++ + " zvm install zig 0.12.0 Install Zig version 0.12.0.\n" ++ + " zvm use 0.12.0 Switch to using Zig version 0.12.0.\n" ++ + " zvm use zig 0.12.0 Switch to using Zig version 0.12.0.\n" ++ + " zvm remove zig 0.12.0 Remove Zig version 0.12.0.\n\n" ++ + additional_info_title ++ + "\n For additional information and contributions, please visit https://github.com/hendriknielaender/zvm\n\n"; + + try color.print("{s}", .{help_message}); } fn handle_unknown() !void { - std.debug.print("Unknown command. Use 'zvm --help' for usage information.\n", .{}); + comptime var color = util_color.Color.ComptimeStyle.init(); + try color.bold().red().print("Unknown command. Use 'zvm --help' for usage information.\n", .{}); } diff --git a/src/install.zig b/src/install.zig index 6f65347..61030ce 100644 --- a/src/install.zig +++ b/src/install.zig @@ -142,7 +142,8 @@ fn install_zig(version: []const u8, root_node: Progress.Node) !void { fn install_zls(version: []const u8) !void { const true_version = blk: { if (util_tool.eql_str("master", version)) { - std.debug.print("Sorry, the 'install zls' feature is not supported at this time. Please compile zls locally.", .{}); + const zls_message = "Sorry, the 'install zls' feature is not supported at this time. Please compile zls locally."; + try std.io.getStdOut().writer().print("{s}", .{zls_message}); return; } diff --git a/src/util/color.zig b/src/util/color.zig new file mode 100644 index 0000000..fc6abfc --- /dev/null +++ b/src/util/color.zig @@ -0,0 +1,161 @@ +// color.zig +const std = @import("std"); + +pub const Color = struct { + /// Compile-Time Style Struct + pub const ComptimeStyle = struct { + open: []const u8 = "", + close: []const u8 = "", + preset: bool = false, + + pub inline fn fmt(self: *ComptimeStyle, comptime text: []const u8) []const u8 { + defer self.removeAll(); + return self.open ++ text ++ self.close; + } + + pub inline fn print(self: *ComptimeStyle, comptime format: []const u8, _: anytype) !void { + defer self.removeAll(); + const formatted_text = self.fmt(format); + try std.io.getStdOut().writer().print("{s}", .{formatted_text}); + } + + pub inline fn printErr(self: *ComptimeStyle, comptime format: []const u8, _: anytype) !void { + defer self.removeAll(); + const formatted_text = self.fmt(format); + try std.io.getStdErr().writer().print("{s}", .{formatted_text}); + } + + pub inline fn add(self: *ComptimeStyle, comptime style_code: []const u8) *ComptimeStyle { + self.open = self.open ++ style_code; + self.close = "\x1b[0m" ++ self.close; + return self; + } + + inline fn removeAll(self: *ComptimeStyle) void { + if (self.preset) return; + self.open = ""; + self.close = ""; + } + + // Style methods + pub inline fn bold(self: *ComptimeStyle) *ComptimeStyle { + return self.add("\x1b[1m"); + } + + pub inline fn red(self: *ComptimeStyle) *ComptimeStyle { + return self.add("\x1b[31m"); + } + + pub inline fn green(self: *ComptimeStyle) *ComptimeStyle { + return self.add("\x1b[32m"); + } + + pub inline fn magenta(self: *ComptimeStyle) *ComptimeStyle { + return self.add("\x1b[35m"); + } + + pub inline fn cyan(self: *ComptimeStyle) *ComptimeStyle { + return self.add("\x1b[36m"); + } + + /// Initializes a new ComptimeStyle instance. + pub inline fn init() ComptimeStyle { + return ComptimeStyle{}; + } + }; + + /// Runtime Style Struct + pub const RuntimeStyle = struct { + open: std.ArrayList(u8), + close: std.ArrayList(u8), + allocator: std.mem.Allocator, + + /// Initializes a new RuntimeStyle instance. + pub fn init(allocator: std.mem.Allocator) !RuntimeStyle { + return RuntimeStyle{ + .open = std.ArrayList(u8).init(allocator), + .close = std.ArrayList(u8).init(allocator), + .allocator = allocator, + }; + } + + pub fn deinit(self: *RuntimeStyle) void { + self.open.deinit(); + self.close.deinit(); + } + + /// Adds a style code to the Style instance. + pub fn addStyle(self: *RuntimeStyle, style_code: []const u8) *RuntimeStyle { + // Ignore allocation errors for simplicity + self.open.appendSlice(style_code) catch {}; + // For correct closure, we need to append the corresponding reset code + self.close.appendSlice("\x1b[0m") catch {}; + return self; + } + + fn removeAll(self: *RuntimeStyle) void { + self.open.clearAndFree(); + self.close.clearAndFree(); + } + + /// Returns the formatted text with styles applied. + pub fn fmt(self: *RuntimeStyle, comptime format: []const u8, args: anytype) ![]u8 { + defer self.removeAll(); + + const formatted = try std.fmt.allocPrint(self.allocator, format, args); + + defer self.allocator.free(formatted); + return std.mem.concat(self.allocator, u8, &.{ self.open.items, formatted, self.close.items }); + } + + /// Prints the formatted text to stdout. + pub fn print(self: *RuntimeStyle, comptime format: []const u8, args: anytype) !void { + defer self.removeAll(); + + const formatted_text = try self.fmt(format, args); + defer self.allocator.free(formatted_text); + try std.io.getStdOut().writer().print("{s}", .{formatted_text}); + } + + /// Prints the formatted text to stderr. + pub fn printErr(self: *RuntimeStyle, comptime format: []const u8, args: anytype) !void { + defer self.removeAll(); + + const formatted_text = try self.fmt(format, args); + defer self.allocator.free(formatted_text); + try std.io.getStdErr().writer().print("{s}", .{formatted_text}); + } + + // Style methods + pub fn bold(self: *RuntimeStyle) *RuntimeStyle { + return self.addStyle("\x1b[1m"); + } + + pub fn red(self: *RuntimeStyle) *RuntimeStyle { + return self.addStyle("\x1b[31m"); + } + + pub fn green(self: *RuntimeStyle) *RuntimeStyle { + return self.addStyle("\x1b[32m"); + } + + pub fn magenta(self: *RuntimeStyle) *RuntimeStyle { + return self.addStyle("\x1b[35m"); + } + + pub fn cyan(self: *RuntimeStyle) *RuntimeStyle { + return self.addStyle("\x1b[36m"); + } + + /// Creates a preset RuntimeStyle. + pub fn createPreset(self: *RuntimeStyle) !RuntimeStyle { + defer self.removeAll(); + + return RuntimeStyle{ + .open = try self.open.clone(self.allocator.*), + .close = try self.close.clone(self.allocator.*), + .allocator = self.allocator, + }; + } + }; +}; diff --git a/src/util/minisign.zig b/src/util/minisign.zig index 4afd001..7d51c6a 100644 --- a/src/util/minisign.zig +++ b/src/util/minisign.zig @@ -51,7 +51,8 @@ pub const Signature = struct { var line: []const u8 = undefined; while (true) { line = tokenizer.next() orelse { - std.debug.print("No more lines to read. Invalid encoding.\n", .{}); + const err_msg = "No more lines to read. Invalid encoding.\n"; + try std.io.getStdErr().writer().print(err_msg, .{}); return Error.invalid_encoding; }; const trimmed_line = mem.trim(u8, line, " \t\r\n"); @@ -70,14 +71,16 @@ pub const Signature = struct { // Decode trusted comment const comment_line = tokenizer.next() orelse { - std.debug.print("Expected trusted comment line but none found.\n", .{}); + const err_msg = "Expected trusted comment line but none found.\n"; + try std.io.getStdErr().writer().print(err_msg, .{}); return Error.invalid_encoding; }; const comment_line_trimmed = mem.trim(u8, comment_line, " \t\r\n"); const trusted_comment_prefix = "trusted comment: "; if (!mem.startsWith(u8, comment_line_trimmed, trusted_comment_prefix)) { - std.debug.print("Trusted comment line does not start with the expected prefix.\n", .{}); + const err_msg = "Trusted comment line does not start with the expected prefix.\n"; + try std.io.getStdErr().writer().print(err_msg, .{}); return Error.invalid_encoding; } const trusted_comment_slice = comment_line_trimmed[trusted_comment_prefix.len..]; @@ -87,7 +90,8 @@ pub const Signature = struct { // Decode global signature const global_sig_line = tokenizer.next() orelse { - std.debug.print("Expected global signature line but none found.\n", .{}); + const err_msg = "Expected global signature line but none found.\n"; + try std.io.getStdErr().writer().print(err_msg, .{}); return Error.invalid_encoding; }; const global_sig_line_trimmed = mem.trim(u8, global_sig_line, " \t\r\n"); @@ -126,7 +130,8 @@ pub const PublicKey = struct { const trimmed_str = std.mem.trim(u8, str, " \t\r\n"); if (trimmed_str.len != 56) { // Base64 for 42-byte key - std.debug.print("Error: Public key string length is {d}, expected 56.\n", .{trimmed_str.len}); + const err_msg = "Error: Public key string length is {d}, expected 56.\n"; + try std.io.getStdErr().writer().print(err_msg, .{trimmed_str.len}); return Error.public_key_format_error; } @@ -136,7 +141,8 @@ pub const PublicKey = struct { const signature_algorithm = bin[0..2]; if (bin[0] != 0x45 or (bin[1] != 0x64 and bin[1] != 0x44)) { - std.debug.print("Unsupported signature algorithm: {x}\n", .{signature_algorithm}); + const err_msg = "Unsupported signature algorithm: {any}\n"; + try std.io.getStdErr().writer().print(err_msg, .{signature_algorithm}); return Error.unsupported_algorithm; }