UNPKG

@diplodoc/transform

Version:

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

253 lines (210 loc) 7.11 kB
import {bold} from 'chalk'; import {log} from '../log'; import evalExp from './evaluation'; import {tagLine} from './lexical'; import {getPreparedLeftContent, removeIndentBlock} from './utils'; import {createSourceMapApi, getLineNumber} from './sourceMap'; type Options = { firstLineNumber: number; lastLineNumber: number; resFirstLineNumber: number; resLastLineNumber: number; linesTotal: number; sourceMap: Record<number, number>; }; function changeSourceMap({ firstLineNumber, lastLineNumber, resFirstLineNumber, resLastLineNumber, linesTotal, sourceMap, }: Options) { if (!sourceMap) { return; } const {getSourceMapValue, moveLines, removeLines} = createSourceMapApi(sourceMap); let offsetRestLines; if (resFirstLineNumber) { // Move condition's content to the top const offsetContentLines = firstLineNumber - resFirstLineNumber; moveLines({ start: resFirstLineNumber, end: resLastLineNumber - 1, offset: offsetContentLines, withReplace: true, }); // Remove the rest lines of the condition block removeLines({start: firstLineNumber, end: resFirstLineNumber - 1}); removeLines({start: resLastLineNumber, end: lastLineNumber}); // Calculate an offset of the rest lines offsetRestLines = getSourceMapValue(resLastLineNumber - 1) - lastLineNumber; } else { // Remove the whole condition block removeLines({start: firstLineNumber, end: lastLineNumber}); // Calculate offset of the rest lines offsetRestLines = firstLineNumber - lastLineNumber - 1; } // Offset the rest lines moveLines({start: lastLineNumber + 1, end: linesTotal, offset: offsetRestLines}); } function getElseProp<B extends keyof Elses>({elses}: {elses: Elses[]}, propName: B, index = 0) { if (!elses.length || index >= elses.length) { return undefined; } return elses[index][propName]; } type Opts = { ifTag: Tag; vars: Record<string, unknown>; content: string; match: RegExpExecArray; lastIndex: number; sourceMap: Record<number, number>; linesTotal: number; }; function inlineConditions({ifTag, vars, content, match, lastIndex, sourceMap, linesTotal}: Opts) { let res = ''; const firstLineNumber = getLineNumber(content, ifTag.startPos); const lastLineNumber = getLineNumber(content, lastIndex); let resFirstLineNumber = 0; let resLastLineNumber = 0; if (evalExp(ifTag.condition, vars)) { const ifRawLastIndex = ifTag.startPos + ifTag.ifRaw.length; const contentLastIndex = getElseProp(ifTag, 'startPos') || match.index; res = content.substring(ifRawLastIndex, contentLastIndex); resFirstLineNumber = getLineNumber(content, ifRawLastIndex + 1); resLastLineNumber = getLineNumber(content, contentLastIndex + 1); } else { ifTag.elses.some(({condition, startPos, raw}, index) => { const isTruthy = !condition || evalExp(condition, vars); if (isTruthy) { const elseRawLastIndex = startPos + raw.length; const contentLastIndex = getElseProp(ifTag, 'startPos', index + 1) || match.index; res = content.substring(elseRawLastIndex, contentLastIndex); resFirstLineNumber = getLineNumber(content, elseRawLastIndex + 1); resLastLineNumber = getLineNumber(content, contentLastIndex + 1); return true; } return false; }); } changeSourceMap({ firstLineNumber, lastLineNumber, resFirstLineNumber, resLastLineNumber, linesTotal, sourceMap, }); const preparedLeftContent = getPreparedLeftContent({ content, tagStartPos: ifTag.startPos, tagContent: res, }); let shift = 0; if ( res === '' && preparedLeftContent[preparedLeftContent.length - 1] === '\n' && content[lastIndex] === '\n' ) { shift = 1; } if (res !== '') { if (res[0] === '\n') { res = res.substring(1); } res = removeIndentBlock(res); if (res[res.length - 1] === '\n') { res = res.slice(0, -1); } } const leftPart = preparedLeftContent + res; return { result: leftPart + content.substring(lastIndex + shift), idx: leftPart.length, }; } type Elses = {startPos: number; raw: string; condition?: string}; type Tag = { isOpen: Boolean; condition: string; startPos: number; ifRaw: string; elses: Elses[]; }; export = function legacyConditions( originInput: string, vars: Record<string, unknown>, path?: string, settings?: { sourceMap: Record<number, number>; }, ) { const sourceMap = settings?.sourceMap || {}; const R_LIQUID = /({%-?([\s\S]*?)-?%})/g; let match; const tagStack: Tag[] = []; let input = originInput; let linesTotal = originInput.split('\n').length; while ((match = R_LIQUID.exec(input)) !== null) { if (!match[1]) { continue; } const tagMatch = match[2].trim().match(tagLine); if (!tagMatch) { continue; } const [type, args] = tagMatch.slice(1); switch (type) { case 'if': tagStack.push({ isOpen: true, condition: args, startPos: match.index, ifRaw: match[1], elses: [], }); break; case 'else': tagStack[tagStack.length - 1].elses.push({ startPos: match.index, raw: match[1], }); break; case 'elsif': tagStack[tagStack.length - 1].elses.push({ condition: args, startPos: match.index, raw: match[1], }); break; case 'endif': { const ifTag = tagStack.pop(); if (!ifTag) { log.error( `If block must be opened before close${path ? ` in ${bold(path)}` : ''}`, ); break; } const {idx, result} = inlineConditions({ ifTag, vars, content: input, match, lastIndex: R_LIQUID.lastIndex, sourceMap, linesTotal, }); R_LIQUID.lastIndex = idx; input = result; linesTotal = result.split('\n').length; break; } } } if (tagStack.length !== 0) { log.error(`Condition block must be closed${path ? ` in ${bold(path)}` : ''}`); } return input; };