summaryrefslogtreecommitdiffstats
path: root/lib/models/templateBlock.js
diff options
context:
space:
mode:
authorSamy Pesse <samypesse@gmail.com>2016-04-30 20:15:08 +0200
committerSamy Pesse <samypesse@gmail.com>2016-04-30 20:15:08 +0200
commit36b49c66c6b75515bc84dd678fd52121a313e8d2 (patch)
treebc7e0f703d4557869943ec7f9495cac7a5027d4f /lib/models/templateBlock.js
parent87db7cf1d412fa6fbd18e9a7e4f4755f2c0c5547 (diff)
parent80b8e340dadc54377ff40500f86b1de631395806 (diff)
downloadgitbook-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.js310
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;