diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/blocks.js | 11 | ||||
-rw-r--r-- | lib/book.js | 2 | ||||
-rw-r--r-- | lib/configuration.js | 10 | ||||
-rw-r--r-- | lib/index.js | 2 | ||||
-rw-r--r-- | lib/plugin.js | 2 | ||||
-rw-r--r-- | lib/pluginslist.js | 8 | ||||
-rw-r--r-- | lib/template.js | 123 | ||||
-rw-r--r-- | lib/utils/code.js | 36 | ||||
-rw-r--r-- | lib/utils/fs.js | 4 | ||||
-rw-r--r-- | lib/utils/git.js | 2 | ||||
-rw-r--r-- | lib/utils/page.js | 41 | ||||
-rw-r--r-- | lib/utils/watch.js | 1 |
12 files changed, 158 insertions, 84 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/book.js b/lib/book.js index 2e0ab58..b306c51 100644 --- a/lib/book.js +++ b/lib/book.js @@ -512,7 +512,7 @@ Book.prototype.parsePage = function(filename, options) { var interpolate = function(fn) { return Q(fn(page)) .then(function(_page) { - page = _page; + page = _page || page; }); }; diff --git a/lib/configuration.js b/lib/configuration.js index 254367b..acff1c1 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) { @@ -121,8 +121,8 @@ Configuration.prototype.load = function() { if (!semver.satisfies(pkg.version, that.options.gitbook)) { throw "GitBook version doesn't satisfy version required by the book: "+that.options.gitbook; } - if (that.options.gitbook == "*") { - that.book.log.warn.ln("you should specify a gitbook version to use in your book.json, for example: "+(_.first(pkg.version.split("."))+".x.x")); + if (that.options.gitbook != '*' && !semver.satisfies(semver.inc(pkg.version, 'patch'), that.options.gitbook)) { + that.book.log.warn.ln("gitbook version specified in your book.json might be too strict for future patches, \""+(_.first(pkg.version.split("."))+".x.x")+"\" is more adequate"); } that.options.output = path.resolve(that.options.output || that.book.resolve("_book")); @@ -156,6 +156,10 @@ Configuration.prototype.normalizeLanguage = function() { return i18n.normalizeLanguage(this.options.language); }; +// Return a configuration +Configuration.prototype.get = function(key, def) { + return _.get(this.options, key, def); +}; // Default configuration Configuration.DEFAULT = { diff --git a/lib/index.js b/lib/index.js index 7a54793..b5aa06d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -158,7 +158,7 @@ module.exports = { .then(function(filepath) { // set livereload path lrPath = filepath; - console.log("Restart after change in files"); + console.log("Restart after change in file", filepath); console.log(''); return generate(); }) diff --git a/lib/plugin.js b/lib/plugin.js index a7a29b9..364aec8 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -33,7 +33,7 @@ var Plugin = function(book, name) { // Type of plugins resources Plugin.RESOURCES = ["js", "css"]; Plugin.HOOKS = [ - "init", "finish", "finish:before" + "init", "finish", "finish:before", "page", "page:before" ] // Load from a name 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 b31fab9..4b2035f 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,24 +101,24 @@ 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) { return blk.body; } - // Return it as a macro - return "%+%"+blk.id+"%+%"; + // Return it as a position marker + return "@%@"+blk.id+"@%@"; }; -// Replace blocks by body after processing -// This is done to avoid that markdown processer parse the block content +// 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.blocks[key]; + return content.replace(/\@\%\@([\s\S]+?)\@\%\@/g, function(match, 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/fs.js b/lib/utils/fs.js index 98a3a87..176a215 100644 --- a/lib/utils/fs.js +++ b/lib/utils/fs.js @@ -64,6 +64,10 @@ function writeStream(filename, st) { d.reject(err); }); + st.on('error', function(err) { + d.reject(err); + }); + st.pipe(wstream); return d.promise; diff --git a/lib/utils/git.js b/lib/utils/git.js index 2c3dd3f..5f17395 100644 --- a/lib/utils/git.js +++ b/lib/utils/git.js @@ -83,7 +83,7 @@ function cloneGitRepo(host, ref) { return exec("git clone "+host+" "+repoPath) .then(function() { return exec("git checkout "+ref, { cwd: repoPath }); - }) + }); }) .thenResolve(repoPath); }); diff --git a/lib/utils/page.js b/lib/utils/page.js index f24013f..5b4eca8 100644 --- a/lib/utils/page.js +++ b/lib/utils/page.js @@ -11,7 +11,8 @@ 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; // Render a cheerio dom as html var renderDom = function($, dom, options) { @@ -196,11 +197,19 @@ function normalizeHtml(src, options) { var absolutePath = links.join(options.base, parts.pathname); var anchor = parts.hash || ""; + // If is in navigation relative: transform as content if (options.navigation[absolutePath]) { absolutePath = options.book.contentLink(absolutePath); } + // If md/adoc/rst files is not in summary + // or for ebook, signal all files that are outside the summary + else if (_.contains(parsableExtensions, path.extname(absolutePath)) + || _.contains(['epub', 'pdf', 'mobi'], options.book.options.generator)) { + options.book.log.warn.ln("page", options.input, "contains an hyperlink to resource outside spine '"+href+"'"); + } + // Transform as absolute href = links.toAbsolute("/"+absolutePath, options.base, options.output)+anchor; } else { @@ -214,26 +223,32 @@ function normalizeHtml(src, options) { // Highlight code blocks $("code").each(function() { - // Extract language + // Normalize language var lang = _.chain( ($(this).attr("class") || "").split(" ") ) .map(function(cl) { + // Markdown if (cl.search("lang-") === 0) return cl.slice("lang-".length); + + // Asciidoc + if (cl.search("language-") === 0) return cl.slice("language-".length); + return null; }) .compact() .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 @@ -285,7 +300,13 @@ function convertImages(images, options) { if (!image.origin && !_.contains(downloaded, image.origin)) return; options.book.log.debug("download image", image.origin, "..."); downloaded.push(image.origin); - return options.book.log.debug.promise(fs.writeStream(imgin, request(image.origin))); + return options.book.log.debug.promise(fs.writeStream(imgin, request(image.origin))) + .fail(function(err) { + if (!_.isError(err)) err = new Error(err); + + err.message = 'Fail downloading '+image.origin+': '+err.message; + throw err; + }); }) // Write svg if content diff --git a/lib/utils/watch.js b/lib/utils/watch.js index b6e18e7..3e73e47 100644 --- a/lib/utils/watch.js +++ b/lib/utils/watch.js @@ -19,6 +19,7 @@ function watch(dir) { var watcher = chokidar.watch(toWatch, { cwd: dir, + ignored: '_book/**', ignoreInitial: true }); |