diff --git a/src/transform/md.ts b/src/transform/md.ts index f5545604..af3d4e61 100644 --- a/src/transform/md.ts +++ b/src/transform/md.ts @@ -1,8 +1,9 @@ -import type {EnvType, MarkdownIt, OptionsType} from './typings'; +import type {EnvType, MarkdownIt, MarkdownItPluginOpts, OptionsType} from './typings'; import type Token from 'markdown-it/lib/token'; import DefaultMarkdownIt from 'markdown-it'; import DefaultPlugins from './plugins'; +import DefaultPreprocessors from './preprocessors'; import {log} from './log'; import makeHighlight from './highlight'; import attrs from 'markdown-it-attrs'; @@ -11,10 +12,21 @@ import getHeadings from './headings'; import sanitizeHtml from './sanitize'; function initMarkdownit(options: OptionsType) { - const {allowHTML = false, linkify = false, breaks = true, highlightLangs = {}} = options; + const { + allowHTML = false, + linkify = false, + breaks = true, + highlightLangs = {}, + disableRules = [], + } = options; const highlight = makeHighlight(highlightLangs); const md = new DefaultMarkdownIt({html: allowHTML, linkify, highlight, breaks}) as MarkdownIt; + + if (disableRules?.length) { + md.disable(disableRules); + } + const env = { // TODO: move md.meta directly to env get meta() { @@ -38,30 +50,32 @@ function initMarkdownit(options: OptionsType) { title: '', } as EnvType; - initPlugins(md, options); + // Plugin options is the plugin context that remains during the build of one file + const pluginOptions = getPluginOptions(options); + + // Init the plugins. Which install the md rules (core, block, ...) + initPlugins(md, options, pluginOptions); - const parse = initParser(md, options, env); + // Init preprocessor and MD parser + const parse = initParser(md, options, env, pluginOptions); + + // Init render to HTML compiler const compile = initCompiler(md, options, env); return {parse, compile, env}; } -function initPlugins(md: MarkdownIt, options: OptionsType) { +function getPluginOptions(options: OptionsType) { const { vars = {}, path, extractTitle, conditionsInCode = false, disableLiquid = false, - linkify = false, - linkifyTlds, - leftDelimiter = '{', - rightDelimiter = '}', - plugins = DefaultPlugins, ...customOptions } = options; - const pluginOptions = { + return { ...customOptions, conditionsInCode, vars, @@ -69,7 +83,17 @@ function initPlugins(md: MarkdownIt, options: OptionsType) { extractTitle, disableLiquid, log, - }; + } as MarkdownItPluginOpts; +} + +function initPlugins(md: MarkdownIt, options: OptionsType, pluginOptions: MarkdownItPluginOpts) { + const { + linkify = false, + linkifyTlds, + leftDelimiter = '{', + rightDelimiter = '}', + plugins = DefaultPlugins, + } = options; // Need for ids of headers md.use(attrs, {leftDelimiter, rightDelimiter}); @@ -81,17 +105,30 @@ function initPlugins(md: MarkdownIt, options: OptionsType) { } } -function initParser(md: MarkdownIt, options: OptionsType, env: EnvType) { +function initParser( + md: MarkdownIt, + options: OptionsType, + env: EnvType, + pluginOptions: MarkdownItPluginOpts, +) { return (input: string) => { const { extractTitle: extractTitleOption, needTitle, needFlatListHeadings = false, getPublicPath, + preprocessors = DefaultPreprocessors, } = options; + // Run input preprocessor + for (const preprocessor of preprocessors) { + input = preprocessor(input, pluginOptions, md); + } + + // Generate global href link const href = getPublicPath ? getPublicPath(options) : ''; + // Generate MD tokens let tokens = md.parse(input, env); if (extractTitleOption) { @@ -121,12 +158,15 @@ function initCompiler(md: MarkdownIt, options: OptionsType, env: EnvType) { const {needToSanitizeHtml = true, renderInline = false, sanitizeOptions} = options; return (tokens: Token[]) => { + // Remove inline tokens if inline mode is activated if (renderInline) { tokens = tokens.filter((token) => token.type === 'inline'); } + // Generate HTML const html = md.renderer.render(tokens, md.options, env); + // Sanitize the page return needToSanitizeHtml ? sanitizeHtml(html, sanitizeOptions) : html; }; } diff --git a/src/transform/plugins/includes/collect.ts b/src/transform/plugins/includes/collect.ts index 72a62dce..12badc39 100644 --- a/src/transform/plugins/includes/collect.ts +++ b/src/transform/plugins/includes/collect.ts @@ -1,24 +1,39 @@ import {relative} from 'path'; import {bold} from 'chalk'; -import {isFileExists, resolveRelativePath} from '../../utilsFS'; +import {getRelativePath, isFileExists, resolveRelativePath} from '../../utilsFS'; import {MarkdownItPluginOpts} from '../typings'; const includesPaths: string[] = []; type Opts = MarkdownItPluginOpts & { destPath: string; - copyFile(path: string, dest: string, opts: Opts): void; + copyFile(path: string, dest: string, opts: Opts): string | null | undefined; singlePage: Boolean; + included: Boolean; + includedParentPath?: string; }; const collect = (input: string, options: Opts) => { - const {root, path, destPath = '', log, copyFile, singlePage} = options; + const { + root, + path, + destPath = '', + log, + copyFile, + singlePage, + includedParentPath: includedParentPathNullable, + included, + } = options; + const includedParentPath = includedParentPathNullable || path; + const INCLUDE_REGEXP = /{%\s*include\s*(notitle)?\s*\[(.+?)]\((.+?)\)\s*%}/g; let match, result = input; + const appendix: Map = new Map(); + while ((match = INCLUDE_REGEXP.exec(result)) !== null) { let [, , , relativePath] = match; const [matchedInclude] = match; @@ -55,7 +70,27 @@ const collect = (input: string, options: Opts) => { }; try { - copyFile(includePath, targetDestPath, includeOptions); + const content = copyFile(includePath, targetDestPath, includeOptions); + + // To reduce file reading we can include the file content into the generated content + if (included && content) { + const includedRelativePath = getRelativePath(includedParentPath, includePath); + + // The appendix is the map that protects from multiple include files + if (!appendix.has(includedRelativePath)) { + // Recursive function to include the depth structure + const includeContent = collect(content, { + ...options, + path: includePath, + includedParentPath, + }); + // Add to appendix set structure + appendix.set( + includedRelativePath, + `{% included (${includedRelativePath}) %}\n${includeContent}\n{% endincluded %}`, + ); + } + } } catch (e) { log.error(`No such file or has no access to ${bold(includePath)} in ${bold(path)}`); } finally { @@ -63,11 +98,16 @@ const collect = (input: string, options: Opts) => { } } + // Appendix should be appended to the end of the file (it supports depth structure, so the included files will have included as well) + if (appendix.size > 0) { + result += '\n' + [...appendix.values()].join('\n'); + } + if (singlePage) { return result; } - return null; + return result; }; export = collect; diff --git a/src/transform/plugins/includes/index.ts b/src/transform/plugins/includes/index.ts index 4ab85c37..3d443c30 100644 --- a/src/transform/plugins/includes/index.ts +++ b/src/transform/plugins/includes/index.ts @@ -1,10 +1,17 @@ import {bold} from 'chalk'; +import Token from 'markdown-it/lib/token'; -import {GetFileTokensOpts, getFileTokens, getFullIncludePath, isFileExists} from '../../utilsFS'; +import {StateCore} from '../../typings'; +import { + GetFileTokensOpts, + getFileTokens, + getFullIncludePath, + isFileExists, + resolveRelativePath, +} from '../../utilsFS'; import {findBlockTokens} from '../../utils'; -import Token from 'markdown-it/lib/token'; import {MarkdownItPluginCb, MarkdownItPluginOpts} from '../typings'; -import {StateCore} from 'src/transform/typings'; +import {MarkdownItIncluded} from './types'; const INCLUDE_REGEXP = /^{%\s*include\s*(notitle)?\s*\[(.+?)]\((.+?)\)\s*%}$/; @@ -20,7 +27,7 @@ type Options = MarkdownItPluginOpts & noReplaceInclude: boolean; }; -function unfoldIncludes(state: StateCore, path: string, options: Options) { +function unfoldIncludes(md: MarkdownItIncluded, state: StateCore, path: string, options: Options) { const {root, notFoundCb, log, noReplaceInclude = false} = options; const {tokens} = state; let i = 0; @@ -41,6 +48,11 @@ function unfoldIncludes(state: StateCore, path: string, options: Options) { const [, keyword /* description */, , includePath] = match; const fullIncludePath = getFullIncludePath(includePath, root, path); + const relativeIncludePath = resolveRelativePath(path, includePath); + + // Check the existed included store and extract it + const included = md.included?.[relativeIncludePath]; + let pathname = fullIncludePath; let hash = ''; const hashIndex = fullIncludePath.lastIndexOf('#'); @@ -55,7 +67,10 @@ function unfoldIncludes(state: StateCore, path: string, options: Options) { continue; } - const fileTokens = getFileTokens(pathname, state, options); + const fileTokens = getFileTokens(pathname, state, { + ...options, + content: included, // The content forces the function to use it instead of reading from the disk + }); let includedTokens; if (hash) { @@ -92,7 +107,7 @@ function unfoldIncludes(state: StateCore, path: string, options: Options) { } } -const index: MarkdownItPluginCb = (md, options) => { +const index: MarkdownItPluginCb = (md: MarkdownItIncluded, options) => { const {path: optPath, log} = options; const plugin = (state: StateCore) => { @@ -113,7 +128,7 @@ const index: MarkdownItPluginCb = (md, options) => { } env.includes.push(path); - unfoldIncludes(state, path, options); + unfoldIncludes(md, state, path, options); env.includes.pop(); }; diff --git a/src/transform/plugins/includes/types.ts b/src/transform/plugins/includes/types.ts new file mode 100644 index 00000000..0e65e2fd --- /dev/null +++ b/src/transform/plugins/includes/types.ts @@ -0,0 +1,7 @@ +import {MarkdownIt} from '../../typings'; + +export interface MarkdownItIncluded extends MarkdownIt { + included?: { + [key: string]: string; + }; +} diff --git a/src/transform/plugins/typings.ts b/src/transform/plugins/typings.ts index 1c323c89..3721b5ff 100644 --- a/src/transform/plugins/typings.ts +++ b/src/transform/plugins/typings.ts @@ -1,16 +1 @@ -import type {Logger} from '../log'; -import type {CacheContext, MarkdownIt} from '../typings'; - -export interface MarkdownItPluginOpts { - path: string; - log: Logger; - lang: 'ru' | 'en' | 'es' | 'fr' | 'cs' | 'ar' | 'he'; - root: string; - rootPublicPath: string; - isLintRun: boolean; - cache?: CacheContext; -} - -export type MarkdownItPluginCb = { - (md: MarkdownIt, opts: T & MarkdownItPluginOpts): void; -}; +export * from '../typings'; // TODO: Remove in major release diff --git a/src/transform/preprocessors.ts b/src/transform/preprocessors.ts new file mode 100644 index 00000000..c1e2a2e4 --- /dev/null +++ b/src/transform/preprocessors.ts @@ -0,0 +1,7 @@ +import type {MarkdownItPreprocessorCb} from './plugins/typings'; + +import included from './preprocessors/included'; + +const defaultPreprocessors = [included] as MarkdownItPreprocessorCb[]; + +export = defaultPreprocessors; diff --git a/src/transform/preprocessors/included/index.ts b/src/transform/preprocessors/included/index.ts new file mode 100644 index 00000000..1fef568d --- /dev/null +++ b/src/transform/preprocessors/included/index.ts @@ -0,0 +1,101 @@ +import {getFullIncludePath} from '../../utilsFS'; +import {MarkdownItPreprocessorCb} from '../../typings'; +import {MarkdownItIncluded} from '../../plugins/includes/types'; + +const INCLUDE_REGEXP = /^\s*{%\s*included\s*\((.+?)\)\s*%}\s*$/; +const INCLUDE_END_REGEXP = /^\s*{% endincluded %}\s*$/; + +const preprocessLine = ( + lines: string[], + start: number, + { + root, + path, + }: { + root?: string; + path?: string; + }, + md?: MarkdownItIncluded, +) => { + const hasIncludedCache = md && root && path; + const str = lines[start]; + const match = str?.match(INCLUDE_REGEXP); + + // Protect from unmatched results + if (!match) { + return false; + } + + const includePathRelative = match[1]; + + // Protect from empty path + if (!includePathRelative) { + return false; + } + + // Read all content from top to bottom(!) char of the included block + const data = []; + let line = start; + while (line < lines.length) { + line++; + const str = lines[line]; + if (str === null) { + break; + } + if (str?.match(INCLUDE_END_REGEXP)) { + break; + } + data.push(str); + } + + // No included cache for lint mode + if (hasIncludedCache) { + if (!md.included) { + md.included = {}; + } + + // Normalize the path to absolute + const includePath = getFullIncludePath(includePathRelative, root, path); + + // Store the included content + md.included[includePath] = data.join('\n'); + } + + // Remove the content of the included file + lines.splice(start, data.length + 2); + + return true; +}; + +const index: MarkdownItPreprocessorCb<{ + included?: boolean; +}> = (input, options, md?: MarkdownItIncluded) => { + const {included, path, root} = options; + + // To reduce file reading we can include the file content into the generated content + if (included) { + const lines = input.split('\n') || []; + + // The finction reads the files from bottom to top(!). It stops the loop if it does not have anything to swap. + // If the function finds something to process then it restarts the loop because the position of the last element has been moved. + // eslint-disable-next-line no-unmodified-loop-condition + while (input?.length) { + let hasChars = false; + for (let line = lines.length - 1; line >= 0; line--) { + hasChars = preprocessLine(lines, line, {path, root}, md); + if (hasChars) { + break; + } + } + if (!hasChars) { + break; + } + } + + input = lines.join('\n'); + } + + return input; +}; + +export = index; diff --git a/src/transform/typings.ts b/src/transform/typings.ts index d6359866..dbc88f9f 100644 --- a/src/transform/typings.ts +++ b/src/transform/typings.ts @@ -2,8 +2,7 @@ import {LanguageFn} from 'highlight.js'; import DefaultMarkdownIt from 'markdown-it'; import DefaultStateCore from 'markdown-it/lib/rules_core/state_core'; import {SanitizeOptions} from './sanitize'; -import {MarkdownItPluginCb} from './plugins/typings'; -import {LogLevels} from './log'; +import {LogLevels, Logger} from './log'; import {ChangelogItem} from './plugins/changelog/types'; export interface MarkdownIt extends DefaultMarkdownIt { @@ -48,7 +47,9 @@ export interface OptionsType { needFlatListHeadings?: boolean; // eslint-disable-next-line @typescript-eslint/no-explicit-any plugins?: MarkdownItPluginCb[]; + preprocessors?: MarkdownItPreprocessorCb[]; // Preprocessors should modify the input before passing it to MD highlightLangs?: HighlightLangMap; + disableRules?: string[]; extractChangelogs?: boolean; root?: string; rootPublicPath?: string; @@ -73,3 +74,26 @@ export type EnvType = { meta?: object; changelogs?: ChangelogItem[]; } & Extras; + +export interface MarkdownItPluginOpts { + path: string; + log: Logger; + lang: 'ru' | 'en' | 'es' | 'fr' | 'cs' | 'ar' | 'he'; + root: string; + rootPublicPath: string; + isLintRun: boolean; + cache?: CacheContext; + conditionsInCode?: boolean; + vars?: Record; + extractTitle?: boolean; + disableLiquid?: boolean; +} + +export type MarkdownItPluginCb = { + // TODO: use "T extends unknown = {}" + (md: MarkdownIt, opts: T & MarkdownItPluginOpts): void; +}; + +export type MarkdownItPreprocessorCb = { + (input: string, opts: T & Partial, md?: MarkdownIt): string; +}; diff --git a/src/transform/utilsFS.ts b/src/transform/utilsFS.ts index 4e19a931..10d03a7d 100644 --- a/src/transform/utilsFS.ts +++ b/src/transform/utilsFS.ts @@ -35,6 +35,7 @@ export type GetFileTokensOpts = { disableCircularError?: boolean; inheritVars?: boolean; conditionsInCode?: boolean; + content?: string; }; export function getFileTokens(path: string, state: StateCore, options: GetFileTokensOpts) { @@ -49,15 +50,18 @@ export function getFileTokens(path: string, state: StateCore, options: GetFileTo inheritVars = true, conditionsInCode, } = options; - let content; + let {content} = options; const builtVars = (getVarsPerFile && !inheritVars ? getVarsPerFile(path) : vars) || {}; - if (filesCache[path]) { - content = filesCache[path]; - } else { - content = readFileSync(path, 'utf8'); - filesCache[path] = content; + // Read the content only if we dont have one in the args + if (!content) { + if (filesCache[path]) { + content = filesCache[path]; + } else { + content = readFileSync(path, 'utf8'); + filesCache[path] = content; + } } let sourceMap; @@ -148,3 +152,10 @@ export function getPublicPath( const href = transformer(filePath); return href; } + +export function getRelativePath(path: string, toPath: string) { + const pathDirs = path.split(sep); + pathDirs.pop(); + const parentPath = pathDirs.join(sep); + return relative(parentPath, toPath); +} diff --git a/src/transform/yfmlint/index.ts b/src/transform/yfmlint/index.ts index 0f52b177..9d78c7a4 100644 --- a/src/transform/yfmlint/index.ts +++ b/src/transform/yfmlint/index.ts @@ -20,13 +20,21 @@ import {Options} from './typings'; import type {Dictionary} from 'lodash'; import {LogLevels, Logger} from '../log'; import {yfm009} from './markdownlint-custom-rule/yfm009'; +import defaultPreprocessors from '../preprocessors'; const defaultLintRules = [yfm001, yfm002, yfm003, yfm004, yfm005, yfm006, yfm007, yfm008, yfm009]; const lintCache = new Set(); function yfmlint(opts: Options) { - const {input, plugins: customPlugins, pluginOptions, customLintRules, sourceMap} = opts; + let {input} = opts; + const { + plugins: customPlugins, + preprocessors = defaultPreprocessors, + pluginOptions, + customLintRules, + sourceMap, + } = opts; const {path = 'input', log} = pluginOptions; pluginOptions.isLintRun = true; @@ -56,6 +64,10 @@ function yfmlint(opts: Options) { const plugins = customPlugins && [attrs, ...customPlugins]; const preparedPlugins = plugins && plugins.map((plugin) => [plugin, pluginOptions]); + for (const preprocessor of preprocessors) { + input = preprocessor(input, pluginOptions); + } + let result; try { result = sync({ diff --git a/src/transform/yfmlint/typings.ts b/src/transform/yfmlint/typings.ts index a19213cb..59239d7a 100644 --- a/src/transform/yfmlint/typings.ts +++ b/src/transform/yfmlint/typings.ts @@ -1,10 +1,12 @@ import type {Dictionary} from 'lodash'; import {Plugin, Rule} from 'markdownlint'; +import {MarkdownItPreprocessorCb} from '../typings'; import {LintConfig, PluginOptions} from '.'; export interface Options { input: string; plugins?: Function[] | Plugin; + preprocessors?: MarkdownItPreprocessorCb[]; pluginOptions: PluginOptions; defaultLintConfig?: LintConfig; lintConfig?: LintConfig; diff --git a/test/include-included.test.ts b/test/include-included.test.ts new file mode 100644 index 00000000..9b7ad513 --- /dev/null +++ b/test/include-included.test.ts @@ -0,0 +1,88 @@ +import {resolve} from 'path'; +import {readFileSync} from 'fs'; +import {readFile} from 'node:fs/promises'; +import transform from '../src/transform'; +import collect from '../src/transform/plugins/includes/collect'; +import includes from '../src/transform/plugins/includes'; +import {log} from './utils'; + +const transformYfm = (text: string, path = 'mocks/included.md') => { + const { + result: {html}, + } = transform(text, { + included: true, + plugins: [includes], + disableRules: ['link'], + path: path, + root: resolve(path, '../'), + }); + return html; +}; + +const collectIncluded = (text: string, path: string) => { + const result = collect(text, { + included: true, + path: path, + root: resolve(path, '../'), + copyFile: (includePath) => readFileSync(includePath, 'utf-8'), + singlePage: false, + destPath: '', + isLintRun: false, + lang: 'ru', + rootPublicPath: '', + log: log.log, + }); + return result; +}; + +describe('Included to md', () => { + describe('includes', () => { + test('compile file with 3 included', async () => { + const inputPath = resolve(__dirname, './mocks/include-included-3.md'); + const input = await readFile(inputPath, 'utf8'); + + const expectPath = resolve(__dirname, './mocks/include-included-3.expect.md'); + const expectContent = await readFile(expectPath, 'utf8'); + + const result = collectIncluded(input, inputPath); + + expect(result).toBe(expectContent); + }); + + test('compile file with 3 included into html', async () => { + const inputPath = resolve(__dirname, './mocks/include-included-3.expect.md'); + const input = await readFile(inputPath, 'utf8'); + + const expectPath = resolve(__dirname, './mocks/include-included-3.expect.html'); + const expectContent = await readFile(expectPath, 'utf8'); + + const html = transformYfm(input); + + expect(html).toBe(expectContent); + }); + + test('compile file with 3 deep included', async () => { + const inputPath = resolve(__dirname, './mocks/include-included-3-deep.md'); + const input = await readFile(inputPath, 'utf8'); + + const expectPath = resolve(__dirname, './mocks/include-included-3-deep.expect.md'); + const expectContent = await readFile(expectPath, 'utf8'); + + const result = collectIncluded(input, inputPath); + + expect(result).toBe(expectContent); + }); + + test('compile file with 3 deep included into html', async () => { + const inputPath = resolve(__dirname, './mocks/include-included-3-deep.expect.md'); + const input = await readFile(inputPath, 'utf8'); + + const expectPath = resolve(__dirname, './mocks/include-included-3-deep.expect.html'); + const expectContent = await readFile(expectPath, 'utf8'); + + const html = transformYfm(input); + + expect(html).toBe(expectContent); + }); + }); +}); diff --git a/test/included.test.ts b/test/included.test.ts new file mode 100644 index 00000000..6b8a0ece --- /dev/null +++ b/test/included.test.ts @@ -0,0 +1,93 @@ +import {resolve} from 'path'; +import {readFile} from 'node:fs/promises'; +import transform from '../src/transform'; +import includes from '../src/transform/plugins/includes'; + +const transformYfm = (text: string, path = 'mocks/included.md') => { + const { + result: {html}, + } = transform(text, { + included: true, + plugins: [includes], + disableRules: ['link'], + path: path, + root: resolve(path, '../'), + }); + return html; +}; + +describe('Included to html', () => { + describe('includes', () => { + test('compile file with 1 included', async () => { + const inputPath = resolve(__dirname, './mocks/included.md'); + const input = await readFile(inputPath, 'utf8'); + + const expectPath = resolve(__dirname, './mocks/included.expect.html'); + const expectContent = await readFile(expectPath, 'utf8'); + + const html = transformYfm(input); + + expect(html).toBe(expectContent); + }); + + test('compile file with 2 included', async () => { + const inputPath = resolve(__dirname, './mocks/included-2.md'); + const input = await readFile(inputPath, 'utf8'); + + const expectPath = resolve(__dirname, './mocks/included-2.expect.html'); + const expectContent = await readFile(expectPath, 'utf8'); + + const html = transformYfm(input); + + expect(html).toBe(expectContent); + }); + + test('compile file with 3 included', async () => { + const inputPath = resolve(__dirname, './mocks/included-3.md'); + const input = await readFile(inputPath, 'utf8'); + + const expectPath = resolve(__dirname, './mocks/included-3.expect.html'); + const expectContent = await readFile(expectPath, 'utf8'); + + const html = transformYfm(input); + + expect(html).toBe(expectContent); + }); + + test('compile file with 4 included', async () => { + const inputPath = resolve(__dirname, './mocks/included-4.md'); + const input = await readFile(inputPath, 'utf8'); + + const expectPath = resolve(__dirname, './mocks/included-4.expect.html'); + const expectContent = await readFile(expectPath, 'utf8'); + + const html = transformYfm(input); + + expect(html).toBe(expectContent); + }); + + test('compile file with 2 nested depth included', async () => { + const inputPath = resolve(__dirname, './mocks/included-2-nest.md'); + const input = await readFile(inputPath, 'utf8'); + + const expectPath = resolve(__dirname, './mocks/included-2-nest.expect.html'); + const expectContent = await readFile(expectPath, 'utf8'); + + const html = transformYfm(input); + + expect(html).toBe(expectContent); + }); + + test('compile file with 3 nested depth included', async () => { + const inputPath = resolve(__dirname, './mocks/included-3-nest.md'); + const input = await readFile(inputPath, 'utf8'); + + const expectPath = resolve(__dirname, './mocks/included-3-nest.expect.html'); + const expectContent = await readFile(expectPath, 'utf8'); + + const html = transformYfm(input); + + expect(html).toBe(expectContent); + }); + }); +}); diff --git a/test/includes.test.ts b/test/includes.test.ts index daec6838..f56620b1 100644 --- a/test/includes.test.ts +++ b/test/includes.test.ts @@ -47,7 +47,7 @@ describe('Includes', () => { { path: mocksPath, root: dirname(mocksPath), - vars: {condition: true}, + vars: {condition: 'true'}, conditionsInCode: true, }, ); diff --git a/test/mocks/include-included-3-deep.expect.html b/test/mocks/include-included-3-deep.expect.html new file mode 100644 index 00000000..04f2f4f6 --- /dev/null +++ b/test/mocks/include-included-3-deep.expect.html @@ -0,0 +1,8 @@ +

start main

+

start file 1

+

start file 2

+

start file 3

+

end file 3

+

end file 2

+

end file 1

+

end main

diff --git a/test/mocks/include-included-3-deep.expect.md b/test/mocks/include-included-3-deep.expect.md new file mode 100644 index 00000000..31a22a5a --- /dev/null +++ b/test/mocks/include-included-3-deep.expect.md @@ -0,0 +1,24 @@ +start main + +{% include [Text](included/file-1-deep.md) %} + +end main +{% included (included/file-1-deep.md) %} +start file 1 + +{% include [Text](file-2-deep.md) %} + +end file 1 +{% included (included/file-2-deep.md) %} +start file 2 + +{% include [Text](file-3.md) %} + +end file 2 +{% included (included/file-3.md) %} +start file 3 + +end file 3 +{% endincluded %} +{% endincluded %} +{% endincluded %} \ No newline at end of file diff --git a/test/mocks/include-included-3-deep.md b/test/mocks/include-included-3-deep.md new file mode 100644 index 00000000..f20c267d --- /dev/null +++ b/test/mocks/include-included-3-deep.md @@ -0,0 +1,5 @@ +start main + +{% include [Text](included/file-1-deep.md) %} + +end main \ No newline at end of file diff --git a/test/mocks/include-included-3.expect.html b/test/mocks/include-included-3.expect.html new file mode 100644 index 00000000..1c22dfbc --- /dev/null +++ b/test/mocks/include-included-3.expect.html @@ -0,0 +1,10 @@ +

start main

+

start file 1

+

end file 1

+

middle 1

+

start file 2

+

end file 2

+

middle 2

+

start file 3

+

end file 3

+

end main

diff --git a/test/mocks/include-included-3.expect.md b/test/mocks/include-included-3.expect.md new file mode 100644 index 00000000..3f126627 --- /dev/null +++ b/test/mocks/include-included-3.expect.md @@ -0,0 +1,28 @@ +start main + +{% include [Text](included/file-1.md) %} + +middle 1 + +{% include [Text](included/file-2.md) %} + +middle 2 + +{% include [Text](included/file-3.md) %} + +end main +{% included (included/file-1.md) %} +start file 1 + +end file 1 +{% endincluded %} +{% included (included/file-2.md) %} +start file 2 + +end file 2 +{% endincluded %} +{% included (included/file-3.md) %} +start file 3 + +end file 3 +{% endincluded %} \ No newline at end of file diff --git a/test/mocks/include-included-3.md b/test/mocks/include-included-3.md new file mode 100644 index 00000000..cb30379d --- /dev/null +++ b/test/mocks/include-included-3.md @@ -0,0 +1,13 @@ +start main + +{% include [Text](included/file-1.md) %} + +middle 1 + +{% include [Text](included/file-2.md) %} + +middle 2 + +{% include [Text](included/file-3.md) %} + +end main \ No newline at end of file diff --git a/test/mocks/included-2-nest.expect.html b/test/mocks/included-2-nest.expect.html new file mode 100644 index 00000000..ec9142ad --- /dev/null +++ b/test/mocks/included-2-nest.expect.html @@ -0,0 +1,5 @@ +

start main

+

start abc

+

abc nested

+

end abc

+

end main

diff --git a/test/mocks/included-2-nest.md b/test/mocks/included-2-nest.md new file mode 100644 index 00000000..3a0fa47b --- /dev/null +++ b/test/mocks/included-2-nest.md @@ -0,0 +1,15 @@ +start main + +{% include [Text](_includes/file.md) %} + +end main +{% included (./_includes/file.md) %} +start abc + +{% include [Text](./plugins.md) %} + +end abc +{% included (./_includes/plugins.md) %} +abc nested +{% endincluded %} +{% endincluded %} \ No newline at end of file diff --git a/test/mocks/included-2.expect.html b/test/mocks/included-2.expect.html new file mode 100644 index 00000000..02fd34b2 --- /dev/null +++ b/test/mocks/included-2.expect.html @@ -0,0 +1,8 @@ +

start main

+

start abc 1

+

middle abc 1

+

end abc 1

+

start abc 2

+

middle abc 2

+

end abc 2

+

end main

diff --git a/test/mocks/included-2.md b/test/mocks/included-2.md new file mode 100644 index 00000000..410b55d6 --- /dev/null +++ b/test/mocks/included-2.md @@ -0,0 +1,21 @@ +start main + +{% include [Text](file.md) %} + +{% include [Text](file-2.md) %} + +end main +{% included (./file.md) %} +start abc 1 + +middle abc 1 + +end abc 1 +{% endincluded %} +{% included (./file-2.md) %} +start abc 2 + +middle abc 2 + +end abc 2 +{% endincluded %} \ No newline at end of file diff --git a/test/mocks/included-3-nest.expect.html b/test/mocks/included-3-nest.expect.html new file mode 100644 index 00000000..58965a5c --- /dev/null +++ b/test/mocks/included-3-nest.expect.html @@ -0,0 +1,14 @@ +

start main

+

start 1

+

start 2

+

start 3

+

end 3

+

end 2

+

end 1

+

start 2

+

start 3

+

end 3

+

end 2

+

start 3

+

end 3

+

end main

diff --git a/test/mocks/included-3-nest.md b/test/mocks/included-3-nest.md new file mode 100644 index 00000000..1f1ed44c --- /dev/null +++ b/test/mocks/included-3-nest.md @@ -0,0 +1,28 @@ +start main + +{% include [Text](_includes/file.md) %} + +{% include [Text](_includes/plugins.md) %} + +{% include [Text](_includes/sub-plugins.md) %} + +end main +{% included (./_includes/file.md) %} +start 1 + +{% include [Text](./plugins.md) %} + +end 1 +{% included (./_includes/plugins.md) %} +start 2 + +{% include [Text](./sub-plugins.md) %} + +end 2 +{% included (./_includes/sub-plugins.md) %} +start 3 + +end 3 +{% endincluded %} +{% endincluded %} +{% endincluded %} \ No newline at end of file diff --git a/test/mocks/included-3.expect.html b/test/mocks/included-3.expect.html new file mode 100644 index 00000000..7f681afe --- /dev/null +++ b/test/mocks/included-3.expect.html @@ -0,0 +1,11 @@ +

start main

+

start abc 1

+

middle abc 1

+

end abc 1

+

start abc 2

+

middle abc 2

+

end abc 2

+

start abc 3

+

middle abc 3

+

end abc 3

+

end main

diff --git a/test/mocks/included-3.md b/test/mocks/included-3.md new file mode 100644 index 00000000..b26d8b88 --- /dev/null +++ b/test/mocks/included-3.md @@ -0,0 +1,30 @@ +start main + +{% include [Text](file.md) %} + +{% include [Text](file-2.md) %} + +{% include [Text](file-3.md) %} + +end main +{% included (./file.md) %} +start abc 1 + +middle abc 1 + +end abc 1 +{% endincluded %} +{% included (./file-2.md) %} +start abc 2 + +middle abc 2 + +end abc 2 +{% endincluded %} +{% included (./file-3.md) %} +start abc 3 + +middle abc 3 + +end abc 3 +{% endincluded %} \ No newline at end of file diff --git a/test/mocks/included-4.expect.html b/test/mocks/included-4.expect.html new file mode 100644 index 00000000..c189d968 --- /dev/null +++ b/test/mocks/included-4.expect.html @@ -0,0 +1,14 @@ +

start main

+

start abc 1

+

middle abc 1

+

end abc 1

+

start abc 2

+

middle abc 2

+

end abc 2

+

start abc 3

+

middle abc 3

+

end abc 3

+

start abc 4

+

middle abc 4

+

end abc 4

+

end main

diff --git a/test/mocks/included-4.md b/test/mocks/included-4.md new file mode 100644 index 00000000..9be38e24 --- /dev/null +++ b/test/mocks/included-4.md @@ -0,0 +1,39 @@ +start main + +{% include [Text](file.md) %} + +{% include [Text](file-2.md) %} + +{% include [Text](file-3.md) %} + +{% include [Text](file-4.md) %} + +end main +{% included (./file.md) %} +start abc 1 + +middle abc 1 + +end abc 1 +{% endincluded %} +{% included (./file-2.md) %} +start abc 2 + +middle abc 2 + +end abc 2 +{% endincluded %} +{% included (./file-3.md) %} +start abc 3 + +middle abc 3 + +end abc 3 +{% endincluded %} +{% included (./file-4.md) %} +start abc 4 + +middle abc 4 + +end abc 4 +{% endincluded %} \ No newline at end of file diff --git a/test/mocks/included.expect.html b/test/mocks/included.expect.html new file mode 100644 index 00000000..be8ce1f1 --- /dev/null +++ b/test/mocks/included.expect.html @@ -0,0 +1,5 @@ +

start main

+

start abc

+

middle abc

+

end abc

+

end main

diff --git a/test/mocks/included.md b/test/mocks/included.md new file mode 100644 index 00000000..ee845058 --- /dev/null +++ b/test/mocks/included.md @@ -0,0 +1,12 @@ +start main + +{% include [Text](link.md) %} + +end main +{% included (./link.md) %} +start abc + +middle abc + +end abc +{% endincluded %} \ No newline at end of file diff --git a/test/mocks/included/file-1-deep.md b/test/mocks/included/file-1-deep.md new file mode 100644 index 00000000..1a482ae3 --- /dev/null +++ b/test/mocks/included/file-1-deep.md @@ -0,0 +1,5 @@ +start file 1 + +{% include [Text](file-2-deep.md) %} + +end file 1 \ No newline at end of file diff --git a/test/mocks/included/file-1.md b/test/mocks/included/file-1.md new file mode 100644 index 00000000..19c747d4 --- /dev/null +++ b/test/mocks/included/file-1.md @@ -0,0 +1,3 @@ +start file 1 + +end file 1 \ No newline at end of file diff --git a/test/mocks/included/file-2-deep.md b/test/mocks/included/file-2-deep.md new file mode 100644 index 00000000..988bd2b4 --- /dev/null +++ b/test/mocks/included/file-2-deep.md @@ -0,0 +1,5 @@ +start file 2 + +{% include [Text](file-3.md) %} + +end file 2 \ No newline at end of file diff --git a/test/mocks/included/file-2.md b/test/mocks/included/file-2.md new file mode 100644 index 00000000..3027e92b --- /dev/null +++ b/test/mocks/included/file-2.md @@ -0,0 +1,3 @@ +start file 2 + +end file 2 \ No newline at end of file diff --git a/test/mocks/included/file-3.md b/test/mocks/included/file-3.md new file mode 100644 index 00000000..189413a8 --- /dev/null +++ b/test/mocks/included/file-3.md @@ -0,0 +1,3 @@ +start file 3 + +end file 3 \ No newline at end of file