summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/blocks.js11
-rw-r--r--lib/book.js31
-rw-r--r--lib/configuration.js22
-rw-r--r--lib/conrefs_loader.js64
-rw-r--r--lib/generator.js6
-rw-r--r--lib/generators/ebook.js4
-rw-r--r--lib/generators/website.js2
-rw-r--r--lib/index.js2
-rw-r--r--lib/plugin.js2
-rw-r--r--lib/pluginslist.js8
-rw-r--r--lib/template.js163
-rw-r--r--lib/utils/code.js36
-rw-r--r--lib/utils/fs.js4
-rw-r--r--lib/utils/git.js23
-rw-r--r--lib/utils/navigation.js9
-rw-r--r--lib/utils/page.js41
-rw-r--r--lib/utils/path.js39
-rw-r--r--lib/utils/watch.js1
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
});