summaryrefslogtreecommitdiff
path: root/src/user_config.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/user_config.zig')
-rw-r--r--src/user_config.zig188
1 files changed, 0 insertions, 188 deletions
diff --git a/src/user_config.zig b/src/user_config.zig
deleted file mode 100644
index 15253ab2..00000000
--- a/src/user_config.zig
+++ /dev/null
@@ -1,188 +0,0 @@
-//! Native startup configuration discovery and loading.
-//!
-//! `pardes` is a directory inside the platform's per-user configuration
-//! directory; its startup command file is `pardes/init`. Discovery is
-//! deliberately launcher-owned: native
-//! launchers opt in by putting the bytes in `Options.startup_config`, while
-//! tests and the browser keep the default `null`.
-
-const std = @import("std");
-const builtin = @import("builtin");
-
-const max_bytes = 1024 * 1024;
-
-pub const init_name = "init";
-pub const builtin_themes_subdir = "themes/builtin";
-
-/// Resolve the native per-user config directory with stdlib environment and path
-/// APIs. XDG_CONFIG_HOME is accepted only when absolute, as required by the
-/// XDG base-directory specification; an empty/relative value falls back.
-pub fn path(gpa: std.mem.Allocator, env: *const std.process.Environ.Map) !?[]u8 {
- if (builtin.os.tag == .windows) {
- if (nonEmpty(env.get("LOCALAPPDATA"))) |base|
- return try std.fs.path.join(gpa, &.{ base, "pardes" });
- if (nonEmpty(env.get("USERPROFILE"))) |home|
- return try std.fs.path.join(gpa, &.{ home, "AppData", "Local", "pardes" });
- return null;
- }
-
- if (xdgBase(env)) |base|
- return try std.fs.path.join(gpa, &.{ base, "pardes" });
-
- const home = nonEmpty(env.get("HOME")) orelse return null;
- if (builtin.os.tag == .macos)
- return try std.fs.path.join(gpa, &.{ home, "Library", "Application Support", "pardes" });
- return try std.fs.path.join(gpa, &.{ home, ".config", "pardes" });
-}
-
-/// The config directory and its init file: WHERE they were looked for, and
-/// what was there. Missing,
-/// unreadable, oversized, or otherwise unusable config is simply no config —
-/// but the path resolves either way, because "nothing is there yet" is the
-/// answer the Config builtin exists to give and a null would erase it. The
-/// arena passed by the launcher owns every returned slice for the process.
-pub const Found = struct {
- dir: ?[]const u8 = null,
- path: ?[]const u8 = null,
- bytes: ?[]const u8 = null,
-};
-
-pub fn load(
- io: std.Io,
- gpa: std.mem.Allocator,
- env: *const std.process.Environ.Map,
-) Found {
- const config_dir = (path(gpa, env) catch return .{}) orelse return .{};
- const config_path = std.fs.path.join(gpa, &.{ config_dir, init_name }) catch return .{ .dir = config_dir };
- return .{
- .dir = config_dir,
- .path = config_path,
- .bytes = std.Io.Dir.cwd().readFileAlloc(io, config_path, gpa, .limited(max_bytes)) catch null,
- };
-}
-
-fn nonEmpty(value: ?[]const u8) ?[]const u8 {
- const v = value orelse return null;
- return if (v.len == 0) null else v;
-}
-
-fn xdgBase(env: *const std.process.Environ.Map) ?[]const u8 {
- const value = nonEmpty(env.get("XDG_CONFIG_HOME")) orelse return null;
- return if (std.fs.path.isAbsolute(value)) value else null;
-}
-
-test "config path honors XDG and rejects a relative XDG directory" {
- if (builtin.os.tag == .windows) return;
-
- var env: std.process.Environ.Map = .init(std.testing.allocator);
- defer env.deinit();
- try env.put("HOME", "/home/pardes-test");
- try env.put("XDG_CONFIG_HOME", "/var/tmp/pardes-xdg");
-
- const xdg = (try path(std.testing.allocator, &env)).?;
- defer std.testing.allocator.free(xdg);
- try std.testing.expectEqualStrings("/var/tmp/pardes-xdg/pardes", xdg);
-
- try env.put("XDG_CONFIG_HOME", "relative/config");
- const fallback = (try path(std.testing.allocator, &env)).?;
- defer std.testing.allocator.free(fallback);
- const expected = if (builtin.os.tag == .macos)
- "/home/pardes-test/Library/Application Support/pardes"
- else
- "/home/pardes-test/.config/pardes";
- try std.testing.expectEqualStrings(expected, fallback);
-}
-
-test "config loader reads init inside the config directory" {
- if (builtin.os.tag == .windows) return;
-
- var tmp = std.testing.tmpDir(.{});
- defer tmp.cleanup();
- var base_buf: [std.fs.max_path_bytes]u8 = undefined;
- const base_len = try tmp.dir.realPath(std.testing.io, &base_buf);
-
- var env: std.process.Environ.Map = .init(std.testing.allocator);
- defer env.deinit();
- try env.put("XDG_CONFIG_HOME", base_buf[0..base_len]);
-
- const missing = load(std.testing.io, std.testing.allocator, &env);
- defer std.testing.allocator.free(missing.dir.?);
- defer std.testing.allocator.free(missing.path.?);
- const expected_dir = try std.fs.path.join(std.testing.allocator, &.{ base_buf[0..base_len], "pardes" });
- defer std.testing.allocator.free(expected_dir);
- const expected = try std.fs.path.join(std.testing.allocator, &.{ expected_dir, init_name });
- defer std.testing.allocator.free(expected);
- try std.testing.expectEqualStrings(expected_dir, missing.dir.?);
- try std.testing.expectEqualStrings(expected, missing.path.?);
- try std.testing.expect(missing.bytes == null);
- const source = "Theme dark\nUnknown command\nTheme acme\n";
- try tmp.dir.createDir(std.testing.io, "pardes", .default_dir);
- try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "pardes/init", .data = source });
- const found = load(std.testing.io, std.testing.allocator, &env);
- defer std.testing.allocator.free(found.dir.?);
- defer std.testing.allocator.free(found.path.?);
- defer std.testing.allocator.free(found.bytes.?);
- try std.testing.expectEqualStrings(expected_dir, found.dir.?);
- try std.testing.expectEqualStrings(source, found.bytes.?);
-}
-
-/// Write one editable `.zon` file per compiled theme. The caller supplies the
-/// ring so this filesystem-only module does not import the core. Existing
-/// generated copies are replaced; unrelated files in the directory are left
-/// alone, which lets a user keep custom themes beside the reference set.
-pub fn dumpThemes(
- io: std.Io,
- gpa: std.mem.Allocator,
- config_dir: []const u8,
- theme_values: anytype,
-) ![]u8 {
- const out_dir = try std.fs.path.join(gpa, &.{ config_dir, builtin_themes_subdir });
- errdefer gpa.free(out_dir);
- try std.Io.Dir.cwd().createDirPath(io, out_dir);
-
- for (theme_values) |theme_value| {
- var encoded: std.Io.Writer.Allocating = .init(gpa);
- defer encoded.deinit();
- try std.zon.stringify.serialize(theme_value, .{ .whitespace = true }, &encoded.writer);
-
- const filename = try std.fmt.allocPrint(gpa, "{s}.zon", .{theme_value.name});
- defer gpa.free(filename);
- const output_path = try std.fs.path.join(gpa, &.{ out_dir, filename });
- defer gpa.free(output_path);
- try std.Io.Dir.cwd().writeFile(io, .{
- .sub_path = output_path,
- .data = encoded.written(),
- });
- }
- return out_dir;
-}
-
-test "theme dump creates the builtin subdirectory and ZON files" {
- const io = std.testing.io;
- const gpa = std.testing.allocator;
- var tmp = std.testing.tmpDir(.{});
- defer tmp.cleanup();
- var base_buf: [std.fs.max_path_bytes]u8 = undefined;
- const base_len = try tmp.dir.realPath(io, &base_buf);
- const Sample = struct { name: []const u8, rgb: [3]u8 };
- const samples = [_]Sample{
- .{ .name = "one", .rgb = .{ 1, 2, 3 } },
- .{ .name = "two", .rgb = .{ 4, 5, 6 } },
- };
- const output = try dumpThemes(io, gpa, base_buf[0..base_len], &samples);
- defer gpa.free(output);
- const expected = try std.fs.path.join(gpa, &.{ base_buf[0..base_len], builtin_themes_subdir });
- defer gpa.free(expected);
- try std.testing.expectEqualStrings(expected, output);
-
- const one_path = try std.fs.path.join(gpa, &.{ output, "one.zon" });
- defer gpa.free(one_path);
- const bytes = try std.Io.Dir.cwd().readFileAlloc(io, one_path, gpa, .limited(4096));
- defer gpa.free(bytes);
- const source = try gpa.dupeZ(u8, bytes);
- defer gpa.free(source);
- const parsed = try std.zon.parse.fromSliceAlloc(Sample, gpa, source, null, .{});
- defer std.zon.parse.free(gpa, parsed);
- try std.testing.expectEqualStrings("one", parsed.name);
- try std.testing.expectEqual([3]u8{ 1, 2, 3 }, parsed.rgb);
-}