diff options
Diffstat (limited to 'lib/models/templateBlock.js')
-rw-r--r-- | lib/models/templateBlock.js | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/lib/models/templateBlock.js b/lib/models/templateBlock.js new file mode 100644 index 0000000..ec7dc7c --- /dev/null +++ b/lib/models/templateBlock.js @@ -0,0 +1,264 @@ +var is = require('is'); +var Immutable = require('immutable'); + +var Promise = require('../utils/promise'); +var genKey = require('../utils/genKey'); + +var NODE_ENDARGS = '%%endargs%%'; + +var blockBodies = {}; + +var TemplateBlock = Immutable.Record({ + name: String(), + end: String(), + process: Function(), + blocks: Immutable.List(), + shortcuts: Immutable.List(), + post: null, + parse: true +}); + +TemplateBlock.prototype.getName = function() { + return this.get('name'); +}; + +TemplateBlock.prototype.getPost = function() { + return this.get('post'); +}; + +TemplateBlock.prototype.getParse = function() { + return this.get('parse'); +}; + +TemplateBlock.prototype.getEndTag = function() { + return this.get('end') || ('end' + this.getName()); +}; + +TemplateBlock.prototype.getProcess = function() { + return this.get('process'); +}; + +TemplateBlock.prototype.getBlocks = function() { + return this.get('blocks'); +}; + +TemplateBlock.prototype.getShortcuts = function() { + return this.get('shortcuts'); +}; + +/** + Return name for the nunjucks extension + + @return {String} +*/ +TemplateBlock.prototype.getExtensionName = function() { + return 'Block' + this.getName() + 'Extension'; +}; + +/** + Return a nunjucks extension to represents this block + + @return {Nunjucks.Extension} +*/ +TemplateBlock.prototype.toNunjucksExt = function() { + var that = this; + var name = this.getName(); + var endTag = this.getEndTag(); + var blocks = this.getBlocks(); + + var Ext = function () { + this.tags = [name]; + + this.parse = function(parser, nodes) { + var lastBlockName = null; + var lastBlockArgs = null; + var allBlocks = blocks.concat([endTag]); + + // Parse first block + var tok = parser.nextToken(); + lastBlockArgs = parser.parseSignature(null, true); + parser.advanceAfterBlockEnd(tok.value); + + var args = new nodes.NodeList(); + var bodies = []; + var blockNamesNode = new nodes.Array(tok.lineno, tok.colno); + var blockArgCounts = new nodes.Array(tok.lineno, tok.colno); + + // Parse while we found "end<block>" + do { + // Read body + var currentBody = parser.parseUntilBlocks.apply(parser, allBlocks); + + // Handle body with previous block name and args + blockNamesNode.addChild(new nodes.Literal(args.lineno, args.colno, lastBlockName)); + blockArgCounts.addChild(new nodes.Literal(args.lineno, args.colno, lastBlockArgs.children.length)); + bodies.push(currentBody); + + // Append arguments of this block as arguments of the run function + lastBlockArgs.children.forEach(function(child) { + args.addChild(child); + }); + + // Read new block + lastBlockName = parser.nextToken().value; + + // Parse signature and move to the end of the block + if (lastBlockName != endTag) { + lastBlockArgs = parser.parseSignature(null, true); + } + + parser.advanceAfterBlockEnd(lastBlockName); + } while (lastBlockName != endTag); + + args.addChild(blockNamesNode); + args.addChild(blockArgCounts); + args.addChild(new nodes.Literal(args.lineno, args.colno, NODE_ENDARGS)); + + return new nodes.CallExtensionAsync(this, 'run', args, bodies); + }; + + this.run = function(context) { + var fnArgs = Array.prototype.slice.call(arguments, 1); + + var args; + var blocks = []; + var bodies = []; + var blockNames; + var blockArgCounts; + var callback; + + // Extract callback + callback = fnArgs.pop(); + + // Detect end of arguments + var endArgIndex = fnArgs.indexOf(NODE_ENDARGS); + + // Extract arguments and bodies + args = fnArgs.slice(0, endArgIndex); + bodies = fnArgs.slice(endArgIndex + 1); + + // Extract block counts + blockArgCounts = args.pop(); + blockNames = args.pop(); + + // Recreate list of blocks + blockNames.forEach(function(name, i) { + var countArgs = blockArgCounts[i]; + var blockBody = bodies.shift(); + + var blockArgs = countArgs > 0? args.slice(0, countArgs) : []; + args = args.slice(countArgs); + var blockKwargs = extractKwargs(blockArgs); + + blocks.push({ + name: name, + body: blockBody(), + args: blockArgs, + kwargs: blockKwargs + }); + }); + + var mainBlock = blocks.shift(); + mainBlock.blocks = blocks; + + Promise() + .then(function() { + return that.applyBlock(mainBlock, context); + }) + .then(function(result) { + return that.blockResultToHtml(result); + }) + .nodeify(callback); + }; + }; + + return Ext; +}; + +/** + Apply a block to a content + @param {Object} inner + @param {Object} context + @return {Promise<String>|String} +*/ +TemplateBlock.prototype.applyBlock = function(inner, context) { + var processFn = this.getProcess(); + + inner = inner || {}; + inner.args = inner.args || []; + inner.kwargs = inner.kwargs || {}; + inner.blocks = inner.blocks || []; + + var r = processFn.call(context, inner); + + if (Promise.isPromiseAlike(r)) { + return r.then(this.handleBlockResult); + } else { + return this.handleBlockResult(r); + } +}; + +/** + Handle result from a block process function + + @param {Object} result + @return {Object} +*/ +TemplateBlock.prototype.handleBlockResult = function(result) { + if (is.string(result)) { + result = { body: result }; + } + + return result; +}; + +/** + Convert a block result to HTML + + @param {Object} result + @return {String} +*/ +TemplateBlock.prototype.blockResultToHtml = function(result) { + var parse = this.getParse(); + var indexedKey; + var toIndex = (!parse) || (this.getPost() !== undefined); + + if (toIndex) { + indexedKey = TemplateBlock.indexBlockResult(result); + } + + // Parsable block, just return it + if (parse) { + return result.body; + } + + // Return it as a position marker + return '{{-%' + indexedKey + '%-}}'; + +}; + +/** + Index a block result, and return the indexed key + + @param {Object} blk + @return {String} +*/ +TemplateBlock.indexBlockResult = function(blk) { + var key = genKey(); + blockBodies[key] = blk; + + return key; +}; + +/** + Extract kwargs from an arguments array + + @param {Array} args + @return {Object} +*/ +function extractKwargs(args) { + var last = args[args.length - 1]; + return (is.object(last) && last.__keywords)? args.pop() : {}; +} + +module.exports = TemplateBlock; |