diff options
author | Samy Pesse <samypesse@gmail.com> | 2016-04-30 20:15:08 +0200 |
---|---|---|
committer | Samy Pesse <samypesse@gmail.com> | 2016-04-30 20:15:08 +0200 |
commit | 36b49c66c6b75515bc84dd678fd52121a313e8d2 (patch) | |
tree | bc7e0f703d4557869943ec7f9495cac7a5027d4f /lib/models/templateBlock.js | |
parent | 87db7cf1d412fa6fbd18e9a7e4f4755f2c0c5547 (diff) | |
parent | 80b8e340dadc54377ff40500f86b1de631395806 (diff) | |
download | gitbook-36b49c66c6b75515bc84dd678fd52121a313e8d2.zip gitbook-36b49c66c6b75515bc84dd678fd52121a313e8d2.tar.gz gitbook-36b49c66c6b75515bc84dd678fd52121a313e8d2.tar.bz2 |
Merge branch 'fixes'
Diffstat (limited to 'lib/models/templateBlock.js')
-rw-r--r-- | lib/models/templateBlock.js | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/lib/models/templateBlock.js b/lib/models/templateBlock.js new file mode 100644 index 0000000..4e47da7 --- /dev/null +++ b/lib/models/templateBlock.js @@ -0,0 +1,310 @@ +var is = require('is'); +var extend = require('extend'); +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 of block, also the start tag + name: String(), + + // End tag, default to "end<name>" + end: String(), + + // Function to process the block content + process: Function(), + + // List of String, for inner block tags + blocks: Immutable.List(), + + // List of shortcuts to replace with this block + shortcuts: Immutable.List(), + + // Function to execute in post processing + post: null, + + parse: true +}, 'TemplateBlock'); + +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(mainContext) { + var that = this; + var name = this.getName(); + var endTag = this.getEndTag(); + var blocks = this.getBlocks().toJS(); + + function Ext() { + 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() { + var ctx = extend({ + ctx: context + }, mainContext || {}); + + return that.applyBlock(mainBlock, ctx); + }) + .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 }; + } + result.name = this.getName(); + + 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; +}; + +/** + Get a block results indexed for a specific key + + @param {String} key + @return {Object|undefined} +*/ +TemplateBlock.getBlockResultByKey = function(key) { + return blockBodies[key]; +}; + +/** + Create a template block from a function or an object + + @param {String} blockName + @param {Object} block + @return {TemplateBlock} +*/ +TemplateBlock.create = function(blockName, block) { + if (is.fn(block)) { + block = new Immutable.Map({ + process: block + }); + } + + block = block.set('name', blockName); + return new TemplateBlock(block); +}; + +/** + 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; |