shelving
Version:
Toolkit for using data in JavaScript.
66 lines (65 loc) • 3.15 kB
JavaScript
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);
}