From 9d0d30a6084da21da17acf55cb01830020724b07 Mon Sep 17 00:00:00 2001 From: copygirl Date: Sun, 3 Sep 2023 00:39:45 +0200 Subject: [PATCH] Add WIP Path helper struct --- src/main.zig | 1 + src/path.zig | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 src/path.zig diff --git a/src/main.zig b/src/main.zig index eecb258..607fe3b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3,6 +3,7 @@ pub usingnamespace @import("./entity.zig"); pub usingnamespace @import("./id.zig"); pub usingnamespace @import("./iter.zig"); pub usingnamespace @import("./pair.zig"); +pub usingnamespace @import("./path.zig"); pub usingnamespace @import("./system.zig"); pub usingnamespace @import("./world.zig"); diff --git a/src/path.zig b/src/path.zig new file mode 100644 index 0000000..713379a --- /dev/null +++ b/src/path.zig @@ -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); +}