UNPKG

muban-convert-hbs

Version:

Convert muban hbs templates to htl, django, twig and others.

367 lines (280 loc) 13.2 kB
"use strict"; 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;