Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <path>` 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
Expand Down
7 changes: 7 additions & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
Expand Down
8 changes: 4 additions & 4 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/app/runtime.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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});
};
Expand Down
53 changes: 53 additions & 0 deletions src/cli_args.zig
Original file line number Diff line number Diff line change
@@ -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 <path>]\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.?);
}
13 changes: 12 additions & 1 deletion src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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");
Expand Down