const std = @import("std"); pub const Config = struct { listen_host: []const u8, listen_port: u16, cache_size: usize, cache_dir: []const u8, geolite_path: []const u8, geocache_file: ?[]const u8, ip2location_api_key: ?[]const u8, ip2location_cache_file: []const u8, pub fn load(allocator: std.mem.Allocator) !Config { var env = try std.process.getEnvMap(allocator); defer env.deinit(); // Get XDG_CACHE_HOME or default to ~/.cache const home = env.get("HOME") orelse "/tmp"; const xdg_cache = env.get("XDG_CACHE_HOME") orelse try std.fmt.allocPrint(allocator, "{s}/.cache", .{home}); defer if (env.get("XDG_CACHE_HOME") == null) allocator.free(xdg_cache); const default_cache_dir = try std.fmt.allocPrint(allocator, "{s}/wttr", .{xdg_cache}); defer allocator.free(default_cache_dir); return Config{ .listen_host = env.get("WTTR_LISTEN_HOST") orelse try allocator.dupe(u8, "0.0.0.0"), .listen_port = if (env.get("WTTR_LISTEN_PORT")) |p| try std.fmt.parseInt(u16, p, 10) else 8002, .cache_size = if (env.get("WTTR_CACHE_SIZE")) |s| try std.fmt.parseInt(usize, s, 10) else 10_000, .cache_dir = try allocator.dupe(u8, env.get("WTTR_CACHE_DIR") orelse default_cache_dir), .geolite_path = blk: { if (env.get("WTTR_GEOLITE_PATH")) |v| { break :blk try allocator.dupe(u8, v); } break :blk try std.fmt.allocPrint(allocator, "{s}/GeoLite2-City.mmdb", .{ env.get("WTTR_CACHE_DIR") orelse default_cache_dir, }); }, .geocache_file = if (env.get("WTTR_GEOCACHE_FILE")) |v| try allocator.dupe(u8, v) else null, .ip2location_api_key = if (env.get("IP2LOCATION_API_KEY")) |v| try allocator.dupe(u8, v) else null, .ip2location_cache_file = blk: { if (env.get("IP2LOCATION_CACHE_FILE")) |v| { break :blk try allocator.dupe(u8, v); } break :blk try std.fmt.allocPrint(allocator, "{s}/ip2location.cache", .{env.get("WTTR_CACHE_DIR") orelse default_cache_dir}); }, }; } pub fn deinit(self: Config, allocator: std.mem.Allocator) void { allocator.free(self.listen_host); allocator.free(self.cache_dir); allocator.free(self.geolite_path); if (self.geocache_file) |f| allocator.free(f); if (self.ip2location_api_key) |k| allocator.free(k); allocator.free(self.ip2location_cache_file); } }; test "config loads defaults" { const allocator = std.testing.allocator; const cfg = try Config.load(allocator); defer cfg.deinit(allocator); try std.testing.expectEqualStrings("0.0.0.0", cfg.listen_host); try std.testing.expectEqual(@as(u16, 8002), cfg.listen_port); try std.testing.expectEqual(@as(usize, 10_000), cfg.cache_size); try std.testing.expect(cfg.geocache_file == null); }