UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

66 lines (65 loc) 3.15 kB
import { walkElements } from "../util/element.js"; import { matchTemplate, renderTemplate } from "../util/template.js"; import { mergeTreeElements } from "./Extractor.js"; import { ThroughExtractor } from "./ThroughExtractor.js"; /** * Default merges map: a markdown file is absorbed into its same-base TypeScript counterpart. * - Key (secondary) and values (primary candidates) are `{base}`-templated key strings. * - Candidates are checked in order — the first one that exists in the tree wins. * - A secondary with no matching primary is left in place as its own element. */ const DEFAULT_MERGES = { "{base}.md": ["{base}.ts", "{base}.tsx", "{base}.js", "{base}.jsx"], }; /** * Through extractor that walks a `DirectoryElement` tree and merges sibling tree elements whose keys match a `merges` template pair. * - Walks every directory recursively, applying the merge at each level. * - The primary (winning) element keeps its `key`, `source`, and `type`; the secondary's `title`, `description`, * `content`, and `children` are folded in via `mergeTreeElements()`. * - A secondary with no matching primary is left in place — pure prose files (e.g. `concepts.md` with no `concepts.ts`) stand alone. */ export class MergingExtractor extends ThroughExtractor { _merges; constructor(source, { merges = DEFAULT_MERGES } = {}) { super(source); this._merges = merges; } async extract(input) { const root = await this.source.extract(input); return _mergeDirectory(root, this._merges); } } /** Recursively merge same-template siblings inside `dir` and all nested directories. */ function _mergeDirectory(dir, merges) { const children = Array.from(walkElements(dir.props.children)); const merged = _mergeChildren(children, merges).map(child => child.type === "tree-directory" ? _mergeDirectory(child, merges) : child); return { ...dir, props: { ...dir.props, children: merged } }; } /** Merge same-template siblings at one directory level. */ function _mergeChildren(children, merges) { // Index children by key so we can look up primary candidates quickly. const byKey = new Map(); for (const child of children) byKey.set(child.key, child); // Walk in original order, deciding for each whether it's a secondary that should fold into a primary. const skip = new Set(); for (const secondary of children) { for (const [lhs, candidates] of Object.entries(merges)) { const matches = matchTemplate(lhs, secondary.key); if (!matches) continue; for (const rhs of candidates) { const primaryKey = renderTemplate(rhs, matches); const primary = byKey.get(primaryKey); if (!primary || primary === secondary) continue; byKey.set(primaryKey, mergeTreeElements(primary, secondary)); skip.add(secondary); break; } if (skip.has(secondary)) break; } } return children.filter(c => !skip.has(c)).map(c => byKey.get(c.key) ?? c); }