~cricket/jazz

9c4f9bbe4e0e644e98ec215d1371a94cb08dd31c — c piapiac 5 months ago
initial commit
A  => .build.yml +18 -0
@@ 1,18 @@
image: archlinux
packages:
    - zig
    - rsync
sources:
    - https://git.sr.ht/~cricket/jazz
environment:
    deploy: c@cro.wtf
secrets:
    - 252829bc-c28b-4183-a594-ffec12921193
tasks:
    - build: |
        cd jazz
        zig build run
    - deploy: |
        cd jazz
        sshopts="ssh -o StrictHostKeyChecking=no"
        rsync --rsh="$sshopts" -rP out/* $deploy:/usr/local/www/jazz.cro.wtf/

A  => .gitignore +2 -0
@@ 1,2 @@
zig-cache/
out/

A  => .gitmodules +3 -0
@@ 1,3 @@
[submodule "include/ckt"]
	path = include/ckt
	url = https://git.sr.ht/~cricket/zckt

A  => LICENSE +9 -0
@@ 1,9 @@
copyright (c) 2021 <_c@piapiac.org>

permission is hereby granted to modify, distribute, and/or use this software 
in any way you damn well please as long as this entire copyright notice, 
along with the disclaimer below, is kept intact

as far as the law allows, this software comes as is, without any warranty 
or condition, and no contributor will be liable to anyone for any damages 
related to this software or license, under any kind of legal claim

A  => README +4 -0
@@ 1,4 @@
this generates my blog!
it isn't very good (i dont think i should embed all my files into the programme) but it gets the job done.

licensed under the CP/PL license (but i'm not sure why you would use it?)

A  => build.zig +22 -0
@@ 1,22 @@
const std = @import("std");

pub fn build(b: *std.build.Builder) void {
    const target = b.standardTargetOptions(.{});
    const mode = b.standardReleaseOptions();

    const exe = b.addExecutable("jazz", "jazz.zig");

    exe.addPackage(.{ .name="zckt", .path="lib/zckt/zckt.zig" });
    exe.setTarget(target);
    exe.setBuildMode(mode);
    exe.install();

    const run_cmd = exe.run();
    run_cmd.step.dependOn(b.getInstallStep());
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }

    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);
}

A  => header.html +18 -0
@@ 1,18 @@
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>CRICKET'S JAZZ > {0s}</title>
        <link rel="stylesheet" href="stylesheet.css">
        <link rel="stylesheet" href="prism-ghcolors.css">

        <meta name="title" content="CRICKET'S JAZZ > {0s}">
        <meta name="description" content="{1s}">
        <meta name="author" content="c piapiac">

        <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
        <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
        <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
        <link rel="manifest" href="/site.webmanifest">
    </head>

A  => include/ckt +1 -0
@@ 1,1 @@
Subproject commit 5d2331a83180b6fb88ee1ef4640ea45e9e489042

A  => index/head.html +5 -0
@@ 1,5 @@
    <body>
        <h1><a href="https://jazz.cro.wtf">JAZZ.CRO.WTF</a>/index.html</h1>
        <p>WELCOME TO CRICKET'S BLOG! i rant here about all kinds of random crap! here's a list of all of my posts:</p>

        <ul>

A  => index/segment.html +4 -0
@@ 1,4 @@
            <li>
                <p><b><a href="https://jazz.cro.wtf/{s}">{s}</a></b></p>
                <p>{s}<p>
            </li>

A  => index/tail.html +5 -0
@@ 1,5 @@
        </ul>
        <p><i><a href="https://jazz.cro.wtf/rss.xml">rss.xml</a> is untested & generated by <a href="https://git.sr.ht/~cricket/jazz">jazz.zig</a>.</i></p>
        <p><i>something to say? <a href="mailto:cricket@piapiac.org">send me an email</a> or make your comments publicly known on the <a href="https://lists.sr.ht/~cricket/jazz-comments">public mailing list</a>.</i></p>
    </body>
</html>

A  => jazz.zig +122 -0
@@ 1,122 @@
// THIS IS A PROGRAMME FOR MYSELF!!
// you're free to try to use it for your own site or whatever, but
// 1. it's not well documented
// 2. i am not concerned with style; this code may be unreadable !
// 3. it sucks ass !
// you have been warned .

const std = @import("std");
const debug = std.debug;
const fs = std.fs;
const heap = std.heap;
const log = std.log;
const mem = std.mem;

const ckt = @import("zckt");
const Ckt = ckt.Parser;

const index_file = .{
    @embedFile("index/head.html"),
    @embedFile("index/segment.html"),
    @embedFile("index/tail.html"),
};

const rss_file = .{
    @embedFile("rss/head.xml"),
    @embedFile("rss/segment.xml"),
    @embedFile("rss/tail.xml"),
};

const header_file = @embedFile("header.html");
const post_file = @embedFile("post.html");

const post_files = .{
    // RANT - ckt.html
    @embedFile("posts/ckt.ckt"),

    // HELLO WORLD - hello.html
    @embedFile("posts/hello.ckt"),
};

pub fn main() !void {
    // stop complaining zig
    @setEvalBranchQuota(1000000);
    var arena = heap.ArenaAllocator.init(heap.page_allocator);
    defer arena.deinit();

    const a = &arena.allocator;

    log.info("generating tha jazz", .{});
    const cwd = fs.cwd();

    log.info("create or open out/", .{});
    // silently fail if out exists
    cwd.makeDir("out") catch {};
    const out = try cwd.openDir("out", .{});

    log.info("open static/\n", .{});
    const static = try cwd.openDir("static", .{.iterate=true});
    var static_iter = static.iterate();

    while (try static_iter.next()) |entry| {
        const name = entry.name;
        log.info("static/{0s} -> out/{0s}", .{name});
        try static.copyFile(name, out, name, .{});
    }

    // GET POSTS
    debug.print("\n",.{});
    var posts: [post_files.len]ckt.Table = undefined;
    inline for (post_files) |post, i| {
        posts[i] = try Ckt.parse(post, a);
        const name = posts[i].getString("filename") orelse unreachable;
        log.info("open & parse posts/{s}",.{name});
    }

    // CREATE INDEX FILE
    debug.print("\n",.{});
    log.info("create out/index.html", .{});
    var index = try out.createFile("index.html", .{.truncate=true});
    log.info("write header", .{});
    try index.writer().print(header_file, .{"index.html", "WELCOME TO CRICKET'S BLOG!"});
    log.info("write index head\n",.{});
    _ = try index.write(index_file[0]);

    // CREATE RSS FILE
    log.info("create out/rss.xml", .{});
    var rss = try out.createFile("rss.xml", .{.truncate=true});
    log.info("write rss head\n",.{});
    _ = try rss.write(rss_file[0]);

    // LOOP THROUGH POST FILES
    log.info("loop through post files:", .{});
    for (posts) |*post| {
        const filename = post.getString("filename") orelse unreachable;
        const title = post.getString("title") orelse unreachable;
        const description = post.getString("description") orelse unreachable;
        const pub_date = post.getString("pubdate") orelse unreachable;
        const last_build_date = post.getString("lastbuilddate") orelse unreachable;
        const content = post.getString("content") orelse unreachable;

        log.info("post {s}", .{title});
        log.info("write index segment",.{});
        try index.writer().print(index_file[1],.{filename,title,description});

        log.info("write rss segment",.{});
        try rss.writer().print(rss_file[1],.{title,filename,description,pub_date,last_build_date});

        log.info("create out/{s}",.{filename});
        var file = try out.createFile(filename, .{.truncate=true});
        log.info("write header",.{});
        try file.writer().print(header_file, .{title,description});
        log.info("write out/{s}\n",.{filename});
        try file.writer().print(post_file,.{filename,title,description,pub_date,content});
    }

    // WRITE INDEX & RSS TAIL
    log.info("write index tail",.{});
    _ = try index.write(index_file[2]);

    log.info("write rss tail",.{});
    _ = try rss.write(rss_file[2]);
}

A  => lib/zckt/.gitignore +1 -0
@@ 1,1 @@
zig-cache/

A  => lib/zckt/LICENSE +9 -0
@@ 1,9 @@
copyright (c) 2021 cricket <_c@piapiac.org>

permission is hereby granted to modify, distribute, and/or use this software 
in any way you damn well please as long as this entire copyright notice, 
along with the disclaimer below, is kept intact

as far as the law allows, this software comes as is, without any warranty 
or condition, and no contributor will be liable to anyone for any damages 
related to this software or license, under any kind of legal claim

A  => lib/zckt/README +44 -0
@@ 1,44 @@
       _   _
 _____| |_| |
|_ / _| / / _|
/__\__|_\_\__|
    zig ckt parser

ABOUT
    this parser will parse a ckt file into to a stringhashmap
    ckt is a stupid simple file format for notating tables
    you can view more about it on https://sr.ht/~cricket/ckt

USAGE
    dont really use this yet; it's sort of just bodged together to work,
    and tests are lacking / incomplete.

    that being said,
    ---
    const zckt = @import("zckt").Parser;

    // ...
    zkct.parse(data, allocator);
    // returns a table of key/value pairs you can work with,
    // with values being either strings or tables.

DEVELOPMENT
    contributions are welcome to zckt and ckt itself!
    
    don't worry too much about coding style! worry more about the code itself, 
    though, please run `zig fmt` on any changed files before contributing
    and try to follow the zig style guide.

    then, send an email with your patch to ~cricket/zckt-devel@lists.sr.ht
    (unsure how? check out git-send-email.io)

LICENSE
    zckt is free software - it is licensed under the CP/PL license

    this means that you're free to use the software in any way you damn well please,
    as long as the copyright notice and warranty disclaimer are kept intact

    read the LICENSE file for more information

AUTHORS
    do `git shortlog -sne` to show a list of all contributors

A  => lib/zckt/zckt.zig +592 -0
@@ 1,592 @@
// small, bodged together ckt parser
//
//
const std = @import("std");
const debug = std.debug;
const fmt = std.fmt;
const mem = std.mem;

pub const Key = []const u8;

pub const Value = union(enum) {
    string: []const u8,
    table: Table,

    pub fn deinit(self: *Value, a: *mem.Allocator) void {
        switch (self.*) {
            .string => |str| a.free(str),
            .table => |*table| table.deinit(),
        }
    }

    pub fn asString(self: Value) error{NotAString}![]const u8 {
        switch (self) {
            .string => |str| return str,
            .table => return error.NotAString,
        }
    }

    pub fn asTable(self: Value) error{NotATable}!Table {
        switch (self) {
            .string => return error.NotATable,
            .table => |table| return table,
        }
    }

    pub fn format(self: Value, comptime _: []const u8, options: fmt.FormatOptions, writer: anytype) !void {
        switch (self) {
            .string => |str| try writer.print("{s}", .{str}),
            .table => |table| try writer.print("[ ... ]", .{}), //try writer.print("{s}", .{table}),
        }
    }
};

pub const Table = struct {
    pub const Map = std.StringArrayHashMap(Value);

    map: Map,
    a: *mem.Allocator,

    pub fn init(a: *mem.Allocator) Table {
        return .{
            .map = Map.init(a),
            .a = a,
        };
    }

    pub fn format(self: Table, comptime _: []const u8, options: fmt.FormatOptions, writer: anytype) !void {
        try writer.print("[ ", .{});
        var iter = self.map.iterator();
        while (iter.next()) |entry| try writer.print("{s}={s};", .{ entry.key, entry.value });
        try writer.print(" ]", .{});
    }

    pub fn get(self: *Table, key: []const u8) ?Value {
        return self.map.get(key);
    }

    pub fn getString(self: *Table, key: []const u8) ?[]const u8 {
        const value = self.map.get(key) orelse return null;
        return value.asString() catch return null;
    }

    pub fn getTable(self: *Table, key: []const u8) ?Table {
        const value = self.map.get(key) orelse return null;
        return value.asTable() catch return null;
    }

    pub fn put(self: *Table, key: []const u8, value: Value) !void {
        var result = try self.map.fetchPut(key, value);

        // free memory if needed
        if (result) |*entry| {
            self.a.free(key);
            entry.value.deinit(self.a);
        }
    }

    pub fn deinit(self: *Table) void {
        var iter = self.map.iterator();

        while (iter.next()) |entry| {
            self.a.free(entry.key);
            entry.value.deinit(self.a);
        }

        self.map.deinit();
    }
};

pub const Token = union(enum) {
    pub const String = union(enum) {
        unquoted: []const u8,
        quoted: []const u8,
        multiline: []const u8,

        pub fn format(self: String, comptime _: []const u8, options: fmt.FormatOptions, writer: anytype) !void {
            switch (self) {
                .unquoted => |str| try writer.print("unquoted: {s}", .{str}),
                .quoted => |str| try writer.print("quoted: {s}", .{str}),
                .multiline => |str| try writer.print("multiline: {s}", .{str}),
            }
        }
    };

    keyeq: String, // key =
    value: String, // value
    table_start, // table start
    table_end,

    // just for pretty printing when debugging lol
    pub fn format(self: Token, comptime _: []const u8, options: fmt.FormatOptions, writer: anytype) !void {
        switch (self) {
            .keyeq => |str| try writer.print("key {{{s}}} =", .{str}),
            .value => |str| try writer.print("value {{{s}}};", .{str}),
            .table_start => |str| try writer.print("[", .{}),
            .table_end => |str| try writer.print("]", .{}),
        }
    }
};

pub const Tokenizer = struct {
    const Error = error{
        EmptyKey,
        InvalidChar,
        UnexpectedNewline,
        UnexpectedEof,
    };

    content: []const u8,

    index: usize,
    last: Token,
    finished: bool,

    pub fn init(data: []const u8) Tokenizer {
        return Tokenizer{
            .content = data,

            .index = 0,
            .last = undefined,
            .finished = false,
        };
    }

    fn eof(self: *Tokenizer) Error!void {
        switch (self.last) {
            .table_start => return Error.UnexpectedEof,
            else => self.finished = true,
        }
    }

    fn skipToNewline(self: *Tokenizer) Error!void {
        while (self.content[self.index] != '\n') {
            self.index += 1;
            if (self.index >= self.content.len) {
                return self.eof();
            }
        }
    }

    fn skipPastNewline(self: *Tokenizer) Error!void {
        try self.skipToNewline();
        self.index += 1;
    }

    fn skipWhitespace(self: *Tokenizer) Error!void {
        while (self.index < self.content.len) : (self.index += 1) {
            switch (self.content[self.index]) {
                // actual whitespace
                ' ', '\t', 0x0B, 0x0C, '\r' => continue,
                '#' => return try self.skipToNewline(),
                else => return,
            }
        }
    }

    fn skipWhitespaceAndNewlines(self: *Tokenizer) Error!void {
        while (self.index < self.content.len) : (self.index += 1) {
            switch (self.content[self.index]) {
                // actual whitespace
                ' ', '\t', 0x0B, 0x0C, '\r' => continue,
                '#' => try self.skipPastNewline(),
                '\n' => continue,
                else => return,
            }
        }
        try self.eof();
    }

    fn skipWhitespaceAndNewlinesAndBreaks(self: *Tokenizer) Error!void {
        while (self.index < self.content.len) : (self.index += 1) {
            switch (self.content[self.index]) {
                // actual whitespace
                ' ', '\t', 0x0B, 0x0C, '\r' => continue,
                ';', ',' => continue,
                '#' => try self.skipPastNewline(),
                '\n' => continue,
                else => return,
            }
        }
        try self.eof();
    }

    fn readString(self: *Tokenizer) ![]const u8 {
        const start_index = self.index;
        while (self.index < self.content.len) : (self.index += 1) {
            const char = self.content[self.index];
            switch (char) {
                '\n', '=', ';', ',', ']' => {
                    // remove trailing whitespace
                    // (eg key = value)
                    var end_index = self.index - 1;
                    while (end_index > start_index) {
                        switch (self.content[end_index]) {
                            ' ', '\t', 0x0B, 0x0C, '\r' => end_index -= 1,
                            else => return self.content[start_index .. end_index + 1],
                        }
                    }
                    return error.EmptyKey;
                },
                else => continue,
            }
        }

        // EOF
        try self.eof();
        return self.content[start_index..self.index];
    }

    fn readQuotedString(self: *Tokenizer, comptime quote: u8) Error![]const u8 {
        const start_index = self.index;

        self.index += 1;

        while (self.index < self.content.len) : (self.index += 1) {
            const char = self.content[self.index];
            switch (char) {
                quote => {
                    self.index += 1;
                    return self.content[start_index..self.index];
                },
                '\n' => return error.UnexpectedNewline,
                '\\' => self.index += 1,
                else => continue,
            }
        }

        // EOF before closing bracket
        return error.UnexpectedEof;
    }

    fn readMultilineString(self: *Tokenizer) Error![]const u8 {
        const start_index = self.index;

        const State = enum { root, line };
        var state = State.root;

        while (self.index < self.content.len) : (self.index += 1) {
            switch (state) {
                .root => {
                    const end_index = self.index;
                    try self.skipWhitespace();
                    switch (self.content[self.index]) {
                        '|' => state = State.line,
                        else => return self.content[start_index..end_index],
                    }
                },
                .line => {
                    switch (self.content[self.index]) {
                        '\n' => state = State.root,
                        else => continue,
                    }
                },
            }
        }

        // empty line or EOF
        return self.content[start_index..self.index];
    }

    pub fn next(self: *Tokenizer) Error!?Token {
        if (!self.finished) {
            self.last = ret: {
                // treat ; and , as whitespace
                try self.skipWhitespaceAndNewlinesAndBreaks();
                if (self.finished) return null;
                const char = self.content[self.index];
                switch (char) {
                    '[' => break :ret Token.table_start,
                    ']' => break :ret Token.table_end,
                    '=' => return error.InvalidChar,
                    else => {
                        var value: Token.String = switch (char) {
                            '"' => Token.String{ .quoted = try self.readQuotedString('"') },
                            '\'' => Token.String{ .quoted = try self.readQuotedString('\'') },
                            '|' => Token.String{ .multiline = try self.readMultilineString() },
                            else => Token.String{ .unquoted = try self.readString() },
                        };
                        try self.skipWhitespaceAndNewlines();
                        if (!self.finished) {
                            switch (self.content[self.index]) {
                                ';', ',' => break :ret Token{ .value = value },
                                '=' => break :ret Token{ .keyeq = value },
                                ']' => {
                                    self.index -= 1;
                                    break :ret Token{ .value = value };
                                },
                                else => {
                                    self.index -= 1;
                                    break :ret Token{ .value = value };
                                },
                            }
                        } else break :ret Token{ .value = value };
                    },
                }
            };
            self.index += 1;
            if (self.index >= self.content.len) try self.eof();
            return self.last;
        } else {
            return null;
        }
    }
};

// just a namespace to put parsing-related functions in,
// rather than having them scattered about in the top-level.
pub const Parser = struct {
    const MemError = error{
        OutOfMemory,
    };

    const StringError = error{
        UnexpectedNewline,
        InvalidEscapeChar,
        InvalidHexEscape,
        InvalidUnicodeEscape,
    };

    fn skipWhitespace(str: []const u8, i: *usize) void {
        while (i.* < str.len) : (i.* += 1) {
            switch (str[i.*]) {
                ' ', '\t', 0x0B, 0x0C, '\r' => continue,
                else => return,
            }
        }
    }

    fn parseMultilineString(str: []const u8, a: *mem.Allocator) MemError![]const u8 {
        const State = enum { root, line };

        var state = State.line;
        var buf = std.ArrayList(u8).init(a);
        defer buf.deinit();

        var i: usize = 0;
        debug.assert(str[i] == '|');
        i += 1; // skip initial pipe

        while (i < str.len) : (i += 1) {
            switch (state) {
                .root => {
                    skipWhitespace(str, &i);
                    switch (str[i]) {
                        '|' => state = State.line,
                        else => return buf.toOwnedSlice(),
                    }
                },
                .line => {
                    const char = str[i];
                    switch (char) {
                        '\\' => {
                            const next_is_newline_or_eof = if (i + 1 < str.len) str[i + 1] == '\n' else true;
                            if (next_is_newline_or_eof) {
                                if (str[i - 1] == '\\') continue;
                                i += 1; // skip over newline
                                state = State.root;
                            } else {
                                try buf.append('\\');
                            }
                        },
                        '\n' => {
                            try buf.append('\n');
                            state = State.root;
                        },
                        else => try buf.append(char),
                    }
                },
            }
        }

        // this shouldn't happen; the tokenizer should've caught and errored on this
        return buf.toOwnedSlice();
    }

    // ~~stolen~~ inspired by std/zig/string_literal.zig
    fn parseQuotedString(str: []const u8, a: *mem.Allocator, comptime quote: u8) (MemError || StringError)![]const u8 {
        const State = enum { root, backslash };

        var state = State.root;
        var buf = std.ArrayList(u8).init(a);
        defer buf.deinit();

        var i: usize = 0;
        i += 1;

        while (i < str.len) : (i += 1) {
            const char = str[i];
            switch (state) {
                .root => switch (char) {
                    '\n' => return error.UnexpectedNewline,
                    '\\' => state = State.backslash,
                    quote => {
                        i += 1;
                        return buf.toOwnedSlice();
                    },
                    else => {
                        try buf.append(char);
                    },
                },
                .backslash => switch (char) {
                    'f' => {
                        try buf.append(0x0C);
                        state = State.root;
                    },
                    'n' => {
                        try buf.append('\n');
                        state = State.root;
                    },
                    'r' => {
                        try buf.append('\r');
                        state = State.root;
                    },
                    't' => {
                        try buf.append('\t');
                        state = State.root;
                    },
                    'v' => {
                        try buf.append(0x0B);
                        state = State.root;
                    },
                    '\\', '"', '\'' => {
                        try buf.append(char);
                        state = State.root;
                    },
                    'x' => {
                        if (str.len < i + 3) return error.InvalidHexEscape;
                        if (fmt.parseUnsigned(u8, str[i + 1 .. i + 3], 16)) |byte| {
                            try buf.append(byte);
                            state = State.root;
                            i += 2;
                        } else |err| {
                            return error.InvalidHexEscape;
                        }
                    },
                    'u' => {
                        if (str.len < i + 5) return error.InvalidUnicodeEscape;
                        if (fmt.parseUnsigned(u16, str[i + 1 .. i + 5], 16)) |unic| {
                            try buf.appendSlice(mem.toBytes(unic)[0..]);
                            state = State.root;
                            i += 4;
                        } else |err| {
                            return error.InvalidUnicodeEscape;
                        }
                    },
                    'U' => {
                        if (str.len < i + 9) return error.InvalidUnicodeEscape;
                        if (fmt.parseUnsigned(u32, str[i + 1 .. i + 9], 16)) |unic| {
                            try buf.appendSlice(mem.toBytes(unic)[0..]);
                            state = State.root;
                            i += 8;
                        } else |err| {
                            return error.InvalidUnicodeEscape;
                        }
                    },
                    else => return error.InvalidEscapeChar,
                },
            }
        }

        // unclosed string
        // the tokenizer should've caught this
        return buf.toOwnedSlice();
    }

    fn parseString(tok: Token.String, a: *mem.Allocator) (MemError || StringError)![]const u8 {
        switch (tok) {
            .unquoted => |str| return a.dupe(u8, str),
            .quoted => |str| return switch (str[0]) {
                '"' => parseQuotedString(str, a, '"'),
                '\'' => parseQuotedString(str, a, '\''),
                else => @panic("unknown quote?"),
            },
            .multiline => |str| return parseMultilineString(str, a),
        }
    }

    pub fn parseTable(tokens: *Tokenizer, a: *mem.Allocator) (MemError || StringError || Tokenizer.Error)!Table {
        var table = Table.init(a);
        var key: ?Key = null;
        var value: ?Value = null;
        var i: usize = 0;

        while (try tokens.next()) |token| {
            switch (token) {
                .table_end => return table,
                .keyeq => |str| key = try parseString(str, a),
                .table_start => value = .{ .table = try parseTable(tokens, a) },
                .value => |str| value = .{ .string = try parseString(str, a) },
            }

            if (value) |val| {
                const keystr = key orelse key: {
                    defer i += 1;
                    break :key try fmt.allocPrint(a, "{d}", .{i});
                };
                try table.put(keystr, val);
                value = null;
                key = null;
            }
        }

        // if no table end, return the table anyway;
        // any unclosed tables should've been caught by the tokenizer
        return table;
    }

    // aka 'put that shit in a hashmap please'
    pub fn parse(data: []const u8, a: *mem.Allocator) !Table {
        var tokenizer = Tokenizer.init(data);
        return parseTable(&tokenizer, a);
    }
};


const testing = std.testing;
const alloc = testing.allocator;

// TODO: more tests
test "key = [ table ]" {
    const zckt =
        \\table = [ this is a table :), this is the second value ]
    ;

    var table = try Parser.parse(zckt, alloc);
    defer table.deinit();

    testing.expectEqualStrings(table.getTable("table").?.getString("1").?, "this is the second value");
}

test "duplicate keys overwrite" {
    const zckt =
        \\key = this is a value
        \\key = haha! overwritten
    ;

    var table = try Parser.parse(zckt, alloc);
    defer table.deinit();

    testing.expectEqualStrings(table.getString("key").?, "haha! overwritten");
}

test "multiline string" {
    const zckt =
        \\multiline string =
        \\  |this is a multiline string
        \\  |that means it can span multiple lines
        \\  |however, i can also make \
        \\  |part of it span one line!
    ;

    const multiline_string =
        \\this is a multiline string
        \\that means it can span multiple lines
        \\however, i can also make part of it span one line!
    ;

    var table = try Parser.parse(zckt, alloc);
    defer table.deinit();

    testing.expectEqualStrings(table.getString("multiline string").?, multiline_string);
}

A  => post.html +18 -0
@@ 1,18 @@
    <body>
        <script src="prism.js"></script>
        <h1><a href="https://jazz.cro.wtf">JAZZ.CRO.WTF</a>/{s}</h1>
        <h2>{s}</h2>
        <p>{s}</p>
        <p><i>(posted {s})</i></p>
        <br />

        <!-- sorry if this is unindented :P -->
        <!-- this is just due to how i generate the webpage -->
        <!-- (https://git.sr.ht/~cricket/jazz) -->
{s}

        <br />
        <p><i>questions? comments? hatred toward me and my opinions? <a href="mailto:cricket@piapiac.org">send me an email</a></i></p>
        <p><i>or make your comments publicly known on the <a href="https://lists.sr.ht/~cricket/jazz-comments">public mailing list</a>.</i></p>
    </body>
</html>

A  => posts/ckt.ckt +242 -0
@@ 1,242 @@
filename=ckt.html
title=A RANT OF CONFIGS
description=all config formats suck and how mine sucks a little less

pubdate="Mon, 20 Apr 2021 00:15:00 PST"
lastbuilddate="Mon, 19 Apr 2021 00:15:00 PST"
content=
    |<p>all configuration formats suck; they're either too complicated, not human readable, not machine readable, not complicated enough, or a combination of all of those.</p>
    |<p>keep in mind i am just critizing these as use for configuration files, and they may excel in other things (data serialization and the like), while ckt may not.</p>
    |<p>here are all my (least) favourite offenders.</p>
    |
    |<h2>XML</h2>
    |<h3>e<b>X</b>tensible <b>M</b>arkup <b>L</b>anguage</h3>
    |<p>i don't think i need to go too much into XML. i don't like it, and i don't think anyone really does. it's not machine or human readable by really any sense of the word.</p>
    |<p>i don't think a lot of people really disagree with me here, and i'm not gonna preach to the choir; nobody (not many) people use XML for simple configuration files.</p>
    |
    |<h2>JSON</h2>
    |<h3><b>J</b>ava<b>S</b>cript <b>O</b>bject <b>N</b>otation</h3>
    |<p>congrats! we've taken a big huge massive fat step up from XML! here's what i really like about JSON.</p>
    |<p><b>1</b>. it's human readable enough with a little bit of noise</p>
    |<p><b>2</b>. it's extremely machine readable!</p><br />
    |
    |<p>though of course, i have a few problems with it:</p>
    |<p><b>1</b>. it's not human typeable enough; that is, all the { " , }s get in the way when you're just trying to just write a damn config file. this isn't a problem for all things, but for little configuration files it can get in the way.</p>
    |<p><b>2</b>. nooo trailing commas! this is a small point but relates back to #1.</p>
    |<p><b>3</b>. though the opposite of what ive typed just a second ago, it is not extremely human readable!</p>
    |<p><b>4</b>. too many data types in its specification; while this might be the opposite of what you've heard / experienced with json, i am just talking about simple config formats here! id like to leave the data types up to the user rather than up to the specification. (in fact, many of these formats have the same issue, though i guess this is a highly opinionated one.)</p>
    |<p><b>5</b>. no comments!</p>
    |<p>here's a quick example in JSON that ill copy in other formats to show you how they look;</p>
    |<pre>
        |<code class="language-json">
            |{
            |   "firstName": "John",
            |   "lastName": "Smith",
            |   "isAlive": true,
            |   "age": 27,
            |   "phoneNumbers": [
            |       {
            |           "type": "home",
            |           "number": "212 555-1234"
            |       },
            |       {
            |           "type": "office",
            |           "number": "646 555-4567"
            |       }
            |   ]
            |}
        |</code>
    |</pre>
    |<p>so, what? should i make a superset of JSON? a JSON with the problems fixed?</p>
    |<p>no! i shouldn't do that. that's 1. too boring, 2. a bit pointless, 3. <a href="https://github.com/bevry/cson">has</a> <a href="https://github.com/lifthrasiir/cson">been</a> <a href="https://json5.org/">tried</a> <a href="https://rome.tools/#rome-json">too</a> <a href="https://github.com/ekon-org/ekon">many</a> <a href="https://github.com/lifthrasiir/cson">times</a><a href="https://yaml.org/">.</a>
    |
    |<h2>YAML</h2>
    |<h3><b>Y</b>aml <b>A</b>in't <b>M</b>arkup <b>L</b>anguage</h3>
    |<p>YAML is a strict superset of JSON that is a tad bit more readable. here's what i like about it:</p>
    |<p><b>1</b>. it is a tad bit more readable than JSON is, with much less noise</p>
    |<p><b>2</b>. it is much more human writable than JSON, making it much better for configuration!</p>
    |<p><b>3</b>. commmennntss :)</p><br />
    |
    |<p>here is what i really don't like about it:</p>
    |<p><b>1</b>. it is super complicated! (seriously. <a href="https://yaml.org/spec/1.2/spec.html">the spec</a> is fucking massive; i can't imagine trying to write a completely spec-compliant YAML parser.</p>
    |<p><b>2</b>. seriously the spec is too large</p>
    |<p>while the JSON example works in YAML, ill put a more yaml-like example here for visuals</p>
    |<pre>
        |<code class="language-yaml">
            |firstName: John
            |lastName: Smith
            |isAlive: true
            |age: 27
            |
            |# john's phone numbers
            |phoneNumbers:
            |   - type: home
            |     number: "212 555-1234"
            |   - type: office
            |     number: "646 555-4567"
        |</code>
    |</pre>
    |<p>this is super readable and the syntax is almost intuitive! though i am not completely satisfied</p>
    |
    |<h2>TOML</h2>
    |<h3><b>T</b>om's <b>O</b>bvious, <b>M</b>inimal <b>L</b>anguage</h3>
    |<p>TOML isn't a superset of JSON! cool! and it's 'obvious' and 'minimal', so i should be happy with it! here is what i like about it:</p>
    |<p><b>1</b>. though minor, key = value is more obvious to me than key: value (or parent: child)</p>
    |<p><b>2</b>. it is designed to map unambiguously to a hash table!</p>
    |<p><b>3</b>. it has a single, formal specification, while ini has many differing competing variants (this isn't completely a good thing)</p>
    |<p><b>4</b>. it is easy enough to implement</p><br />
    |
    |<p>here's what i don't like about it:</p>
    |<p><b>1</b>. toml is <a href="https://toml.io/en/v1.0.0#keys">not</a> <a href="https://toml.io/en/v1.0.0#string">completely</a> <a href="https://toml.io/en/v1.0.0#array-of-tables">obvious</a>, indicating 'obvious' is merely part of a <a href="https://www.boringcactus.com/2021/03/21/coins.html">naming template</a> rather than an actual descriptor.</p>
    |<p><b>2</b>. the spec decides data types (as in JSON, YAML, but not INI) rather than the user/implementation</p>
    |<p><b>3</b>. less typeable than YAML; it is a bit more noisy</p>
    |<p>and of course, the example in TOML</p>
    |
    |<pre>
        |<code class="language-toml">
            |first_name="John"
            |last_name="Smith"
            |is_alive="true"
            |age=27
            |
            |# john's phone numbers
            |[[phone_numbers]]
            |type="home"
            |number="212 555-1234"
            |
            |[[phone_numbers]]
            |type="office"
            |number="646 555-4567"
        |</code>
    |</pre>
    |<p>it is really unobvious how to do array of tables like this, a not immediately clear from the syntax either.</p>
    |
    |<h2>ZZZ</h2>
    |<h3>boring human readable data format for <b>Z</b>ig</h3>
    |<p>zzz syntax describes a tree of strings. it has very little syntactic noise and is really easy to implement!</p>
    |<p>here is what i really really really really like about zzz</p>
    |<p><b>1</b>. it is super duper simple and lightweight - you can read its (sparse) spec in about 2-3 minutes</p>
    |<p><b>2</b>. the spec itself doesn't define any data-types, only a parent:child relationship</p>
    |<p><b>3</b>. super duper readable and typeable</p>
    |<p><b>4</b>. reference implementation is in zig!! (yay zig!)</p><br />
    |
    |<p>in fact, i really like zzz! its implementation is really great, and it's super nice and simple.</p>
    |<p>here is what i don't like about it:</p>
    |<p><b>1</b>. it doesn't always unambiguously map into a hash table. consider the following:</p>
    |<pre>
        |<code class="language-yaml">
            |popup:
            |  menuitem:
            |    : value: New
            |      onclick: CreateNewDoc()
            |    : value: Open
            |      onclick: OpenDoc()
            |    : value: Close
            |      onclick: CloseDoc()
        |</code>
    |</pre>
    |<p>how would i go about putting this into a hash map?</p>
    |<p>specifically "menuitem"; it is just a bunch of k-v pairs with empty keys. hash tables can't have repeating keys, so i would have to see if the key was empty or something and use that to make a list. this is not unambiguous and lead me to a little bit of headache while trying to use it.</p>
    |
    |<h2>RECAP / TL;DR</h2>
    |<p><b>1</b>. i hate <a href="https://www.w3.org/XML/">XML</a>.</p>
    |<p><b>2</b>. i hate using <a href="https://www.json.org/">JSON</a> for config.</p>
    |<p><b>3</b>. i hate implementing <a href="https://yaml.org/">YAML</a>.</p>
    |<p><b>4</b>. i hate the lies and unobviousness of <a href="https://toml.io">TOML</a>.</p>
    |<p><b>5</b>. i really like <a href="https://github.com/gruebite/zzz">ZZZ</a>, but i just want to parse some god damn thing into a hash table.</p>
    |
    |<h2>CKT</h2>
    |<h3><b>C</b>ric<b>K</b>et's <b>T</b>able notation</h3>
    |<p>here's some things i thought about and tried to perfect while ckt:</p>
    |<p><b>1</b>. supreme typeability (e.g. using [] rather than {} for tables means less shift holding)</p>
    |<p><b>2</b>. a sort-of specific spec? one that's specific enough for ckt files not to differ in syntax, but not specific enough to make it extremely complicated to implement properly</p>
    |<p><b>3</b>. supreme readability! config files are for humans to write, after all</p>
    |<p><b>4</b>. obviousness - a super short read of the spec or just reading a ckt file and you should know all about how to write a ckt file</p><br />
    |
    |<p>and here are some things i did not try to make ckt for:</p>
    |<p><b>1</b>. complex data types (if you want/need to use complex datatypes, i do not think ckt is the way to go)
    |<p><b>2</b>. speed - while i'm sure one could create a blazing fast static implementation or something, i didn't really have it in mind when designing the spec or writing my implementation</p>
    |<p><b>3</b>. other people - you might absolutely despise this format. that's okay. i made this format really only for myself; others are free to use, implement, fork, etc and that's great, but i had really only thought about myself while writing it. i only got opinion from <a href="https://github.com/luawtf">one other person</a>.
    |<h3>shut up and show me!</h3>
    |<p>ok! here's that repeated example as a ckt file</p>
    |<pre>
        |<code class="language-toml">
            |first_name=John
            |last_name=Smith
            |is_alive=true
            |age=27
            |
            |# john's phone numbers
            |phone_numbers = [
            |    [
            |        type=home
            |        number=212 555-1234
            |    ]
            |    [
            |        type=office
            |        number=646 555-4567
            |    ]
            |]
        |</code>
    |</pre>
    |<p>here is a little description of ckt:</p>
    |<pre>
        |<code class="language-toml">
            |# ckt files describe tables
            |# tables inside this top-level one are
            |# surrounded with square braces []
            |
            |# initializations are separated by
            |# newlines, commas, or semicolons
            |
            |# key value pairs are
            |# 'record style initializations'
            |
            |# keys are strings
            |# values are strings or tables
            |record style = woah isnt it cool ?
            |init table = [ key = value ]
            |
            |# surrounding whitespace is trimmed
            |# the below is equivelant to the above
            |"record style" = "woah isnt it cool ?"
            |
            |# duplicate keys are overwritten
            |x = 13
            |x = 161
            |# (x = 161)
            |
            |# bare values are
            |# "list style initializations"
            |value1, value2
            |table = [ list, style, initialization ]
            |
            |# the keys of list style initializations
            |# should be the previous list-style key+1,
            |# counting up in decimal
            |equivelant table = [
            |    0 = list,
            |    1 = style,
            |    2 = initialization
            |]
            |
            |# you can mix and match
            |polyline = [
            |    color= blue; thickness = 2
            |    [ x = 0;   y = 0 ]
            |    [ x = -10; y = 0 ]
            |    [ x = -10; y = 1 ]
            |    [ x = 0;   y = 1 ]
            |]
            |
            |# also, multiline strings
            |hehehe =
            |    |look at this COOL
            |    |MULTILINE
            |    |STRING
        |</code>
    |</pre>
    |<p>you can read the "complete" specification <a href="https://git.sr.ht/~cricket/ckt">here</a>.</p>
    |<p>if you want more examples, <a href="https://git.sr.ht/~cricket/jazz.zig">this blog</a> uses ckt for its posts.</p>
    |<p>if you want to use it right away, i have a <a href="https://git.sr.ht/~cricket/zckt">small implementation</a> written in zig! (though ckt is designed to be as easy to implement as possible)</p>
    |<p><i>(do note that no actual hate is directed at any of the formats hated on above.)</i></p>

A  => posts/hello.ckt +10 -0
@@ 1,10 @@
filename=hello.html
title=HELLO WORLD
description=a test of my extremely small blog

pubdate="Mon, 19 Apr 2021 23:52:00 PST"
lastbuilddate="Mon, 19 Apr 2021 23:52:00 PST"
content=
    |<p>HELLOOOO, WORLD!</p>
    |<p>all of this blog was generated using <a href="https://git.sr.ht/~cricket/jazz">jazz.zig</a></p>
    |<p>i hope you're ready for some incoherent ramblings!</p>

A  => posts/template.ckt +7 -0
@@ 1,7 @@
filename=
title=
description=

pubdate="Mon, 19 Apr 2021 23:30:00 PST"
lastbuilddate="Mon, 19 Apr 2021 23:30:00 PST"
content=

A  => rss/head.xml +6 -0
@@ 1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="1.0">
    <channel>
        <title>CRICKET'S JAZZ</title>
        <link>https://jazz.cro.wtf</link>
        <description>blog about random crap on my mind</description>

A  => rss/segment.xml +7 -0
@@ 1,7 @@
            <item>
                <title>{s}</title>
                <link>https://bush.cro.wtf/{s}</link>
                <description>{s}</description>
                <pubDate>{s}</pubDate>
                <lastBuildDate>{s}</lastBuildDate>
            </item>

A  => rss/tail.xml +2 -0
@@ 1,2 @@
    </channel>
</rss>

A  => static/android-chrome-192x192.png +0 -0
A  => static/android-chrome-512x512.png +0 -0
A  => static/apple-touch-icon.png +0 -0
A  => static/favicon-16x16.png +0 -0
A  => static/favicon-32x32.png +0 -0
A  => static/favicon.ico +0 -0
A  => static/prism-ghcolors.css +122 -0
@@ 1,122 @@
/**
 * GHColors theme by Avi Aryan (http://aviaryan.in)
 * Inspired by Github syntax coloring
 */

