diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/blocks.js | 11 | ||||
-rw-r--r-- | lib/book.js | 31 | ||||
-rw-r--r-- | lib/configuration.js | 22 | ||||
-rw-r--r-- | lib/conrefs_loader.js | 64 | ||||
-rw-r--r-- | lib/generator.js | 6 | ||||
-rw-r--r-- | lib/generators/ebook.js | 4 | ||||
-rw-r--r-- | lib/generators/website.js | 2 | ||||
-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 | 163 | ||||
-rw-r--r-- | lib/utils/code.js | 36 | ||||
-rw-r--r-- | lib/utils/fs.js | 4 | ||||
-rw-r--r-- | lib/utils/git.js | 23 | ||||
-rw-r--r-- | lib/utils/navigation.js | 9 | ||||
-rw-r--r-- | lib/utils/page.js | 41 | ||||
-rw-r--r-- | lib/utils/path.js | 39 | ||||
-rw-r--r-- | lib/utils/watch.js | 1 |
18 files changed, 326 insertions, 142 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..08dd6dc 100644 --- a/lib/book.js +++ b/lib/book.js @@ -10,6 +10,7 @@ var fs = require("./utils/fs"); var parseNavigation = require("./utils/navigation"); var parseProgress = require("./utils/progress"); var pageUtil = require("./utils/page"); +var pathUtil = require("./utils/path"); var batch = require("./utils/batch"); var links = require("./utils/links"); var i18n = require("./utils/i18n"); @@ -512,7 +513,7 @@ Book.prototype.parsePage = function(filename, options) { var interpolate = function(fn) { return Q(fn(page)) .then(function(_page) { - page = _page; + page = _page || page; }); }; @@ -630,21 +631,26 @@ Book.prototype.findFile = function(filename) { // Check if a file exists in the book Book.prototype.fileExists = function(filename) { return fs.exists( - path.join(this.root, filename) + this.resolve(filename) ); }; +// Check if a file path is inside the book +Book.prototype.fileIsInBook = function(filename) { + return pathUtil.isInRoot(this.root, filename); +}; + // Read a file Book.prototype.readFile = function(filename) { return fs.readFile( - path.join(this.root, filename), + this.resolve(filename), { encoding: "utf8" } ); }; // Return stat for a file Book.prototype.statFile = function(filename) { - return fs.stat(path.join(this.root, filename)); + return fs.stat(this.resolve(filename)); }; // List all files in the book @@ -702,9 +708,20 @@ Book.prototype.isEntryPoint = function(fp) { return fp == this.readmeFile; }; -// Resolve a path in book -Book.prototype.resolve = function(p) { - return path.resolve(this.root, p); +// Alias to book.config.get +Book.prototype.getConfig = function(key, def) { + return this.config.get(key, def); +}; + +// Resolve a path in the book source +// Enforce that the output path in the root folder +Book.prototype.resolve = function() { + return pathUtil.resolveInRoot.apply(null, [this.root].concat(_.toArray(arguments))); +}; + +// Convert an abslute path into a relative path to this +Book.prototype.relative = function(p) { + return path.relative(this.root, p); }; // Normalize a path to .html and convert README -> index diff --git a/lib/configuration.js b/lib/configuration.js index 29776bd..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) { @@ -100,7 +100,7 @@ Configuration.prototype.load = function() { try { configPath = require.resolve( - path.resolve(that.book.root, that.options.configFile) + that.book.resolve(that.options.configFile) ); // Invalidate node.js cache for livreloading @@ -121,11 +121,11 @@ 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 || path.join(that.book.root, "_book")); + that.options.output = path.resolve(that.options.output || that.book.resolve("_book")); that.options.plugins = normalizePluginsList(that.options.plugins); that.options.defaultsPlugins = normalizePluginsList(that.options.defaultsPlugins || ""); that.options.plugins = _.union(that.options.plugins, that.options.defaultsPlugins); @@ -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 = { @@ -246,6 +250,14 @@ Configuration.DEFAULT = { // Choices are [u’a0’, u’a1’, u’a2’, u’a3’, u’a4’, u’a5’, u’a6’, u’b0’, u’b1’, u’b2’, u’b3’, u’b4’, u’b5’, u’b6’, u’legal’, u’letter’] "paperSize": "a4", + // How to mark detected chapters. + // Choices are “pagebreak”, “rule”, "both" or “none”. + "chapterMark" : "pagebreak", + + // An XPath expression. Page breaks are inserted before the specified elements. + // To disable use the expression: "/" + "pageBreaksBefore": "/", + // Margin (in pts) // Note: 72 pts equals 1 inch "margin": { diff --git a/lib/conrefs_loader.js b/lib/conrefs_loader.js new file mode 100644 index 0000000..72dce8a --- /dev/null +++ b/lib/conrefs_loader.js @@ -0,0 +1,64 @@ +var Q = require("q"); +var path = require("path"); +var nunjucks = require("nunjucks"); + +var git = require("./utils/git"); +var fs = require("./utils/fs"); +var pathUtil = require("./utils/path"); + +// The loader should handle relative and git url +var BookLoader = nunjucks.Loader.extend({ + async: true, + + init: function(book) { + this.book = book; + }, + + getSource: function(fileurl, callback) { + var that = this; + + git.resolveFile(fileurl) + .then(function(filepath) { + // Is local file + if (!filepath) filepath = path.resolve(fileurl); + else that.book.log.debug.ln("resolve from git", fileurl, "to", filepath); + + // Read file from absolute path + return fs.readFile(filepath) + .then(function(source) { + return { + src: source.toString(), + path: filepath + } + }); + }) + .nodeify(callback); + }, + + resolve: function(from, to) { + // If origin is in the book, we enforce result file to be in the book + if (this.book.fileIsInBook(from)) { + return this.book.resolve( + this.book.relative(path.dirname(from)), + to + ); + } + + // If origin is in a git repository, we resolve file in the git repository + var gitRoot = git.resolveRoot(from); + if (gitRoot) { + return pathUtil.resolveInRoot(gitRoot, to); + } + + // If origin is not in the book (include from a git content ref) + return path.resolve(path.dirname(from), to); + }, + + // Handle all files as relative, so that nunjucks pass responsability to "resolve" + // Only git urls are considered as absolute + isRelative: function(filename) { + return !git.checkUrl(filename); + } +}); + +module.exports = BookLoader; diff --git a/lib/generator.js b/lib/generator.js index c809de2..407afa5 100644 --- a/lib/generator.js +++ b/lib/generator.js @@ -36,7 +36,7 @@ BaseGenerator.prototype.convertFile = function(input) { // Copy file to the output (non parsable) BaseGenerator.prototype.transferFile = function(input) { return fs.copy( - path.join(this.book.root, input), + this.book.resolve(input), path.join(this.options.output, input) ); }; @@ -53,8 +53,8 @@ BaseGenerator.prototype.copyCover = function() { var that = this; return Q.all([ - fs.copy(path.join(that.book.root, "cover.jpg"), path.join(that.options.output, "cover.jpg")), - fs.copy(path.join(that.book.root, "cover_small.jpg"), path.join(that.options.output, "cover_small.jpg")) + fs.copy(that.book.resolve("cover.jpg"), path.join(that.options.output, "cover.jpg")), + fs.copy(that.book.resolve("cover_small.jpg"), path.join(that.options.output, "cover_small.jpg")) ]) .fail(function() { // If orignaly from multi-lang, try copy from parent diff --git a/lib/generators/ebook.js b/lib/generators/ebook.js index cff9ef6..cdb667c 100644 --- a/lib/generators/ebook.js +++ b/lib/generators/ebook.js @@ -68,8 +68,6 @@ Generator.prototype.finish = function() { "--book-producer": "GitBook", "--publisher": "GitBook", "--chapter": "descendant-or-self::*[contains(concat(' ', normalize-space(@class), ' '), ' book-chapter ')]", - "--chapter-mark": "pagebreak", - "--page-breaks-before": "/", "--level1-toc": "descendant-or-self::*[contains(concat(' ', normalize-space(@class), ' '), ' book-chapter-1 ')]", "--level2-toc": "descendant-or-self::*[contains(concat(' ', normalize-space(@class), ' '), ' book-chapter-2 ')]", "--level3-toc": "descendant-or-self::*[contains(concat(' ', normalize-space(@class), ' '), ' book-chapter-3 ')]", @@ -82,6 +80,8 @@ Generator.prototype.finish = function() { var pdfOptions = that.options.pdf; _.extend(_options, { + "--chapter-mark": String(pdfOptions.chapterMark), + "--page-breaks-before": String(pdfOptions.pageBreaksBefore), "--margin-left": String(pdfOptions.margin.left), "--margin-right": String(pdfOptions.margin.right), "--margin-top": String(pdfOptions.margin.top), diff --git a/lib/generators/website.js b/lib/generators/website.js index de833d3..675092f 100644 --- a/lib/generators/website.js +++ b/lib/generators/website.js @@ -50,7 +50,7 @@ Generator.prototype.prepareStyles = function() { this.styles = _.chain(this.styles) .map(function(style) { var stylePath = that.options.styles[style]; - if (stylePath && fs.existsSync(path.resolve(that.book.root, stylePath))) { + if (stylePath && fs.existsSync(that.book.resolve(stylePath))) { return [style, stylePath]; } return null; 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 3a4985c..9f01d3c 100644 --- a/lib/template.js +++ b/lib/template.js @@ -4,45 +4,16 @@ var path = require("path"); var nunjucks = require("nunjucks"); var escapeStringRegexp = require("escape-string-regexp"); -var git = require("./utils/git"); -var fs = require("./utils/fs"); var batch = require("./utils/batch"); var pkg = require("../package.json"); +var defaultBlocks = require("./blocks"); +var BookLoader = require("./conrefs_loader") - -// The loader should handle relative and git url -var BookLoader = nunjucks.Loader.extend({ - async: true, - - init: function(book) { - this.book = book; - }, - - getSource: function(fileurl, callback) { - var that = this; - - git.resolveFile(fileurl) - .then(function(filepath) { - // Is local file - if (!filepath) filepath = path.resolve(that.book.root, fileurl); - else that.book.log.debug.ln("resolve from git", fileurl, "to", filepath) - - // Read file from absolute path - return fs.readFile(filepath) - .then(function(source) { - return { - src: source.toString(), - path: filepath - } - }); - }) - .nodeify(callback); - }, - - resolve: function(from, to) { - return path.resolve(path.dirname(from), to); - } -}); +// Normalize result from a block +function normBlockResult(blk) { + if (_.isString(blk)) blk = { body: blk }; + return blk; +} var TemplateEngine = function(book) { @@ -71,22 +42,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 +66,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 +130,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 +173,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 +266,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 +282,6 @@ TemplateEngine.prototype.addBlock = function(name, block) { }; }; - // Add the Extension this.env.addExtension(extName, new Ext()); @@ -287,6 +290,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 +302,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; @@ -316,6 +354,9 @@ TemplateEngine.prototype.renderString = function(content, context, options) { // Variables from book.json book: this.book.options.variables, + // Complete book.json + config: this.book.options, + // infos about gitbook gitbook: { version: pkg.version, @@ -380,7 +421,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() @@ -389,7 +430,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..6eb9681 100644 --- a/lib/utils/git.js +++ b/lib/utils/git.js @@ -6,6 +6,7 @@ var path = require("path"); var crc = require("crc"); var exec = Q.denodeify(require("child_process").exec); var URI = require("URIjs"); +var pathUtil = require("./path"); var fs = require("./fs"); @@ -83,13 +84,12 @@ function cloneGitRepo(host, ref) { return exec("git clone "+host+" "+repoPath) .then(function() { return exec("git checkout "+ref, { cwd: repoPath }); - }) + }); }) .thenResolve(repoPath); }); } - // Get file from a git repo function resolveFileFromGit(giturl) { if (_.isString(giturl)) giturl = parseGitUrl(giturl); @@ -104,9 +104,26 @@ function resolveFileFromGit(giturl) { }); }; +// Return root of git repo from a filepath +function resolveGitRoot(filepath) { + var relativeToGit, repoId + + // No git repo cloned, or file is not in a git repository + if (!GIT_TMP || !pathUtil.isInRoot(GIT_TMP, filepath)) return null; + + // Extract first directory (is the repo id) + relativeToGit = path.relative(GIT_TMP, filepath); + repoId = _.first(relativeToGit.split(path.sep)); + if (!repoId) return; + + // Return an absolute file + return path.resolve(GIT_TMP, repoId); +}; + module.exports = { checkUrl: checkGitUrl, parseUrl: parseGitUrl, - resolveFile: resolveFileFromGit + resolveFile: resolveFileFromGit, + resolveRoot: resolveGitRoot }; diff --git a/lib/utils/navigation.js b/lib/utils/navigation.js index af9330d..d825c2c 100644 --- a/lib/utils/navigation.js +++ b/lib/utils/navigation.js @@ -27,7 +27,7 @@ function navigation(summary, files) { files = _.isArray(files) ? files : (_.isString(files) ? [files] : null); // List of all navNodes - // Flatten chapters, then add in default README node if needed etc ... + // Flatten chapters var navNodes = flattenChapters(summary.chapters); // Mapping of prev/next for a give path @@ -39,8 +39,7 @@ function navigation(summary, files) { if(!current.exists) return null; // Find prev - prev = _.chain(navNodes) - .slice(0, i) + prev = _.chain(navNodes.slice(0, i)) .reverse() .find(function(node) { return node.exists && !node.external; @@ -48,14 +47,12 @@ function navigation(summary, files) { .value(); // Find next - next = _.chain(navNodes) - .slice(i+1) + next = _.chain(navNodes.slice(i+1)) .find(function(node) { return node.exists && !node.external; }) .value(); - return [current.path, { index: i, title: current.title, 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/path.js b/lib/utils/path.js new file mode 100644 index 0000000..d5b98f7 --- /dev/null +++ b/lib/utils/path.js @@ -0,0 +1,39 @@ +var _ = require("lodash"); +var path = require('path'); + +// Return true if file path is inside a folder +function isInRoot(root, filename) { + filename = path.normalize(filename); + return (filename.substr(0, root.length) === root); +} + +// Resolve paths in a specific folder +// Throw error if file is outside this folder +function resolveInRoot(root) { + var input = _.chain(arguments) + .toArray() + .slice(1) + .reduce(function(current, p, i) { + // Handle path relative to book root ('/README.md') + if (p[0] == '/' || p[0] == '\\') return p.slice(1); + + return current? path.join(current, p) : path.normalize(p); + }, '') + .value(); + + var result = path.resolve(root, input); + + if (!isInRoot(root, result)) { + err = new Error("EACCESS: '" + result + "' not in '" + root + "'"); + err.code = "EACCESS"; + throw err; + } + + return result +}; + + +module.exports = { + isInRoot: isInRoot, + resolveInRoot: resolveInRoot +}; 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 }); |