From a6453733f37aa031168c6fafc47e449d1e54905c Mon Sep 17 00:00:00 2001 From: Pascal Zittlau Date: Fri, 24 Oct 2025 14:05:36 +0200 Subject: [PATCH] init --- build.zig | 13 ++++++ build.zig.zon | 13 ++++++ src/root.zig | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 src/root.zig diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..1c4bda9 --- /dev/null +++ b/build.zig @@ -0,0 +1,13 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseSafe }); + const target = b.standardTargetOptions(.{}); + + const faller = b.addModule("faller", .{ + .root_source_file = b.path("src/root.zig"), + .optimize = optimize, + .target = target, + }); + faller.addImport("faller", faller); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..11f2c93 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,13 @@ +.{ + .name = .faller, + .version = "0.1.0", + .minimum_zig_version = "0.15.1", + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + "LICENSE", + "README.md", + }, + .fingerprint = 0x5d81e6140d8dd19c, +} diff --git a/src/root.zig b/src/root.zig new file mode 100644 index 0000000..77f77fc --- /dev/null +++ b/src/root.zig @@ -0,0 +1,108 @@ +const std = @import("std"); +const root = @import("root"); + +pub const options: Options = if (@hasDecl(root, "faller_options")) root.faller_options else .{}; + +/// The base logger to create scopes from. Or just use `Logger` directly. +pub const logger = Logger(&.{}); + +pub const Options = struct { + function: fn ( + comptime []const @Type(.enum_literal), + comptime []const u8, + anytype, + ) void = defaultLogFunction, + /// If more specialised ways to determine whether something should be logged are required, + /// replace this function. + /// This could be useful for instance for specifying it with environment variables or + /// `and`-based queries. + enabled: fn (comptime []const @Type(.enum_literal)) bool = logEnabled, + /// If there is at least one tag given this acts like a whitelist. Meaning if **any** of the + /// tags is present it is logged. May be overwritten by `tags_disabled`. + tags_enabled: []const @Type(.enum_literal) = &.{}, + /// If there is at least one tag given this acts like a blacklist. Meaning if **any** of the + /// tags is present it **isn't** logged. May overwrite `tags_enabled`. + tags_disabled: []const @Type(.enum_literal) = &.{}, + buffer_size: u64 = 64, +}; + +pub fn Logger(comptime base_tags: []const @Type(.enum_literal)) type { + return struct { + /// Creates a new logger with an extended tag prefix. + pub fn scope(comptime tag: @Type(.enum_literal)) type { + return Logger(base_tags ++ [_]@Type(.enum_literal){tag}); + } + + /// Log with one `tag`. Checks whether the log is enabled. + pub fn log( + comptime tag: @Type(.enum_literal), + comptime format: []const u8, + args: anytype, + ) void { + logTags(&.{tag}, format, args); + } + + /// Log with multiple `tags`. Checks whether the log is enabled. + pub fn logTags( + comptime tags: []const @Type(.enum_literal), + comptime format: []const u8, + args: anytype, + ) void { + const all_tags = comptime base_tags ++ tags; + if (!options.enabled(all_tags)) return; + + options.function(all_tags, format, args); + } + }; +} + +/// Return `false` if `as` and `bs` have no common member. Else return `true`. +fn intersect(comptime as: []const @Type(.enum_literal), comptime bs: []const @Type(.enum_literal)) bool { + for (as) |a| { + for (bs) |b| { + if (std.mem.eql(u8, @tagName(a), @tagName(b))) { + return true; + } + } + } + return false; +} + +/// Checks whether the log is enabled for a specific combination of `tags`. Depends on the `options` +/// set. +pub fn logEnabled(comptime tags: []const @Type(.enum_literal)) bool { + comptime { + // If `tags` has at least one in `options.tags_disabled` then we never log. + if (options.tags_disabled.len > 0) { + if (intersect(tags, options.tags_disabled)) { + return false; + } + } + + // If `tags` has at least one in `options.tags_enabled` then we should log. + if (options.tags_enabled.len > 0) { + return intersect(tags, options.tags_enabled); + } + } + // If neither is set then we just log everything. + return true; +} + +fn defaultLogFunction( + comptime tags: []const @Type(.enum_literal), + comptime format: []const u8, + args: anytype, +) void { + if (tags.len == 0) @compileError("Need at least one tag for logging."); + + comptime var prefix: []const u8 = ""; + comptime for (tags) |tag| { + prefix = prefix ++ @tagName(tag) ++ "|"; + }; + prefix = prefix[0 .. prefix.len - 1]; // Remove last '|' + + var buffer: [options.buffer_size]u8 = undefined; + const stderr = std.debug.lockStderrWriter(&buffer); + defer std.debug.unlockStderrWriter(); + nosuspend stderr.print(prefix ++ ": " ++ format ++ "\n", args) catch return; +}