From 51f82576af3acb43b5a2e7bd9c6bb46e45e16abc Mon Sep 17 00:00:00 2001 From: copygirl Date: Mon, 4 Sep 2023 17:50:10 +0200 Subject: [PATCH] Add Path.buildParts helper function --- src/path.zig | 62 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/src/path.zig b/src/path.zig index bde7d38..5c2d052 100644 --- a/src/path.zig +++ b/src/path.zig @@ -54,6 +54,31 @@ pub const Path = struct { pub var default = flecs_c; }; + /// Creates an array of `EntityPath`s with known size equal to the number of + /// elements in the specified tuple argument. Each element of the tuple is + /// either converted to a `.name` part, or an `.id` part. + /// + /// Keep in mind the mutability and lifetime of the string elements passed + /// to this function, as they aren't cloned and ownership stays the same. + /// In many cases, the lifetime of `Path`s is relatively short. When this + /// is not the case, it's recommended to `.clone()` the path after creation. + pub fn buildParts(parts: anytype) t: { + if (!std.meta.trait.isTuple(@TypeOf(parts))) + @compileError("Expected tuple, got '" ++ @typeName(@TypeOf(parts)) ++ "'"); + const len = @typeInfo(@TypeOf(parts)).Struct.fields.len; + break :t [len]EntityPart; + } { + const len = @typeInfo(@TypeOf(parts)).Struct.fields.len; + var result: [len]EntityPart = undefined; + inline for (&result, parts) |*res, part| + res.* = switch (@TypeOf(part)) { + []u8, []const u8 => |name| .{ .name = name }, + u32, comptime_int => |id| .{ .id = id }, + else => @compileError("Expected []u8, []const u8, u32 or comptime_int, got '" ++ @typeName(@TypeOf(part)) ++ "'"), + }; + return result; + } + /// Creates a new `Path` from the specified string parts. /// /// The resulting path does not own any of the given slices. @@ -287,8 +312,22 @@ test Path { // Alternatively they can be made by specifying the individual component // parts they're made of, as well as an argument specifying whether it's // an absolute path. - const absolute1 = try Path.fromParts(true, &.{ .{ .name = "I'm" }, .{ .name = "absolute!" } }); - // No need for `deinit()`, does not own outer array nor inner string parts. + // To do this you can use the `buildParts` helper function, which is less + // wordy than building `EntityPart` structs manually. + const absolute1_parts = Path.buildParts(.{ "I'm", "absolute!" }); + const absolute1 = try Path.fromParts(true, &absolute1_parts); + // No need to call `deinit()`. The path does not own the `absolute1_parts` + // array nor the string parts, which in this case are comptime constants. + + // When handling paths, always be aware of the lifetime of its array and + // any string parts contained within it. This API allows you to completely + // avoid allocation if you know what you are doing. + // In the above example, `absolute_parts` is an array allocated onto the + // stack, and as such will only be valid until the end of the scope. This + // means no allocation is necessary, but it also means `absolute1` is only + // valid for as long as its parts. + // If a path instance is not immediately consumed and you're uncertain + // about the lifetime of its parts, consider using `.clone(alloc)`. // With `options` unspecified, it's not possible to represent an absolute // path using a string. Pass your own `FormatOptions` to be able to. @@ -309,12 +348,19 @@ test Path { try expectEqualStrings("path", relative.parts[2].name); // Parts can also be numeric ids, used for entities that don't have a name. - const numeric = try Path.fromString("100.101.bar", null, alloc); - defer numeric.deinit(); - try expectEqual(@as(usize, 3), numeric.parts.len); - try expectEqual(@as(u32, 100), numeric.parts[0].id); - try expectEqual(@as(u32, 101), numeric.parts[1].id); - try expectEqualStrings("bar", numeric.parts[2].name); + const numeric1 = try Path.fromString("100.101.bar", null, alloc); + defer numeric1.deinit(); + try expectEqual(@as(usize, 3), numeric1.parts.len); + try expectEqual(@as(u32, 100), numeric1.parts[0].id); + try expectEqual(@as(u32, 101), numeric1.parts[1].id); + try expectEqualStrings("bar", numeric1.parts[2].name); + + // Numeric ids can also be passed to `buildParts`. + const numeric2_parts = Path.buildParts(.{ 100, 101, "bar" }); + const numeric2 = try Path.fromParts(false, &numeric2_parts); + + // TODO: Let's check numeric1 and numeric2 for equality here. + _ = numeric2; // Paths are formattable. As format specifier you can use options defined // on the `FormatOptions` type, or an empty string to use the default.