muban-convert-hbs
Version:
Convert muban hbs templates to htl, django, twig and others.
367 lines (280 loc) • 13.2 kB
JavaScript
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.HtlTranspiler = void 0;
var _handlebars = _interopRequireDefault(require("handlebars"));
var _Context = _interopRequireWildcard(require("./Context"));
var HtlTranspiler =
/*#__PURE__*/
function () {
function HtlTranspiler(input, context, depth) {
if (depth === void 0) {
depth = 0;
}
Object.defineProperty(this, "buffer", {
configurable: true,
enumerable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "parsed", {
configurable: true,
enumerable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "context", {
configurable: true,
enumerable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "depth", {
configurable: true,
enumerable: true,
writable: true,
value: void 0
});
this.buffer = [];
this.context = context || new _Context.default();
this.depth = depth;
if (input) {
this.parsed = _handlebars.default.parse(input);
this.parseProgram(this.parsed);
}
}
var _proto = HtlTranspiler.prototype;
_proto.parseProgram = function parseProgram(program, _temp) {
var _this = this;
var _ref = _temp === void 0 ? {} : _temp,
_ref$isConditionalInI = _ref.isConditionalInInverse,
isConditionalInInverse = _ref$isConditionalInI === void 0 ? false : _ref$isConditionalInI,
_ref$elseIfResultList = _ref.elseIfResultList,
elseIfResultList = _ref$elseIfResultList === void 0 ? [] : _ref$elseIfResultList,
_ref$indent = _ref.indent,
indent = _ref$indent === void 0 ? '' : _ref$indent;
// console.log('\n\n -- PROGRAM -- \n');
// console.log(program);
program.body.forEach(function (statement) {
switch (statement.type) {
case 'ContentStatement':
_this.buffer.push(statement.original);
break;
case 'MustacheStatement':
// console.log('\n\nMustacheStatement\n');
// console.log(statement);
var path = statement.path;
var escaped = statement.escaped ? '' : " @ context='html'";
var variable;
if (path.original === '@index') {
variable = _this.context.getCurrentScope().value + "List.index";
} else {
variable = _this.context.getScopedVariable(path);
}
if (path.type === 'PathExpression') {
_this.buffer.push("${ " + variable + escaped + " }");
} else if (path.type === 'Literal') {
throw new Error('not implemented');
}
break;
case 'CommentStatement':
_this.buffer.push("<!--/*" + statement.value + "*/-->");
break;
case 'BlockStatement':
// console.log('\n\nBlockStatement\n');
// console.log(statement);
var type = statement.path.original;
switch (type) {
case 'if':
{
var condition = statement.params[0];
var scopedCondition;
if (condition.type === 'SubExpression') {
if (condition.path.original === 'condition') {
scopedCondition = condition.params.map(function (param) {
if (param.type === 'PathExpression') {
return _this.context.getScopedVariable(param);
}
return param.value;
}).join(' ');
}
} else {
scopedCondition = _this.context.getScopedVariable(condition);
}
var currentTestResult = ''; // check for if alias
if (statement.params.length === 3 && statement.params[1].original === 'as') {
currentTestResult = "" + statement.params[2].original;
} else if (statement.inverse) {
++_this.context.shared.ifCounter;
currentTestResult = "result" + _this.context.shared.ifCounter;
}
var testResultDeclaration = currentTestResult ? "." + currentTestResult : ''; // use `else if` instead of else when this is the only if statement in an else block
if (isConditionalInInverse) {
var elseIfCheck = "!(" + elseIfResultList.join(' || ') + ")";
_this.buffer.push("\n" + indent + "<sly data-sly-test" + testResultDeclaration + "=\"${ " + elseIfCheck + " && " + scopedCondition + " }\">");
} else {
_this.buffer.push("<sly data-sly-test" + testResultDeclaration + "=\"${ " + scopedCondition + " }\">");
}
var t = new HtlTranspiler(null, _this.context, _this.depth);
_this.buffer.push(t.parseProgram(statement.program).toString());
_this.buffer.push("</sly>"); // else section
if (statement.inverse) {
var _indent = '';
var ifContentStatement = (statement.program.body.concat().reverse().find(function (s) {
return s.type === 'ContentStatement';
}) || {}).original || '';
var match = /\n([\t ]*)$/gi.exec(ifContentStatement);
if (match && match[1]) {
_indent = match[1];
}
var childElseIfResultList = elseIfResultList.concat(currentTestResult); // if the else body only has an 'if' statement, we can combine it into an 'else if'
var isInverseOnlyConditional = HtlTranspiler.isOnlyCondition(statement.inverse);
var _t = new HtlTranspiler(null, _this.context, _this.depth);
_t.parseProgram(statement.inverse, {
indent: _indent,
isConditionalInInverse: isInverseOnlyConditional,
elseIfResultList: childElseIfResultList
}); // child will render a `else if`
if (!isInverseOnlyConditional) {
var elseCheck = "!(" + childElseIfResultList.join(' || ') + ")";
_this.buffer.push("\n" + _indent + "<sly data-sly-test=\"${ " + elseCheck + " }\">");
}
_this.buffer.push(_t.toString());
if (!isInverseOnlyConditional) {
_this.buffer.push("</sly>");
}
}
break;
}
case 'each':
{
var _condition = _this.context.getScopedVariable(statement.params[0]);
var childContext;
if (statement.program.blockParams) {
var _replacements;
// {{#each foo as |key, value|}
var blockParams = statement.program.blockParams; // TODO: in AEM, when iterating an object, only the key will be assigned, and value will be `condition[key]`
// This means that we should replace the first blockParams by that
// childContext = this.context.createChildContext([`${condition}[${blockParams[1] || 'key'}]`, ...blockParams.slice(1)]);
var replacements = (_replacements = {}, _replacements[blockParams[0]] = _condition + "[" + (blockParams[1] || 'key') + "]", _replacements);
childContext = _this.context.createChildContext(blockParams, replacements); // {{#each foo as |k, v|} => has 2 variable in the same context, k and v
if (blockParams.length === 2) {
childContext.getCurrentScope().iterateAsObject();
}
} else {
// {{#each foo}}
childContext = _this.context.createChildContext([_condition.split('.').pop() + "_i"]);
}
var _t2 = new HtlTranspiler(null, childContext, _this.depth + 1);
_t2.parseProgram(statement.program);
var childScope = childContext.getCurrentScope(); // Rules:
// - default = array
// - when using 2 block params = object
// - when using @key = object
if (childScope.iterationType === _Context.IterationType.ARRAY) {
// Array iteration
_this.buffer.push("<sly data-sly-list." + childScope.value + "=\"${ " + _condition + " }\">");
} else {
// Object iteration
var key = 'key'; // let value = 'value';
if (childScope.value) {
// value = childScope.value;
key = childScope.key || 'key';
} // TODO 'value' cannot be used, you have to do condition[key], so that has to be renamed
_this.buffer.push("<sly data-sly-list." + key + "=\"${ " + _condition + " }\">"); // this.buffer.push(`{% for ${key}, ${value} in ${condition}.items %}`);
}
_this.buffer.push(_t2.toString());
_this.buffer.push("</sly>");
break;
}
default:
{
// tslint:disable-next-line:no-console
console.log('Unsupported block ', type);
_this.buffer.push("{{# " + type + " }}");
var _t3 = new HtlTranspiler(null, _this.context, _this.depth);
_t3.parseProgram(statement.program);
_this.buffer.push(_t3.toString());
_this.buffer.push("{{/" + type + "}}");
}
}
break;
case 'PartialStatement':
{
// console.log('\nPartialStatement\n');
var name;
var templateName;
if (statement.name.type === 'StringLiteral') {
var expression = statement.name;
name = "" + expression.value.replace('.hbs', '.html');
var varName = 'todo';
templateName = "lib." + varName;
} else if (statement.name.type === 'SubExpression') {
var _expression = statement.name; // TODO: add generic helper support, which includes lookup
if (_expression.path.original === 'lookup') {
// TODO: add scope support, now always assumes '.' (current scope)
var _varName = _expression.params[1].value;
name = "${ " + _varName + " }";
templateName = "lib[" + _varName + "]";
}
} else {
var nameParts = statement.name.parts.filter(function (p) {
return p !== 'hbs';
});
templateName = "lib." + nameParts[nameParts.length - 1];
name = nameParts.join('/') + ".html";
}
if (statement.params.length) {// TODO: AEM doesn't support pushing/replacing the context, only adding/replacing additional variables
}
var params = '';
if (statement.hash) {
params = ' @ ' + statement.hash.pairs.map(function (pair) {
var key = pair.key + "=";
if (pair.value.type === 'PathExpression') {
return "" + key + _this.context.getScopedVariable(pair.value);
}
if (pair.value.type === 'StringLiteral') {
return key + "'" + pair.value.value + "'";
}
if (pair.value.type === 'NumberLiteral') {
return "" + key + pair.value.value;
}
if (pair.value.type === 'BooleanLiteral') {
return "" + key + pair.value.value;
}
return '';
}).filter(function (_) {
return _;
}).join(', ');
}
_this.buffer.push("<sly data-sly-use.lib=\"" + name + "\" data-sly-call=\"${ " + templateName + params + " }\" />");
break;
}
default:
{
// tslint:disable-next-line:no-console
console.log('Unsupported statements ', statement.type);
}
}
});
return this;
};
/**
* Checks if this sub-program has only a condition statement.
* If that's the case, and used in a inverse (else) section, the parent should render `else if`
* instead of an `if` nested in an `else`.
* @param {hbs.AST.Program} program
* @return {boolean}
*/
HtlTranspiler.isOnlyCondition = function isOnlyCondition(program) {
return program.body.length === 1 && program.body[0].type === 'BlockStatement' && program.body[0].path.original === 'if';
};
_proto.toString = function toString() {
return this.buffer.reduce(function (str, op) {
return str + op;
}, '');
};
return HtlTranspiler;
}();
exports.HtlTranspiler = HtlTranspiler;
;