/** * Copyright (c) 2022 Amorphous * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { createCommandObject } from '@a-morphous/recital/dist/lib/tools/command-shorthand' import eol from 'eol' /** * Parses a recital file for the `$include` tag, which link to a separate `.stage` file whose contents are inserted at the location of the original `$include` command. * * The include command's usage is: * $include <file to include> <raw: boolean> * 'raw' determines whether we process the included stage file for more includes, or just leave the text unprocessed. * * @param fs The node 'fs' module - or an API equivalent in the browser * @param path The node 'path' module - or an API equivalent in the browser * @param filePath Path to file that serves as the entrypoint. Advised for this to be the path to the file provided for `stageString`. * @param stageString The entry point's file contents. This is separate from filePath in case you want the 'working directory' to be different * @param layer Unused. This is mostly as a guard against infinite recursion. * @returns a new string representing the .stage file with all its includes added in. */ export const resolveIncludes = ( fs, path, filePath: string, stageString: string, layer: number = 0 ): string => { // infinite recursion guard if (layer > 20) { throw new Error( 'Include command recursed more than 20 layers deep. This is a lot. Is there an infinite loop somewhere?' ) } const tokens = eol.split(stageString) let finalString = '' for (let token of tokens) { if (!token.startsWith('$include')) { finalString += token + '\n' continue } let commandObj = createCommandObject(token) if (!commandObj.args) { console.warn('empty $include command found.') continue } let givenFilepath = commandObj.args[0].toString() let targetFilepath = path.resolve(path.dirname(filePath), givenFilepath) if (!fs.existsSync(targetFilepath)) { console.log(targetFilepath) console.warn("File '" + givenFilepath + "' used in $include command does not exist") continue } const newFileContents = fs.readFileSync(targetFilepath, { encoding: 'utf-8' }) // by default, we recursively process includes // processing just means that we continue to look for $includes let shouldProcessInclude = true // ...but if we specifically say that it should be raw, then do not if ( shouldProcessInclude == true && commandObj.args.length > 1 && commandObj.args[1] === 'raw' ) { shouldProcessInclude = false } if (shouldProcessInclude) { const processedContents = resolveIncludes( fs, path, targetFilepath, newFileContents, layer + 1 ) finalString += processedContents + '\n' continue } finalString += newFileContents + '\n' } return finalString.trim() }