import { ASTContent, ASTNode } from '@stridespark/model-core'; import { mapDef } from '@stridespark/commonwealth'; //AST-TS export interface TransformFunction { (content: null | (string | T)[]): T; } export interface TransformMap { [tag: string]: TransformFunction; } export function n(tag: string, content: ASTContent[]): ASTNode { return { tag, content, }; } export function isASTNode(content: ASTContent): content is ASTNode { return typeof content !== 'string'; } /** * Reduce an ASTNode to a 'T' by bottoms-up applying the TransformFunction * specified by tag in the TransformMap (or the default) */ export function transform(node: ASTNode, transformMap: TransformMap): T { // first transform content const transformedContent = node.content && node.content.map(child => { if (isASTNode(child)) { return transform(child, transformMap); } return child; }); // then possibly transform this const transformer = transformMap[node.tag] || transformMap['default']; if (transformer) { return transformer(transformedContent); } throw new Error('no transform found for ' + node.tag); } /** * Replaces any nodes (and their children) that match toRemove with toReplace. Will modify node. **/ export function replaceNode(node: ASTContent, toRemove: (node: ASTContent) => boolean, toReplace: ASTNode): ASTContent { if (toRemove(node)) { return toReplace; } if (typeof node === 'string' || node.content == undefined) { return node; } const newContents = mapDef(node.content, (nd: ASTContent) => replaceNode(nd, toRemove, toReplace)); return { ...node, content: newContents || [] }; } /** * Return array of nodes (and their children) that match predicate. */ export function filter(node: ASTContent, predicate: (node: ASTNode) => boolean): ASTNode[] { if (typeof node === 'string' || node.content == undefined) { // no children return []; } if (predicate(node)) { return [node]; } const matchingContent = node.content .map((m: ASTContent) => filter(m, predicate)) .reduce((a: ASTNode[], b: ASTNode[]) => a.concat(b), []); if (matchingContent) { return matchingContent; } else { return []; } } export function retagNodes(node: ASTNode, replace: string[], replaceWith: string): ASTNode { const copy = JSON.parse(JSON.stringify(node)); _retagInner(copy, replace, replaceWith); return copy; } function _retagInner(node: ASTContent, replace: string[], replaceWith: string) { if (typeof node === 'string') { return; } if (replace.indexOf(node.tag) !== -1) { node.tag = replaceWith; } if (node.content != undefined) { node.content.forEach(c => _retagInner(c, replace, replaceWith)); } }