summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSamy Pessé <samypesse@gmail.com>2016-02-15 14:07:26 +0100
committerSamy Pessé <samypesse@gmail.com>2016-02-15 14:07:26 +0100
commit354fe1274242bef87b739dbe43cdb634ea573662 (patch)
treed4a7c1f48878ba4eb6793535cc42a516a143bc0e
parent1b19c6660303cb6ce98ac0ad76580f66b52985f1 (diff)
downloadgitbook-354fe1274242bef87b739dbe43cdb634ea573662.zip
gitbook-354fe1274242bef87b739dbe43cdb634ea573662.tar.gz
gitbook-354fe1274242bef87b739dbe43cdb634ea573662.tar.bz2
Fix addBlock for template engine
Add test for code block
-rw-r--r--lib/book.js2
-rw-r--r--lib/output/ebook.js2
-rw-r--r--lib/output/website/index.js2
-rw-r--r--lib/page/index.js8
-rw-r--r--lib/template/index.js83
-rw-r--r--lib/utils/error.js18
-rw-r--r--lib/utils/logger.js (renamed from lib/logger.js)2
-rw-r--r--package.json5
-rw-r--r--test/page.js34
-rw-r--r--test/template.js24
10 files changed, 168 insertions, 12 deletions
diff --git a/lib/book.js b/lib/book.js
index cca1da5..173e9cc 100644
--- a/lib/book.js
+++ b/lib/book.js
@@ -3,7 +3,6 @@ var path = require('path');
var Ignore = require('ignore');
var parsers = require('gitbook-parsers');
-var Logger = require('./logger');
var Config = require('./config');
var Readme = require('./backbone/readme');
var Glossary = require('./backbone/glossary');
@@ -13,6 +12,7 @@ var Page = require('./page');
var pathUtil = require('./utils/path');
var error = require('./utils/error');
var Promise = require('./utils/promise');
+var Logger = require('./utils/logger');
/*
diff --git a/lib/output/ebook.js b/lib/output/ebook.js
index b968006..b4cd550 100644
--- a/lib/output/ebook.js
+++ b/lib/output/ebook.js
@@ -10,5 +10,7 @@ function EbookOutput() {
util.inherits(EbookOutput, AssetsInliner);
util.inherits(EbookOutput, WebsiteOutput);
+EbookOutput.prototype.name = 'ebook';
+
module.exports = EbookOutput;
diff --git a/lib/output/website/index.js b/lib/output/website/index.js
index a35cafe..b268eec 100644
--- a/lib/output/website/index.js
+++ b/lib/output/website/index.js
@@ -8,6 +8,8 @@ function WebsiteOutput() {
}
util.inherits(WebsiteOutput, FolderOutput);
+WebsiteOutput.prototype.name = 'ebook';
+
// Write a page (parsable file)
WebsiteOutput.prototype.onPage = function(page) {
diff --git a/lib/page/index.js b/lib/page/index.js
index b3ff00f..342830a 100644
--- a/lib/page/index.js
+++ b/lib/page/index.js
@@ -139,7 +139,7 @@ Page.prototype.toHTML = function(output) {
// Render template
.then(function() {
- return output.template.renderString(that.content, that.getContext(), {
+ return output.template.render(that.content, that.getContext(), {
file: that.path
})
.then(that.update);
@@ -154,6 +154,12 @@ Page.prototype.toHTML = function(output) {
});
})
+ // Post process templating
+ .then(function() {
+ return output.template.postProcess(that.content)
+ .then(that.update);
+ })
+
// Normalize HTML output
.then(function() {
var pipelineOpts = {
diff --git a/lib/template/index.js b/lib/template/index.js
index 3f74267..8266572 100644
--- a/lib/template/index.js
+++ b/lib/template/index.js
@@ -64,6 +64,20 @@ function TemplateEngine(output) {
this.addBlocks(defaultBlocks);
}
+// Bind a function to a context
+// Filters and blocks are binded to this context
+TemplateEngine.prototype.bindContext = function(func) {
+ var ctx = {
+ ctx: this.ctx,
+ book: this.book,
+ output: this.output
+ };
+
+ error.deprecateField(ctx, 'generator', this.output.name, 'generator property is deprecated, use output.name instead');
+
+ return _.bind(func, ctx);
+};
+
// Interpolate a string content to replace shortcuts according to the filetype
TemplateEngine.prototype.interpolate = function(filepath, source) {
var parser = parsers.get(path.extname(filepath));
@@ -240,7 +254,7 @@ TemplateEngine.prototype.addBlock = function(name, block) {
}, context);
})
- // process the block returned
+ // Process the block returned
.then(that.processBlock)
.nodeify(callback);
};
@@ -303,9 +317,30 @@ TemplateEngine.prototype.applyBlock = function(name, blk, ctx) {
else return normBlockResult(r);
};
+// Process the result of block in a context
+TemplateEngine.prototype.processBlock = function(blk) {
+ blk = _.defaults(blk, {
+ parse: false,
+ post: undefined
+ });
+ blk.id = _.uniqueId('blk');
-// Render a string
-TemplateEngine.prototype.renderString = function(content, context, options) {
+ 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+'@%@';
+};
+
+// Render a string (without post processing)
+TemplateEngine.prototype.render = function(content, context, options) {
options = _.defaults(options || {}, {
path: null
});
@@ -345,6 +380,12 @@ TemplateEngine.prototype.renderString = function(content, context, options) {
});
};
+// Render a string with post-processing
+TemplateEngine.prototype.renderString = function(content, context, options) {
+ return this.render(content, context, options)
+ .then(this.postProcess);
+};
+
// Apply a shortcut to a string
TemplateEngine.prototype.applyShortcut = function(content, shortcut) {
var regex = new RegExp(
@@ -356,6 +397,21 @@ TemplateEngine.prototype.applyShortcut = function(content, shortcut) {
});
};
+// 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;
+ });
+};
+
// Apply all shortcuts to a template
TemplateEngine.prototype.applyShortcuts = function(type, content) {
return _.chain(this.shortcuts)
@@ -367,4 +423,25 @@ TemplateEngine.prototype.applyShortcuts = function(type, content) {
};
+// Post process content
+TemplateEngine.prototype.postProcess = function(content) {
+ var that = this;
+
+ return Promise(content)
+ .then(that.replaceBlocks)
+ .then(function(_content) {
+ return Promise.serie(that.blockBodies, function(blk, blkId) {
+ return Promise()
+ .then(function() {
+ if (!blk.post) return;
+ return blk.post();
+ })
+ .then(function() {
+ delete that.blockBodies[blkId];
+ });
+ })
+ .thenResolve(_content);
+ });
+};
+
module.exports = TemplateEngine;
diff --git a/lib/utils/error.js b/lib/utils/error.js
index 883a7c6..c1abebd 100644
--- a/lib/utils/error.js
+++ b/lib/utils/error.js
@@ -1,6 +1,11 @@
var _ = require('lodash');
var TypedError = require('error/typed');
var WrappedError = require('error/wrapped');
+var deprecated = require('deprecated');
+
+var Logger = require('./logger');
+
+var log = new Logger();
// Enforce as an Error object, and cleanup message
function enforce(err) {
@@ -43,6 +48,14 @@ var TemplateError = WrappedError({
filename: null
});
+// Deprecate methods/fields
+function deprecateMethod(fn, msg) {
+ return deprecated.method(msg, log.warn.ln, fn);
+}
+function deprecateField(obj, prop, value, msg) {
+ return deprecated.field(msg, log.warn.ln, obj, prop, value);
+}
+
module.exports = {
enforce: enforce,
@@ -52,5 +65,8 @@ module.exports = {
FileNotFoundError: FileNotFoundError,
FileOutOfScopeError: FileOutOfScopeError,
- TemplateError: TemplateError
+ TemplateError: TemplateError,
+
+ deprecateMethod: deprecateMethod,
+ deprecateField: deprecateField
};
diff --git a/lib/logger.js b/lib/utils/logger.js
index 7428180..9dda57d 100644
--- a/lib/logger.js
+++ b/lib/utils/logger.js
@@ -20,7 +20,7 @@ var COLORS = {
function Logger(write, logLevel) {
if (!(this instanceof Logger)) return new Logger(write, logLevel);
- this._write = write;
+ this._write = write || function(msg) { process.stdout.write(msg); };
this.lastChar = '\n';
// Define log level
diff --git a/package.json b/package.json
index 69e91b7..3eb6001 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,7 @@
"gitbook-plugin-sharing": "1.0.1",
"gitbook-plugin-search": "1.1.0",
"gitbook-plugin-fontsettings": "1.0.2",
- "nunjucks": "2.2.0",
+ "nunjucks": "2.3.0",
"nunjucks-autoescape": "1.0.0",
"nunjucks-filter": "1.0.0",
"i18n": "0.5.0",
@@ -43,7 +43,8 @@
"merge-defaults": "0.2.1",
"github-slugid": "1.0.0",
"destroy": "1.0.4",
- "ignore": "2.2.19"
+ "ignore": "2.2.19",
+ "deprecated": "0.0.1"
},
"devDependencies": {
"eslint": "1.5.0",
diff --git a/test/page.js b/test/page.js
index fb3e3e5..b947519 100644
--- a/test/page.js
+++ b/test/page.js
@@ -8,7 +8,13 @@ describe('Page', function() {
return mock.setupDefaultBook({
'heading.md': '# Hello\n\n## World',
'links.md': '[link](hello.md) [readme](README.md)',
+
+ 'codes/simple.md': '```hello world```',
+ 'codes/lang.md': '```js\nhello world\n```',
+ 'codes/lang.adoc': '```js\nhello world\n```',
+
'folder/paths.md': '',
+
'variables/file/mtime.md': '{{ file.mtime }}',
'variables/file/path.md': '{{ file.path }}',
'variables/page/title.md': '{{ page.title }}',
@@ -86,6 +92,34 @@ describe('Page', function() {
});
});
+ describe('Code Blocks', function() {
+ var page;
+
+ before(function() {
+ output.template.addBlock('code', function(blk) {
+ return (blk.kwargs.language || '') + blk.body + 'test';
+ });
+ });
+
+ it('should apply "code" block', function() {
+ page = book.addPage('codes/simple.md');
+ return page.toHTML(output)
+ .should.be.fulfilledWith('<p><code>hello worldtest</code></p>\n');
+ });
+
+ it('should add language as kwargs', function() {
+ page = book.addPage('codes/lang.md');
+ return page.toHTML(output)
+ .should.be.fulfilledWith('<pre><code class="lang-js">jshello world\ntest</code></pre>\n');
+ });
+
+ it('should add language as kwargs (asciidoc)', function() {
+ page = book.addPage('codes/lang.adoc');
+ return page.toHTML(output)
+ .should.be.fulfilledWith('<div class="listingblock">\n<div class="content">\n<pre class="highlight"><code class="language-js" data-lang="js">jshello worldtest</code></pre>\n</div>\n</div>');
+ });
+ });
+
describe('Links', function() {
var page;
diff --git a/test/template.js b/test/template.js
index 536043d..d12a641 100644
--- a/test/template.js
+++ b/test/template.js
@@ -6,9 +6,7 @@ describe('Template', function() {
var output;
before(function() {
- return mock.outputDefaultBook(Output, {
- 'test.md': 'World'
- })
+ return mock.outputDefaultBook(Output, {})
.then(function(_output) {
output = _output;
});
@@ -25,4 +23,24 @@ describe('Template', function() {
.should.be.fulfilledWith('Version is '+pkg.version);
});
});
+
+ describe('Blocks', function() {
+ it('should correctly add a block', function() {
+ output.template.addBlock('sayhello', function(blk) {
+ return 'Hello ' + blk.body + '!';
+ });
+
+ return output.template.renderString('{% sayhello %}World{% endsayhello %}')
+ .should.be.fulfilledWith('Hello World!');
+ });
+
+ it('should correctly add a block with kwargs', function() {
+ output.template.addBlock('sayhello_kwargs', function(blk) {
+ return 'Hello ' + blk.kwargs.name + '!';
+ });
+
+ return output.template.renderString('{% sayhello_kwargs name="World" %}{% endsayhello_kwargs %}')
+ .should.be.fulfilledWith('Hello World!');
+ });
+ });
});