summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorSamy Pessé <samypesse@gmail.com>2015-09-14 18:26:26 +0200
committerSamy Pessé <samypesse@gmail.com>2015-09-14 18:26:26 +0200
commitafd5465a6129e96bce62dab26a4ee41e7af7365c (patch)
tree6e500ed55e65a6f6e5991913f11850d17c8ed721 /lib
parent3bf592f870eb24d1b4753fa538bad2cbfaa98a24 (diff)
parentfe604733debe42a287f3c44705e16d9d0ec85908 (diff)
downloadgitbook-afd5465a6129e96bce62dab26a4ee41e7af7365c.zip
gitbook-afd5465a6129e96bce62dab26a4ee41e7af7365c.tar.gz
gitbook-afd5465a6129e96bce62dab26a4ee41e7af7365c.tar.bz2
Merge pull request #928 from GitbookIO/feature/highlight_block
Code highlighting extended by plugins
Diffstat (limited to 'lib')
-rw-r--r--lib/blocks.js11
-rw-r--r--lib/configuration.js2
-rw-r--r--lib/pluginslist.js8
-rw-r--r--lib/template.js113
-rw-r--r--lib/utils/code.js36
-rw-r--r--lib/utils/page.js18
6 files changed, 116 insertions, 72 deletions
diff --git a/lib/blocks.js b/lib/blocks.js
new file mode 100644
index 0000000..92097a7
--- /dev/null
+++ b/lib/blocks.js
@@ -0,0 +1,11 @@
+var _ = require('lodash');
+
+module.exports = {
+ // Return non-parsed html
+ // since blocks are by default non-parsable, a simple identity method works fine
+ html: _.identity,
+
+ // Highlight a code block
+ // This block can be extent by plugins
+ code: _.identity
+};
diff --git a/lib/configuration.js b/lib/configuration.js
index 3dee9f5..3a73c10 100644
--- a/lib/configuration.js
+++ b/lib/configuration.js
@@ -8,7 +8,7 @@ var fs = require("./utils/fs");
var i18n = require("./utils/i18n");
// Default plugins added to each books
-var defaultsPlugins = [];
+var defaultsPlugins = ['highlight'];
// Normalize a list of plugins to use
function normalizePluginsList(plugins) {
diff --git a/lib/pluginslist.js b/lib/pluginslist.js
index 227a013..37dbd41 100644
--- a/lib/pluginslist.js
+++ b/lib/pluginslist.js
@@ -79,14 +79,10 @@ PluginsList.prototype.load = function(plugin, options) {
}
// Extract filters
- _.each(plugin.getFilters(), function(filterFunc, filterName) {
- that.book.template.addFilter(filterName, filterFunc);
- });
+ that.book.template.addFilters(plugin.getFilters());
// Extract blocks
- _.each(plugin.getBlocks(), function(block, blockName) {
- that.book.template.addBlock(blockName, block);
- });
+ that.book.template.addBlocks(plugin.getBlocks());
return _.reduce(_.keys(that.namespaces), function(prev, namespaceName) {
return prev.then(function() {
diff --git a/lib/template.js b/lib/template.js
index 8014405..cc247c0 100644
--- a/lib/template.js
+++ b/lib/template.js
@@ -8,7 +8,13 @@ var git = require("./utils/git");
var fs = require("./utils/fs");
var batch = require("./utils/batch");
var pkg = require("../package.json");
+var defaultBlocks = require("./blocks");
+// Normalize result from a block
+function normBlockResult(blk) {
+ if (_.isString(blk)) blk = { body: blk };
+ return blk;
+}
// The loader should handle relative and git url
var BookLoader = nunjucks.Loader.extend({
@@ -71,22 +77,21 @@ var TemplateEngine = function(book) {
// List of tags shortcuts
this.shortcuts = [];
- // Map of blocks
+ // Map of blocks bodies (that requires post-processing)
+ this.blockBodies = {};
+
+ // Map of added blocks
this.blocks = {};
// Bind methods
_.bindAll(this);
- // Default block "html" that return html not parsed
- this.addBlock("html", {
- process: _.identity
- });
+ // Add default blocks
+ this.addBlocks(defaultBlocks);
};
-// Process a block in a context
+// Process the result of block in a context
TemplateEngine.prototype.processBlock = function(blk) {
- if (_.isString(blk)) blk = { body: blk };
-
blk = _.defaults(blk, {
parse: false,
post: undefined
@@ -96,7 +101,7 @@ TemplateEngine.prototype.processBlock = function(blk) {
var toAdd = (!blk.parse) || (blk.post != undefined);
// Add to global map
- if (toAdd) this.blocks[blk.id] = blk;
+ if (toAdd) this.blockBodies[blk.id] = blk;
//Parsable block, just return it
if (blk.parse) {
@@ -113,7 +118,7 @@ TemplateEngine.prototype.replaceBlocks = function(content) {
var that = this;
return content.replace(/\@\%\@([\s\S]+?)\@\%\@/g, function(match, key) {
- var blk = that.blocks[key];
+ var blk = that.blockBodies[key];
if (!blk) return match;
var body = blk.body;
@@ -160,9 +165,41 @@ TemplateEngine.prototype.addFilter = function(filterName, func) {
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;
+ var that = this, Ext, extName;
+
+ if (_.isFunction(block)) block = { process: block };
block = _.defaults(block || {}, {
shortcuts: [],
@@ -171,13 +208,17 @@ TemplateEngine.prototype.addBlock = function(name, block) {
blocks: []
});
- var extName = 'Block'+name+'Extension';
- if (this.env.getExtension(extName)) {
+ var extName = this.blockExtName(name);
+
+ if (this.hasBlock(name) && !defaultBlocks[name]) {
this.log.warn.ln("conflict in blocks, '"+name+"' is already defined");
- return false;
}
+ // Cleanup previous block
+ this.removeBlock(name);
+
this.log.debug.ln("add block '"+name+"'");
+ this.blocks[name] = block;
var Ext = function () {
this.tags = [name];
@@ -260,11 +301,9 @@ TemplateEngine.prototype.addBlock = function(name, block) {
};
});
- var func = that.bindContext(block.process);
-
Q()
.then(function() {
- return func.call(context, {
+ return that.applyBlock(name, {
body: body(),
args: args,
kwargs: kwargs,
@@ -278,7 +317,6 @@ TemplateEngine.prototype.addBlock = function(name, block) {
};
};
-
// Add the Extension
this.env.addExtension(extName, new Ext());
@@ -287,6 +325,7 @@ TemplateEngine.prototype.addBlock = function(name, block) {
_.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,
@@ -298,6 +337,40 @@ TemplateEngine.prototype.addBlock = function(name, block) {
}, 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) {
+ var func, block, func, 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(context, 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;
@@ -383,7 +456,7 @@ TemplateEngine.prototype.postProcess = function(content) {
return Q(content)
.then(that.replaceBlocks)
.then(function(_content) {
- return batch.execEach(that.blocks, {
+ return batch.execEach(that.blockBodies, {
max: 20,
fn: function(blk, blkId) {
return Q()
@@ -392,7 +465,7 @@ TemplateEngine.prototype.postProcess = function(content) {
return blk.post();
})
.then(function() {
- delete that.blocks[blkId];
+ delete that.blockBodies[blkId];
});
}
})
diff --git a/lib/utils/code.js b/lib/utils/code.js
deleted file mode 100644
index 0d98869..0000000
--- a/lib/utils/code.js
+++ /dev/null
@@ -1,36 +0,0 @@
-var hljs = require('highlight.js');
-
-var MAP = {
- 'py': 'python',
- 'js': 'javascript',
- 'json': 'javascript',
- 'rb': 'ruby',
- 'csharp': 'cs',
-};
-
-function normalize(lang) {
- if(!lang) { return null; }
-
- var lower = lang.toLowerCase();
- return MAP[lower] || lower;
-}
-
-function highlight(lang, code) {
- if(!lang) return code;
-
- // Normalize lang
- lang = normalize(lang);
-
- try {
- return hljs.highlight(lang, code).value;
- } catch(e) { }
-
- return code;
-}
-
-// Exports
-module.exports = {
- highlight: highlight,
- normalize: normalize,
- MAP: MAP
-};
diff --git a/lib/utils/page.js b/lib/utils/page.js
index e2a7cd5..5b4eca8 100644
--- a/lib/utils/page.js
+++ b/lib/utils/page.js
@@ -11,7 +11,6 @@ var links = require('./links');
var imgUtils = require('./images');
var fs = require('./fs');
var batch = require('./batch');
-var code = require('./code');
var parsableExtensions = require('gitbook-parsers').extensions;
@@ -224,7 +223,7 @@ function normalizeHtml(src, options) {
// Highlight code blocks
$("code").each(function() {
- // Extract language
+ // Normalize language
var lang = _.chain(
($(this).attr("class") || "").split(" ")
)
@@ -241,14 +240,15 @@ function normalizeHtml(src, options) {
.first()
.value();
- if (lang) {
- var html = code.highlight(
- lang,
- $(this).text()
- );
+ var source = $(this).text();
+ var html = options.book.template.applyBlock('code', {
+ body: source,
+ kwargs: {
+ language: lang
+ }
+ }).body;
- $(this).html(html);
- }
+ $(this).html(html);
});
// Replace glossary terms