UNPKG

@diplodoc/transform

Version:

A simple transformer of text in YFM (Yandex Flavored Markdown) to HTML

122 lines (91 loc) 4.12 kB
import type {IncludeCollectOpts} from './types'; import {relative} from 'path'; import {bold} from 'chalk'; import {readFileSync} from 'fs'; import {isFileExists, resolveRelativePath} from '../../utilsFS'; const includesPaths: string[] = []; function processRecursive( relativePath: string, includePath: string, targetDestPath: string, options: IncludeCollectOpts, ) { const {path, log, copyFile, includedParentPath: includedParentPathNullable, included} = options; const includedParentPath = includedParentPathNullable || []; const includeOptions = { ...options, path: includePath, destPath: targetDestPath, }; try { const contentProcessed = copyFile(includePath, targetDestPath, includeOptions); // To reduce file reading we can include the file content into the generated content if (included !== false) { const content = contentProcessed ?? readFileSync(targetDestPath, 'utf8'); if (content) { const key = [...includedParentPath, relativePath]; const hash = key.join(':'); // The appendix is the map that protects from multiple include files if (!options.appendix?.has(hash)) { // Recursive function to include the depth structure const includeContent = collectRecursive(content, { ...options, path: includePath, includedParentPath: key, }); // Add to appendix set structure options.appendix?.set( relativePath, `{% included (${hash}) %}\n${includeContent}\n{% endincluded %}`, ); } } } } catch (e) { log.error(`No such file or has no access to ${bold(includePath)} in ${bold(path)}`); } } function collectRecursive(result: string, options: IncludeCollectOpts) { const {root, path, destPath = '', log, singlePage} = options; const INCLUDE_REGEXP = /{%\s*include\s*(notitle)?\s*\[(.+?)]\((.+?)\)\s*%}/g; let match: RegExpExecArray | null; while ((match = INCLUDE_REGEXP.exec(result)) !== null) { let [, , , relativePath] = match; const [matchedInclude] = match; let includePath = resolveRelativePath(path, relativePath); const hashIndex = relativePath.lastIndexOf('#'); if (hashIndex > -1 && !isFileExists(includePath)) { includePath = includePath.slice(0, includePath.lastIndexOf('#')); relativePath = relativePath.slice(0, hashIndex); } const targetDestPath = resolveRelativePath(destPath, relativePath); if (includesPaths.includes(includePath)) { log.error(`Circular includes: ${bold(includesPaths.concat(path).join(' ▶ '))}`); break; } if (singlePage && !includesPaths.length) { const newRelativePath = relative(root, includePath); const newInclude = matchedInclude.replace(relativePath, newRelativePath); result = result.replace(matchedInclude, newInclude); const delta = matchedInclude.length - newInclude.length; INCLUDE_REGEXP.lastIndex = INCLUDE_REGEXP.lastIndex - delta; } includesPaths.push(includePath); processRecursive(relativePath, includePath, targetDestPath, options); includesPaths.pop(); } return result; } function collect(input: string, options: IncludeCollectOpts) { const shouldWriteAppendix = !options.appendix; options.appendix = options.appendix ?? new Map(); input = collectRecursive(input, options); if (shouldWriteAppendix) { // Appendix should be appended to the end of the file (it supports depth structure, so the included files will have included as well) if (options.appendix.size > 0) { input += '\n' + [...options.appendix.values()].join('\n'); } } return input; } export = collect;