about summary refs log tree commit diff
path: root/src/exe.zig
blob: b246d56b6422fbf79d6092b57a50f93c3eccb2e0 (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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
const std = @import("std");

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

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

pub fn WaitPool(
    comptime T: type,
    comptime worker: anytype,
) type {
    return struct {
        const Self = @This();

        pub fn work(ctx: *Context, items: []const T, args: anytype) !void {
            var wg: std.Thread.WaitGroup = undefined;
            wg.reset();

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

            for (items) |*item| {
                // 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 func(
                        wwg: *std.Thread.WaitGroup,
                        wctx: *Context,
                        witem: *const T,
                        wargs: anytype,
                    ) void {
                        wwg.start();
                        defer wwg.finish();

                        const result = @call(
                            .auto,
                            worker,
                            .{ wctx, witem.* } ++ wargs,
                        );

                        result catch |err|
                            std.log.err("worker failed with: {}", .{err});
                    }
                }.func, .{ &wg, ctx, item, args });
            }

            pool.waitAndWork(&wg);
        }
    };
}

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

    try WaitPool(
        []const u8,
        pandocWorker,
    ).work(ctx, ctx.markdown.items, .{});

    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)
    try fs.setMtime(target_path, 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, env_map: ?*std.process.EnvMap) !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();
    p.env_map = env_map;

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