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()); }