@diplodoc/transform
Version:
A simple transformer of text in YFM (Yandex Flavored Markdown) to HTML
243 lines • 8.65 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
const chalk_1 = require("chalk");
const log_1 = require("../log");
const evaluation_1 = require("./evaluation");
const lexical_1 = require("./lexical");
const sourceMap_1 = require("./sourceMap");
const legacyConditions_1 = __importDefault(require("./legacyConditions"));
function resourcemap(source, ifTag, ifCon, api) {
const [sourseStartLine, sourceEndLine] = [
(0, sourceMap_1.getLineNumber)(source, ifTag.start + 1),
(0, sourceMap_1.getLineNumber)(source, ifTag.end - 1),
];
if (sourseStartLine === sourceEndLine || ifTag === ifCon) {
return;
}
const linesTotal = source.split('\n').length;
const { getSourceMapValue, moveLines, removeLines } = api;
let offsetRestLines;
if (ifCon) {
const [resultStartLine, resultEndLine] = [
(0, sourceMap_1.getLineNumber)(source, ifCon.start),
(0, sourceMap_1.getLineNumber)(source, ifCon.end),
];
// Move condition's content to the top
const offsetContentLines = sourseStartLine - resultStartLine;
moveLines({
start: resultStartLine,
end: resultEndLine,
offset: offsetContentLines,
withReplace: true,
});
// Remove the rest lines of the condition block
removeLines({ start: sourseStartLine, end: resultStartLine - 1 });
removeLines({ start: resultEndLine + 1, end: sourceEndLine });
// Calculate an offset of the rest lines
offsetRestLines = getSourceMapValue(resultEndLine) - sourceEndLine;
}
else {
// Remove the whole condition block
removeLines({ start: sourseStartLine, end: sourceEndLine });
// Calculate offset of the rest lines
offsetRestLines = sourseStartLine - sourceEndLine - 1;
}
// Offset the rest lines
moveLines({ start: sourceEndLine + 1, end: linesTotal, offset: offsetRestLines });
}
function headLinebreak(raw) {
const match = raw.match(/^([^{]+){.*/);
return match ? match[1] : '';
}
function tailLinebreak(raw) {
const match = raw.match(/.*}(\s*\n)$/);
return match ? match[1] : '';
}
function trimResult(content, ifTag, ifCon) {
if (!ifCon) {
const head = headLinebreak(ifTag.rawStart);
const tail = tailLinebreak(ifTag.rawEnd);
let rest = head + tail;
if (rest !== head && rest !== tail) {
// We have extra line break, if condition was placed on individual line
rest = rest.replace('\n', '');
}
return ifTag.isBlock ? '\n' : rest;
}
content = content.substring(ifCon.start, ifCon.end);
if (ifTag.isBlock) {
return trimBlockResult(content, ifCon);
}
else {
return trimInlineResult(content, ifTag);
}
}
function trimBlockResult(content, ifCon) {
const head = headLinebreak(ifCon.rawStart);
if (head) {
content = '\n' + content;
}
const tail = tailLinebreak(ifCon.rawEnd);
if (tail) {
content = content + '\n';
}
return content;
}
function trimInlineResult(content, ifTag) {
const head = headLinebreak(ifTag.rawStart);
if (head) {
content = head + content;
}
const tail = tailLinebreak(ifTag.rawEnd);
if (tail) {
content = content + tail;
}
return content;
}
class IfTag {
constructor() {
this.conditions = [];
}
get start() {
if (!this.conditions.length) {
return -1;
}
const first = this.conditions[0];
return first.start - first.rawStart.length;
}
get end() {
if (!this.conditions.length) {
return -1;
}
const last = this.conditions[this.conditions.length - 1];
return last.end + last.rawEnd.length;
}
get rawStart() {
if (!this.conditions.length) {
return '';
}
const first = this.conditions[0];
return first.rawStart;
}
get rawEnd() {
if (!this.conditions.length) {
return '';
}
const last = this.conditions[this.conditions.length - 1];
return last.rawEnd;
}
get isBlock() {
const first = this.conditions[0];
const last = this.conditions[this.conditions.length - 1];
return tailLinebreak(first.rawStart) && headLinebreak(last.rawEnd);
}
*[Symbol.iterator]() {
for (const condition of this.conditions) {
yield condition;
}
}
openCondition(raw, expr, start) {
this.closeCondition(raw, start);
this.conditions.push({
rawStart: raw,
start: start + raw.length,
expr,
});
return start + raw.length - tailLinebreak(raw).length;
}
closeCondition(raw, end) {
const condition = this.conditions[this.conditions.length - 1];
if (condition) {
condition.rawEnd = raw;
condition.end = end;
}
}
}
function inlineConditions(content, ifTag, vars, strict) {
let ifCon = null;
for (const condition of ifTag) {
const value = (0, evaluation_1.evalExp)(condition.expr, vars, strict);
if (condition.expr && value === evaluation_1.NoValue) {
return {
result: content,
// Fix offset for next matches.
// There can be some significant linebreak and spaces.
lastIndex: ifTag.end - tailLinebreak(ifTag.rawEnd).length,
ifCon: ifTag,
};
}
if (!condition.expr || value) {
ifCon = condition;
break;
}
}
const start = content.slice(0, ifTag.start);
const end = content.slice(ifTag.end);
const result = trimResult(content, ifTag, ifCon);
return {
result: start + result + end,
lastIndex: start.length + result.length - tailLinebreak(ifTag.rawEnd).length,
ifCon,
};
}
module.exports = function conditions(input, vars, path, settings) {
if (settings === null || settings === void 0 ? void 0 : settings.useLegacyConditions) {
return (0, legacyConditions_1.default)(input, vars, path, settings);
}
const sourceMap = (settings === null || settings === void 0 ? void 0 : settings.sourceMap) || {};
const strict = (settings === null || settings === void 0 ? void 0 : settings.strict) || false;
const tagStack = [];
// Consumes all between curly braces
// and all closest upon to first linebreak before and after braces.
const R_LIQUID = /((?:\n[\t ]*)?{%-?([\s\S]*?)-?%}(?:[\t ]*\n)?)/g;
let match;
while ((match = R_LIQUID.exec(input)) !== null) {
if (!match[1]) {
continue;
}
const tagMatch = match[2].trim().match(lexical_1.tagLine);
if (!tagMatch) {
continue;
}
const [type, args] = tagMatch.slice(1);
switch (type) {
case 'if': {
const tag = new IfTag();
R_LIQUID.lastIndex = tag.openCondition(match[1], args, match.index);
tagStack.push(tag);
break;
}
case 'elsif':
case 'else': {
const tag = tagStack[tagStack.length - 1];
R_LIQUID.lastIndex = tag.openCondition(match[1], args, match.index);
break;
}
case 'endif': {
const ifTag = tagStack.pop();
if (!ifTag) {
// TODO(3y3): make lint rule
log_1.log.error(`If block must be opened before close${path ? ` in ${(0, chalk_1.bold)(path)}` : ''}`);
break;
}
ifTag.closeCondition(match[1], match.index);
const { result, lastIndex, ifCon } = inlineConditions(input, ifTag, vars, strict);
resourcemap(input, ifTag, ifCon, (0, sourceMap_1.createSourceMapApi)(sourceMap));
R_LIQUID.lastIndex = lastIndex;
input = result;
break;
}
default:
// This is not condition.
// Step back last linebreaks to match them on next condition
R_LIQUID.lastIndex -= tailLinebreak(match[1]).length;
}
}
if (tagStack.length !== 0) {
log_1.log.error(`Condition block must be closed${path ? ` in ${(0, chalk_1.bold)(path)}` : ''}`);
}
return input;
};
//# sourceMappingURL=conditions.js.map