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};