const std = @import("std"); const assert = std.debug.assert; const fatal = @import("cli.zig").fatal; pub const Path = struct { buf: [std.fs.max_path_bytes]u8, len: usize, pub fn init(path: []const u8) Path { var self: Path = undefined; assert(path.len < self.buf.len); self.len = path.len; @memcpy(self.buf[0..path.len], path); self.buf[self.len] = 0; return self; } pub fn join(segments: anytype) Path { var self: Path = undefined; self.len = 0; self.append(segments); return self; } pub fn append(self: *Path, segments: anytype) void { const info = @typeInfo(@TypeOf(segments)).@"struct"; comptime assert(info.is_tuple); inline for (info.fields) |field| { var seg: []const u8 = @field(segments, field.name); if (seg.len > 0) { if (self.len > 0 and seg[0] == std.fs.path.sep) seg = seg[1..]; if (seg[seg.len - 1] == std.fs.path.sep) seg = seg[0 .. seg.len - 1]; } if (seg.len > 0) { if (self.len > 0) { self.buf[self.len] = std.fs.path.sep; self.len += 1; } @memcpy(self.buf[self.len .. self.len + seg.len], seg); self.len += seg.len; } } self.buf[self.len] = 0; } pub fn name(self: *const Path) [:0]const u8 { return self.buf[0..self.len :0]; } pub fn basename(self: *const Path) []const u8 { return std.fs.path.basename(self.name()); } pub fn dir(self: *const Path) ?Path { const dirname = std.fs.path.dirname(self.name()) orelse return null; return .init(dirname); } /// Returns the relative name of `self` regarding to `root`. /// Both paths must be absolute. pub fn relname(self: *const Path, root: Path) ?[]const u8 { assert(std.fs.path.isAbsolute(root.name())); assert(std.fs.path.isAbsolute(self.name())); if (!std.mem.startsWith(u8, self.name(), root.name())) return null; if (self.len == root.len) return ""; var rel = self.buf[root.len..self.len]; if (rel[0] == std.fs.path.sep) rel = rel[1..]; return rel; } }; pub fn mkdir_all(path: Path) !void { try std.fs.cwd().makePath(path.name()); } pub fn touch(path: Path) !void { const cwd = std.fs.cwd(); if (std.fs.path.dirname(path.name())) |dir| { try cwd.makePath(dir); } const f = try cwd.createFileZ(path.name(), .{ .mode = 0o644 }); f.close(); } pub fn rm(path: Path) !void { std.fs.deleteTreeAbsolute(path.name()) catch |err| { if (err != error.FileNotFound) return err; }; } pub fn cp_file(from_path: Path, to_path: Path) !void { const from = from_path.name(); const to = to_path.name(); std.fs.deleteTreeAbsolute(to) catch |err| { if (err != error.FileNotFound) return err; }; if (to_path.dir()) |to_parent| try mkdir_all(to_parent); try std.fs.copyFileAbsolute(from, to, .{}); } pub fn cp_dir(allocator: std.mem.Allocator, from_path: Path, to_path: Path) !void { const from = from_path.name(); const to = to_path.name(); std.fs.deleteTreeAbsolute(to) catch |err| { if (err != error.FileNotFound) return err; }; try mkdir_all(to_path); try cp_tree(allocator, from, to); } pub fn mv_dir(allocator: std.mem.Allocator, from_path: Path, to_path: Path) !void { const from = from_path.name(); const to = to_path.name(); std.fs.deleteTreeAbsolute(to) catch |err| { if (err != error.FileNotFound) return err; }; if (to_path.dir()) |to_parent| try mkdir_all(to_parent); std.fs.renameAbsoluteZ(from, to) catch |err| { if (err != error.RenameAcrossMountPoints) return err; try std.fs.makeDirAbsoluteZ(to); try cp_tree(allocator, from, to); try rm(from_path); }; } pub fn mv_file(from_path: Path, to_path: Path) !void { const from = from_path.name(); const to = to_path.name(); std.fs.deleteTreeAbsolute(to) catch |err| { if (err != error.FileNotFound) return err; }; if (to_path.dir()) |to_parent| try mkdir_all(to_parent); std.fs.renameAbsoluteZ(from, to) catch |err| { if (err != error.RenameAcrossMountPoints) return err; try std.fs.copyFileAbsolute(from, to, .{}); try rm(from_path); }; } fn cp_tree(allocator: std.mem.Allocator, from: [:0]const u8, to: [:0]const u8) !void { var from_dir = try std.fs.openDirAbsoluteZ(from, .{ .iterate = true }); defer from_dir.close(); var walker = try from_dir.walk(allocator); defer walker.deinit(); while (try walker.next()) |entry| { const dest: Path = .join(.{ to, entry.path }); switch (entry.kind) { .directory => try std.fs.makeDirAbsoluteZ(dest.name()), .file => { const src: Path = .join(.{ from, entry.path }); try std.fs.copyFileAbsolute(src.name(), dest.name(), .{}); }, else => |k| fatal("cannot copy {s}", .{@tagName(k)}), } } } pub const File_Iterator = struct { iter: std.fs.Dir.Iterator, pub fn deinit(self: *File_Iterator) void { self.iter.dir.close(); } pub fn next(self: *File_Iterator) ?[]const u8 { while (true) { const entry = self.iter.next() catch unreachable orelse return null; if (entry.kind == .file) return entry.name; } } }; /// Iterate files of a directory with the given path. The path must be absolute. /// If the directory does not exist, null will be returned. pub fn iterate_files(path: Path) !?File_Iterator { var dir = std.fs.openDirAbsoluteZ(path.name(), .{ .iterate = true }) catch |err| { return switch (err) { error.FileNotFound => null, error.BadPathName, error.InvalidUtf8 => error.BadPathName, error.NameTooLong => unreachable, else => err, }; }; return .{ .iter = dir.iterate(), }; } pub const Dir_Info = struct { exists: bool, empty: bool, }; pub fn dir_info(path: Path) !Dir_Info { var dir = std.fs.openDirAbsoluteZ(path.name(), .{ .iterate = true }) catch |err| { return switch (err) { error.FileNotFound => .{ .exists = false, .empty = false }, error.BadPathName, error.InvalidUtf8 => error.BadPathName, error.NameTooLong => unreachable, else => err, }; }; defer dir.close(); var iter = dir.iterate(); const child = iter.next() catch unreachable; return .{ .exists = true, .empty = child == null, }; } pub fn file_exists(path: Path) !bool { std.fs.accessAbsoluteZ(path.name(), .{}) catch |err| { switch (err) { error.FileNotFound => return false, error.PermissionDenied => return false, error.NameTooLong => unreachable, else => return err, } }; return true; } /// Read the whole file into a buffer and returns the buffer. The caller is responsible /// to deallocate the memory with the given allocator. /// If the file does not exist, null will be returned. pub fn read_file(allocator: std.mem.Allocator, path: Path) !?[]const u8 { var f = std.fs.openFileAbsoluteZ(path.name(), .{}) catch |err| { return switch (err) { error.FileNotFound => null, error.NameTooLong => unreachable, else => err, }; }; defer f.close(); const stats = try f.stat(); const buf = try allocator.alloc(u8, @intCast(stats.size)); errdefer allocator.free(buf); var r = f.reader(buf); var off: usize = 0; while (off < buf.len) { const n = try r.readStreaming(buf[off..]); off += n; } return buf; } test "path join" { var path: Path = undefined; path = .join(.{}); try std.testing.expectEqualStrings("", path.name()); path = .join(.{ "relative", "local/dir" }); try std.testing.expectEqualStrings("relative/local/dir", path.name()); path = .join(.{ "relative/", "/local/dir" }); try std.testing.expectEqualStrings("relative/local/dir", path.name()); path = .join(.{ "/absolute", "local/dir" }); try std.testing.expectEqualStrings("/absolute/local/dir", path.name()); }