From 226eeb6b0c8754920a7d5088611e7ce15978a0d5 Mon Sep 17 00:00:00 2001 From: Antoine GAGNIERE Date: Sun, 9 Nov 2025 17:13:03 +0100 Subject: [PATCH 1/6] Zig 0.16: start using IO as a parameter --- build.zig.zon | 4 ++-- example/example.zig | 18 ++++++++++++++---- src/axe.zig | 18 ++++++++++-------- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index e4a0f68..79c3c5d 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,8 +5,8 @@ .minimum_zig_version = "0.15.1", .dependencies = .{ .zeit = .{ - .url = "git+https://github.com/rockorager/zeit?ref=zig-0.15#ed2ca60db118414bda2b12df2039e33bad3b0b88", - .hash = "zeit-0.6.0-5I6bk0J9AgCVa0nnyL0lNY9Xa9F68hHq-ZarhuXNV-Jb", + .url = "git+https://github.com/elerch/zeit?ref=main#8190461dc1f892f6370fa9d5cd76690aac0e1c71", + .hash = "zeit-0.6.0-5I6bk99-AgDNMIDuw2Zcoe_9QYIpzwZJqeqMpU54egTd", }, }, .paths = .{ diff --git a/example/example.zig b/example/example.zig index f4b628d..831dc1b 100644 --- a/example/example.zig +++ b/example/example.zig @@ -18,8 +18,18 @@ pub fn main() !void { var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); const allocator = gpa.allocator(); + + var threaded: std.Io.Threaded = .init(allocator); + defer threaded.deinit(); + const io = threaded.io(); + var env = try std.process.getEnvMap(allocator); defer env.deinit(); + + return juicyMain(allocator, io, env); +} + +pub fn juicyMain(allocator: std.mem.Allocator, io: std.Io, env: std.process.EnvMap) !void { var buffer: [256]u8 = undefined; { @@ -41,7 +51,7 @@ pub fn main() !void { .mutex = .none, // none by default }); var writer = std.fs.File.stdout().writer(&buffer); - try stdout_log.init(allocator, &.{&writer.interface}, &env); + try stdout_log.init(allocator, io, &.{&writer.interface}, &env); defer stdout_log.deinit(allocator); // wait we actually don't want stderr logging let's disable it @@ -57,7 +67,7 @@ pub fn main() !void { // writers, it should be called at the very start of the program. // std.log supports all the features of axe.Axe even additional writers, // time or custom mutex. - try std_log.init(std_log_fba.allocator(), null, &env); + try std_log.init(std_log_fba.allocator(), io, null, &env); // defer std_log.deinit(allocator); std.log.info("std.log.info with axe.Axe(.{{}})", .{}); @@ -90,7 +100,7 @@ pub fn main() !void { .mutex = .default, // default to std.Thread.Mutex }); var writer = f.writer(&buffer); - try log.init(allocator, &.{&writer.interface}, &env); + try log.init(allocator, io, &.{&writer.interface}, &env); defer log.deinit(allocator); log.debug("Hello! This will have no color if NO_COLOR is defined or if piped", .{}); @@ -115,7 +125,7 @@ pub fn main() !void { .color = .never, }); var writer = json_file.writer(&buffer); - try json_log.init(allocator, &.{&writer.interface}, &env); + try json_log.init(allocator, io, &.{&writer.interface}, &env); defer json_log.deinit(allocator); json_log.debug("\"json log\"", .{}); diff --git a/src/axe.zig b/src/axe.zig index 7404d94..882f4e7 100644 --- a/src/axe.zig +++ b/src/axe.zig @@ -70,6 +70,7 @@ pub fn Axe(comptime config: Config) type { var stderr_tty_config = defaultTtyConfig(config.color); var writers_tty_config = defaultTtyConfig(config.color); var timezone = if (config.time_format != .disabled) zeit.utc else {}; + var io: std.Io = undefined; var mutex = switch (config.mutex) { .none, .function => {}, .default => if (builtin.single_threaded) {} else std.Thread.Mutex{}, @@ -85,6 +86,7 @@ pub fn Axe(comptime config: Config) type { /// `env` is only used during initialization and is not stored. pub fn init( allocator: std.mem.Allocator, + _io: std.Io, additional_writers: ?[]const *std.Io.Writer, env: ?*const std.process.EnvMap, ) !void { @@ -96,9 +98,9 @@ pub fn Axe(comptime config: Config) type { var void_writer: std.Io.Writer.Discarding = .init(&.{}); try bogus.strftime(&void_writer.writer, config.time_format.strftime); } - + io = _io; if (config.time_format != .disabled) { - timezone = try zeit.local(allocator, env); + timezone = try zeit.local(allocator, io, env); } if (additional_writers) |_writers| { writers = try allocator.dupe(*std.Io.Writer, _writers); @@ -278,7 +280,7 @@ pub fn Axe(comptime config: Config) type { }; const time = if (config.time_format != .disabled) t: { - const now = zeit.instant(.{ .timezone = &timezone }) catch unreachable; + const now = zeit.instant(.{ .timezone = &timezone, .io = io }) catch unreachable; break :t now.time(); } else {}; @@ -746,7 +748,7 @@ test "log without styles" { .styles = .none, .quiet = true, }); - try log.init(std.testing.allocator, &.{&writer}, null); + try log.init(std.testing.allocator, std.testing.io, &.{&writer}, null); defer log.deinit(std.testing.allocator); log.info("Hello, {s}!", .{"world"}); @@ -787,7 +789,7 @@ test "log with complex config" { .quiet = true, .mutex = .default, }); - try log.init(std.testing.allocator, &.{&writer}, null); + try log.init(std.testing.allocator, std.testing.io, &.{&writer}, null); defer log.deinit(std.testing.allocator); log.info("Hello, {s}!", .{"world"}); @@ -824,7 +826,7 @@ test "time format" { }, }); log.quiet = true; - try log.init(std.testing.allocator, &.{&dest.writer}, null); + try log.init(std.testing.allocator, std.testing.io, &.{&dest.writer}, null); defer log.deinit(std.testing.allocator); log.info("Hello {c}", .{'W'}); @@ -856,7 +858,7 @@ test "json log" { .quiet = true, .color = .never, }); - try log.init(std.testing.allocator, &.{&writer}, null); + try log.init(std.testing.allocator, std.testing.io, &.{&writer}, null); defer log.deinit(std.testing.allocator); log.debug("\"json log\"", .{}); @@ -890,7 +892,7 @@ test "updateTtyConfig" { .color = .never, .quiet = true, }); - try log.init(std.testing.allocator, &.{&writer}, null); + try log.init(std.testing.allocator, std.testing.io, &.{&writer}, null); defer log.deinit(std.testing.allocator); log.debug("No color", .{}); From 9892eb1567d9d95e2281ab913f1b1a7afd1b6e34 Mon Sep 17 00:00:00 2001 From: Antoine GAGNIERE Date: Sun, 9 Nov 2025 17:29:11 +0100 Subject: [PATCH 2/6] Use zig master in CI --- .github/workflows/ci.yml | 4 ++-- .github/workflows/docs.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9beffa1..3999db3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/checkout@v5 - uses: mlugg/setup-zig@v2 with: - version: 0.15.1 + version: master - run: zig build test --summary all - run: zig build run working-directory: example @@ -32,5 +32,5 @@ jobs: - uses: actions/checkout@v5 - uses: mlugg/setup-zig@v2 with: - version: 0.15.1 + version: master - run: zig fmt --check . diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index c86f6bc..5de6687 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/configure-pages@v5 - uses: mlugg/setup-zig@v2 with: - version: 0.15.1 + version: master - run: zig build docs - uses: actions/upload-pages-artifact@v4 id: deployment From d3b65f689325be08a1c47d83ccc729e637d48f86 Mon Sep 17 00:00:00 2001 From: Antoine GAGNIERE Date: Sun, 9 Nov 2025 17:30:17 +0100 Subject: [PATCH 3/6] Bump axe version --- build.zig.zon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 79c3c5d..be7771e 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,8 +1,8 @@ .{ .name = .axe, - .version = "0.7.0", + .version = "0.8.0", .fingerprint = 0x6c6a1e2ce5546233, - .minimum_zig_version = "0.15.1", + .minimum_zig_version = "0.16.0", .dependencies = .{ .zeit = .{ .url = "git+https://github.com/elerch/zeit?ref=main#8190461dc1f892f6370fa9d5cd76690aac0e1c71", From f046d1b4bf6c2d51a1472d28f430008e967ac921 Mon Sep 17 00:00:00 2001 From: Antoine GAGNIERE Date: Fri, 27 Mar 2026 17:39:35 +0100 Subject: [PATCH 4/6] Update for zig 0.16 - Io.tty -> Io.Terminal - Thread.Mutex -> Io.Mutex - process.EnvMap -> process.Environ.Map - fs -> Io - @Type(.enum_literal) -> @EnumLiteral() --- example/example.zig | 45 +++++++++++++++--------------------------- src/axe.zig | 48 +++++++++++++++++++++------------------------ 2 files changed, 38 insertions(+), 55 deletions(-) diff --git a/example/example.zig b/example/example.zig index 831dc1b..84a96f8 100644 --- a/example/example.zig +++ b/example/example.zig @@ -2,9 +2,7 @@ const std = @import("std"); const axe = @import("axe"); const std_log = axe.Axe(.{ - // .progress_stderr uses std.Progress.[un]lockStdErr. - // This specific mutex is recommended for a global stderr logger. - .mutex = .{ .function = .progress_stderr }, + .mutex = .default, }); pub const std_options: std.Options = .{ @@ -14,22 +12,11 @@ pub const std_options: std.Options = .{ var std_log_fba_buffer: [4 * 4096]u8 = undefined; var std_log_fba: std.heap.FixedBufferAllocator = .init(&std_log_fba_buffer); -pub fn main() !void { - var gpa: std.heap.DebugAllocator(.{}) = .init; - defer _ = gpa.deinit(); - const allocator = gpa.allocator(); - - var threaded: std.Io.Threaded = .init(allocator); - defer threaded.deinit(); - const io = threaded.io(); - - var env = try std.process.getEnvMap(allocator); - defer env.deinit(); - - return juicyMain(allocator, io, env); +pub fn main(init: std.process.Init) !void { + return juicyMain(init.gpa, init.io, init.environ_map); } -pub fn juicyMain(allocator: std.mem.Allocator, io: std.Io, env: std.process.EnvMap) !void { +pub fn juicyMain(allocator: std.mem.Allocator, io: std.Io, env: *std.process.Environ.Map) !void { var buffer: [256]u8 = undefined; { @@ -50,8 +37,8 @@ pub fn juicyMain(allocator: std.mem.Allocator, io: std.Io, env: std.process.EnvM .quiet = false, // disable stderr logging, default is false .mutex = .none, // none by default }); - var writer = std.fs.File.stdout().writer(&buffer); - try stdout_log.init(allocator, io, &.{&writer.interface}, &env); + var writer = std.Io.File.stdout().writer(io, &buffer); + try stdout_log.init(allocator, io, &.{&writer.interface}, env); defer stdout_log.deinit(allocator); // wait we actually don't want stderr logging let's disable it @@ -67,7 +54,7 @@ pub fn juicyMain(allocator: std.mem.Allocator, io: std.Io, env: std.process.EnvM // writers, it should be called at the very start of the program. // std.log supports all the features of axe.Axe even additional writers, // time or custom mutex. - try std_log.init(std_log_fba.allocator(), io, null, &env); + try std_log.init(std_log_fba.allocator(), io, null, env); // defer std_log.deinit(allocator); std.log.info("std.log.info with axe.Axe(.{{}})", .{}); @@ -80,8 +67,8 @@ pub fn juicyMain(allocator: std.mem.Allocator, io: std.Io, env: std.process.EnvM { // Custom writers: - var f = try std.fs.cwd().createFile("log.txt", .{}); - defer f.close(); + var f = try std.Io.Dir.cwd().createFile(io, "log.txt", .{}); + defer f.close(io); const log = axe.Axe(.{ .format = "%t %l%s%L %m\n", .scope_format = "@%", @@ -97,10 +84,10 @@ pub fn juicyMain(allocator: std.mem.Allocator, io: std.Io, env: std.process.EnvM .info = "INFO", .debug = "DEBUG", }, - .mutex = .default, // default to std.Thread.Mutex + .mutex = .default, }); - var writer = f.writer(&buffer); - try log.init(allocator, io, &.{&writer.interface}, &env); + var writer = f.writer(io, &buffer); + try log.init(allocator, io, &.{&writer.interface}, env); defer log.deinit(allocator); log.debug("Hello! This will have no color if NO_COLOR is defined or if piped", .{}); @@ -111,8 +98,8 @@ pub fn juicyMain(allocator: std.mem.Allocator, io: std.Io, env: std.process.EnvM { // JSON log: - var json_file = try std.fs.cwd().createFile("log.json", .{}); - defer json_file.close(); + var json_file = try std.Io.Dir.cwd().createFile(io, "log.json", .{}); + defer json_file.close(io); const json_log = axe.Axe(.{ .format = \\{"level":"%l",%s"time":"%t","data":%m} @@ -124,8 +111,8 @@ pub fn juicyMain(allocator: std.mem.Allocator, io: std.Io, env: std.process.EnvM .time_format = .{ .gofmt = .rfc3339 }, // .rfc3339 is a preset but custom format is also possible .color = .never, }); - var writer = json_file.writer(&buffer); - try json_log.init(allocator, io, &.{&writer.interface}, &env); + var writer = json_file.writer(io, &buffer); + try json_log.init(allocator, io, &.{&writer.interface}, env); defer json_log.deinit(allocator); json_log.debug("\"json log\"", .{}); diff --git a/src/axe.zig b/src/axe.zig index 882f4e7..5bc5d41 100644 --- a/src/axe.zig +++ b/src/axe.zig @@ -2,7 +2,7 @@ const std = @import("std"); const builtin = @import("builtin"); const zeit = @import("zeit"); const windows = std.os.windows; -const tty = std.Io.tty; +const tty = std.Io.Terminal; pub const Config = struct { /// The format to use for the log messages. @@ -73,7 +73,7 @@ pub fn Axe(comptime config: Config) type { var io: std.Io = undefined; var mutex = switch (config.mutex) { .none, .function => {}, - .default => if (builtin.single_threaded) {} else std.Thread.Mutex{}, + .default => if (builtin.single_threaded) {} else std.Io.Mutex.init, .custom => |T| T{}, }; @@ -88,7 +88,7 @@ pub fn Axe(comptime config: Config) type { allocator: std.mem.Allocator, _io: std.Io, additional_writers: ?[]const *std.Io.Writer, - env: ?*const std.process.EnvMap, + env: ?*const std.process.Environ.Map, ) !void { // Validate strftime format // This used to be comptime but sadly it's not possible since zig @@ -127,11 +127,11 @@ pub fn Axe(comptime config: Config) type { pub fn updateTtyConfig(color: Color) void { writers_tty_config = defaultTtyConfig(color); stderr_tty_config = switch (color) { - .auto => .detect(std.fs.File.stderr()), + .auto => tty.Mode.detect(io, std.Io.File.stderr(), false, false) catch .no_color, .always => if (builtin.os.tag == .windows) - switch (tty.Config.detect(std.fs.File.stderr())) { + switch (tty.Mode.detect(io, std.Io.File.stderr(), false, false) catch .no_color) { .no_color, .escape_codes => .escape_codes, - .windows_api => |ctx| .{ .windows_api = ctx }, + .windows_api => |wa| .{ .windows_api = wa }, } else .escape_codes, @@ -140,7 +140,7 @@ pub fn Axe(comptime config: Config) type { } /// Returns a scoped logging namespace that logs all messages using the scope provided. - pub fn scoped(comptime scope: @Type(.enum_literal)) type { + pub fn scoped(comptime scope: @EnumLiteral()) type { return struct { /// Log an error message. This log level is intended to be used /// when something has gone wrong. This might be recoverable or might @@ -248,7 +248,7 @@ pub fn Axe(comptime config: Config) type { /// ``` pub fn log( comptime level: Level, - comptime scope: @Type(.enum_literal), + comptime scope: @EnumLiteral(), comptime format: []const u8, args: anytype, ) void { @@ -258,7 +258,7 @@ pub fn Axe(comptime config: Config) type { fn logAt( comptime src: ?std.builtin.SourceLocation, // should this be comptime? comptime level: Level, - comptime scope: @Type(.enum_literal), + comptime scope: @EnumLiteral(), comptime format: []const u8, args: anytype, ) void { @@ -269,13 +269,13 @@ pub fn Axe(comptime config: Config) type { switch (config.mutex) { .none => {}, .function => |f| f.lock(), - .default => if (!builtin.single_threaded) mutex.lock(), + .default => if (!builtin.single_threaded) mutex.lockUncancelable(io), .custom => mutex.lock(), } defer switch (config.mutex) { .none => {}, .function => |f| f.unlock(), - .default => if (!builtin.single_threaded) mutex.unlock(), + .default => if (!builtin.single_threaded) mutex.unlock(io), .custom => mutex.unlock(), }; @@ -291,7 +291,7 @@ pub fn Axe(comptime config: Config) type { } if (!quiet) { var buffer: [256]u8 = undefined; - var stderr = std.fs.File.stderr().writer(&buffer); + var stderr = std.Io.File.stderr().writer(io, &buffer); print(src, &stderr.interface, stderr_tty_config, time, level, scope, format, args); stderr.interface.flush() catch {}; } @@ -301,10 +301,10 @@ pub fn Axe(comptime config: Config) type { fn print( comptime src: ?std.builtin.SourceLocation, writer: *std.Io.Writer, - tty_config: tty.Config, + tty_config: tty.Mode, time: if (config.time_format != .disabled) zeit.Time else void, comptime level: Level, - comptime scope: @Type(.enum_literal), + comptime scope: @EnumLiteral(), comptime format: []const u8, args: anytype, ) void { @@ -545,8 +545,8 @@ pub const Style = union(enum) { } } - fn applyWindows(style: Style, ctx: tty.Config.WindowsContext) void { - const attributes = switch (style) { + fn applyWindows(style: Style, wa: tty.Mode.WindowsApi) void { + const attributes: windows.WORD = switch (style) { .black => 0, .red => windows.FOREGROUND_RED, .green => windows.FOREGROUND_GREEN, @@ -565,10 +565,11 @@ pub const Style = union(enum) { .bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, // "dim" is not supported using basic character attributes, but let's still make it do *something*. .dim => windows.FOREGROUND_INTENSITY, - .reset => ctx.reset_attributes, + .reset => wa.reset_attributes, else => return, }; - windows.SetConsoleTextAttribute(ctx.handle, attributes) catch unreachable; + var op = windows.CONSOLE.USER_IO.SET_TEXT_ATTRIBUTE(attributes); + _ = op.operate(wa.io, wa.file) catch {}; } }; @@ -645,16 +646,11 @@ pub const GoTimeFormat = struct { pub const FunctionMutex = struct { lock: fn () void, unlock: fn () void, - - pub const progress_stderr: FunctionMutex = .{ - .lock = std.Progress.lockStdErr, - .unlock = std.Progress.unlockStdErr, - }; }; inline fn callWithStyles( writer: *std.Io.Writer, - tty_config: tty.Config, + tty_config: tty.Mode, comptime styles: []const Style, comptime callback: anytype, args: anytype, @@ -677,7 +673,7 @@ inline fn callWithStyles( } } -fn parseScopeFormat(comptime format: []const u8, comptime scope: @Type(.enum_literal)) []const u8 { +fn parseScopeFormat(comptime format: []const u8, comptime scope: @EnumLiteral()) []const u8 { comptime { var text: []const u8 = ""; var i: usize = 0; @@ -732,7 +728,7 @@ fn writeLocation( } } -inline fn defaultTtyConfig(color: Color) tty.Config { +inline fn defaultTtyConfig(color: Color) tty.Mode { return switch (color) { .always => .escape_codes, .auto, .never => .no_color, From 93e8c9bdec483876b6d219b749bc181fd78b013c Mon Sep 17 00:00:00 2001 From: Antoine GAGNIERE Date: Fri, 27 Mar 2026 17:59:11 +0100 Subject: [PATCH 5/6] (Bump zeit) --- .gitignore | 1 + build.zig.zon | 4 ++-- src/axe.zig | 9 ++++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 73e6197..cffdb20 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .zig-cache/ zig-out/ +zig-pkg/ .direnv/ example/log.txt diff --git a/build.zig.zon b/build.zig.zon index be7771e..128f8f6 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,8 +5,8 @@ .minimum_zig_version = "0.16.0", .dependencies = .{ .zeit = .{ - .url = "git+https://github.com/elerch/zeit?ref=main#8190461dc1f892f6370fa9d5cd76690aac0e1c71", - .hash = "zeit-0.6.0-5I6bk99-AgDNMIDuw2Zcoe_9QYIpzwZJqeqMpU54egTd", + .url = "git+https://github.com/rockorager/zeit?ref=0.16#fdaad9c5884f41a3640c768f4f9fc116e763c321", + .hash = "zeit-0.6.0-5I6bk3K6AgCFnc_9CN7KJjaeNJz1VuwP4DA7Gs9vgxuz", }, }, .paths = .{ diff --git a/src/axe.zig b/src/axe.zig index 5bc5d41..496edf8 100644 --- a/src/axe.zig +++ b/src/axe.zig @@ -70,7 +70,7 @@ pub fn Axe(comptime config: Config) type { var stderr_tty_config = defaultTtyConfig(config.color); var writers_tty_config = defaultTtyConfig(config.color); var timezone = if (config.time_format != .disabled) zeit.utc else {}; - var io: std.Io = undefined; + var io: std.Io = std.Io.Threaded.global_single_threaded.io(); var mutex = switch (config.mutex) { .none, .function => {}, .default => if (builtin.single_threaded) {} else std.Io.Mutex.init, @@ -100,7 +100,10 @@ pub fn Axe(comptime config: Config) type { } io = _io; if (config.time_format != .disabled) { - timezone = try zeit.local(allocator, io, env); + timezone = try zeit.local(allocator, io, .{ + .tz = if (env) |e| e.get("TZ") else null, + .tzdir = if (env) |e| e.get("TZDIR") else null, + }); } if (additional_writers) |_writers| { writers = try allocator.dupe(*std.Io.Writer, _writers); @@ -280,7 +283,7 @@ pub fn Axe(comptime config: Config) type { }; const time = if (config.time_format != .disabled) t: { - const now = zeit.instant(.{ .timezone = &timezone, .io = io }) catch unreachable; + const now = zeit.instant(io, .{ .timezone = &timezone }) catch unreachable; break :t now.time(); } else {}; From 657ef5b79a57bfea7a975c7161b90c503be4b0d3 Mon Sep 17 00:00:00 2001 From: Antoine GAGNIERE Date: Mon, 30 Mar 2026 00:45:56 +0200 Subject: [PATCH 6/6] Review: init's IO is optional since axe's IO has a default value --- src/axe.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/axe.zig b/src/axe.zig index 496edf8..5a2b419 100644 --- a/src/axe.zig +++ b/src/axe.zig @@ -86,7 +86,7 @@ pub fn Axe(comptime config: Config) type { /// `env` is only used during initialization and is not stored. pub fn init( allocator: std.mem.Allocator, - _io: std.Io, + _io: ?std.Io, additional_writers: ?[]const *std.Io.Writer, env: ?*const std.process.Environ.Map, ) !void { @@ -98,7 +98,8 @@ pub fn Axe(comptime config: Config) type { var void_writer: std.Io.Writer.Discarding = .init(&.{}); try bogus.strftime(&void_writer.writer, config.time_format.strftime); } - io = _io; + if (_io) |new_io| + io = new_io; if (config.time_format != .disabled) { timezone = try zeit.local(allocator, io, .{ .tz = if (env) |e| e.get("TZ") else null,