about summary refs log tree commit diff
path: root/src/exe.zig
blob: 63efd9ae473ee1939c5d911e9805ec46d4bf0421 (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
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)
    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) !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;
}