You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
133 lines
4.9 KiB
133 lines
4.9 KiB
1 year ago
|
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);
|
||
|
}
|