From 44e5ad763794a438ecfd50c8b7f6ea760ea82da5 Mon Sep 17 00:00:00 2001 From: tsne Date: Wed, 1 Jan 2025 14:58:28 +0100 Subject: initial commit --- src/env.zig | 200 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 src/env.zig (limited to 'src/env.zig') diff --git a/src/env.zig b/src/env.zig new file mode 100644 index 0000000..7e8b355 --- /dev/null +++ b/src/env.zig @@ -0,0 +1,200 @@ +const options = @import("options"); +const std = @import("std"); +const assert = std.debug.assert; + +const cli = @import("cli.zig"); +const fs = @import("fs.zig"); +const conf = @import("conf.zig"); + +const Self = @This(); + +allocator: std.mem.Allocator, +prefix: []const u8, +etc: []const u8, // ${PREFIX}/etc/ +args: *cli.Args, + +config: conf.Config, +ports_dir: []const u8, +distfiles_dir: []const u8, +distfiles_history: usize, +default_category: ?[]const u8, +default_repo_branch: []const u8, +editor_cmd: ?[]const u8, + +pub fn init(allocator: std.mem.Allocator, etc: []const u8, args: *cli.Args) Self { + const prefix = if (options.path_prefix[options.path_prefix.len - 1] == '/') + options.path_prefix[0 .. options.path_prefix.len - 1] + else + options.path_prefix; + + const config = read_config(allocator, .join(.{ etc, "porteur/porteur.conf" })); + var self: Self = .{ + .allocator = allocator, + .prefix = prefix, + .etc = etc, + .args = args, + + .config = config, + .ports_dir = std.posix.getenvZ("PORTSDIR") orelse "/usr/ports", + .distfiles_dir = prefix ++ "/porteur/distfiles/{{TREENAME}}", + .distfiles_history = 1, + .default_category = null, + .default_repo_branch = "main", + .editor_cmd = null, + }; + + var iter = config.iterate(); + while (iter.next()) |v| { + if (std.mem.eql(u8, v.key, "portsdir") and v.val.len > 0) { + self.ports_dir = v.val; + } else if (std.mem.eql(u8, v.key, "distfiles-dir") and v.val.len > 0) { + self.distfiles_dir = std.mem.trimEnd(u8, v.val, "/"); + } else if (std.mem.eql(u8, v.key, "distfiles-history") and v.val.len > 0) { + self.distfiles_history = history: { + var res: usize = 0; + for (v.val) |c| { + if (c < '0' or c > '9') { + self.warn("invalid distfile history `{s}` (falling back to 1)", .{v.val}); + break :history 1; + } + const m = @mulWithOverflow(res, @as(usize, 10)); + const a = @addWithOverflow(m[0], @as(usize, @intCast(c - '0'))); + if (m[1] != 0 or a[1] != 0) { + break :history std.math.maxInt(usize); + } + res = a[0]; + } + break :history res; + }; + } else if (std.mem.eql(u8, v.key, "category") and v.val.len > 0) { + self.default_category = v.val; + } else if (std.mem.eql(u8, v.key, "git-branch") and v.val.len > 0) { + self.default_repo_branch = v.val; + } else if (std.mem.eql(u8, v.key, "editor") and v.val.len > 0) { + self.editor_cmd = v.val; + } else { + self.warn("unknown porteur configuration `{s}`", .{v.key}); + } + } + + return self; +} + +/// Return a subpath of the configuration directory. +pub fn etc_path(self: Self, subpath: anytype) fs.Path { + var etc: fs.Path = .join(.{ self.etc, "porteur" }); + etc.append(subpath); + return etc; +} + +/// Return a subpath of a ports tree's root directory. +pub fn ports_tree_path(self: Self, tree_name: []const u8, subpath: anytype) fs.Path { + var p: fs.Path = .join(.{ self.prefix, "porteur/ports", tree_name }); + p.append(subpath); + return p; +} + +/// Return the directory that contains the port's distfiles. +pub fn ports_dist_dir(self: Self, tree_name: []const u8, port_name: []const u8) fs.Path { + var dest: fs.Path = undefined; + dest.len = 0; + + var src = self.distfiles_dir; + loop: while (src.len > 0) { + inline for ([_]struct { key: []const u8, val: []const u8 }{ + .{ .key = "{{TREENAME}}", .val = tree_name }, + .{ .key = "{{PORTNAME}}", .val = port_name }, + }) |v| { + if (std.mem.indexOf(u8, src, v.key)) |idx| { + @memcpy(dest.buf[dest.len .. dest.len + idx], src[0..idx]); + dest.len += idx; + @memcpy(dest.buf[dest.len .. dest.len + v.val.len], v.val); + dest.len += v.val.len; + src = src[idx + v.key.len ..]; + continue :loop; + } + } + + @memcpy(dest.buf[dest.len .. dest.len + src.len], src); + dest.len += src.len; + break :loop; + } + + dest.buf[dest.len] = 0; + return dest; +} + +/// Return a subpath of the directory that contains the sources of all the ports of a +/// ports tree. +pub fn ports_src_path(self: Self, tree_name: []const u8, subpath: anytype) fs.Path { + var p: fs.Path = .join(.{ self.prefix, "porteur/src", tree_name }); + p.append(subpath); + return p; +} + +/// Return the subpath of a temporary working directory. +pub fn work_path(self: Self, subpath: anytype) fs.Path { + var p: fs.Path = .join(.{ self.prefix, "porteur/.wrk" }); + p.append(subpath); + return p; +} + +pub fn alloc(self: Self, size: usize) []u8 { + return self.allocator.alloc(u8, size) catch cli.fatal("out of memory", .{}); +} + +pub fn dealloc(self: Self, bytes: []const u8) void { + self.allocator.free(bytes); +} + +pub fn err(self: Self, comptime fmt: []const u8, args: anytype) void { + _ = self; + cli.stderr.write_line("Error: " ++ fmt, args); + cli.stderr.flush(); +} + +pub fn warn(self: Self, comptime fmt: []const u8, args: anytype) void { + _ = self; + cli.stderr.write_line("Warning: " ++ fmt, args); + cli.stderr.flush(); +} + +pub fn info(self: Self, comptime fmt: []const u8, args: anytype) void { + _ = self; + cli.stdout.write_line(fmt, args); + cli.stdout.flush(); +} + +fn read_config(allocator: std.mem.Allocator, conf_file: fs.Path) conf.Config { + if (conf.Config.from_file(allocator, conf_file.name())) |res| { + return switch (res) { + .conf => |c| c, + .err => |e| empty: { + cli.stderr.write_line("Error: {s} (line {d})", .{ e.msg, e.line }); + cli.stderr.flush(); + break :empty .init(allocator); + }, + }; + } else |e| { + cli.stderr.write_line("Error: cannot read config ({s})", .{@errorName(e)}); + cli.stderr.flush(); + return .init(allocator); + } +} + +test "replace distdir variables" { + var env: Self = undefined; + var dir: fs.Path = undefined; + + env.distfiles_dir = "/porteur/distfiles"; + dir = env.ports_dist_dir("not-relevant", "not-relevant"); + try std.testing.expectEqualStrings("/porteur/distfiles", dir.name()); + + env.distfiles_dir = "/porteur/distfiles/{{TREENAME}}"; + dir = env.ports_dist_dir("default", "not-relevant"); + try std.testing.expectEqualStrings("/porteur/distfiles/default", dir.name()); + + env.distfiles_dir = "/porteur/distfiles/{{TREENAME}}/{{PORTNAME}}"; + dir = env.ports_dist_dir("default", "myport"); + try std.testing.expectEqualStrings("/porteur/distfiles/default/myport", dir.name()); +} -- cgit v1.2.3