summaryrefslogtreecommitdiffstats
path: root/lib/template.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/template.js')
-rw-r--r--lib/template.js466
1 files changed, 0 insertions, 466 deletions
diff --git a/lib/template.js b/lib/template.js
deleted file mode 100644
index dac1201..0000000
--- a/lib/template.js
+++ /dev/null
@@ -1,466 +0,0 @@
-var _ = require('lodash');
-var Q = require('q');
-var path = require('path');
-var nunjucks = require('nunjucks');
-var parsers = require('gitbook-parsers');
-var escapeStringRegexp = require('escape-string-regexp');
-
-var batch = require('./utils/batch');
-var pkg = require('../package.json');
-var defaultBlocks = require('./blocks');
-var BookLoader = require('./conrefs_loader');
-
-// Normalize result from a block
-function normBlockResult(blk) {
- if (_.isString(blk)) blk = { body: blk };
- return blk;
-}
-
-
-var TemplateEngine = function(book) {
- var that = this;
-
- this.book = book;
- this.log = this.book.log;
-
- // Template loader
- this.loader = new BookLoader(book, {
- // Replace shortcuts in imported files
- interpolate: function(filepath, source) {
- var parser = parsers.get(path.extname(filepath));
- var type = parser? parser.name : null;
-
- return that.applyShortcuts(type, source);
- }
- });
-
- // Nunjucks env
- this.env = new nunjucks.Environment(
- this.loader,
- {
- // Escaping is done after by the markdown parser
- autoescape: false,
-
- // Tags
- tags: {
- blockStart: '{%',
- blockEnd: '%}',
- variableStart: '{{',
- variableEnd: '}}',
- commentStart: '{###',
- commentEnd: '###}'
- }
- }
- );
-
- // List of tags shortcuts
- this.shortcuts = [];
-
- // Map of blocks bodies (that requires post-processing)
- this.blockBodies = {};
-
- // Map of added blocks
- this.blocks = {};
-
- // Bind methods
- _.bindAll(this);
-
- // Add default blocks
- this.addBlocks(defaultBlocks);
-};
-
-// Process the result of block in a context
-TemplateEngine.prototype.processBlock = function(blk) {
- blk = _.defaults(blk, {
- parse: false,
- post: undefined
- });
- blk.id = _.uniqueId('blk');
-
- var toAdd = (!blk.parse) || (blk.post !== undefined);
-
- // Add to global map
- if (toAdd) this.blockBodies[blk.id] = blk;
-
- // Parsable block, just return it
- if (blk.parse) {
- return blk.body;
- }
-
- // Return it as a position marker
- return '@%@'+blk.id+'@%@';
-};
-
-// Replace position markers of blocks by body after processing
-// This is done to avoid that markdown/asciidoc processer parse the block content
-TemplateEngine.prototype.replaceBlocks = function(content) {
- var that = this;
-
- return content.replace(/\@\%\@([\s\S]+?)\@\%\@/g, function(match, key) {
- var blk = that.blockBodies[key];
- if (!blk) return match;
-
- var body = blk.body;
-
- return body;
- });
-};
-
-// Bind a function to a context
-TemplateEngine.prototype.bindContext = function(func) {
- var that = this;
-
- return function() {
- var ctx = {
- ctx: this.ctx,
- book: that.book,
- generator: that.book.options.generator
- };
-
- return func.apply(ctx, arguments);
- };
-};
-
-// Add filter
-TemplateEngine.prototype.addFilter = function(filterName, func) {
- try {
- this.env.getFilter(filterName);
- this.log.warn.ln('conflict in filters, \''+filterName+'\' is already set');
- return false;
- } catch(e) {
- // Filter doesn't exist
- }
-
- this.log.debug.ln('add filter \''+filterName+'\'');
- this.env.addFilter(filterName, this.bindContext(function() {
- var ctx = this;
- var args = Array.prototype.slice.apply(arguments);
- var callback = _.last(args);
-
- Q()
- .then(function() {
- return func.apply(ctx, args.slice(0, -1));
- })
- .nodeify(callback);
- }), true);
- return true;
-};
-
-// Add multiple filters
-TemplateEngine.prototype.addFilters = function(filters) {
- _.each(filters, function(filter, name) {
- this.addFilter(name, filter);
- }, this);
-};
-
-// Return nunjucks extension name of a block
-TemplateEngine.prototype.blockExtName = function(name) {
- return 'Block'+name+'Extension';
-};
-
-// Test if a block is defined
-TemplateEngine.prototype.hasBlock = function(name) {
- return this.env.hasExtension(this.blockExtName(name));
-};
-
-// Remove a block
-TemplateEngine.prototype.removeBlock = function(name) {
- if (!this.hasBlock(name)) return;
-
- // Remove nunjucks extension
- this.env.removeExtension(this.blockExtName(name));
-
- // Cleanup shortcuts
- this.shortcuts = _.reject(this.shortcuts, {
- block: name
- });
-};
-
-// Add a block
-TemplateEngine.prototype.addBlock = function(name, block) {
- var that = this, Ext, extName;
-
- if (_.isFunction(block)) block = { process: block };
-
- block = _.defaults(block || {}, {
- shortcuts: [],
- end: 'end'+name,
- process: _.identity,
- blocks: []
- });
-
- extName = this.blockExtName(name);
-
- if (this.hasBlock(name) && !defaultBlocks[name]) {
- this.log.warn.ln('conflict in blocks, \''+name+'\' is already defined');
- }
-
- // Cleanup previous block
- this.removeBlock(name);
-
- this.log.debug.ln('add block \''+name+'\'');
- this.blocks[name] = block;
-
- Ext = function () {
- this.tags = [name];
-
- this.parse = function(parser, nodes) {
- var body = null;
- var lastBlockName = null;
- var lastBlockArgs = null;
- var allBlocks = block.blocks.concat([block.end]);
- var subbodies = {};
-
- var tok = parser.nextToken();
- var args = parser.parseSignature(null, true);
- parser.advanceAfterBlockEnd(tok.value);
-
- while (1) {
- // Read body
- var currentBody = parser.parseUntilBlocks.apply(parser, allBlocks);
-
- // Handle body with previous block name and args
- if (lastBlockName) {
- subbodies[lastBlockName] = subbodies[lastBlockName] || [];
- subbodies[lastBlockName].push({
- body: currentBody,
- args: lastBlockArgs
- });
- } else {
- body = currentBody;
- }
-
- // Read new block
- lastBlockName = parser.peekToken().value;
- if (lastBlockName == block.end) {
- break;
- }
-
- // Parse signature and move to the end of the block
- lastBlockArgs = parser.parseSignature(null, true);
- parser.advanceAfterBlockEnd(lastBlockName);
- }
- parser.advanceAfterBlockEnd();
-
- var bodies = [body];
- _.each(block.blocks, function(blockName) {
- subbodies[blockName] = subbodies[blockName] || [];
- if (subbodies[blockName].length === 0) {
- subbodies[blockName].push({
- args: new nodes.NodeList(),
- body: new nodes.NodeList()
- });
- }
-
- bodies.push(subbodies[blockName][0].body);
- });
-
- return new nodes.CallExtensionAsync(this, 'run', args, bodies);
- };
-
- this.run = function(context) {
- var args = Array.prototype.slice.call(arguments, 1);
- var callback = args.pop();
-
- // Extract blocks
- var blocks = args
- .concat([])
- .slice(-block.blocks.length);
-
- // Eliminate blocks from list
- if (block.blocks.length > 0) args = args.slice(0, -block.blocks.length);
-
- // Extract main body and kwargs
- var body = args.pop();
- var kwargs = _.isObject(_.last(args))? args.pop() : {};
-
- // Extract blocks body
- var _blocks = _.map(block.blocks, function(blockName, i){
- return {
- name: blockName,
- body: blocks[i]()
- };
- });
-
- Q()
- .then(function() {
- return that.applyBlock(name, {
- body: body(),
- args: args,
- kwargs: kwargs,
- blocks: _blocks
- }, context);
- })
-
- // process the block returned
- .then(that.processBlock)
- .nodeify(callback);
- };
- };
-
- // Add the Extension
- this.env.addExtension(extName, new Ext());
-
- // Add shortcuts
- if (!_.isArray(block.shortcuts)) block.shortcuts = [block.shortcuts];
- _.each(block.shortcuts, function(shortcut) {
- this.log.debug.ln('add template shortcut from \''+shortcut.start+'\' to block \''+name+'\' for parsers ', shortcut.parsers);
- this.shortcuts.push({
- block: name,
- parsers: shortcut.parsers,
- start: shortcut.start,
- end: shortcut.end,
- tag: {
- start: name,
- end: block.end
- }
- });
- }, this);
-};
-
-// Add multiple blocks
-TemplateEngine.prototype.addBlocks = function(blocks) {
- _.each(blocks, function(block, name) {
- this.addBlock(name, block);
- }, this);
-};
-
-// Apply a block to some content
-// This method result depends on the type of block (async or sync)
-TemplateEngine.prototype.applyBlock = function(name, blk, ctx) {
- var func, block, r;
-
- block = this.blocks[name];
- if (!block) throw new Error('Block not found \''+name+'\'');
- if (_.isString(blk)) {
- blk = {
- body: blk
- };
- }
-
- blk = _.defaults(blk, {
- args: [],
- kwargs: {},
- blocks: []
- });
-
- // Bind and call block processor
- func = this.bindContext(block.process);
- r = func.call(ctx || {}, blk);
-
- if (Q.isPromise(r)) return r.then(normBlockResult);
- else return normBlockResult(r);
-};
-
-// Apply a shortcut to a string
-TemplateEngine.prototype._applyShortcut = function(parser, content, shortcut) {
- if (!_.contains(shortcut.parsers, parser)) return content;
- var regex = new RegExp(
- escapeStringRegexp(shortcut.start) + '([\\s\\S]*?[^\\$])' + escapeStringRegexp(shortcut.end),
- 'g'
- );
- return content.replace(regex, function(all, match) {
- return '{% '+shortcut.tag.start+' %}'+ match + '{% '+shortcut.tag.end+' %}';
- });
-};
-
-// Apply all shortcuts to some template string
-TemplateEngine.prototype.applyShortcuts = function(type, content) {
- return _.reduce(this.shortcuts, _.partial(this._applyShortcut.bind(this), type), content);
-};
-
-// Render a string from the book
-TemplateEngine.prototype.renderString = function(content, context, options) {
- context = _.extend({}, context, {
- // Variables from book.json
- book: this.book.options.variables,
-
- // Complete book.json
- config: this.book.options,
-
- // infos about gitbook
- gitbook: {
- version: pkg.version,
- generator: this.book.options.generator
- }
- });
- options = _.defaults(options || {}, {
- path: null,
- type: null
- });
- if (options.path) options.path = this.book.resolve(options.path);
- if (!options.type && options.path) {
- var parser = parsers.get(path.extname(options.path));
- options.type = parser? parser.name : null;
- }
-
- // Replace shortcuts
- content = this.applyShortcuts(options.type, content);
-
- return Q.nfcall(this.env.renderString.bind(this.env), content, context, options)
- .fail(function(err) {
- if (_.isString(err)) err = new Error(err);
- err.message = err.message.replace(/^Error: /, '');
-
- throw err;
- });
-};
-
-// Render a file from the book
-TemplateEngine.prototype.renderFile = function(filename) {
- var that = this;
-
- return that.book.readFile(filename)
- .then(function(content) {
- return that.renderString(content, {}, {
- path: filename
- });
- });
-};
-
-// Render a page from the book
-TemplateEngine.prototype.renderPage = function(page) {
- var that = this;
-
- return that.book.statFile(page.path)
- .then(function(stat) {
- var context = {
- // infos about the file
- file: {
- path: page.path,
- mtime: stat.mtime
- }
- };
-
- return that.renderString(page.content, context, {
- path: page.path,
- type: page.type
- });
- });
-};
-
-// Post process content
-TemplateEngine.prototype.postProcess = function(content) {
- var that = this;
-
- return Q(content)
- .then(that.replaceBlocks)
- .then(function(_content) {
- return batch.execEach(that.blockBodies, {
- max: 20,
- fn: function(blk, blkId) {
- return Q()
- .then(function() {
- if (!blk.post) return Q();
- return blk.post();
- })
- .then(function() {
- delete that.blockBodies[blkId];
- });
- }
- })
- .thenResolve(_content);
- });
-};
-
-module.exports = TemplateEngine;