about summary refs log tree commit diff
path: root/src/exe.zig
blob: ae5cfdddcdac101d1a7d7820aeab4866ccf2575a (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
117
118
119
120
121
122
123
124
125
126
127
128
129
const std = @import("std");

const Context = @import("main.zig").Context;
const fs = @import("fs.zig");

const log = std.log.scoped(.exe);

pub fn runPandoc(ctx: *Context) !void {
    ctx.progress_start("Processing markdown files");
    defer ctx.progress_end();

    var pool: std.Thread.Pool = undefined;
    try pool.init(.{
        .n_jobs = (try std.Thread.getCpuCount()) - 1,
        .allocator = ctx.allocator(),
    });
    defer pool.deinit();

    for (ctx.markdown.items) |*page| {
        // the inline struct trick is a workardoun for Thread.Pool::spawn not
        // accepting functions that can error:
        // https://github.com/ziglang/zig/issues/18810
        try pool.spawn(struct {
            fn worker(worker_ctx: *Context, worker_page: *const []const u8) void {
                pandocWorker(worker_ctx, worker_page.*) catch |err| std.log.err("pandoc worker failed with: {}", .{err});
            }
        }.worker, .{ ctx, page });
    }

    log.info("Processed {d} markdown files.", .{ctx.markdown.items.len});
}

fn pandocWorker(ctx: *Context, page: []const u8) !void {
    const source_path = try std.fs.path.join(ctx.allocator(), &.{ Context.params.PAGE_PATH, page });
    defer ctx.allocator().free(source_path);
    const target_path = try fs.replaceExtension(ctx.allocator(), source_path, "htm");
    defer ctx.allocator().free(target_path);

    // get mtime of the source document
    const mtime = try (fs.getGitTime(source_path) catch fs.getMtime(source_path));
    const mtime_str = try fs.formatTimespec(ctx.allocator(), mtime, "%s");
    defer ctx.allocator().free(mtime_str);

    const f = try fs.FileSwapper.createAt(ctx.allocator(), target_path);
    defer f.close();

    // open new scope to close the file swapper when we're done writing
    try f.writer().print(
        \\<!-- Generated file, you may edit and discard {s} :) -->
        \\
    ,
        .{page},
    );

    // make pandoc runs reproducable
    var env_map = std.process.EnvMap.init(ctx.allocator());
    defer env_map.deinit();
    try env_map.put("SOURCE_DATE_EPOCH", mtime_str);

    try captureStdout(ctx, &.{
        Context.params.PANDOC,
        "--from",
        "markdown+wikilinks_title_after_pipe+autolink_bare_uris",
        "--to",
        "html5",
        "--lua-filter",
        // TODO: embed the preproc filter
        Context.params.PANDOC_PREPROC,
        "--strip-comments",
        "--output=-",
        source_path,
    }, &env_map, f.writer().any());

    // swap now, we want to edit mtime
    try f.swap();

    // fixup mtime of the resulting file, this has to happen after the file
    // swapper has been closed, because it works on a tmp file which is going
    // to be renamed (meaning mtime is changed again)
    const fd = try std.fs.cwd().openFile(target_path, .{ .mode = .write_only });
    defer fd.close();

    try std.posix.futimens(
        fd.handle,
        &[2]std.os.linux.timespec{ mtime, mtime },
    );
}

pub fn captureStdout(ctx: *Context, argv: []const []const u8, env_map: ?*std.process.EnvMap, buf: std.io.AnyWriter) !void {
    const command = try std.mem.join(ctx.allocator(), " ", argv);
    defer ctx.allocator().free(command);
    const label = try std.fmt.allocPrint(ctx.allocator(), "running {s}", .{command});
    defer ctx.allocator().free(label);

    ctx.progress_start(label);
    defer ctx.progress_end();

    var p = std.process.Child.init(argv, std.heap.page_allocator);
    p.stdout_behavior = .Pipe;
    p.progress_node = ctx.progress_nodes.getLast();
    p.env_map = env_map;

    try p.spawn();

    try buf.writeFile(p.stdout.?);

    const result = try p.wait();
    _ = result;
}

pub fn shell(ctx: *Context, command: []const u8) !void {
    const label = try std.fmt.allocPrint(ctx.allocator(), "running {s}", .{command});
    defer ctx.allocator().free(label);

    ctx.progress_start(label);
    defer ctx.progress_end();

    var p = std.process.Child.init(&.{
        "sh",
        "-c",
        command,
    }, std.heap.page_allocator);

    p.progress_node = ctx.progress_nodes.getLast();

    try p.spawn();
    const result = try p.wait();
    _ = result;
}