2024-07-07 21:00:20 -04:00
|
|
|
export type DistinguisherParser<T> = (distinguisher: string) => T;
|
|
|
|
export type DocumentParser<T> = (document: string[]) => T;
|
2024-07-19 23:41:11 -04:00
|
|
|
export type UnionParser<T> = (distinguisher: string, document: string[]) => T;
|
2024-07-14 22:19:26 -04:00
|
|
|
export type IdvConverter<T> = (idv: Idv) => T;
|
2024-07-07 21:00:20 -04:00
|
|
|
|
|
|
|
const LEADING_WHITESPACE = /^([ \t]+)/;
|
|
|
|
const ENTRY = /^(.+?):\s*(.*)/;
|
|
|
|
|
|
|
|
export class Idv {
|
|
|
|
collections: Record<string, undefined | [string, string[]][]> = {};
|
|
|
|
|
|
|
|
public static parse(input: string): Idv {
|
2024-07-14 21:33:01 -04:00
|
|
|
const idv = new Idv();
|
|
|
|
return idv.import(input);
|
2024-07-07 21:00:20 -04:00
|
|
|
}
|
2024-07-14 21:33:01 -04:00
|
|
|
public static parseLines(input: string[]): Idv {
|
2024-07-07 21:00:20 -04:00
|
|
|
const idv = new Idv();
|
2024-07-14 21:33:01 -04:00
|
|
|
return idv.importLines(input);
|
|
|
|
}
|
|
|
|
public import(input: string): Idv {
|
|
|
|
const lines = input.split("\n").map((line) => line.trimEnd());
|
|
|
|
return this.importLines(lines);
|
|
|
|
}
|
|
|
|
public importLines(input: string[]): Idv {
|
2024-07-10 00:17:33 -04:00
|
|
|
let currentDocument: string[] | undefined = undefined;
|
|
|
|
let currentIndent: string | undefined = undefined;
|
|
|
|
let bufferedBlankLines: string[] = [];
|
2024-07-07 21:00:20 -04:00
|
|
|
|
|
|
|
input.forEach((line) => {
|
|
|
|
const indent = LEADING_WHITESPACE.exec(line)?.[1];
|
|
|
|
if (indent) {
|
2024-07-10 00:17:33 -04:00
|
|
|
if (currentDocument == undefined) {
|
|
|
|
throw new Error("Indented document found before an entry");
|
|
|
|
}
|
|
|
|
if (currentIndent == undefined) {
|
|
|
|
currentIndent = indent;
|
|
|
|
}
|
|
|
|
if (line.startsWith(currentIndent)) {
|
|
|
|
currentDocument.push(...bufferedBlankLines);
|
|
|
|
bufferedBlankLines = [];
|
|
|
|
currentDocument.push(line.substring(currentIndent.length));
|
|
|
|
} else {
|
|
|
|
throw new Error(
|
|
|
|
"Inconsistent indentation- line indented less than the first line of its document"
|
|
|
|
);
|
|
|
|
}
|
2024-07-07 21:00:20 -04:00
|
|
|
} else if (line == "") {
|
2024-07-10 00:17:33 -04:00
|
|
|
bufferedBlankLines.push("");
|
2024-07-07 21:00:20 -04:00
|
|
|
} else if (line[0] == "#") {
|
|
|
|
// skip
|
|
|
|
} else {
|
|
|
|
const matches = ENTRY.exec(line);
|
|
|
|
if (matches) {
|
|
|
|
const [, collection, distinguisher] = matches;
|
|
|
|
|
2024-07-14 21:33:01 -04:00
|
|
|
if (this.collections[collection] == undefined) {
|
|
|
|
this.collections[collection] = [];
|
2024-07-07 21:00:20 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
currentDocument = [];
|
2024-07-10 00:17:33 -04:00
|
|
|
currentIndent = undefined;
|
2024-07-19 23:41:11 -04:00
|
|
|
// TODO: implement backslash escaping in the distinguisher
|
2024-07-14 21:33:01 -04:00
|
|
|
this.collections[collection].push([distinguisher, currentDocument]);
|
2024-07-07 21:00:20 -04:00
|
|
|
} else {
|
|
|
|
throw new Error("Failed to parse a property");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-07-14 21:33:01 -04:00
|
|
|
return this;
|
2024-07-07 21:00:20 -04:00
|
|
|
}
|
|
|
|
|
2024-07-19 23:41:11 -04:00
|
|
|
public getProperty<T>(name: string, parse: UnionParser<T>): T | undefined {
|
2024-07-07 21:00:20 -04:00
|
|
|
const firstEntry = this.collections[name]?.[0];
|
2024-07-19 23:41:11 -04:00
|
|
|
return firstEntry && parse(firstEntry[0], firstEntry[1]);
|
2024-07-07 21:00:20 -04:00
|
|
|
}
|
|
|
|
|
2024-07-19 23:41:11 -04:00
|
|
|
public getList<T>(name: string, parse: UnionParser<T>): T[] {
|
2024-07-07 21:00:20 -04:00
|
|
|
return (this.collections[name] ?? []).map(([distinguisher, document]) =>
|
2024-07-19 23:41:11 -04:00
|
|
|
parse(distinguisher, document)
|
2024-07-07 21:00:20 -04:00
|
|
|
);
|
|
|
|
}
|
2024-07-10 00:31:33 -04:00
|
|
|
|
|
|
|
public getMap<T>(
|
|
|
|
name: string,
|
|
|
|
parseDocument: DocumentParser<T>
|
|
|
|
): Record<string, T> {
|
|
|
|
return Object.fromEntries(
|
|
|
|
(this.collections[name] ?? []).map(([distinguisher, document]) => [
|
|
|
|
distinguisher,
|
|
|
|
parseDocument(document),
|
|
|
|
])
|
|
|
|
);
|
|
|
|
}
|
2024-07-14 22:19:26 -04:00
|
|
|
|
|
|
|
public getMergedMap<T>(
|
|
|
|
name: string,
|
|
|
|
convertIdv: IdvConverter<T>
|
|
|
|
): Record<string, T> {
|
|
|
|
const idvMap: Map<string, Idv> = new Map();
|
|
|
|
|
|
|
|
(this.collections[name] ?? []).forEach(([distinguisher, document]) => {
|
|
|
|
let idv = idvMap.get(distinguisher);
|
|
|
|
if (idv == undefined) {
|
|
|
|
idvMap.set(distinguisher, (idv = new Idv()));
|
|
|
|
}
|
|
|
|
|
|
|
|
idv.importLines(document);
|
|
|
|
});
|
|
|
|
|
|
|
|
const result: Record<string, T> = {};
|
|
|
|
|
|
|
|
for (const [distinguisher, idv] of idvMap.entries()) {
|
|
|
|
result[distinguisher] = convertIdv(idv);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
2024-07-07 21:00:20 -04:00
|
|
|
}
|
|
|
|
|
2024-07-19 23:41:11 -04:00
|
|
|
// TODO: implement backslash-escaping in the document?
|
|
|
|
export const StringProperty = (distinguisher: string, lines: string[]) =>
|
|
|
|
lines.length == 0 ? distinguisher : lines.join("\n");
|