As a similar thing, I was doing lookup table. Now I don’t know which is better:
/// Strategy for creating destination path
const mk_lookup = [8]MkPathFn{
MkPathContext.mkNone, // 000: !flat !mk !acl
MkPathContext.mkAcl, // 001: !flat !mk acl
MkPathContext.mkMkdir, // 010: !flat mk !acl
MkPathContext.mkMkdirAcl, // 011: !flat mk acl
MkPathContext.mkFlatten, // 100: flat !mk !acl
MkPathContext.mkFlattenAcl, // 101: flat !mk acl
MkPathContext.mkMkdir, // 110: flat mk !acl (Identical to 010)
MkPathContext.mkMkdirAcl, // 111: flat mk acl (Identical to 011)
};
const MkPathContext = struct {
io: Io,
gpa: Allocator,
dest: []const u8,
acl_ctx: ?AclContext,
strat: MkPathFn,
/// Private function for path creation
fn mkPath(self: *const MkPathContext, src_path: []const u8, anchor: []const u8) anyerror!void {
// Pass self as a generic pointer to the strategy function
return self.strat(self, src_path, anchor);
}
/// !flatten + !mkdir + !acls
fn mkNone(ctx_ptr: *const anyopaque, src_path: []const u8, _: []const u8) anyerror!void {
const ctx: *const MkPathContext = @ptrCast(@alignCast(ctx_ptr));
const dest_path = try Io.Dir.path.join(ctx.gpa, &.{ ctx.dest, src_path });
defer ctx.gpa.free(dest_path);
std.log.debug("Creating path: {s}", .{dest_path});
Io.Dir.cwd().createDirPath(ctx.io, dest_path) catch |err| {
std.log.err("Failed to make path: {s}\n{s}\n", .{ dest_path, @errorName(err) });
return;
};
}
/// !flatten + !mkdir + acls
fn mkAcl(ctx_ptr: *const anyopaque, src_path: []const u8, _: []const u8) anyerror!void {
const ctx: *const MkPathContext = @ptrCast(@alignCast(ctx_ptr));
std.debug.assert(ctx.acl_ctx != null);
const dest_path = try Io.Dir.path.join(ctx.gpa, &.{ ctx.dest, src_path });
defer ctx.gpa.free(dest_path);
std.log.debug("Creating path: {s}", .{dest_path});
const status = Io.Dir.cwd().createDirPathStatus(ctx.io, dest_path, .default_dir) catch |err| {
std.log.err("Failed to make path: {s}\n{s}\n", .{ dest_path, @errorName(err) });
return;
};
if (status == .created) {
try ctx.acl_ctx.?.exec(&ctx.acl_ctx.?, 0, src_path);
}
}
/// mkdir (standard and flatten variants)
fn mkMkdir(ctx_ptr: *const anyopaque, src_path: []const u8, anchor: []const u8) anyerror!void {
const ctx: *const MkPathContext = @ptrCast(@alignCast(ctx_ptr));
const stripped = stripAfterAnchor(src_path, anchor);
const dest_path = try Io.Dir.path.join(ctx.gpa, &.{ ctx.dest, anchor, stripped });
defer ctx.gpa.free(dest_path);
std.log.debug("Creating path: {s}", .{dest_path});
Io.Dir.cwd().createDirPath(ctx.io, dest_path) catch |err| {
std.log.err("Failed to make path: {s}\n{s}\n", .{ dest_path, @errorName(err) });
};
}
/// mkdir + acls (standard and flatten variants)
fn mkMkdirAcl(ctx_ptr: *const anyopaque, src_path: []const u8, anchor: []const u8) anyerror!void {
const ctx: *const MkPathContext = @ptrCast(@alignCast(ctx_ptr));
std.debug.assert(ctx.acl_ctx != null);
const stripped = stripAfterAnchor(src_path, anchor);
const dest_root = try Io.Dir.path.join(ctx.gpa, &.{ ctx.dest, anchor });
defer ctx.gpa.free(dest_root);
const dest_path = try Io.Dir.path.join(ctx.gpa, &.{ dest_root, stripped });
defer ctx.gpa.free(dest_path);
const base_end = src_path.len - stripped.len;
const end = base_end - @intFromBool(base_end > 0 and std.fs.path.isSep(src_path[base_end - 1]));
std.log.debug("Creating path: {s}", .{dest_path});
const status = Io.Dir.cwd().createDirPathStatus(ctx.io, dest_path, .default_dir) catch |err| {
std.log.err("Failed to make path: {s}\n{s}\n", .{ dest_path, @errorName(err) });
return;
};
if (status == .created) {
var local_acl = ctx.acl_ctx.?;
local_acl.dest_root = dest_root;
try local_acl.exec(&local_acl, end, src_path);
}
}
/// flatten + !mkdir + !acls
fn mkFlatten(ctx_ptr: *const anyopaque, src_path: []const u8, anchor: []const u8) anyerror!void {
const ctx: *const MkPathContext = @ptrCast(@alignCast(ctx_ptr));
const stripped = stripAfterAnchor(src_path, anchor);
const dest_path = try Io.Dir.path.join(ctx.gpa, &.{ ctx.dest, stripped });
defer ctx.gpa.free(dest_path);
std.log.debug("Creating path: {s}", .{dest_path});
Io.Dir.cwd().createDirPath(ctx.io, dest_path) catch |err| {
std.log.err("Failed to make path: {s}\n{s}\n", .{ dest_path, @errorName(err) });
};
}
/// flatten + !mkdir + acls
fn mkFlattenAcl(ctx_ptr: *const anyopaque, src_path: []const u8, anchor: []const u8) anyerror!void {
const ctx: *const MkPathContext = @ptrCast(@alignCast(ctx_ptr));
std.debug.assert(ctx.acl_ctx != null);
const stripped = stripAfterAnchor(src_path, anchor);
const dest_path = try Io.Dir.path.join(ctx.gpa, &.{ ctx.dest, stripped });
defer ctx.gpa.free(dest_path);
const base_end = src_path.len - stripped.len;
const end = base_end - @intFromBool(base_end > 0 and std.fs.path.isSep(src_path[base_end - 1]));
std.log.debug("Creating path: {s}", .{dest_path});
const status = Io.Dir.cwd().createDirPathStatus(ctx.io, dest_path, .default_dir) catch |err| {
std.log.err("Failed to make path: {s}\n{s}\n", .{ dest_path, @errorName(err) });
return;
};
if (status == .created) {
try ctx.acl_ctx.?.exec(&ctx.acl_ctx.?, end, src_path);
}
}
};
Selecting fn pointers here:
/// The master build function that sets up and resolves all runtime branching.
pub fn build(
io: Io,
gpa: Allocator,
buffers: *Buffers,
buf: []u8,
dest: []const u8,
max_chunk: usize,
acls: bool,
checksum: bool,
algo: ChecksumAlgo,
mkdir: bool,
flatten: bool,
falloc_switch: *bool,
) CopyEngine {
// Bitwise Indexing (flatten: bit 2, mkdir: bit 1, acls: bit 0)
// 0b000 - 0b111
const idx: u3 = (@as(u3, @intFromBool(flatten)) << 2) |
(@as(u3, @intFromBool(mkdir)) << 1) |
(@as(u3, @intFromBool(acls)));
// ACL Strategy
const acl_ctx: ?AclContext = if (acls) .{
.io = io,
.gpa = gpa,
.dest_root = dest,
.acl_list = buffers.acl_list.allocatedSlice(),
.acl_value = buffers.acl_value.allocatedSlice(),
// Setup of the ACL execution function
.exec = acl_exec_lookup[idx],
} else null;
// Copy strategy indexing
// (acls: bit 1, checksum: bit 0)
const cp_idx: u2 = (@as(u2, @intFromBool(acls)) << 1) | @as(u2, @intFromBool(checksum));
// Path resolver strategy indexing (2 bits: flatten, mkdir)
// 00: None, 01: Mkdir, 10: Flatten, 11: FlattenMkdir
const res_idx: u2 = (@as(u2, @intFromBool(flatten)) << 1) | @as(u2, @intFromBool(mkdir));
return .{
.gpa = gpa,
.buffers = buffers,
.mkpath_ctx = .{
.io = io,
.gpa = gpa,
.dest = dest,
.acl_ctx = acl_ctx,
// Setup of the path creation strategy
.strat = mk_lookup[idx],
},
.copy_strategy = .{
.io = io,
.gpa = gpa,
.buf = buf,
.acl_list = buffers.acl_list.allocatedSlice(),
.acl_value = buffers.acl_value.allocatedSlice(),
.dest = dest,
.max_chunk = max_chunk,
.falloc_switch = falloc_switch,
.algo = algo,
// Setup of the path strategy
.path_resolver = cp_path_res_lookup[res_idx],
// Setup of the file copy strategy
.copy_logic = cp_lookup[cp_idx],
},
.link_strategy = .{
.io = io,
.gpa = gpa,
.buf = buf,
.acl_list = buffers.acl_list.allocatedSlice(),
.acl_value = buffers.acl_value.allocatedSlice(),
.dest = dest,
// Setup of the path strategy
.path_resolver = ln_path_res_lookup[res_idx],
// Setup of the link copy strategy
.link_logic = if (acls)
@as(LnLogicFn, LinkStrategy.linkAcl)
else
@as(LnLogicFn, LinkStrategy.link),
},
.arg_path_strategy = .{
.io = io,
.gpa = gpa,
.dest = dest,
// Setup of the arg path destination strategy
.strat = arg_path_lookup[res_idx],
},
.file_op = if (flatten) copyFlat else copyStandard,
.link_op = if (flatten) linkFlat else linkStandard,
};
}
I think I should rename it from strategy to vtable, since I believe that is a vtable in Zig. I should learn what vtables are.