aboutsummaryrefslogtreecommitdiff
path: root/src/fs.zig
diff options
context:
space:
mode:
authortsne <tsne.dev@outlook.com>2025-01-01 14:58:28 +0100
committertsne <tsne.dev@outlook.com>2025-10-30 08:32:49 +0100
commit44e5ad763794a438ecfd50c8b7f6ea760ea82da5 (patch)
tree575a1fc72ceb1d7f052cf582abc1e038e27f69a3 /src/fs.zig
downloadporteur-main.tar.gz
initial commitHEADmain
Diffstat (limited to 'src/fs.zig')
-rw-r--r--src/fs.zig269
1 files changed, 269 insertions, 0 deletions
diff --git a/src/fs.zig b/src/fs.zig
new file mode 100644
index 0000000..f1bee2d
--- /dev/null
+++ b/src/fs.zig
@@ -0,0 +1,269 @@
+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());
+}