|
|
|
@ -11,7 +11,7 @@ pub const Path = struct { |
|
|
|
|
/// Note that a relative path can still be interpreted as absolute. |
|
|
|
|
absolute: bool, |
|
|
|
|
/// The string parts that make up the path. |
|
|
|
|
parts: []const []const u8, |
|
|
|
|
parts: []const EntityPart, |
|
|
|
|
/// The allocator that was used to allocate `parts`. |
|
|
|
|
alloc: ?Allocator = null, |
|
|
|
|
/// Whether the parts outer array itself is owned by this `Path`. |
|
|
|
@ -19,6 +19,12 @@ pub const Path = struct { |
|
|
|
|
/// Whether the parts inner strings are owned by this `Path`. |
|
|
|
|
owns_parts: bool = false, |
|
|
|
|
|
|
|
|
|
/// Represents an `Entity` in a `Path`, either by name or its numeric id. |
|
|
|
|
pub const EntityPart = union(enum) { |
|
|
|
|
name: []const u8, |
|
|
|
|
id: u32, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/// Format used to parse and stringify `Path`s. |
|
|
|
|
/// |
|
|
|
|
/// When this type is formatted, you may use any of the `FormatOption` |
|
|
|
@ -49,7 +55,7 @@ pub const Path = struct { |
|
|
|
|
/// Creates a new `Path` from the specified string parts. |
|
|
|
|
/// |
|
|
|
|
/// The resulting path does not own any of the given slices. |
|
|
|
|
pub fn fromParts(absolute: bool, parts: []const []const u8) !Path { |
|
|
|
|
pub fn fromParts(absolute: bool, parts: []const EntityPart) !Path { |
|
|
|
|
std.debug.assert(parts.len > 0); |
|
|
|
|
return .{ .absolute = absolute, .parts = parts }; |
|
|
|
|
} |
|
|
|
@ -79,11 +85,15 @@ pub const Path = struct { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const parts_len = std.mem.count(u8, remaining, opt.sep) + 1; |
|
|
|
|
const parts = try alloc.alloc([]const u8, parts_len); |
|
|
|
|
const parts = try alloc.alloc(EntityPart, parts_len); |
|
|
|
|
|
|
|
|
|
var i: usize = 0; |
|
|
|
|
var it = std.mem.splitSequence(u8, remaining, opt.sep); |
|
|
|
|
while (it.next()) |part| : (i += 1) parts[i] = part; |
|
|
|
|
while (it.next()) |str| : (i += 1) |
|
|
|
|
parts[i] = if (parseNumericId(str)) |id| |
|
|
|
|
.{ .id = id } |
|
|
|
|
else |
|
|
|
|
.{ .name = str }; |
|
|
|
|
|
|
|
|
|
return .{ |
|
|
|
|
.absolute = absolute, |
|
|
|
@ -95,15 +105,16 @@ pub const Path = struct { |
|
|
|
|
|
|
|
|
|
/// Creates a deep clone of this `Path` using the specified `Allocator`. |
|
|
|
|
pub fn clone(orig: Path, alloc: Allocator) !Path { |
|
|
|
|
var parts = try alloc.dupe([]const u8, orig.parts); |
|
|
|
|
var parts = try alloc.dupe(EntityPart, orig.parts); |
|
|
|
|
errdefer alloc.free(parts); |
|
|
|
|
|
|
|
|
|
var num_allocated: usize = 0; |
|
|
|
|
errdefer for (parts[0..num_allocated]) |part| |
|
|
|
|
alloc.free(part); |
|
|
|
|
if (part == .name) alloc.free(part.name); |
|
|
|
|
|
|
|
|
|
for (parts) |*part| { |
|
|
|
|
part.* = try alloc.dupe(u8, part.*); |
|
|
|
|
if (part.* == .name) |
|
|
|
|
part.* = .{ .name = try alloc.dupe(u8, part.name) }; |
|
|
|
|
num_allocated += 1; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -120,7 +131,8 @@ pub const Path = struct { |
|
|
|
|
pub fn deinit(self: Path) void { |
|
|
|
|
if (self.owns_parts) |
|
|
|
|
for (self.parts) |part| |
|
|
|
|
self.alloc.?.free(part); |
|
|
|
|
if (part == .name) |
|
|
|
|
self.alloc.?.free(part.name); |
|
|
|
|
if (self.owns_array) |
|
|
|
|
self.alloc.?.free(self.parts); |
|
|
|
|
} |
|
|
|
@ -166,23 +178,64 @@ pub const Path = struct { |
|
|
|
|
} |
|
|
|
|
// Parts. |
|
|
|
|
for (self.parts) |part| |
|
|
|
|
result += part.len; |
|
|
|
|
result += switch (part) { |
|
|
|
|
.name => |name| name.len, |
|
|
|
|
.id => |id| numDigits(id), |
|
|
|
|
}; |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn write(self: Path, opt: FormatOptions, writer: anytype) !void { |
|
|
|
|
// Write prefix (if applicable), |
|
|
|
|
// Write prefix (if applicable). |
|
|
|
|
if (self.absolute) { |
|
|
|
|
if (opt.prefix) |p| |
|
|
|
|
try writer.writeAll(p); |
|
|
|
|
} |
|
|
|
|
// Write first part. |
|
|
|
|
try writer.writeAll(self.parts[0]); |
|
|
|
|
// Write other parts, each preceeded by separator. |
|
|
|
|
// Write the first part. |
|
|
|
|
switch (self.parts[0]) { |
|
|
|
|
.name => |name| try writer.writeAll(name), |
|
|
|
|
.id => |id| try writer.writeIntNative(u32, id), |
|
|
|
|
} |
|
|
|
|
// Write the remaining parts, each preceeded bu separator. |
|
|
|
|
for (self.parts[1..]) |part| { |
|
|
|
|
try writer.writeAll(opt.sep); |
|
|
|
|
try writer.writeAll(part); |
|
|
|
|
switch (part) { |
|
|
|
|
.name => |name| try writer.writeAll(name), |
|
|
|
|
.id => |id| try writer.writeIntNative(u32, id), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Attempts to parse the specified string as a numeric entity id. |
|
|
|
|
fn parseNumericId(str: []const u8) ?u32 { |
|
|
|
|
if (str.len == 0 or str.len > 10) return null; |
|
|
|
|
var result: u32 = 0; |
|
|
|
|
var place: u32 = 1; |
|
|
|
|
var it = std.mem.reverseIterator(str); |
|
|
|
|
while (it.next()) |chr| { |
|
|
|
|
const d = std.fmt.charToDigit(chr, 10) catch return null; |
|
|
|
|
result = std.math.add(u32, result, d * place) catch return null; |
|
|
|
|
place *%= 10; // Wrapping to avoid overflow error. |
|
|
|
|
} |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Calculates the numbers of digits of an entity id when formatted as a |
|
|
|
|
/// string. Since entity ids tend to be small this is technically optimized |
|
|
|
|
/// for smaller numbers but performance likely doesn't matter much. |
|
|
|
|
fn numDigits(n: u32) usize { |
|
|
|
|
// zig fmt: off |
|
|
|
|
return if (n < 10) 1 |
|
|
|
|
else if (n < 100) 2 |
|
|
|
|
else if (n < 1000) 3 |
|
|
|
|
else if (n < 10000) 4 |
|
|
|
|
else if (n < 100000) 5 |
|
|
|
|
else if (n < 1000000) 6 |
|
|
|
|
else if (n < 10000000) 7 |
|
|
|
|
else if (n < 100000000) 8 |
|
|
|
|
else if (n < 1000000000) 9 |
|
|
|
|
else 10; |
|
|
|
|
// zig fmt: on |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
@ -190,7 +243,6 @@ test Path { |
|
|
|
|
const alloc = std.testing.allocator; |
|
|
|
|
const expectFmt = std.testing.expectFmt; |
|
|
|
|
const expectEqual = std.testing.expectEqual; |
|
|
|
|
const expectEqualDeep = std.testing.expectEqualDeep; |
|
|
|
|
const expectEqualStrings = std.testing.expectEqualStrings; |
|
|
|
|
|
|
|
|
|
// Paths may be constructed by parsing strings. |
|
|
|
@ -200,7 +252,7 @@ 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, &.{ "I'm", "absolute!" }); |
|
|
|
|
const absolute1 = try Path.fromParts(true, &.{ .{ .name = "I'm" }, .{ .name = "absolute!" } }); |
|
|
|
|
// No need for `deinit()`, does not own outer array nor inner string parts. |
|
|
|
|
|
|
|
|
|
// With `options` unspecified, it's not possible to represent an absolute |
|
|
|
@ -217,15 +269,20 @@ test Path { |
|
|
|
|
|
|
|
|
|
// The internal component parts of the path can be accessed with `.parts`. |
|
|
|
|
try expectEqual(@as(usize, 3), relative.parts.len); |
|
|
|
|
try expectEqualStrings("some", relative.parts[0]); |
|
|
|
|
try expectEqualStrings("relative", relative.parts[1]); |
|
|
|
|
try expectEqualStrings("path", relative.parts[2]); |
|
|
|
|
try expectEqualDeep(@as([]const []const u8, &.{ "I'm", "absolute!" }), absolute1.parts); |
|
|
|
|
try expectEqualDeep(@as([]const []const u8, &.{ "home", "copygirl" }), absolute2.parts); |
|
|
|
|
|
|
|
|
|
// Paths are formattable, and as the `fmt` specifier you can either use one |
|
|
|
|
// of the pre-defined ones defined in the `FormatOptions` type, or just an |
|
|
|
|
// empty string to use the default. |
|
|
|
|
try expectEqualStrings("some", relative.parts[0].name); |
|
|
|
|
try expectEqualStrings("relative", relative.parts[1].name); |
|
|
|
|
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); |
|
|
|
|
|
|
|
|
|
// Paths are formattable. As format specifier you can use options defined |
|
|
|
|
// on the `FormatOptions` type, or an empty string to use the default. |
|
|
|
|
try expectFmt("some.relative.path", "{}", .{relative}); |
|
|
|
|
try expectFmt("::I'm::absolute!", "{flecs_cpp}", .{absolute1}); |
|
|
|
|
try expectFmt("/home/copygirl", "{unix}", .{absolute2}); |
|
|
|
|