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