code[class*="language-"],
pre[class*="language-"] {
	color: #393A34;
	font-family: "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
	direction: ltr;
	text-align: left;
	white-space: pre;
	word-spacing: normal;
	word-break: normal;
	font-size: .9em;
	line-height: 1.2em;

	-moz-tab-size: 4;
	-o-tab-size: 4;
	tab-size: 4;

	-webkit-hyphens: none;
	-moz-hyphens: none;
	-ms-hyphens: none;
	hyphens: none;
}

pre > code[class*="language-"] {
	font-size: 1em;
}

pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
	background: #b3d4fc;
}

pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
	background: #b3d4fc;
}

/* Code blocks */
pre[class*="language-"] {
	padding: 1em;
	margin: .5em 0;
	overflow: auto;
	border: 1px solid #dddddd;
	background-color: white;
}

/* Inline code */
:not(pre) > code[class*="language-"] {
	padding: .2em;
	padding-top: 1px;
	padding-bottom: 1px;
	background: #f8f8f8;
	border: 1px solid #dddddd;
}

.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
	color: #999988;
	font-style: italic;
}

.token.namespace {
	opacity: .7;
}

.token.string,
.token.attr-value {
	color: #e3116c;
}

.token.punctuation,
.token.operator {
	color: #393A34; /* no highlight */
}

