#!/usr/bin/env -S bun run
import { $ } from "bun";
import { readdir } from "node:fs/promises"
import { parseArgs } from "util";
import { JSDOM } from "jsdom";
import { Dirent } from "node:fs";
import { mkdir, stat } from "node:fs/promises";
function out_path(path: string) {
}
enum LogLevel {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3,
FATAL = 4
}
let {DEBUG, INFO, WARN, ERROR, FATAL} = LogLevel
let minLogLevel : LogLevel = INFO
function log(level: LogLevel, message: any) {
const p = Bun.argv[1]?.split("/").slice(-1)[0] ?? "build script";
switch (level) {
case DEBUG: if (minLogLevel <= DEBUG) console.log(`\x1b[90m${p}: \x1b[1;45;97m [DEBUG] \x1b[0m ${message}`); break;
case INFO: if (minLogLevel <= INFO) console.log(`\x1b[90m${p}: \x1b[1;44;97m [INFO] \x1b[0m ${message}`); break;
case WARN: if (minLogLevel <= WARN) console.log(`\x1b[90m${p}: \x1b[1;48;5;208;97m [WARN] \x1b[0m ${message}`); break;
case ERROR: if (minLogLevel <= ERROR) console.log(`\x1b[90m${p}: \x1b[1;41;97m [ERROR] \x1b[0m ${message}`); break;
case FATAL: /*Cannot suppress fatal*/console.log(`\x1b[90m${p}: \x1b[1;41;97m [FATAL] ${message} \x1b[0m`); process.exit(); break;
default: log(FATAL, `${level} isn't a valid log level`);
}
}
log(INFO, "Annwan’s wiki’s custom build script v2")
async function build_typst_file(file: Dirent) {
let path = file.parentPath + "/" + file.name
let dir = "public_html" + file.parentPath.slice(3)
let slug = path.slice(4, -4)
let outfile = `public_html/${slug}.html`
log(INFO, `Compiling ${slug}`)
await mkdir(dir, {recursive: true})
await $`typst c --features html --format html --root . ${path} ${outfile}`
log(DEBUG, `Checking if ${slug} needs post processing`)
let html = await Bun.file(outfile).text();
let heads = html.match(new RegExp("
.*?", "sg"));
if (heads != null && heads.length >= 2) {
log(DEBUG, `Postprocessing ${slug}`)
html = html.replace(heads[1], "");
html = html.replace(heads[0], heads[1]);
await Bun.write(outfile, html);
}
log(INFO, `${slug} done`)
}
async function build_all_typst() {
let files = await readdir("src", {withFileTypes: true, recursive: true});
let promises: Promise[] = [];
for (let f of files) {
if (f.isFile() && f.name.endsWith(".typ"))
promises.push(build_typst_file(f))
}
await Promise.all(promises)
}
async function copy_assets() {
log(INFO, "Updating assets")
await $`rm -rf ${minLogLevel == DEBUG ? "-v" : ""} public_html/assets/`
await $`cp -r ${minLogLevel == DEBUG ? "-v" : ""} assets/ public_html/`
}
type IndexEntry = { id: string; title: string; body: string; tags: string;}
async function collect_data(f:string) : Promise {
let id = f.slice(12, -5)
log(INFO, `collecting metadata for ${id}`)
let dom = await JSDOM.fromFile(f)
log(DEBUG, `${id}: read`)
let things = dom.window.document.getElementsByTagName("main")
let body = ""
for (let thing of things) body += thing.textContent;
log(DEBUG, `${id}: body extracted`)
let title = dom.window.document.getElementById("search-title")?.getAttribute("content") ?? ""
log(DEBUG, `${id} title extracted: ${title}`)
let tags = dom.window.document.getElementById("search-tags")?.getAttribute("content") ?? ""
log(DEBUG, `${id}: tags extracted: ${tags}`)
return {id, title, body, tags}
}
async function gen_index() {
let files = await readdir("public_html", {withFileTypes: true, recursive: true})
log(INFO, "Generating the search index");
let promises : Promise[] = [];
for (let f of files) {
if (f.isFile() && f.name.endsWith(".html")) {
promises.push(collect_data(f.parentPath + "/" + f.name));
}
}
let data : IndexEntry[] = await Promise.all(promises)
log(INFO, `Writing index`)
await Bun.write("public_html/index.json", JSON.stringify(data), {mode: 0o644, createPath: true})
}
const args = parseArgs({
args: Bun.argv,
options: {
"build": {type: "boolean", short: "b"},
"assets": {type: "boolean", short: "a"},
"index": {short: "i", type: "boolean" },
"verbose": {short: "v", type: "string"},
"help": {type: "boolean", short: "h"}
},
allowPositionals: true
}).values
if (args.verbose) {
try {
minLogLevel = parseInt(args.verbose);
} catch {
log(FATAL, `'${args.verbose}' isn't a number`);
}
}
if (args.help) {
log(INFO, `USAGE:`)
log(INFO, `\t${Bun.argv[1]} [-b|--build] [-a|--assets] [-i|--index] [-v ⟨n⟩|--verbose ⟨n⟩]`)
log(INFO, `\t${Bun.argv[1]} -h|--help`)
} else {
if (!args.build && !args.assets && !args.index) {
await build_all_typst()
await copy_assets()
await gen_index()
}
if (args.build) await build_all_typst();
if (args.assets) await copy_assets();
if (args.index) await gen_index();
log(INFO, "DONE");
}