139 lines
4.9 KiB
TypeScript
Executable File
139 lines
4.9 KiB
TypeScript
Executable File
#!/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("<head>.*?</head>", "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<void>[] = [];
|
||
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<IndexEntry> {
|
||
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<IndexEntry>[] = [];
|
||
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");
|
||
}
|