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 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 e4a0f68..128f8f6 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,12 +1,12 @@ .{ .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/rockorager/zeit?ref=zig-0.15#ed2ca60db118414bda2b12df2039e33bad3b0b88", - .hash = "zeit-0.6.0-5I6bk0J9AgCVa0nnyL0lNY9Xa9F68hHq-ZarhuXNV-Jb", + .url = "git+https://github.com/rockorager/zeit?ref=0.16#fdaad9c5884f41a3640c768f4f9fc116e763c321", + .hash = "zeit-0.6.0-5I6bk3K6AgCFnc_9CN7KJjaeNJz1VuwP4DA7Gs9vgxuz", }, }, .paths = .{ diff --git a/example/example.zig b/example/example.zig index f4b628d..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,12 +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 env = try std.process.getEnvMap(allocator); - defer env.deinit(); +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.Environ.Map) !void { var buffer: [256]u8 = undefined; { @@ -40,8 +37,8 @@ pub fn main() !void { .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, &.{&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 @@ -57,7 +54,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(.{{}})", .{}); @@ -70,8 +67,8 @@ pub fn main() !void { { // 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 = "@%", @@ -87,10 +84,10 @@ pub fn main() !void { .info = "INFO", .debug = "DEBUG", }, - .mutex = .default, // default to std.Thread.Mutex + .mutex = .default, }); - var writer = f.writer(&buffer); - try log.init(allocator, &.{&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", .{}); @@ -101,8 +98,8 @@ pub fn main() !void { { // 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} @@ -114,8 +111,8 @@ pub fn main() !void { .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, &.{&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 7404d94..5a2b419 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. @@ -70,9 +70,10 @@ 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 = std.Io.Threaded.global_single_threaded.io(); 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{}, }; @@ -85,8 +86,9 @@ 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, + env: ?*const std.process.Environ.Map, ) !void { // Validate strftime format // This used to be comptime but sadly it's not possible since zig @@ -96,9 +98,13 @@ 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); } - + if (_io) |new_io| + io = new_io; if (config.time_format != .disabled) { - timezone = try zeit.local(allocator, 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); @@ -125,11 +131,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, @@ -138,7 +144,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 @@ -246,7 +252,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 { @@ -256,7 +262,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 { @@ -267,18 +273,18 @@ 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(), }; const time = if (config.time_format != .disabled) t: { - const now = zeit.instant(.{ .timezone = &timezone }) catch unreachable; + const now = zeit.instant(io, .{ .timezone = &timezone }) catch unreachable; break :t now.time(); } else {}; @@ -289,7 +295,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 {}; } @@ -299,10 +305,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 { @@ -543,8 +549,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, @@ -563,10 +569,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 {}; } }; @@ -643,16 +650,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, @@ -675,7 +677,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; @@ -730,7 +732,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, @@ -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", .{});