const std = @import("std"); const Allocator = std.mem.Allocator; const c = @import("./c.zig"); pub var default_seperator = "/"; pub var default_prefix = "/"; // TODO: Do something better than just `std.debug.assert`. pub const Path = struct { /// Whether the path is specified to be absolute. /// 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, /// Specifies how the path has been allocated. /// Defines what happens when `deinit()` is called. alloc: union(enum) { /// None of the data is owned by the path. none, /// The parts array itself is owned by the path. array: Allocator, /// The parts array and the contained parts are owned by the path. parts: Allocator, }, /// 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 { std.debug.assert(parts.len > 0); return .{ .absolute = absolute, .parts = parts, .alloc = .none }; } /// Creates a new `Path` from the specified string using the default /// seperator and prefix strings. /// /// If the string starts with the default prefix (if any), the resulting /// path will be absolute. The rest of the string will be split by the /// default seperator, becoming its parts. /// /// The parts array will be allocated with the specified allocator and is /// owned by the resulting path. `deinit()` must be called to free it. pub fn fromString(path: []const u8, alloc: Allocator) !Path { return fromStringCustom(path, default_seperator, default_prefix, alloc); } /// Creates a new `Path` from the specified string using the specified /// seperator and optionally a prefix strings. /// /// If the string starts with the specified prefix (if any), the resulting /// path will be absolute. The rest of the string will be split by the /// specified seperator, becoming its parts. /// /// The parts array will be allocated with the specified allocator and is /// owned by the resulting path. `deinit()` must be called to free it. pub fn fromStringCustom(path: []const u8, sep: []const u8, prefix: ?[]const u8, alloc: Allocator) !Path { std.debug.assert(path.len > 0); var remaining = path; var absolute = false; // If prefix is defined and path starts with it, the path is absolute. if (prefix) |p| { if (std.mem.startsWith(u8, remaining, p)) { remaining = remaining[p.len..]; absolute = true; } } const parts_len = std.mem.count(u8, remaining, sep) + 1; const parts = try alloc.alloc([]const u8, parts_len); var i: usize = 0; var it = std.mem.splitSequence(u8, remaining, sep); while (it.next()) |part| : (i += 1) parts[i] = part; return .{ .absolute = absolute, .parts = parts, .alloc = .{ .array = alloc } }; } pub fn clone(self: Path) !Path { _ = self; return error.NotImplemented; } pub fn deinit(self: Path) void { switch (self.alloc) { .none => {}, // Do nothing. .array => |alloc| alloc.free(self.parts), .parts => |alloc| { for (self.parts) |p| alloc.free(p); alloc.free(self.parts); }, } } }; test Path { const alloc = std.testing.allocator; // const expect = std.testing.expect; const expectEql = std.testing.expectEqual; const expectStrEql = std.testing.expectEqualStrings; const path1 = try Path.fromStringCustom("some/relative/path", "/", "/", alloc); defer path1.deinit(); try expectEql(false, path1.absolute); try expectEql(@as(usize, 3), path1.parts.len); try expectStrEql("some", path1.parts[0]); try expectStrEql("relative", path1.parts[1]); try expectStrEql("path", path1.parts[2]); const path2 = try Path.fromStringCustom("/absolute", "/", "/", alloc); defer path2.deinit(); try expectEql(true, path2.absolute); try expectEql(@as(usize, 1), path2.parts.len); try expectStrEql("absolute", path2.parts[0]); const path3 = try Path.fromStringCustom("foo.bar.baz", ".", null, alloc); defer path3.deinit(); try expectEql(false, path3.absolute); try expectEql(@as(usize, 3), path3.parts.len); try expectPartsEql(&.{ "foo", "bar", "baz" }, path3.parts); const path4 = try Path.fromParts(true, &.{ "I", "am", "Groot!" }); // defer path4.deinit(); -- Not necessary, cannot leak. try expectEql(true, path4.absolute); try expectPartsEql(&.{ "I", "am", "Groot!" }, path4.parts); } fn expectPartsEql(expected: []const []const u8, actual: []const []const u8) !void { try std.testing.expectEqualDeep(expected, actual); }