From 25a7d06d4031a4288d58ef339e1326dc15b7879b Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Tue, 3 Mar 2026 13:14:07 -0800 Subject: [PATCH] config.zig refactorings --- src/config.zig | 64 ++++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/src/config.zig b/src/config.zig index 400e0a0..c2f4388 100644 --- a/src/config.zig +++ b/src/config.zig @@ -1,5 +1,7 @@ const std = @import("std"); +const EnvMap = std.StringHashMap([]const u8); + pub const Config = struct { twelvedata_key: ?[]const u8 = null, polygon_key: ?[]const u8 = null, @@ -8,7 +10,10 @@ pub const Config = struct { openfigi_key: ?[]const u8 = null, cache_dir: []const u8, allocator: ?std.mem.Allocator = null, + /// Raw .env file contents (keys/values in env_map point into this). env_buf: ?[]const u8 = null, + /// Parsed KEY=VALUE pairs from .env file. + env_map: ?EnvMap = null, pub fn fromEnv(allocator: std.mem.Allocator) Config { var self = Config{ @@ -16,8 +21,11 @@ pub const Config = struct { .allocator = allocator, }; - // Try loading .env file from the project directory or home directory - self.env_buf = loadEnvFile(allocator); + // Try loading .env file from the current working directory + self.env_buf = std.fs.cwd().readFileAlloc(allocator, ".env", 4096) catch null; + if (self.env_buf) |buf| { + self.env_map = parseEnvFile(allocator, buf); + } self.twelvedata_key = self.resolve("TWELVEDATA_API_KEY"); self.polygon_key = self.resolve("POLYGON_API_KEY"); @@ -27,8 +35,14 @@ pub const Config = struct { const env_cache = self.resolve("ZFIN_CACHE_DIR"); self.cache_dir = env_cache orelse blk: { - const home = std.posix.getenv("HOME") orelse "/tmp"; - break :blk std.fs.path.join(allocator, &.{ home, ".cache", "zfin" }) catch @panic("OOM"); + // XDG Base Directory: $XDG_CACHE_HOME/zfin, falling back to $HOME/.cache/zfin + const base = std.posix.getenv("XDG_CACHE_HOME") orelse fallback: { + const home = std.posix.getenv("HOME") orelse "/tmp"; + break :fallback std.fs.path.join(allocator, &.{ home, ".cache" }) catch @panic("OOM"); + }; + const base_allocated = std.posix.getenv("XDG_CACHE_HOME") == null; + defer if (base_allocated) allocator.free(base); + break :blk std.fs.path.join(allocator, &.{ base, "zfin" }) catch @panic("OOM"); }; return self; @@ -36,8 +50,13 @@ pub const Config = struct { pub fn deinit(self: *Config) void { if (self.allocator) |a| { - // Check if cache_dir was allocated (not from env/envfile) BEFORE freeing env_buf + // cache_dir is allocated (via path.join) unless ZFIN_CACHE_DIR was set directly. + // Check BEFORE freeing env_map/env_buf, since resolve() reads from them. const cache_dir_from_env = self.resolve("ZFIN_CACHE_DIR") != null; + if (self.env_map) |*m| { + var map = m.*; + map.deinit(); + } if (self.env_buf) |buf| a.free(buf); if (!cache_dir_from_env) { a.free(self.cache_dir); @@ -55,41 +74,24 @@ pub const Config = struct { /// Look up a key: environment variable first, then .env file fallback. fn resolve(self: Config, key: []const u8) ?[]const u8 { if (std.posix.getenv(key)) |v| return v; - return envFileGet(self.env_buf, key); + if (self.env_map) |m| return m.get(key); + return null; } }; -/// Parse a KEY=VALUE line from .env content. Returns value for the given key. -fn envFileGet(buf: ?[]const u8, key: []const u8) ?[]const u8 { - const data = buf orelse return null; +/// Parse all KEY=VALUE pairs from .env content into a HashMap. +/// Values are slices into the original buffer (no extra allocations per entry). +fn parseEnvFile(allocator: std.mem.Allocator, data: []const u8) ?EnvMap { + var map = EnvMap.init(allocator); var iter = std.mem.splitScalar(u8, data, '\n'); while (iter.next()) |line| { const trimmed = std.mem.trim(u8, line, &std.ascii.whitespace); if (trimmed.len == 0 or trimmed[0] == '#') continue; if (std.mem.indexOfScalar(u8, trimmed, '=')) |eq| { const k = std.mem.trim(u8, trimmed[0..eq], &std.ascii.whitespace); - if (std.mem.eql(u8, k, key)) { - return std.mem.trim(u8, trimmed[eq + 1 ..], &std.ascii.whitespace); - } + const v = std.mem.trim(u8, trimmed[eq + 1 ..], &std.ascii.whitespace); + map.put(k, v) catch return null; } } - return null; -} - -/// Try to load .env from the executable's directory, then cwd. -fn loadEnvFile(allocator: std.mem.Allocator) ?[]const u8 { - // Try relative to the executable - const exe_dir = std.fs.selfExeDirPathAlloc(allocator) catch null; - defer if (exe_dir) |d| allocator.free(d); - - if (exe_dir) |dir| { - const path = std.fs.path.join(allocator, &.{ dir, "..", ".env" }) catch null; - defer if (path) |p| allocator.free(p); - if (path) |p| { - if (std.fs.cwd().readFileAlloc(allocator, p, 4096)) |data| return data else |_| {} - } - } - - // Try cwd - return std.fs.cwd().readFileAlloc(allocator, ".env", 4096) catch null; + return map; }