diff --git a/docs/configuration.md b/docs/configuration.md index c99a41c..c1b3ce1 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -159,6 +159,8 @@ Logs rotate by size. When `architect.log` exceeds 10 MiB, it is archived to a ti Event markers (startup/shutdown and grid/full view transitions) are always recorded at `INFO` level. +Pass `--log-dir ` on the command line to write `architect.log` (and its rotated archives) to a custom directory instead of the default location, e.g. `zig build run -- --log-dir /path/to/logs` or `just run --log-dir /path/to/logs`. + ### Worktree Configuration ```toml diff --git a/docs/development.md b/docs/development.md index ad25dba..1f11db8 100644 --- a/docs/development.md +++ b/docs/development.md @@ -58,6 +58,13 @@ just run zig build run ``` +Run with a custom log directory (see `docs/configuration.md` for logging details): +```bash +just run --log-dir .tmp/architect-debug-logs +# or +zig build run -- --log-dir .tmp/architect-debug-logs +``` + ## Dependencies and Tooling - **ghostty-vt** is fetched as a pinned tarball via the Zig package manager (`build.zig.zon`). diff --git a/justfile b/justfile index e5adcd9..5b73939 100644 --- a/justfile +++ b/justfile @@ -17,11 +17,11 @@ build: test: zig build test -run: - zig build run +run *args: + zig build run -- {{args}} -run-release: - zig build run -Doptimize=ReleaseFast +run-release *args: + zig build run -Doptimize=ReleaseFast -- {{args}} lint: #!/usr/bin/env bash diff --git a/src/app/runtime.zig b/src/app/runtime.zig index 6f9647f..c0951b2 100644 --- a/src/app/runtime.zig +++ b/src/app/runtime.zig @@ -1193,7 +1193,7 @@ fn startQuitFlow( return false; } -pub fn run() !void { +pub fn run(log_dir_override: ?[]const u8) !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); @@ -1243,6 +1243,7 @@ pub fn run() !void { var file_logging_enabled = false; logging_mod.init(allocator, .{ .min_level = config.logging.getMinLevel(), + .directory_override = log_dir_override, }) catch |err| { std.debug.print("Failed to initialize file logging: {}\n", .{err}); }; diff --git a/src/cli_args.zig b/src/cli_args.zig new file mode 100644 index 0000000..edaf486 --- /dev/null +++ b/src/cli_args.zig @@ -0,0 +1,53 @@ +const std = @import("std"); + +pub const ParsedArgs = struct { + log_dir_override: ?[]const u8 = null, +}; + +pub const ParseError = error{ + MissingLogDirValue, + UnknownArgument, +}; + +pub const usage_text = "usage: architect [--log-dir ]\n"; + +/// Parses CLI arguments, excluding argv[0] (the executable path). +/// Returned slices borrow from `args` and are only valid as long as `args` is. +pub fn parse(args: []const []const u8) ParseError!ParsedArgs { + var result: ParsedArgs = .{}; + var i: usize = 0; + while (i < args.len) : (i += 1) { + const arg = args[i]; + if (std.mem.eql(u8, arg, "--log-dir")) { + i += 1; + if (i >= args.len) return error.MissingLogDirValue; + result.log_dir_override = args[i]; + } else { + return error.UnknownArgument; + } + } + return result; +} + +test "parse with no arguments leaves log_dir_override unset" { + const parsed = try parse(&.{}); + try std.testing.expectEqual(@as(?[]const u8, null), parsed.log_dir_override); +} + +test "parse --log-dir sets log_dir_override" { + const parsed = try parse(&.{ "--log-dir", "/tmp/architect-logs" }); + try std.testing.expectEqualStrings("/tmp/architect-logs", parsed.log_dir_override.?); +} + +test "parse --log-dir without a value returns MissingLogDirValue" { + try std.testing.expectError(error.MissingLogDirValue, parse(&.{"--log-dir"})); +} + +test "parse rejects unknown arguments" { + try std.testing.expectError(error.UnknownArgument, parse(&.{"--bogus"})); +} + +test "parse takes the last --log-dir when passed multiple times" { + const parsed = try parse(&.{ "--log-dir", "/first", "--log-dir", "/second" }); + try std.testing.expectEqualStrings("/second", parsed.log_dir_override.?); +} diff --git a/src/main.zig b/src/main.zig index 38f2bd3..83902a3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2,6 +2,7 @@ const std = @import("std"); const builtin = @import("builtin"); const runtime = @import("app/runtime.zig"); const logging = @import("logging.zig"); +const cli_args = @import("cli_args.zig"); pub const std_options: std.Options = .{ // Keep compile-time logging permissive; runtime filtering is handled by @@ -11,7 +12,16 @@ pub const std_options: std.Options = .{ }; pub fn main() !void { - try runtime.run(); + const allocator = std.heap.page_allocator; + const argv = try std.process.argsAlloc(allocator); + defer std.process.argsFree(allocator, argv); + + const parsed = cli_args.parse(argv[1..]) catch |err| { + std.debug.print("architect: {s}\n{s}", .{ @errorName(err), cli_args.usage_text }); + std.process.exit(1); + }; + + try runtime.run(parsed.log_dir_override); } // Zig only collects tests from files reachable through this block, so every @@ -27,6 +37,7 @@ test { _ = @import("app/layout.zig"); _ = @import("app/runtime.zig"); _ = @import("app/terminal_history.zig"); + _ = @import("cli_args.zig"); _ = @import("colors.zig"); _ = @import("config.zig"); _ = @import("font.zig");