.token.entity,
.token.url,
.token.symbol,
.token.number,
.token.boolean,
.token.variable,
.token.constant,
.token.property,
.token.regex,
.token.inserted {
	color: #36acaa;
}

.token.atrule,
.token.keyword,
.token.attr-name,
.language-autohotkey .token.selector {
	color: #00a4db;
}

.token.function,
.token.deleted,
.language-autohotkey .token.tag {
	color: #9a050f;
}

.token.tag,
.token.selector,
.language-autohotkey .token.keyword {
	color: #00009f;
}

.token.important,
.token.function,
.token.bold {
	font-weight: bold;
}

.token.italic {
	font-style: italic;
}

A  => static/prism.css +153 -0
@@ 1,153 @@
/* PrismJS 1.23.0
https://prismjs.com/download.html#themes=prism-solarizedlight&languages=markup+css+clike+javascript+json+json5+toml+xml-doc+yaml+zig */
/*
 Solarized Color Schemes originally by Ethan Schoonover
 http://ethanschoonover.com/solarized

 Ported for PrismJS by Hector Matos
 Website: https://krakendev.io
 Twitter Handle: https://twitter.com/allonsykraken)
*/

/*
SOLARIZED HEX
--------- -------
base03    #002b36
base02    #073642
base01    #586e75
base00    #657b83
base0     #839496
base1     #93a1a1
base2     #eee8d5
base3     #fdf6e3
yellow    #b58900
orange    #cb4b16
red       #dc322f
magenta   #d33682
violet    #6c71c4
blue      #268bd2
cyan      #2aa198
green     #859900
*/

