about summary refs log tree commit diff
path: root/src/fs.zig
blob: c009afd478b75920ccca1012d6c9d3ad101ffeab (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
const std = @import("std");
const c = @cImport({
    @cInclude("time.h");
});

pub const FileSwapper = struct {
    allocator: std.mem.Allocator,
    target_path: []const u8,
    tmp_path: []const u8,
    tmp: std.fs.File,

    pub fn createAt(alloc: std.mem.Allocator, path: []const u8) !FileSwapper {
        const target_dir = std.fs.path.dirname(path) orelse ".";
        const target_name = std.fs.path.basename(path);

        const tmp_name = try std.fmt.allocPrint(
            alloc,
            ".{s}",
            .{target_name},
        );
        defer alloc.free(tmp_name);

        const tmp_path = try std.fs.path.join(
            alloc,
            &.{ target_dir, tmp_name },
        );

        const f = try std.fs.cwd().createFile(tmp_path, .{});

        return .{
            .allocator = alloc,
            .target_path = path,
            .tmp_path = tmp_path,
            .tmp = f,
        };
    }

    pub fn writer(self: FileSwapper) std.fs.File.Writer {
        return self.tmp.writer();
    }

    pub fn close(self: FileSwapper) void {
        defer self.allocator.free(self.tmp_path);
        defer self.tmp.close();

        // flush writes
        self.tmp.sync() catch
            @panic("sync tmp file");

        // swap file into final location
        std.fs.rename(
            std.fs.cwd(),
            self.tmp_path,
            std.fs.cwd(),
            self.target_path,
        ) catch
            @panic("rename tmp file");
    }
};

pub fn replace_extension(alloc: std.mem.Allocator, p: []const u8, new: []const u8) ![]const u8 {
    const dirname = std.fs.path.dirname(p) orelse ".";
    const basename = try strip_extension(p);
    return try join_path_and_filename(
        alloc,
        &.{dirname},
        &.{ basename, ".", new },
    );
}

// also trims off base paths
pub fn strip_extension(path: []const u8) ![]const u8 {
    const ext = std.fs.path.extension(path);
    const ext_idx = std.mem.indexOfPosLinear(u8, path, 0, ext);
    std.debug.assert(ext_idx != null);
    return path[0..ext_idx.?];
}

pub fn join_path_and_filename(alloc: std.mem.Allocator, paths: []const []const u8, filename_parts: []const []const u8) ![]const u8 {
    const filename = try std.mem.concat(alloc, u8, filename_parts);
    defer alloc.free(filename);

    var p = try std.ArrayList([]const u8).initCapacity(alloc, paths.len + 1);
    defer p.deinit();

    p.appendSliceAssumeCapacity(paths);
    p.appendAssumeCapacity(filename);

    const paths2 = try p.toOwnedSlice();
    defer alloc.free(paths2);

    std.debug.assert(paths.len + 1 == paths2.len);
    return try std.fs.path.join(alloc, paths2);
}

pub fn get_mtime(alloc: std.mem.Allocator, path: []const u8) ![]const u8 {
    const attr = try alloc.create(std.os.linux.Stat);
    defer alloc.destroy(attr);

    const pathZ = try alloc.dupeZ(u8, path);
    defer alloc.free(pathZ);

    // use stat to get mtime
    _ = std.os.linux.stat(pathZ, attr);

    // use libc strftime to format time string
    const mtime = attr.mtime();
    const mtime_str = try alloc.alloc(u8, 512);
    const n = c.strftime(
        mtime_str.ptr,
        mtime_str.len,
        "%F",
        c.localtime(&mtime.sec),
    );
    return try alloc.realloc(mtime_str, n);
}