1const Path = @This();
2
3const std = @import("../../std.zig");
4const Io = std.Io;
5const assert = std.debug.assert;
6const Allocator = std.mem.Allocator;
7const Cache = std.Build.Cache;
8
9root_dir: Cache.Directory,
10/// The path, relative to the root dir, that this `Path` represents.
11/// Empty string means the root_dir is the path.
12sub_path: []const u8 = "",
13
14pub fn clone(p: Path, arena: Allocator) Allocator.Error!Path {
15 return .{
16 .root_dir = try p.root_dir.clone(arena),
17 .sub_path = try arena.dupe(u8, p.sub_path),
18 };
19}
20
21pub fn cwd() Path {
22 return initCwd("");
23}
24
25pub fn initCwd(sub_path: []const u8) Path {
26 return .{ .root_dir = .cwd(), .sub_path = sub_path };
27}
28
29pub fn join(p: Path, arena: Allocator, sub_path: []const u8) Allocator.Error!Path {
30 if (sub_path.len == 0) return p;
31 const parts: []const []const u8 =
32 if (p.sub_path.len == 0) &.{sub_path} else &.{ p.sub_path, sub_path };
33 return .{
34 .root_dir = p.root_dir,
35 .sub_path = try Io.Dir.path.join(arena, parts),
36 };
37}
38
39pub fn resolvePosix(p: Path, arena: Allocator, sub_path: []const u8) Allocator.Error!Path {
40 if (sub_path.len == 0) return p;
41 const new_sub_path = try Io.Dir.path.resolvePosix(arena, &.{ p.sub_path, sub_path });
42 return .{
43 .root_dir = p.root_dir,
44 // Use "" instead of "." to represent `root_dir` itself.
45 .sub_path = if (std.mem.eql(u8, new_sub_path, ".")) "" else new_sub_path,
46 };
47}
48
49pub fn joinString(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Error![]u8 {
50 const parts: []const []const u8 =
51 if (p.sub_path.len == 0) &.{sub_path} else &.{ p.sub_path, sub_path };
52 return p.root_dir.join(gpa, parts);
53}
54
55pub fn joinStringZ(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Error![:0]u8 {
56 const parts: []const []const u8 =
57 if (p.sub_path.len == 0) &.{sub_path} else &.{ p.sub_path, sub_path };
58 return p.root_dir.joinZ(gpa, parts);
59}
60
61pub fn openFile(p: Path, io: Io, sub_path: []const u8, flags: Io.Dir.OpenFileOptions) !Io.File {
62 var buf: [Io.Dir.max_path_bytes]u8 = undefined;
63 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
64 break :p std.mem.print(&buf, "{s}" ++ Io.Dir.path.sep_str ++ "{s}", .{
65 p.sub_path, sub_path,
66 }) catch return error.NameTooLong;
67 };
68 return p.root_dir.handle.openFile(io, joined_path, flags);
69}
70
71pub fn openDir(
72 p: Path,
73 io: Io,
74 sub_path: []const u8,
75 args: Io.Dir.OpenOptions,
76) Io.Dir.OpenError!Io.Dir {
77 var buf: [Io.Dir.max_path_bytes]u8 = undefined;
78 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
79 break :p std.mem.print(&buf, "{s}" ++ Io.Dir.path.sep_str ++ "{s}", .{
80 p.sub_path, sub_path,
81 }) catch return error.NameTooLong;
82 };
83 return p.root_dir.handle.openDir(io, joined_path, args);
84}
85
86pub fn createDirPathOpen(p: Path, io: Io, sub_path: []const u8, opts: Io.Dir.CreateDirPathOpenOptions) !Io.Dir {
87 var buf: [Io.Dir.max_path_bytes]u8 = undefined;
88 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
89 break :p std.mem.print(&buf, "{s}" ++ Io.Dir.path.sep_str ++ "{s}", .{
90 p.sub_path, sub_path,
91 }) catch return error.NameTooLong;
92 };
93 return p.root_dir.handle.createDirPathOpen(io, joined_path, opts);
94}
95
96pub fn statFile(p: Path, io: Io, sub_path: []const u8) !Io.Dir.Stat {
97 var buf: [Io.Dir.max_path_bytes]u8 = undefined;
98 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
99 break :p std.mem.print(&buf, "{s}" ++ Io.Dir.path.sep_str ++ "{s}", .{
100 p.sub_path, sub_path,
101 }) catch return error.NameTooLong;
102 };
103 return p.root_dir.handle.statFile(io, joined_path, .{});
104}
105
106pub fn atomicFile(
107 p: Path,
108 io: Io,
109 sub_path: []const u8,
110 options: Io.Dir.CreateFileAtomicOptions,
111 buf: *[Io.Dir.max_path_bytes]u8,
112) !Io.File.Atomic {
113 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
114 break :p std.mem.print(buf, "{s}" ++ Io.Dir.path.sep_str ++ "{s}", .{
115 p.sub_path, sub_path,
116 }) catch return error.NameTooLong;
117 };
118 return p.root_dir.handle.createFileAtomic(io, joined_path, options);
119}
120
121pub fn access(p: Path, io: Io, sub_path: []const u8, flags: Io.Dir.AccessOptions) !void {
122 var buf: [Io.Dir.max_path_bytes]u8 = undefined;
123 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
124 break :p std.mem.print(&buf, "{s}" ++ Io.Dir.path.sep_str ++ "{s}", .{
125 p.sub_path, sub_path,
126 }) catch return error.NameTooLong;
127 };
128 return p.root_dir.handle.access(io, joined_path, flags);
129}
130
131pub fn createDirPath(p: Path, io: Io, sub_path: []const u8) !void {
132 var buf: [Io.Dir.max_path_bytes]u8 = undefined;
133 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
134 break :p std.mem.print(&buf, "{s}" ++ Io.Dir.path.sep_str ++ "{s}", .{
135 p.sub_path, sub_path,
136 }) catch return error.NameTooLong;
137 };
138 return p.root_dir.handle.createDirPath(io, joined_path);
139}
140
141pub fn toString(p: Path, allocator: Allocator) Allocator.Error![]u8 {
142 return allocator.print("{f}", .{p});
143}
144
145pub fn toStringZ(p: Path, allocator: Allocator) Allocator.Error![:0]u8 {
146 return allocator.printSentinel("{f}", .{p}, 0);
147}
148
149pub fn fmtEscapeString(path: Path) std.fmt.Alt(Path, formatEscapeString) {
150 return .{ .data = path };
151}
152
153pub fn formatEscapeString(path: Path, writer: *Io.Writer) Io.Writer.Error!void {
154 if (path.root_dir.path) |p| {
155 try std.zig.stringEscape(p, writer);
156 if (path.sub_path.len > 0) try std.zig.stringEscape(Io.Dir.path.sep_str, writer);
157 }
158 if (path.sub_path.len > 0) {
159 try std.zig.stringEscape(path.sub_path, writer);
160 }
161}
162
163/// Deprecated, use double quoted escape to print paths.
164pub fn fmtEscapeChar(path: Path) std.fmt.Alt(Path, formatEscapeChar) {
165 return .{ .data = path };
166}
167
168/// Deprecated, use double quoted escape to print paths.
169pub fn formatEscapeChar(path: Path, writer: *Io.Writer) Io.Writer.Error!void {
170 if (path.root_dir.path) |p| {
171 for (p) |byte| try std.zig.charEscape(byte, writer);
172 if (path.sub_path.len > 0) try writer.writeByte(Io.Dir.path.sep);
173 }
174 if (path.sub_path.len > 0) {
175 for (path.sub_path) |byte| try std.zig.charEscape(byte, writer);
176 }
177}
178
179pub fn format(self: Path, writer: *Io.Writer) Io.Writer.Error!void {
180 if (Io.Dir.path.isAbsolute(self.sub_path)) {
181 return writer.writeAll(self.sub_path);
182 } else if (self.root_dir.path) |p| {
183 var bufs: [3][]const u8 = .{ p, Io.Dir.path.sep_str, self.sub_path };
184 return writer.writeVecAll(bufs[0..if (self.sub_path.len > 0) 3 else 1]);
185 } else if (self.sub_path.len > 0) {
186 return writer.writeAll(self.sub_path);
187 } else {
188 return writer.writeByte('.');
189 }
190}
191
192pub fn eql(self: Path, other: Path) bool {
193 return self.root_dir.eql(other.root_dir) and std.mem.eql(u8, self.sub_path, other.sub_path);
194}
195
196pub fn subPathOpt(self: Path) ?[]const u8 {
197 return if (self.sub_path.len == 0) null else self.sub_path;
198}
199
200pub fn subPathOrDot(self: Path) []const u8 {
201 return if (self.sub_path.len == 0) "." else self.sub_path;
202}
203
204pub fn stem(p: Path) []const u8 {
205 return Io.Dir.path.stem(p.sub_path);
206}
207
208pub fn dirname(p: Path) ?Path {
209 return .{
210 .root_dir = p.root_dir,
211 .sub_path = Io.Dir.path.dirname(p.subPathOpt() orelse return null) orelse "",
212 };
213}
214
215pub fn basename(p: Path) []const u8 {
216 return Io.Dir.path.basename(p.sub_path);
217}
218
219/// Useful to make `Path` a key in `std.ArrayHashMap`.
220pub const TableAdapter = struct {
221 pub const Hash = std.hash.Wyhash;
222
223 pub fn hash(self: TableAdapter, a: Cache.Path) u32 {
224 _ = self;
225 const seed = switch (@typeInfo(@TypeOf(a.root_dir.handle.handle))) {
226 .pointer => @intFromPtr(a.root_dir.handle.handle),
227 .int => @as(u32, @bitCast(a.root_dir.handle.handle)),
228 else => @compileError("unimplemented hash function"),
229 };
230 return @truncate(Hash.hash(seed, a.sub_path));
231 }
232 pub fn eql(self: TableAdapter, a: Cache.Path, b: Cache.Path, b_index: usize) bool {
233 _ = self;
234 _ = b_index;
235 return a.eql(b);
236 }
237};