code[class*="language-"],
pre[class*="language-"] {
	color: #657b83; /* base00 */
	font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
	font-size: 1em;
	text-align: left;
	white-space: pre;
	word-spacing: normal;
	word-break: normal;
	word-wrap: normal;

	line-height: 1.5;

	-moz-tab-size: 4;
	-o-tab-size: 4;
	tab-size: 4;

	-webkit-hyphens: none;
	-moz-hyphens: none;
	-ms-hyphens: none;
	hyphens: none;
}

pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
	background: #073642; /* base02 */
}

pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
	background: #073642; /* base02 */
}

/* Code blocks */
pre[class*="language-"] {
	padding: 1em;
	margin: .5em 0;
	overflow: auto;
	border-radius: 0.3em;
}

:not(pre) > code[class*="language-"],
pre[class*="language-"] {
	background-color: #fdf6e3; /* base3 */
}

/* Inline code */
:not(pre) > code[class*="language-"] {
	padding: .1em;
	border-radius: .3em;
}

.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
	color: #93a1a1; /* base1 */
}

.token.punctuation {
	color: #586e75; /* base01 */
}

.token.namespace {
	opacity: .7;
}

.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
	color: #268bd2; /* blue */
}

.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.url,
.token.inserted {
	color: #2aa198; /* cyan */
}

