diff --git a/debug.html b/debug.html index c79ae55..7019993 100644 --- a/debug.html +++ b/debug.html @@ -6,6 +6,7 @@ diff --git a/debug.ts b/debug.ts index 19908a0..b181e09 100644 --- a/debug.ts +++ b/debug.ts @@ -1,3 +1,4 @@ import { TickDebug } from "./debug/tick"; +import { IdvDebug } from "./debug/idv"; -Object.assign(globalThis, { TickDebug }); +Object.assign(globalThis, { TickDebug, IdvDebug }); diff --git a/debug/idv.ts b/debug/idv.ts new file mode 100644 index 0000000..43dbd7e --- /dev/null +++ b/debug/idv.ts @@ -0,0 +1,43 @@ +import { h } from "../lib/html"; +import { Idv, StringFromDocument } from "../lib/idv"; + +export function IdvDebug() { + const textarea = h( + "textarea", + { + cols: 80, + rows: 40, + oninput(ev) { + parse(); + }, + }, + `# Idv Testing Ground +Uid: 0 +Shell: tclsh +Group: users +Group: sudo + ` + ); + + const pre = h("pre"); + + function parse() { + try { + const idv = Idv.parse(textarea.value); + + pre.textContent = JSON.stringify( + { + shell: idv.getProperty("Shell", String, StringFromDocument), + groups: idv.getList("Group", String, StringFromDocument), + }, + null, + 2 + ); + } catch (e) { + pre.textContent = String(e); + } + } + parse(); + + return [textarea, pre]; +} diff --git a/lib/idv.ts b/lib/idv.ts new file mode 100644 index 0000000..2521e82 --- /dev/null +++ b/lib/idv.ts @@ -0,0 +1,72 @@ +export type DistinguisherParser = (distinguisher: string) => T; +export type DocumentParser = (document: string[]) => T; + +const LEADING_WHITESPACE = /^([ \t]+)/; +const ENTRY = /^(.+?):\s*(.*)/; + +export class Idv { + collections: Record = {}; + + public static parse(input: string): Idv { + const lines = input.split("\n").map((line) => line.trimEnd()); + return Idv.parseLines(lines); + } + static parseLines(input: string[]): Idv { + const idv = new Idv(); + let currentDocument: string[] = []; + + input.forEach((line) => { + const indent = LEADING_WHITESPACE.exec(line)?.[1]; + if (indent) { + // TODO + } else if (line == "") { + // TODO + } else if (line[0] == "#") { + // skip + } else { + const matches = ENTRY.exec(line); + if (matches) { + const [, collection, distinguisher] = matches; + + if (idv.collections[collection] == undefined) { + idv.collections[collection] = []; + } + + currentDocument = []; + idv.collections[collection].push([distinguisher, currentDocument]); + } else { + throw new Error("Failed to parse a property"); + } + } + }); + + return idv; + } + + public getProperty( + name: string, + parseDistinguisher: DistinguisherParser, + parseDocument: DocumentParser + ): T | null { + const firstEntry = this.collections[name]?.[0]; + return firstEntry && firstEntry[1].length > 0 + ? parseDocument(firstEntry[1]) + : firstEntry?.[0] + ? parseDistinguisher(firstEntry[0]) + : null; + } + + public getList( + name: string, + parseDistinguisher: DistinguisherParser, + parseDocument: DocumentParser + ): T[] { + return (this.collections[name] ?? []).map(([distinguisher, document]) => + document.length > 0 + ? parseDocument(document) + : parseDistinguisher(distinguisher) + ); + } +} + +export const StringFromDocument = (lines: string[]) => lines.join("\n");