summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/backbone/page.js2
-rw-r--r--lib/config/index.js12
-rw-r--r--lib/generators/json.js2
-rw-r--r--lib/output.js19
-rw-r--r--lib/plugins/index.js61
-rw-r--r--lib/plugins/manager.js61
-rw-r--r--lib/template/blocks.js (renamed from lib/templating/blocks.js)0
-rw-r--r--lib/template/index.js333
-rw-r--r--lib/template/loader.js (renamed from lib/templating/loader.js)0
-rw-r--r--lib/templating/index.js27
-rw-r--r--lib/utils/error.js13
11 files changed, 433 insertions, 97 deletions
diff --git a/lib/backbone/page.js b/lib/backbone/page.js
index eb3cd61..13b9c59 100644
--- a/lib/backbone/page.js
+++ b/lib/backbone/page.js
@@ -27,7 +27,7 @@ Page.prototype.read = function() {
// Parse the page and return its content
Page.prototype.parse = function() {
-
+ return this.read();
};
diff --git a/lib/config/index.js b/lib/config/index.js
index 7734392..563f03b 100644
--- a/lib/config/index.js
+++ b/lib/config/index.js
@@ -36,14 +36,15 @@ Config.prototype.load = function() {
that.log.debug.ln('try loading configuration from', filename);
return that.fs.loadAsObject(that.book.resolve(filename))
+ .then(function(_config) {
+ that.filename = filename;
+ return that.replace(_config);
+ })
.fail(function(err) {
if (err.code != 'MODULE_NOT_FOUND') throw(err);
else return Promise(false);
});
})
- .then(function(_config) {
- return that.replace(_config);
- })
.then(function() {
if (!that.book.isLanguageBook()) {
if (!gitbook.satisfies(that.options.gitbook)) {
@@ -124,4 +125,9 @@ Config.prototype.set = function(key, value) {
return _.set(this.options, key, value);
};
+// Return a dump of the configuration
+Config.prototype.dump = function() {
+ return _.cloneDeep(this.options);
+};
+
module.exports = Config;
diff --git a/lib/generators/json.js b/lib/generators/json.js
index b76a62e..0cfaeb7 100644
--- a/lib/generators/json.js
+++ b/lib/generators/json.js
@@ -17,7 +17,7 @@ JSONGenerator.prototype.writePage = function(page) {
.then(function() {
var json = {};
- return this.output.writeFile(
+ return that.output.writeFile(
page.withExtension('.json'),
JSON.stringify(json, null, 4)
);
diff --git a/lib/output.js b/lib/output.js
index 89233da..01610c6 100644
--- a/lib/output.js
+++ b/lib/output.js
@@ -11,20 +11,22 @@ function Output(book, type) {
if (!generators[type]) throw new Error('Generator not found"' + type + '"');
this.book = book;
+ this.log = this.book.log;
+
this.type = type;
this.plugins = new PluginsManager(book);
this.generator = new generators[type](this, type);
// Files to ignore in output
this.ignore = Ignore();
- this.ignore.addPattern([
+ this.ignore.addPattern(_.compact([
'.gitignore',
'.ignore',
'.bookignore',
// The configuration file should not be copied in the output
this.book.config.filename
- ]);
+ ]));
}
@@ -36,16 +38,25 @@ Output.prototype.resolve = function(filename) {
// Write a file/buffer to the output folder
Output.prototype.writeFile = function(filename, buf) {
filename = this.resolve(filename);
- return Promise.nfcall(fs.writeFileSync, filename, buf);
+ return Promise.nfcall(fs.writeFile, filename, buf);
};
// Start the generation, for a parsed book
Output.prototype.generate = function() {
var that = this;
- var isMultilingual = this.isMultilingual();
+ var isMultilingual = this.book.isMultilingual();
return Promise()
+ // Load all plugins
+ .then(function() {
+ that.log.info.ln('Loading and preparing plugins');
+
+ var plugins = _.pluck(that.book.config.get('plugins'), 'name');
+
+ return that.plugins.load(plugins);
+ })
+
// Initialize the generation
.then(function() {
return that.generator.prepare();
diff --git a/lib/plugins/index.js b/lib/plugins/index.js
index e69de29..33a2047 100644
--- a/lib/plugins/index.js
+++ b/lib/plugins/index.js
@@ -0,0 +1,61 @@
+var _ = require('lodash');
+
+var Promise = require('../utils/promise');
+var BookPlugin = require('./plugin');
+
+
+/*
+PluginsManager is an interface to work with multiple plugins at once:
+- Extract assets from plugins
+- Call hooks for all plugins, etc
+*/
+
+function PluginsManager(book) {
+ this.book = book;
+ this.plugins = [];
+}
+
+// Returns a plugin by its name
+PluginsManager.prototype.get = function(name) {
+ return _.find(this.plugins, {
+ id: name
+ });
+};
+
+// Load a plugin, or a list of plugins
+PluginsManager.prototype.load = function(name) {
+ var that = this;
+
+ if (_.isArray(name)) {
+ return Promise.serie(name, function(_name) {
+ return that.load(_name);
+ });
+ }
+
+ return Promise()
+
+ // Initiate and load the plugin
+ .then(function() {
+ var plugin;
+
+ if (!_.isString(name)) plugin = name;
+ else plugin = new BookPlugin(that.book, name);
+
+ if (that.get(plugin.id)) {
+ throw new Error('Plugin "'+plugin.id+'" is already loaded');
+ }
+
+
+ if (plugin.isLoaded()) return plugin;
+ else return plugin.load()
+ .thenResolve(plugin);
+ })
+
+ .then(function(plugin) {
+ that.plugins.push(plugin);
+ });
+};
+
+
+
+module.exports = PluginsManager;
diff --git a/lib/plugins/manager.js b/lib/plugins/manager.js
deleted file mode 100644
index b6549d6..0000000
--- a/lib/plugins/manager.js
+++ /dev/null
@@ -1,61 +0,0 @@
-var _ = require('lodash');
-
-var Promise = require('../utils/promise');
-var BookPlugin = require('./plugin');
-
-
-/*
-PluginsManager is an interface to work with multiple plugins at once:
-- Extract assets from plugins
-- Call hooks for all plugins, etc
-*/
-
-function PluginsManager(book) {
- this.book = book;
- this.plugins = [];
-}
-
-// Returns a plugin by its name
-PluginsManager.prototype.get = function(name) {
- return _.find(this.plugins, {
- id: name
- });
-};
-
-// Load a plugin, or a list of plugins
-PluginsManager.prototype.load = function(name) {
- var that = this;
-
- if (!_.isArray(name)) {
- return Promise.serie(name, function(_name) {
- return that.load(_name);
- });
- }
-
- return Promise()
-
- // Initiate and load the plugin
- .then(function() {
- var plugin;
-
- if (!_.isString(name)) plugin = name;
- else plugin = new BookPlugin(that.book, name);
-
- if (that.get(plugin.id)) {
- throw new Error('Plugin "'+plugin.id+'" is already loaded');
- }
-
-
- if (plugin.isLoaded()) return plugin;
- else return plugin.load()
- .thenResolve(plugin);
- })
-
- .then(function(plugin) {
- that.plugins.push(plugin);
- });
-};
-
-
-
-module.exports = PluginsManager;
diff --git a/lib/templating/blocks.js b/lib/template/blocks.js
index 92097a7..92097a7 100644
--- a/lib/templating/blocks.js
+++ b/lib/template/blocks.js
diff --git a/lib/template/index.js b/lib/template/index.js
new file mode 100644
index 0000000..128e171
--- /dev/null
+++ b/lib/template/index.js
@@ -0,0 +1,333 @@
+var _ = require('lodash');
+var path = require('path');
+var nunjucks = require('nunjucks');
+var parsers = require('gitbook-parsers');
+
+var Promise = require('../utils/promise');
+var error = require('../utils/error');
+var gitbook = require('../gitbook');
+var defaultBlocks = require('./blocks');
+
+// Return extension name for a specific block
+function blockExtName(name) {
+ return 'Block'+name+'Extension';
+}
+
+// Normalize the result of block process function
+function normBlockResult(blk) {
+ if (_.isString(blk)) blk = { body: blk };
+ return blk;
+}
+
+function TemplateEngine(book) {
+ this.book = book;
+ this.log = book.log;
+
+ this.env = new nunjucks.Environment(
+ this.loader,
+ {
+ // Escaping is done after by the asciidoc/markdown parser
+ autoescape: false,
+
+ // Syntax
+ 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);
+}
+
+// Add a new custom filter
+TemplateEngine.prototype.addFilter = function(filterName, func) {
+ try {
+ this.env.getFilter(filterName);
+ this.log.error.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);
+
+ Promise()
+ .then(function() {
+ return func.apply(ctx, args.slice(0, -1));
+ })
+ .nodeify(callback);
+ }), true);
+ return true;
+};
+
+// Add multiple filters at once
+TemplateEngine.prototype.addFilters = function(filters) {
+ _.each(filters, function(filter, name) {
+ this.addFilter(name, filter);
+ }, this);
+};
+
+// Return true if a block is defined
+TemplateEngine.prototype.hasBlock = function(name) {
+ return this.env.hasExtension(blockExtName(name));
+};
+
+// Remove/Disable a block
+TemplateEngine.prototype.removeBlock = function(name) {
+ if (!this.hasBlock(name)) return;
+
+ // Remove nunjucks extension
+ this.env.removeExtension(blockExtName(name));
+
+ // Cleanup shortcuts
+ this.shortcuts = _.reject(this.shortcuts, {
+ block: name
+ });
+};
+
+// Add a block
+// Using the extensions of nunjucks: https://mozilla.github.io/nunjucks/api.html#addextension
+TemplateEngine.prototype.addBlock = function(name, block) {
+ var that = this, Ext, extName;
+
+ // Block can be a simple function
+ if (_.isFunction(block)) block = { process: block };
+
+ block = _.defaults(block || {}, {
+ shortcuts: [],
+ end: 'end'+name,
+ process: _.identity,
+ blocks: []
+ });
+
+ extName = 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]()
+ };
+ });
+
+ Promise()
+ .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 any
+ 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 at once
+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 (Promise.isPromise(r)) return r.then(normBlockResult);
+ else return normBlockResult(r);
+};
+
+
+// Render a string
+TemplateEngine.prototype.renderString = function(content, context, options) {
+ options = _.defaults(options || {}, {
+ path: null,
+ type: null
+ });
+
+ // Setup context for the template
+ context = _.extend({}, context, {
+ // Variables from book.json
+ book: this.book.config.get('variables'),
+
+ // Complete book.json
+ config: this.book.config.dump(),
+
+ // infos about gitbook
+ gitbook: {
+ version: gitbook.version,
+ generator: this.book.config.get('generator')
+ }
+ });
+
+ // Setup path and type
+ 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 Promise.nfcall(this.env.renderString.bind(this.env), content, context, options)
+ .fail(function(err) {
+ throw error.enforce(err);
+ });
+};
+
+
+module.exports = TemplateEngine;
diff --git a/lib/templating/loader.js b/lib/template/loader.js
index e69de29..e69de29 100644
--- a/lib/templating/loader.js
+++ b/lib/template/loader.js
diff --git a/lib/templating/index.js b/lib/templating/index.js
deleted file mode 100644
index 3a0fc16..0000000
--- a/lib/templating/index.js
+++ /dev/null
@@ -1,27 +0,0 @@
-var nunjucks = require('nunjucks');
-
-function TemplatingEngine(book) {
- this.book = book;
- this.log = book.log;
-
- this.nunjucks = new nunjucks.Environment(
- this.loader,
- {
- // Escaping is done after by the asciidoc/markdown parser
- autoescape: false,
-
- // Syntax
- tags: {
- blockStart: '{%',
- blockEnd: '%}',
- variableStart: '{{',
- variableEnd: '}}',
- commentStart: '{###',
- commentEnd: '###}'
- }
- }
- );
-}
-
-
-module.exports = TemplatingEngine;
diff --git a/lib/utils/error.js b/lib/utils/error.js
new file mode 100644
index 0000000..66e20db
--- /dev/null
+++ b/lib/utils/error.js
@@ -0,0 +1,13 @@
+var _ = require('lodash');
+
+// Enforce as an Error object, and cleanup message
+function enforce(err) {
+ if (_.isString(err)) err = new Error(err);
+ err.message = err.message.replace(/^Error: /, '');
+
+ return err;
+}
+
+module.exports = {
+ enforce: enforce
+};