.token.entity {
	color: #657b83; /* base00 */
	background: #eee8d5; /* base2 */
}

.token.atrule,
.token.attr-value,
.token.keyword {
	color: #859900; /* green */
}

.token.function,
.token.class-name {
	color: #b58900; /* yellow */
}

.token.regex,
.token.important,
.token.variable {
	color: #cb4b16; /* orange */
}

.token.important,
.token.bold {
	font-weight: bold;
}
.token.italic {
	font-style: italic;
}

.token.entity {
	cursor: help;
}


A  => static/prism.js +13 -0
@@ 1,13 @@
/* PrismJS 1.23.0
https://prismjs.com/download.html#themes=prism-solarizedlight&languages=markup+css+clike+javascript+json+json5+toml+xml-doc+yaml+zig */
var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(u){var c=/\blang(?:uage)?-([\w-]+)\b/i,n=0,e={},M={manual:u.Prism&&u.Prism.manual,disableWorkerMessageHandler:u.Prism&&u.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof W?new W(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++n}),e.__id},clone:function t(e,r){var a,n;switch(r=r||{},M.util.type(e)){case"Object":if(n=M.util.objId(e),r[n])return r[n];for(var i in a={},r[n]=a,e)e.hasOwnProperty(i)&&(a[i]=t(e[i],r));return a;case"Array":return n=M.util.objId(e),r[n]?r[n]:(a=[],r[n]=a,e.forEach(function(e,n){a[n]=t(e,r)}),a);default:return e}},getLanguage:function(e){for(;e&&!c.test(e.className);)e=e.parentElement;return e?(e.className.match(c)||[,"none"])[1].toLowerCase():"none"},currentScript:function(){if("undefined"==typeof document)return null;if("currentScript"in document)return document.currentScript;try{throw new Error}catch(e){var n=(/at [^(\r\n]*\((.*):.+:.+\)$/i.exec(e.stack)||[])[1];if(n){var t=document.getElementsByTagName("script");for(var r in t)if(t[r].src==n)return t[r]}return null}},isActive:function(e,n,t){for(var r="no-"+n;e;){var a=e.classList;if(a.contains(n))return!0;if(a.contains(r))return!1;e=e.parentElement}return!!t}},languages:{plain:e,plaintext:e,text:e,txt:e,extend:function(e,n){var t=M.util.clone(M.languages[e]);for(var r in n)t[r]=n[r];return t},insertBefore:function(t,e,n,r){var a=(r=r||M.languages)[t],i={};for(var l in a)if(a.hasOwnProperty(l)){if(l==e)for(var o in n)n.hasOwnProperty(o)&&(i[o]=n[o]);n.hasOwnProperty(l)||(i[l]=a[l])}var s=r[t];return r[t]=i,M.languages.DFS(M.languages,function(e,n){n===s&&e!=t&&(this[e]=i)}),i},DFS:function e(n,t,r,a){a=a||{};var i=M.util.objId;for(var l in n)if(n.hasOwnProperty(l)){t.call(n,l,n[l],r||l);var o=n[l],s=M.util.type(o);"Object"!==s||a[i(o)]?"Array"!==s||a[i(o)]||(a[i(o)]=!0,e(o,t,l,a)):(a[i(o)]=!0,e(o,t,null,a))}}},plugins:{},highlightAll:function(e,n){M.highlightAllUnder(document,e,n)},highlightAllUnder:function(e,n,t){var r={callback:t,container:e,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};M.hooks.run("before-highlightall",r),r.elements=Array.prototype.slice.apply(r.container.querySelectorAll(r.selector)),M.hooks.run("before-all-elements-highlight",r);for(var a,i=0;a=r.elements[i++];)M.highlightElement(a,!0===n,r.callback)},highlightElement:function(e,n,t){var r=M.util.getLanguage(e),a=M.languages[r];e.className=e.className.replace(c,"").replace(/\s+/g," ")+" language-"+r;var i=e.parentElement;i&&"pre"===i.nodeName.toLowerCase()&&(i.className=i.className.replace(c,"").replace(/\s+/g," ")+" language-"+r);var l={element:e,language:r,grammar:a,code:e.textContent};function o(e){l.highlightedCode=e,M.hooks.run("before-insert",l),l.element.innerHTML=l.highlightedCode,M.hooks.run("after-highlight",l),M.hooks.run("complete",l),t&&t.call(l.element)}if(M.hooks.run("before-sanity-check",l),(i=l.element.parentElement)&&"pre"===i.nodeName.toLowerCase()&&!i.hasAttribute("tabindex")&&i.setAttribute("tabindex","0"),!l.code)return M.hooks.run("complete",l),void(t&&t.call(l.element));if(M.hooks.run("before-highlight",l),l.grammar)if(n&&u.Worker){var s=new Worker(M.filename);s.onmessage=function(e){o(e.data)},s.postMessage(JSON.stringify({language:l.language,code:l.code,immediateClose:!0}))}else o(M.highlight(l.code,l.grammar,l.language));else o(M.util.encode(l.code))},highlight:function(e,n,t){var r={code:e,grammar:n,language:t};return M.hooks.run("before-tokenize",r),r.tokens=M.tokenize(r.code,r.grammar),M.hooks.run("after-tokenize",r),W.stringify(M.util.encode(r.tokens),r.language)},tokenize:function(e,n){var t=n.rest;if(t){for(var r in t)n[r]=t[r];delete n.rest}var a=new i;return I(a,a.head,e),function e(n,t,r,a,i,l){for(var o in r)if(r.hasOwnProperty(o)&&r[o]){var s=r[o];s=Array.isArray(s)?s:[s];for(var u=0;u<s.length;++u){if(l&&l.cause==o+","+u)return;var c=s[u],g=c.inside,f=!!c.lookbehind,h=!!c.greedy,d=c.alias;if(h&&!c.pattern.global){var p=c.pattern.toString().match(/[imsuy]*$/)[0];c.pattern=RegExp(c.pattern.source,p+"g")}for(var v=c.pattern||c,m=a.next,y=i;m!==t.tail&&!(l&&y>=l.reach);y+=m.value.length,m=m.next){var b=m.value;if(t.length>n.length)return;if(!(b instanceof W)){var k,x=1;if(h){if(!(k=z(v,y,n,f)))break;var w=k.index,A=k.index+k[0].length,P=y;for(P+=m.value.length;P<=w;)m=m.next,P+=m.value.length;if(P-=m.value.length,y=P,m.value instanceof W)continue;for(var E=m;E!==t.tail&&(P<A||"string"==typeof E.value);E=E.next)x++,P+=E.value.length;x--,b=n.slice(y,P),k.index-=y}else if(!(k=z(v,0,b,f)))continue;var w=k.index,S=k[0],O=b.slice(0,w),L=b.slice(w+S.length),N=y+b.length;l&&N>l.reach&&(l.reach=N);var j=m.prev;O&&(j=I(t,j,O),y+=O.length),q(t,j,x);var C=new W(o,g?M.tokenize(S,g):S,d,S);if(m=I(t,j,C),L&&I(t,m,L),1<x){var _={cause:o+","+u,reach:N};e(n,t,r,m.prev,y,_),l&&_.reach>l.reach&&(l.reach=_.reach)}}}}}}(e,a,n,a.head,0),function(e){var n=[],t=e.head.next;for(;t!==e.tail;)n.push(t.value),t=t.next;return n}(a)},hooks:{all:{},add:function(e,n){var t=M.hooks.all;t[e]=t[e]||[],t[e].push(n)},run:function(e,n){var t=M.hooks.all[e];if(t&&t.length)for(var r,a=0;r=t[a++];)r(n)}},Token:W};function W(e,n,t,r){this.type=e,this.content=n,this.alias=t,this.length=0|(r||"").length}function z(e,n,t,r){e.lastIndex=n;var a=e.exec(t);if(a&&r&&a[1]){var i=a[1].length;a.index+=i,a[0]=a[0].slice(i)}return a}function i(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function I(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function q(e,n,t){for(var r=n.next,a=0;a<t&&r!==e.tail;a++)r=r.next;(n.next=r).prev=n,e.length-=a}if(u.Prism=M,W.stringify=function n(e,t){if("string"==typeof e)return e;if(Array.isArray(e)){var r="";return e.forEach(function(e){r+=n(e,t)}),r}var a={type:e.type,content:n(e.content,t),tag:"span",classes:["token",e.type],attributes:{},language:t},i=e.alias;i&&(Array.isArray(i)?Array.prototype.push.apply(a.classes,i):a.classes.push(i)),M.hooks.run("wrap",a);var l="";for(var o in a.attributes)l+=" "+o+'="'+(a.attributes[o]||"").replace(/"/g,"&quot;")+'"';return"<"+a.tag+' class="'+a.classes.join(" ")+'"'+l+">"+a.content+"</"+a.tag+">"},!u.document)return u.addEventListener&&(M.disableWorkerMessageHandler||u.addEventListener("message",function(e){var n=JSON.parse(e.data),t=n.language,r=n.code,a=n.immediateClose;u.postMessage(M.highlight(r,M.languages[t],t)),a&&u.close()},!1)),M;var t=M.util.currentScript();function r(){M.manual||M.highlightAll()}if(t&&(M.filename=t.src,t.hasAttribute("data-manual")&&(M.manual=!0)),!M.manual){var a=document.readyState;"loading"===a||"interactive"===a&&t&&t.defer?document.addEventListener("DOMContentLoaded",r):window.requestAnimationFrame?window.requestAnimationFrame(r):window.setTimeout(r,16)}return M}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
Prism.languages.markup={comment:/<!--[\s\S]*?-->/,prolog:/<\?[\s\S]+?\?>/,doctype:{pattern:/<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^<!|>$|[[\]]/,"doctype-tag":/^DOCTYPE/,name:/[^\s<>'"]+/}},cdata:/<!\[CDATA\[[\s\S]*?]]>/i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&amp;/,"&"))}),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^<!\[CDATA\[|\]\]>$/i;var t={"included-cdata":{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:<!\\[CDATA\\[(?:[^\\]]|\\](?!\\]>))*\\]\\]>|(?!<!\\[CDATA\\[)[^])*?(?=</__>)".replace(/__/g,function(){return a}),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml;
!function(s){var e=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-](?:[^;{\s]|\s+(?![\s{]))*(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+e.source+"|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+e.source+"$"),alias:"url"}}},selector:RegExp("[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+e.source+")*(?=\\s*\\{)"),string:{pattern:e,greedy:!0},property:/(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,important:/!important\b/i,function:/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(Prism);
Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/};
Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:prototype|constructor))/,lookbehind:!0}],keyword:[{pattern:/((?:^|})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:/\b(?:(?:0[xX](?:[\dA-Fa-f](?:_[\dA-Fa-f])?)+|0[bB](?:[01](?:_[01])?)+|0[oO](?:[0-7](?:_[0-7])?)+)n?|(?:\d(?:_\d)?)+n|NaN|Infinity)\b|(?:\b(?:\d(?:_\d)?)+\.?(?:\d(?:_\d)?)*|\B\.(?:\d(?:_\d)?)+)(?:[Ee][+-]?(?:\d(?:_\d)?)+)?/,operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*]|\\.|[^/\\\[\r\n])+\/[gimyus]{0,6}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-flags":/[a-z]+$/,"regex-delimiter":/^\/|\/$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}|(?!\${)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\${|}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript;
Prism.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:true|false)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},Prism.languages.webmanifest=Prism.languages.json;
!function(n){var e=/("|')(?:\\(?:\r\n?|\n|.)|(?!\1)[^\\\r\n])*\1/;n.languages.json5=n.languages.extend("json",{property:[{pattern:RegExp(e.source+"(?=\\s*:)"),greedy:!0},{pattern:/(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/,alias:"unquoted"}],string:{pattern:e,greedy:!0},number:/[+-]?\b(?:NaN|Infinity|0x[a-fA-F\d]+)\b|[+-]?(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[eE][+-]?\d+\b)?/})}(Prism);
!function(e){function n(e){return e.replace(/__/g,function(){return"(?:[\\w-]+|'[^'\n\r]*'|\"(?:\\\\.|[^\\\\\"\r\n])*\")"})}e.languages.toml={comment:{pattern:/#.*/,greedy:!0},table:{pattern:RegExp(n("(^\\s*\\[\\s*(?:\\[\\s*)?)__(?:\\s*\\.\\s*__)*(?=\\s*\\])"),"m"),lookbehind:!0,greedy:!0,alias:"class-name"},key:{pattern:RegExp(n("(^\\s*|[{,]\\s*)__(?:\\s*\\.\\s*__)*(?=\\s*=)"),"m"),lookbehind:!0,greedy:!0,alias:"property"},string:{pattern:/"""(?:\\[\s\S]|[^\\])*?"""|'''[\s\S]*?'''|'[^'\n\r]*'|"(?:\\.|[^\\"\r\n])*"/,greedy:!0},date:[{pattern:/\b\d{4}-\d{2}-\d{2}(?:[T\s]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?)?\b/i,alias:"number"},{pattern:/\b\d{2}:\d{2}:\d{2}(?:\.\d+)?\b/,alias:"number"}],number:/(?:\b0(?:x[\da-zA-Z]+(?:_[\da-zA-Z]+)*|o[0-7]+(?:_[0-7]+)*|b[10]+(?:_[10]+)*))\b|[-+]?\b\d+(?:_\d+)*(?:\.\d+(?:_\d+)*)?(?:[eE][+-]?\d+(?:_\d+)*)?\b|[-+]?\b(?:inf|nan)\b/,boolean:/\b(?:true|false)\b/,punctuation:/[.,=[\]{}]/}}(Prism);
!function(n){function a(a,e){n.languages[a]&&n.languages.insertBefore(a,"comment",{"doc-comment":e})}var e=n.languages.markup.tag,t={pattern:/\/\/\/.*/,greedy:!0,alias:"comment",inside:{tag:e}},g={pattern:/'''.*/,greedy:!0,alias:"comment",inside:{tag:e}};a("csharp",t),a("fsharp",t),a("vbnet",g)}(Prism);
!function(e){var n=/[*&][^\s[\]{},]+/,r=/!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/,t="(?:"+r.source+"(?:[ \t]+"+n.source+")?|"+n.source+"(?:[ \t]+"+r.source+")?)",a="(?:[^\\s\\x00-\\x08\\x0e-\\x1f!\"#%&'*,\\-:>?@[\\]`{|}\\x7f-\\x84\\x86-\\x9f\\ud800-\\udfff\\ufffe\\uffff]|[?:-]<PLAIN>)(?:[ \t]*(?:(?![#:])<PLAIN>|:<PLAIN>))*".replace(/<PLAIN>/g,function(){return"[^\\s\\x00-\\x08\\x0e-\\x1f,[\\]{}\\x7f-\\x84\\x86-\\x9f\\ud800-\\udfff\\ufffe\\uffff]"}),d="\"(?:[^\"\\\\\r\n]|\\\\.)*\"|'(?:[^'\\\\\r\n]|\\\\.)*'";function o(e,n){n=(n||"").replace(/m/g,"")+"m";var r="([:\\-,[{]\\s*(?:\\s<<prop>>[ \t]+)?)(?:<<value>>)(?=[ \t]*(?:$|,|]|}|(?:[\r\n]\\s*)?#))".replace(/<<prop>>/g,function(){return t}).replace(/<<value>>/g,function(){return e});return RegExp(r,n)}e.languages.yaml={scalar:{pattern:RegExp("([\\-:]\\s*(?:\\s<<prop>>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\\S[^\r\n]*(?:\\2[^\r\n]+)*)".replace(/<<prop>>/g,function(){return t})),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp("((?:^|[:\\-,[{\r\n?])[ \t]*(?:<<prop>>[ \t]+)?)<<key>>(?=\\s*:\\s)".replace(/<<prop>>/g,function(){return t}).replace(/<<key>>/g,function(){return"(?:"+a+"|"+d+")"})),lookbehind:!0,greedy:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:o("\\d{4}-\\d\\d?-\\d\\d?(?:[tT]|[ \t]+)\\d\\d?:\\d{2}:\\d{2}(?:\\.\\d*)?(?:[ \t]*(?:Z|[-+]\\d\\d?(?::\\d{2})?))?|\\d{4}-\\d{2}-\\d{2}|\\d\\d?:\\d{2}(?::\\d{2}(?:\\.\\d*)?)?"),lookbehind:!0,alias:"number"},boolean:{pattern:o("true|false","i"),lookbehind:!0,alias:"important"},null:{pattern:o("null|~","i"),lookbehind:!0,alias:"important"},string:{pattern:o(d),lookbehind:!0,greedy:!0},number:{pattern:o("[+-]?(?:0x[\\da-f]+|0o[0-7]+|(?:\\d+(?:\\.\\d*)?|\\.?\\d+)(?:e[+-]?\\d+)?|\\.inf|\\.nan)","i"),lookbehind:!0},tag:r,important:n,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},e.languages.yml=e.languages.yaml}(Prism);
!function(n){function e(e){return function(){return e}}var r=/\b(?:align|allowzero|and|asm|async|await|break|cancel|catch|comptime|const|continue|defer|else|enum|errdefer|error|export|extern|fn|for|if|inline|linksection|nakedcc|noalias|null|or|orelse|packed|promise|pub|resume|return|stdcallcc|struct|suspend|switch|test|threadlocal|try|undefined|union|unreachable|usingnamespace|var|volatile|while)\b/,a="\\b(?!"+r.source+")(?!\\d)\\w+\\b",o="align\\s*\\((?:[^()]|\\([^()]*\\))*\\)",s="(?!\\s)(?:!?\\s*(?:"+"(?:\\?|\\bpromise->|(?:\\[[^[\\]]*\\]|\\*(?!\\*)|\\*\\*)(?:\\s*<ALIGN>|\\s*const\\b|\\s*volatile\\b|\\s*allowzero\\b)*)".replace(/<ALIGN>/g,e(o))+"\\s*)*"+"(?:\\bpromise\\b|(?:\\berror\\.)?<ID>(?:\\.<ID>)*(?!\\s+<ID>))".replace(/<ID>/g,e(a))+")+";n.languages.zig={comment:[{pattern:/\/{3}.*/,alias:"doc-comment"},/\/{2}.*/],string:[{pattern:/(^|[^\\@])c?"(?:[^"\\\r\n]|\\.)*"/,lookbehind:!0,greedy:!0},{pattern:/([\r\n])([ \t]+c?\\{2}).*(?:(?:\r\n?|\n)\2.*)*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\])'(?:[^'\\\r\n]|\\(?:.|x[a-fA-F\d]{2}|u\{[a-fA-F\d]{1,6}\}))'/,lookbehind:!0,greedy:!0}],builtin:/\B@(?!\d)\w+(?=\s*\()/,label:{pattern:/(\b(?:break|continue)\s*:\s*)\w+\b|\b(?!\d)\w+\b(?=\s*:\s*(?:\{|while\b))/,lookbehind:!0},"class-name":[/\b(?!\d)\w+(?=\s*=\s*(?:(?:extern|packed)\s+)?(?:enum|struct|union)\s*[({])/,{pattern:RegExp("(:\\s*)<TYPE>(?=\\s*(?:<ALIGN>\\s*)?[=;,)])|<TYPE>(?=\\s*(?:<ALIGN>\\s*)?\\{)".replace(/<TYPE>/g,e(s)).replace(/<ALIGN>/g,e(o))),lookbehind:!0,inside:null},{pattern:RegExp("(\\)\\s*)<TYPE>(?=\\s*(?:<ALIGN>\\s*)?;)".replace(/<TYPE>/g,e(s)).replace(/<ALIGN>/g,e(o))),lookbehind:!0,inside:null}],"builtin-types":{pattern:/\b(?:anyerror|bool|c_u?(?:short|int|long|longlong)|c_longdouble|c_void|comptime_(?:float|int)|[iu](?:8|16|32|64|128|size)|f(?:16|32|64|128)|noreturn|type|void)\b/,alias:"keyword"},keyword:r,function:/\b(?!\d)\w+(?=\s*\()/,number:/\b(?:0b[01]+|0o[0-7]+|0x[a-fA-F\d]+(?:\.[a-fA-F\d]*)?(?:[pP][+-]?[a-fA-F\d]+)?|\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)\b/,boolean:/\b(?:false|true)\b/,operator:/\.[*?]|\.{2,3}|[-=]>|\*\*|\+\+|\|\||(?:<<|>>|[-+*]%|[-+*/%^&|<>!=])=?|[?~]/,punctuation:/[.:,;(){}[\]]/},n.languages.zig["class-name"].forEach(function(e){null===e.inside&&(e.inside=n.languages.zig)})}(Prism);

A  => static/site.webmanifest +1 -0
@@ 1,1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
\ No newline at end of file

A  => static/stylesheet.css +45 -0
@@ 1,45 @@
:root {
    --bg: white;
    --fg: black;
    --link: lightcoral;
}

@media (prefers-color-scheme: dark) {
    :root {
        --bg: black;
        --fg: white;
    }
}

body {
    /* fonts and colours */
    font-family: "Bitstream Charter", serif;
    background-color: var(--bg);
    color: var(--fg);

    /* positioning */
    line-height: 1.5;
    max-width: 400px;
    padding: 2em;
    margin: 0 auto;
}

a {
    text-decoration: none;
    background-color: var(--bg);
    color: var(--link);
}

a:hover {
    background-color: var(--link);
    color: var(--bg);
}

h1, p {
    margin: 0;
    color: var(--fg);
}

ul {
    list-style-type: none;
}