Changes
4 changed files (+251/-1)
-
-
@@ -36,7 +36,7 @@ * Skip this parser.* If no parsers left, do not include the file in a document tree. */ export interface Skip { skip: true; readonly skip: true; } export interface MetadataParser {
-
-
-
@@ -0,0 +1,40 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // // SPDX-License-Identifier: Apache-2.0 import type { DirectoryReader, FileReader, FileSystemReader, } from "../filesystem_reader/interface.ts"; import type { DocumentMetadata, MetadataParser, } from "../metadata_parser/interface.ts"; export interface Document { readonly metadata: DocumentMetadata; readonly file: FileReader; } export interface DocumentDirectory { readonly metadata: DocumentMetadata; readonly directory: DirectoryReader; readonly entries: ReadonlyArray<Document | DocumentDirectory>; } export interface DocumentTree { readonly locales: ReadonlyMap< string, ReadonlyArray<Document | DocumentDirectory> >; } export interface BuildParameters { readonly fileSystemReader: FileSystemReader; readonly metadataParser: MetadataParser; } export interface TreeBuilder { build(opts: BuildParameters): Promise<DocumentTree>; }
-
-
-
@@ -0,0 +1,108 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // // SPDX-License-Identifier: Apache-2.0 import { assertEquals, assertObjectMatch, } from "../deps/deno.land/std/assert/mod.ts"; import { MemoryFsReader } from "../filesystem_reader/memory_fs.ts"; import { VaultParser } from "../metadata_parser/vault_parser.ts"; import { SingleLocaleTreeBuilder } from "./single_locale_tree_builder.ts"; Deno.test("Should read from top-level directory, as-is", async () => { const fileSystemReader = new MemoryFsReader([ { path: "Foo Bar/Baz Qux.md", content: "" }, { path: "Foo.md", content: "" }, ]); const metadataParser = new VaultParser(); const builder = new SingleLocaleTreeBuilder({ locale: "en" }); const tree = await builder.build({ fileSystemReader, metadataParser }); const items = tree.locales.get("en")!; assertObjectMatch(items[0], { metadata: { name: "foo%20bar", title: "Foo Bar", }, directory: { name: "Foo Bar", }, entries: [ { metadata: { name: "baz%20qux", title: "Baz Qux", }, file: { name: "Baz Qux.md", }, }, ], }); assertObjectMatch(items[1], { metadata: { name: "foo", title: "Foo", }, file: { name: "Foo.md", }, }); }); Deno.test("Should ignore files and directories matches to `ignore` callback", async () => { const fileSystemReader = new MemoryFsReader([ { path: "foo/bar/baz.md", content: "" }, { path: "foo/bar.md", content: "" }, { path: "foo.md", content: "" }, { path: "bar/foo.md", content: "" }, { path: "bar/foo/baz.md", content: "" }, ]); const metadataParser = new VaultParser(); const builder = new SingleLocaleTreeBuilder({ locale: "en", ignore(node) { return node.name === "foo"; }, }); const tree = await builder.build({ fileSystemReader, metadataParser }); const items = tree.locales.get("en")!; assertEquals(items.length, 2); assertObjectMatch(items[0], { metadata: { name: "foo", title: "foo", }, file: { name: "foo.md" }, }); assertObjectMatch(items[1], { metadata: { name: "bar", title: "bar", }, directory: { name: "bar", }, entries: [ { metadata: { name: "foo", title: "foo", }, file: { name: "foo.md", }, }, ], }); });
-
-
-
@@ -0,0 +1,102 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // // SPDX-License-Identifier: Apache-2.0 import type { DirectoryReader, FileReader, } from "../filesystem_reader/interface.ts"; import type { MetadataParser } from "../metadata_parser/interface.ts"; import type { BuildParameters, Document, DocumentDirectory, DocumentTree, TreeBuilder, } from "./interface.ts"; export interface SingleLocaleTreeBuilderConfig { /** * Locale string to use. */ locale: string; /** * Callback function to be invoked on every file and directory. * If this function returned true, the file or directory is skipped and does not * appear on the resulted document tree. */ ignore?(fileOrDirectory: FileReader | DirectoryReader): boolean; } export class SingleLocaleTreeBuilder implements TreeBuilder { #locale: string; #ignore?: (fileOrDirectory: FileReader | DirectoryReader) => boolean; constructor({ locale, ignore }: SingleLocaleTreeBuilderConfig) { this.#locale = locale; this.#ignore = ignore; } async build( { fileSystemReader, metadataParser }: BuildParameters, ): Promise<DocumentTree> { const root = await fileSystemReader.getRootDirectory(); const children = await root.read(); const entries = await Promise.all( children.map((child) => this.#build(child, metadataParser)), ); const map = new Map<string, Array<Document | DocumentDirectory>>(); map.set( this.#locale, entries.filter((entry): entry is NonNullable<typeof entry> => !!entry), ); return { locales: map, }; } async #build( node: FileReader | DirectoryReader, metadataParser: MetadataParser, ): Promise<DocumentDirectory | Document | null> { if (this.#ignore && this.#ignore(node)) { // TODO: Debug log return null; } const metadata = await metadataParser.parse(node); // This SHOULD have check for `metadata.skip` being `true`. However, a bug // (or "feature") in TypeScript breaks type-narrowing by doing so. if ("skip" in metadata) { // TODO: Debug log return null; } if (node.type === "file") { return { metadata, file: node, }; } const children = await node.read(); const entries = await Promise.all( children.map((child) => this.#build(child, metadataParser)), ); return { metadata, directory: node, entries: entries.filter((child): child is NonNullable<typeof child> => !!child ), }; } }
-