about summary refs log tree commit diff
path: root/src/main.zig
blob: 8e0b27ee102b06ab7f58c762724912acac2bc244 (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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
// TODO: just let compiler infer error set through recursive calls
// depends on: https://github.com/ziglang/zig/issues/2971

// This code is heavily insprired by Devine's implementation in C:
// https://github.com/hundredrabbits/100r.co/blob/main/src/main.c and his
// personal site https://github.com/XXIIVV/Oscean

const std = @import("std");
const gen = @import("gen.zig");
const exe = @import("exe.zig");

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

// tune std.log options
pub const std_options = std.Options{
    .log_level = .info,
};

pub const Context = struct {
    arena: std.heap.ArenaAllocator,
    arena_mx: std.Thread.Mutex,
    alloc: std.heap.ThreadSafeAllocator,

    progress_nodes: std.ArrayListUnmanaged(std.Progress.Node),
    progress_nodes_mx: std.Thread.Mutex,

    pages: std.ArrayListUnmanaged([]const u8),
    markdown: std.ArrayListUnmanaged([]const u8),

    generated: usize = 0,
    meta: usize = 0,
    tilde: usize = 0,

    pub const params = struct {
        pub const PAGE_PATH = "src/inc";
        pub const SITE_PATH = "site";
        pub const LINK_PATH = "links";

        pub const SITE_NAME = "gzr.im";
        pub const LICENSE_HOLDER = "robertgzr";
        pub const LICENSE_YEAR = "2025";
        pub const LICENSE_NAME = "BY-NC-SA 4.0";
        pub const LICENSE_URL = "https://git.sr.ht/~robertgzr/site/blob/master/LICENSE.content";
        pub const SOURCE_URL = "https://git.sr.ht/~robertgzr/site/tree/master";
        pub const PUBLIC_INBOX = "~robertgzr/public-inbox@lists.sr.ht";

        pub const PANDOC = "pandoc";
        pub const PANDOC_PREPROC = "preproc.lua";
    };

    fn create(parent_alloc: std.mem.Allocator) !*Context {
        const self = try parent_alloc.create(Context);
        self.* = .{
            .arena = std.heap.ArenaAllocator.init(parent_alloc),
            .arena_mx = .{},
            .alloc = undefined,
            .progress_nodes = .{},
            .progress_nodes_mx = .{},
            .pages = .{},
            .markdown = .{},
        };
        // create thread safe allocator
        self.alloc = std.heap.ThreadSafeAllocator{
            .child_allocator = self.arena.allocator(),
            .mutex = self.arena_mx,
        };
        // create root progress node
        try self.progress_nodes.append(
            self.allocator(),
            std.Progress.start(.{ .root_name = "" }),
        );
        return self;
    }

    fn destroy(self: *Context) void {
        self.progress_end();
        self.progress_nodes.deinit(self.allocator());
        self.arena.deinit();
        self.arena.child_allocator.destroy(self);
    }

    pub fn allocator(self: *Context) std.mem.Allocator {
        return self.alloc.allocator();
    }

    // progress handling
    pub fn progress_start(self: *Context, name: []const u8) void {
        self.progress_nodes_mx.lock();
        const new = self.progress_nodes.getLast().start(name, 0);
        self.progress_nodes.append(self.allocator(), new) catch @panic("insufficient memory");
        self.progress_nodes_mx.unlock();
    }
    pub fn progress_end(self: *Context) void {
        self.progress_nodes_mx.lock();
        const current = self.progress_nodes.pop() orelse return;
        self.progress_nodes_mx.unlock();
        current.end();
    }
};

fn index(ctx: *Context) !void {
    // avoid reindexing
    if ((ctx.pages.items.len != 0) and (ctx.markdown.items.len != 0)) return;

    ctx.progress_start("index");
    defer ctx.progress_end();

    var root = std.fs.cwd().openDir(Context.params.PAGE_PATH, .{ .iterate = true }) catch |err| switch (err) {
        error.FileNotFound => {
            std.log.err("Missing {s} folder.", .{Context.params.PAGE_PATH});
            std.os.linux.exit(1);
        },
        else => |e| return e,
    };
    defer root.close();

    var walk = try root.walk(ctx.allocator());
    defer walk.deinit();

    while (try walk.next()) |e| {
        switch (e.kind) {
            .file => {
                ctx.progress_start(e.basename);
                defer ctx.progress_end();

                if (std.mem.endsWith(u8, e.basename, ".htm")) {
                    std.log.debug("Indexing {s}", .{e.path});
                    try ctx.pages.append(ctx.allocator(), try ctx.allocator().dupe(u8, e.path));
                }
                if (std.mem.endsWith(u8, e.basename, ".md")) {
                    std.log.debug("Indexing {s}", .{e.path});
                    try ctx.markdown.append(ctx.allocator(), try ctx.allocator().dupe(u8, e.path));
                }
            },
            else => {},
        }
    }

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

    return;
}

const Cli = struct {
    fn run(ctx: *Context) !void {
        var argv = std.process.args();

        _ = argv.skip(); // skip arg0

        if (argv.inner.count == 1) {
            try Cli.mdCmd(ctx);
            try Cli.genCmd(ctx);
        }

        while (argv.next()) |arg| {
            if (std.mem.eql(u8, arg, "help")) {
                Cli.helpCmd(ctx);
            } else if (std.mem.eql(u8, arg, "gen")) {
                try Cli.genCmd(ctx);
            } else if (std.mem.eql(u8, arg, "md")) {
                try Cli.mdCmd(ctx);
            } else if (std.mem.eql(u8, arg, "watch")) {
                try Cli.watchCmd(ctx);
            } else {
                Cli.helpCmd(ctx);
            }
        }
    }

    fn helpCmd(_: *Context) void {
        std.debug.print("usage: rbl [gen|md]\n\n", .{});
    }

    fn genCmd(ctx: *Context) !void {
        try index(ctx);
        try gen.fixMtimes(ctx);
        try gen.generate(ctx);
    }

    fn mdCmd(ctx: *Context) !void {
        try index(ctx);
        try exe.runPandoc(ctx);
    }

    fn watchCmd(ctx: *Context) !void {
        try exe.shell(ctx,
            \\find ./src/inc/ -type f -print | entr -nc -dd -- rbl md gen
        );
    }
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}).init;
    const allocator = gpa.allocator();
    defer _ = gpa.deinit();

    var ctx = try Context.create(allocator);
    defer ctx.destroy();

    try Cli.run(ctx);
}