parent
418bff8183
commit
9d0d30a608
2 changed files with 133 additions and 0 deletions
@ -0,0 +1,132 @@ |
|||||||
|
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); |
||||||
|
} |
Loading…
Reference in new issue