~subsetpark/erasmus

ref: b8d292340fd0b390cdc9e8ef44c6a5e3d1dc6253 erasmus/src/main.zig -rw-r--r-- 4.7 KiB
b8d29234 — Zach Smith Streamline error output 3 months ago
                                                                                
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
const std = @import("std");
const util = @import("./util.zig");
const Note = @import("./note.zig").Note;

const fs = std.fs;
const mem = std.mem;

pub const io_mode = .evented;
var debug_mode = false;

// Presumably this should be longer?
const max_line_length = 1024;

const help_text =
    \\Usage: er [-h] [-d] [DIRECTORY_NAME]
    \\
    \\  -h                Display this help message and quit.
    \\  -d                Enable debug mode.
    \\  DIRECTORY_NAME    Specify the directory `er` runs in. Defaults to ".".
    \\
;

// Read a note from disk, filtering out any lines
// inserted by the indexing process.
fn readBody(entry_name: []const u8, contents: *util.CharBuffer) !void {
    const file = try fs.cwd().openFile(entry_name, .{});
    defer file.close();

    const reader = file.reader();
    var started_body = false;
    var newline_counter: usize = 0;

    while (true) {
        var buf: [max_line_length]u8 = undefined;
        var maybe_line = util.nextLine(reader, &buf) catch |err| {
            std.debug.print("[{s}] Read error. ", .{entry_name});
            switch (err) {
                error.StreamTooLong => {
                    std.debug.print("Maximum line length: {d}\n", .{max_line_length});
                },
                else => {},
            }
            return err;
        };
        if (maybe_line) |line| {
            if (line.len > util.ref_prefix.len and mem.eql(
                u8,
                util.ref_prefix,
                line[0..util.ref_prefix.len],
            )) {
                // Filter out a ref line.
                continue;
            }
            if (!started_body and line.len == 0) {
                // Filter out padding before body.
                continue;
            }

            started_body = true;

            if (line.len == 0) {
                // If we are in the note body and encounter an empty line, fill
                // the newline buffer.
                newline_counter += 1;
            } else {
                // If we encounter a non-empty line, we're still in the note
                // body, so be sure to flush the newline buffer.
                try contents.appendNTimes('\n', newline_counter);
                newline_counter = 0;

                try contents.appendSlice(line);
                try contents.append('\n');
            }
        } else {
            break;
        }
    }
}

pub fn main() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const allocator = arena.allocator();

    // Process CLI args.
    var maybe_directory_name: ?[]const u8 = null;
    var args = try std.process.argsWithAllocator(allocator);
    // Skip the program name.
    _ = args.skip();
    while (args.next()) |arg| {
        if (mem.eql(u8, arg, "-d")) {
            debug_mode = true;
        } else if (mem.eql(u8, arg, "-h")) {
            std.debug.print(help_text, .{});
            std.os.exit(0);
        } else if (maybe_directory_name == null) {
            maybe_directory_name = arg;
        }
    }
    args.deinit();

    const directory_name = maybe_directory_name orelse ".";

    var notes = std.ArrayList(Note).init(allocator);
    defer notes.deinit();

    // Load note bodies into memory.
    const dir = try fs.cwd().openDir(directory_name, .{ .iterate = true });
    var iter = dir.iterate();
    while (try iter.next()) |entry| {
        if (entry.kind == .File) {
            const path = try fs.path.join(allocator, &[_][]const u8{
                directory_name,
                entry.name,
            });
            defer allocator.free(path);

            var note = Note.init(entry.name, allocator);
            try readBody(path, &note.body);
            try notes.append(note);
        }
    }

    // Populate all note metadata required to generate a new note body.
    // Populate in two passes, references first, because backlinks will look
    // for references to the current note.
    for (notes.items) |*note| {
        try note.populateReferences(allocator);
    }
    for (notes.items) |*note| {
        try note.populateBacklinks(notes, allocator);
    }

    // Assemble and write new note bodies.
    for (notes.items) |*note| {
        var buf = util.CharBuffer.init(allocator);
        defer buf.deinit();

        try note.generateBody(&buf, allocator);
        note.deinitData(allocator);

        // Optional debugging.
        if (debug_mode) {
            std.debug.print("{s}:\n\n{s}\n", .{
                note.name,
                buf.items,
            });
        }

        const path = try fs.path.join(allocator, &[_][]const u8{
            directory_name,
            note.name,
        });
        defer allocator.free(path);
        allocator.free(note.name);

        try fs.cwd().writeFile(path, buf.items);
    }
}