summaryrefslogtreecommitdiffstats
path: root/lib/models/templateBlock.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/models/templateBlock.js')
-rw-r--r--lib/models/templateBlock.js264
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;