summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorSamy Pessé <samypesse@gmail.com>2016-01-22 21:04:36 +0100
committerSamy Pessé <samypesse@gmail.com>2016-01-22 21:04:36 +0100
commit877f2e477b010f9f37a9044606f110a90f077680 (patch)
tree5cd61cf3b00ba10dc6110535ed9fdf67d8baba72 /lib
parentc8e2fc0e57d223c01a51d6ee186fc1662cd74d13 (diff)
downloadgitbook-877f2e477b010f9f37a9044606f110a90f077680.zip
gitbook-877f2e477b010f9f37a9044606f110a90f077680.tar.gz
gitbook-877f2e477b010f9f37a9044606f110a90f077680.tar.bz2
Start rewrite
Diffstat (limited to 'lib')
-rw-r--r--lib/backbone/file.js61
-rw-r--r--lib/backbone/glossary.js8
-rw-r--r--lib/backbone/index.js8
-rw-r--r--lib/backbone/langs.js15
-rw-r--r--lib/backbone/readme.js26
-rw-r--r--lib/backbone/summary.js9
-rw-r--r--lib/blocks.js11
-rw-r--r--lib/book.js824
-rw-r--r--lib/config.js110
-rw-r--r--lib/configuration.js210
-rw-r--r--lib/conrefs_loader.js73
-rw-r--r--lib/fs/index.js148
-rw-r--r--lib/fs/node.js72
-rw-r--r--lib/generator.js76
-rw-r--r--lib/generators/ebook.js172
-rw-r--r--lib/generators/index.js11
-rw-r--r--lib/generators/json.js76
-rw-r--r--lib/generators/website.js268
-rw-r--r--lib/gitbook.js (renamed from lib/version.js)4
-rw-r--r--lib/index.js212
-rw-r--r--lib/init.js83
-rw-r--r--lib/logger.js123
-rw-r--r--lib/page.js10
-rw-r--r--lib/plugin.js241
-rw-r--r--lib/pluginslist.js230
-rw-r--r--lib/template.js466
-rw-r--r--lib/utils/batch.js52
-rw-r--r--lib/utils/fs.js193
-rw-r--r--lib/utils/git.js127
-rw-r--r--lib/utils/i18n.js80
-rw-r--r--lib/utils/images.js37
-rw-r--r--lib/utils/links.js81
-rw-r--r--lib/utils/logger.js102
-rw-r--r--lib/utils/navigation.js79
-rw-r--r--lib/utils/page.js397
-rw-r--r--lib/utils/path.js8
-rw-r--r--lib/utils/progress.js55
-rw-r--r--lib/utils/server.js94
-rw-r--r--lib/utils/string.js27
-rw-r--r--lib/utils/watch.js40
40 files changed, 692 insertions, 4227 deletions
diff --git a/lib/backbone/file.js b/lib/backbone/file.js
new file mode 100644
index 0000000..71fc78c
--- /dev/null
+++ b/lib/backbone/file.js
@@ -0,0 +1,61 @@
+
+function BackboneFile(book) {
+ if (!(this instanceof BackboneFile)) return new BackboneFile(book);
+
+ this.book = book;
+ this.log = this.book.log;
+
+ // Filename in the book
+ this.filename = '';
+ this.parser;
+}
+
+// Type of the backbone file
+BackboneFile.prototype.type = '';
+
+// Parse a backbone file
+BackboneFile.prototype.parse = function() {
+
+};
+
+// Return true if backbone file exists
+BackboneFile.prototype.exists = function() {
+ return Boolean(this.filename);
+};
+
+// Locate a backbone file, could be .md, .asciidoc, etc
+BackboneFile.prototype.locate = function() {
+ var that = this;
+ var filename = this.book.config.getStructure(this.type, true);
+ this.log.debug.ln('locating', this.type, ':', filename);
+
+ return this.book.findParsableFile(filename)
+ .then(function(result) {
+ if (!result) return;
+
+ that.filename = result.path;
+ that.parser = result.parser;
+ });
+};
+
+// Read and parse the file
+BackboneFile.prototype.load = function() {
+ var that = this;
+ this.log.debug.ln('loading', this.type, ':', that.filename);
+
+ return this.locate()
+ .then(function() {
+ if (!that.filename) return;
+
+ that.log.debug.ln(that.type, 'located at', that.filename);
+
+ return that.book.readFile(that.filename)
+
+ // Parse it
+ .then(function(content) {
+ return that.parse(content);
+ });
+ });
+};
+
+module.exports = BackboneFile;
diff --git a/lib/backbone/glossary.js b/lib/backbone/glossary.js
new file mode 100644
index 0000000..aba83cf
--- /dev/null
+++ b/lib/backbone/glossary.js
@@ -0,0 +1,8 @@
+
+function Glossary() {
+ if (!(this instanceof Glossary)) return new Glossary();
+}
+
+Glossary.prototype.type = 'glossary';
+
+module.exports = Glossary;
diff --git a/lib/backbone/index.js b/lib/backbone/index.js
new file mode 100644
index 0000000..4c3c3f3
--- /dev/null
+++ b/lib/backbone/index.js
@@ -0,0 +1,8 @@
+
+module.exports = {
+ Readme: require('./readme'),
+ Summary: require('./summary'),
+ Glossary: require('./glossary'),
+ Langs: require('./langs')
+};
+
diff --git a/lib/backbone/langs.js b/lib/backbone/langs.js
new file mode 100644
index 0000000..2818519
--- /dev/null
+++ b/lib/backbone/langs.js
@@ -0,0 +1,15 @@
+
+function Langs() {
+ if (!(this instanceof Langs)) return new Langs();
+
+ this.languages = [];
+}
+
+Langs.prototype.type = 'langs';
+
+// Return the count of languages
+Langs.prototype.count = function() {
+ return this.languages.length;
+};
+
+module.exports = Langs;
diff --git a/lib/backbone/readme.js b/lib/backbone/readme.js
new file mode 100644
index 0000000..a4cd9d8
--- /dev/null
+++ b/lib/backbone/readme.js
@@ -0,0 +1,26 @@
+var util = require('util');
+var BackboneFile = require('./file');
+
+function Readme() {
+ BackboneFile.apply(this, arguments);
+
+ this.title;
+ this.description;
+}
+util.inherits(Readme, BackboneFile);
+
+Readme.prototype.type = 'readme';
+
+// Parse the readme content
+Readme.prototype.parse = function(content) {
+ var that = this;
+
+ return this.parser.readme(content)
+ .then(function(out) {
+ that.title = out.title;
+ that.description = out.description;
+ });
+};
+
+
+module.exports = Readme;
diff --git a/lib/backbone/summary.js b/lib/backbone/summary.js
new file mode 100644
index 0000000..e2cd485
--- /dev/null
+++ b/lib/backbone/summary.js
@@ -0,0 +1,9 @@
+
+function Summary() {
+ if (!(this instanceof Summary)) return new Summary();
+}
+
+Summary.prototype.type = 'summary';
+
+
+module.exports = Summary;
diff --git a/lib/blocks.js b/lib/blocks.js
deleted file mode 100644
index 92097a7..0000000
--- a/lib/blocks.js
+++ /dev/null
@@ -1,11 +0,0 @@
-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 9ba27f3..3bb5bf1 100644
--- a/lib/book.js
+++ b/lib/book.js
@@ -1,25 +1,27 @@
-var Q = require('q');
var _ = require('lodash');
+var Q = require('q');
var path = require('path');
+var Ignore = require('ignore');
var parsers = require('gitbook-parsers');
-var fs = require('./utils/fs');
-var parseNavigation = require('./utils/navigation');
-var parseProgress = require('./utils/progress');
-var pageUtil = require('./utils/page');
+var Logger = require('./logger');
+var Config = require('./config');
+var Readme = require('./backbone/readme');
+var Glossary = require('./backbone/glossary');
+var Summary = require('./backbone/summary');
+var Langs = require('./backbone/langs');
+var Page = require('./page');
var pathUtil = require('./utils/path');
-var links = require('./utils/links');
-var i18n = require('./utils/i18n');
-var logger = require('./utils/logger');
-var Configuration = require('./configuration');
-var TemplateEngine = require('./template');
-var PluginsList = require('./pluginslist');
+function Book(opts) {
+ if (!(this instanceof Book)) return new Book(opts);
-var generators = require('./generators');
+ opts = _.defaults(opts || {}, {
+ fs: null,
+
+ // Root path for the book
+ root: '',
-var Book = function(root, context, parent) {
- this.context = _.defaults(context || {}, {
// Extend book configuration
config: {},
@@ -32,572 +34,116 @@ var Book = function(root, context, parent) {
logLevel: 'info'
});
- // Log
- this.log = logger(this.context.log, this.context.logLevel);
-
- // Root folder of the book
- this.root = path.resolve(root);
+ if (!opts.fs) throw new Error('Book requires a fs instance');
- // Parent book
- this.parent = parent;
+ // Root path for the book
+ this.root = opts.root;
- // Configuration
- this.config = new Configuration(this, this.context.config);
- Object.defineProperty(this, 'options', {
- get: function () {
- return this.config.options;
- }
- });
-
- // Template
- this.template = new TemplateEngine(this);
+ // If multi-lingual, book can have a parent
+ this.parent = opts.parent;
- // Summary
- this.summary = {};
- this.navigation = [];
+ // A book is linked to an fs, to access its content
+ this.fs = opts.fs;
- // Glossary
- this.glossary = [];
+ // Rules to ignore some files
+ this.ignore = Ignore();
+ this.ignore.addPattern(['.git', '.svn', '.DS_Store']);
- // Langs
- this.langs = [];
+ // Create a logger for the book
+ this.log = new Logger(opts.log, opts.logLevel);
- // Sub-books
- this.books = [];
+ // Create an interface to access the configuration
+ this.config = new Config(this, opts.config);
- // Files in the book
- this.files = [];
+ // Interfaces for the book structure
+ this.readme = new Readme(this);
+ this.summary = new Summary(this);
+ this.glossary = new Glossary(this);
+ this.langs = new Langs(this);
- // List of plugins
- this.plugins = new PluginsList(this);
+ // Map of pages in the bok
+ this.pages = {};
- // Structure files
- this.summaryFile = null;
- this.glossaryFile = null;
- this.readmeFile = null;
- this.langsFile = null;
-
- // Bind methods
_.bindAll(this);
-};
+}
-// Return string representation
-Book.prototype.toString = function() {
- return '[Book '+this.root+']';
+// Parse and prepare the configuration, fail if invalid
+Book.prototype.prepareConfig = function() {
+ return this.config.load();
};
-// Initialize and parse the book: config, summary, glossary
-Book.prototype.parse = function() {
- var that = this;
- var multilingual = false;
-
- return this.parseConfig()
-
- .then(function() {
- return that.parsePlugins();
- })
-
- .then(function() {
- return that.parseLangs()
- .then(function() {
- multilingual = that.langs.length > 0;
- if (multilingual) that.log.info.ln('Parsing multilingual book, with', that.langs.length, 'languages');
-
- // Sub-books that inherit from the current book configuration
- that.books = _.map(that.langs, function(lang) {
- that.log.info.ln('Preparing language book', lang.lang);
- return new Book(
- path.join(that.root, lang.path),
- _.merge({}, that.context, {
- config: _.extend({}, that.options, {
- 'output': path.join(that.options.output, lang.lang),
- 'language': lang.lang
- })
- }),
- that
- );
- });
- });
- })
-
- .then(function() {
- if (multilingual) return;
- return that.listAllFiles();
- })
- .then(function() {
- if (multilingual) return;
- return that.parseReadme();
- })
- .then(function() {
- if (multilingual) return;
- return that.parseSummary();
- })
- .then(function() {
- if (multilingual) return;
- return that.parseGlossary();
- })
-
- .then(function() {
- // Init sub-books
- return _.reduce(that.books, function(prev, book) {
- return prev.then(function() {
- return book.parse();
- });
- }, Q());
- })
-
- .thenResolve(this);
+// 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)));
};
-// Generate the output
-Book.prototype.generate = function(generator) {
+// Parse .gitignore, etc to extract rules
+Book.prototype.parseIgnoreRules = function() {
var that = this;
- that.options.generator = generator || that.options.generator;
- that.log.info.ln('start generation with', that.options.generator, 'generator');
- return Q()
-
- // Clean output folder
- .then(function() {
- that.log.info('clean', that.options.generator, 'generator');
- return fs.clean(that.options.output)
- .progress(function(p) {
- that.log.debug.ln('remove', p.file, '('+p.i+'/'+p.count+')');
+ return _.reduce([
+ '.ignore',
+ '.gitignore',
+ '.bookignore'
+ ], function(prev, filename) {
+ return prev.then(function() {
+ return that.readFile(filename);
})
- .then(function() {
- that.log.info.ok();
- });
- })
-
- // Create generator
- .then(function() {
- var Generator = generators[generator];
- if (!Generator) throw 'Generator \''+that.options.generator+'\' doesn\'t exist';
- generator = new Generator(that);
-
- return generator.prepare();
- })
-
- // Transform configuration
- .then(function() {
- return that.callHook('config', that.config.dump())
- .then(function(newConfig) {
- that.config.replace(newConfig);
+ .then(function(content) {
+ that.ignore.addPattern(content.toString().split(/\r?\n/));
});
- })
-
- // Generate content
- .then(function() {
- if (that.isMultilingual()) {
- return that.generateMultiLingual(generator);
- } else {
- // Separate list of files into the different operations needed
- var ops = _.groupBy(that.files, function(file) {
- if (file[file.length -1] == '/') {
- return 'directories';
- } else if (_.contains(parsers.extensions, path.extname(file)) && that.navigation[file]) {
- return 'content';
- } else {
- return 'files';
- }
- });
-
-
- return Q()
-
- // First, let's create folder
- .then(function() {
- return _.reduce(ops.directories || [], function(prev, folder) {
- return prev.then(function() {
- that.log.debug.ln('transferring folder', folder);
- return Q(generator.transferFolder(folder));
- });
- }, Q());
- })
-
- // Then, let's copy other files
- .then(function() {
- return Q.all(_.map(ops.files || [], function(file) {
- that.log.debug.ln('transferring file', file);
- return Q(generator.transferFile(file));
- }));
- })
-
- // Finally let's generate content
- .then(function() {
- var nFiles = (ops.content || []).length;
- return _.reduce(ops.content || [], function(prev, file, i) {
- return prev.then(function() {
- var p = ((i*100)/nFiles).toFixed(0)+'%';
- that.log.debug.ln('processing', file, p);
-
- return Q(generator.convertFile(file))
- .fail(function(err) {
- // Transform error message to signal file
- throw that.normError(err, {
- fileName: file
- });
- });
- });
- }, Q());
- });
- }
- })
-
- // Finish generation
- .then(function() {
- return that.callHook('finish:before');
- })
- .then(function() {
- return generator.finish();
- })
- .then(function() {
- return that.callHook('finish');
- })
- .then(function() {
- that.log.info.ln('generation is finished');
- });
+ }, Q());
};
-// Generate the output for a multilingual book
-Book.prototype.generateMultiLingual = function() {
+// Parse the whole book
+Book.prototype.parse = function() {
var that = this;
return Q()
- .then(function() {
- // Generate sub-books
- return _.reduce(that.books, function(prev, book) {
- return prev.then(function() {
- return book.generate(that.options.generator);
- });
- }, Q());
- });
-};
-
-// Extract files from ebook generated
-Book.prototype.generateFile = function(output, options) {
- var book = this;
-
- options = _.defaults(options || {}, {
- ebookFormat: path.extname(output).slice(1)
- });
- output = output || path.resolve(book.root, 'book.'+options.ebookFormat);
-
- return fs.tmp.dir()
- .then(function(tmpDir) {
- book.setOutput(tmpDir);
-
- return book.generate(options.ebookFormat)
- .then(function() {
- var copyFile = function(lang) {
- var _outputFile = output;
- var _tmpDir = tmpDir;
-
- if (lang) {
- _outputFile = _outputFile.slice(0, -path.extname(_outputFile).length)+'_'+lang+path.extname(_outputFile);
- _tmpDir = path.join(_tmpDir, lang);
- }
-
- book.log.debug.ln('copy ebook to', _outputFile);
- return fs.copy(
- path.join(_tmpDir, 'index.'+options.ebookFormat),
- _outputFile
- );
- };
-
- // Multi-langs book
- return Q()
- .then(function() {
- if (book.isMultilingual()) {
- return Q.all(
- _.map(book.langs, function(lang) {
- return copyFile(lang.lang);
- })
- )
- .thenResolve(book.langs.length);
- } else {
- return copyFile().thenResolve(1);
- }
- })
- .then(function(n) {
- book.log.info.ok(n+' file(s) generated');
-
- return fs.remove(tmpDir);
- });
- });
- });
-};
+ .then(this.prepareConfig)
+ .then(this.parseIgnoreRules)
-// Parse configuration
-Book.prototype.parseConfig = function() {
- var that = this;
-
- that.log.info('loading book configuration....');
- return that.config.load()
+ // Parse languages
.then(function() {
- that.log.info.ok();
- });
-};
-
-// Parse list of plugins
-Book.prototype.parsePlugins = function() {
- var that = this;
-
- // Load plugins
- return that.plugins.load(that.options.plugins)
- .then(function() {
- if (_.size(that.plugins.failed) > 0) return Q.reject(new Error('Error loading plugins: '+that.plugins.failed.join(',')+'. Run \'gitbook install\' to install plugins from NPM.'));
-
- that.log.info.ok(that.plugins.count()+' plugins loaded');
- that.log.debug.ln('normalize plugins list');
- });
-};
-
-// Parse readme to extract defaults title and description
-Book.prototype.parseReadme = function() {
- var that = this;
- var filename = that.config.getStructure('readme', true);
- that.log.debug.ln('start parsing readme:', filename);
-
- return that.findFile(filename)
- .then(function(readme) {
- if (!readme) throw 'No README file';
- if (!_.contains(that.files, readme.path)) throw 'README file is ignored';
-
- that.readmeFile = readme.path;
- that._defaultsStructure(that.readmeFile);
-
- that.log.debug.ln('readme located at', that.readmeFile);
- return that.template.renderFile(that.readmeFile)
- .then(function(content) {
- return readme.parser.readme(content)
- .fail(function(err) {
- throw that.normError(err, {
- name: err.name || 'Readme Parse Error',
- fileName: that.readmeFile
- });
- });
- });
+ return that.langs.load();
})
- .then(function(readme) {
- that.options.title = that.options.title || readme.title;
- that.options.description = that.options.description || readme.description;
- });
-};
-
-// Parse langs to extract list of sub-books
-Book.prototype.parseLangs = function() {
- var that = this;
-
- var filename = that.config.getStructure('langs', true);
- that.log.debug.ln('start parsing languages index:', filename);
-
- return that.findFile(filename)
- .then(function(langs) {
- if (!langs) return [];
-
- that.langsFile = langs.path;
- that._defaultsStructure(that.langsFile);
-
- that.log.debug.ln('languages index located at', that.langsFile);
- return that.template.renderFile(that.langsFile)
- .then(function(content) {
- return langs.parser.langs(content)
- .fail(function(err) {
- throw that.normError(err, {
- name: err.name || 'Langs Parse Error',
- fileName: that.langsFile
- });
- });
- });
- })
- .then(function(langs) {
- that.langs = langs;
- });
-};
+ .then(function() {
+ if (that.isMultilingual()) return;
-// Parse summary to extract list of chapters
-Book.prototype.parseSummary = function() {
- var that = this;
+ return Q()
+ .then(that.readme.load)
+ .then(function() {
+ if (that.readme.exists()) return;
- var filename = that.config.getStructure('summary', true);
- that.log.debug.ln('start parsing summary:', filename);
+ throw new Error('No README file (or is ignored)');
+ })
- return that.findFile(filename)
- .then(function(summary) {
- if (!summary) throw 'No SUMMARY file';
+ .then(that.summary.load)
+ .then(function() {
+ if (that.summary.exists()) return;
- // Remove the summary from the list of files to parse
- that.summaryFile = summary.path;
- that._defaultsStructure(that.summaryFile);
- that.files = _.without(that.files, that.summaryFile);
+ throw new Error('No SUMMARY file (or is ignored)');
+ })
- that.log.debug.ln('summary located at', that.summaryFile);
- return that.template.renderFile(that.summaryFile)
- .then(function(content) {
- return summary.parser.summary(content, {
- entryPoint: that.readmeFile,
- entryPointTitle: that.i18n('SUMMARY_INTRODUCTION'),
- files: that.files
- })
- .fail(function(err) {
- throw that.normError(err, {
- name: err.name || 'Summary Parse Error',
- fileName: that.summaryFile
- });
- });
- });
- })
- .then(function(summary) {
- that.summary = summary;
- that.navigation = parseNavigation(that.summary, that.files);
+ .then(that.glossary.load);
});
};
-// Parse glossary to extract terms
-Book.prototype.parseGlossary = function() {
- var that = this;
-
- var filename = that.config.getStructure('glossary', true);
- that.log.debug.ln('start parsing glossary: ', filename);
-
- return that.findFile(filename)
- .then(function(glossary) {
- if (!glossary) return [];
-
- // Remove the glossary from the list of files to parse
- that.glossaryFile = glossary.path;
- that._defaultsStructure(that.glossaryFile);
- that.files = _.without(that.files, that.glossaryFile);
-
- that.log.debug.ln('glossary located at', that.glossaryFile);
- return that.template.renderFile(that.glossaryFile)
- .then(function(content) {
- return glossary.parser.glossary(content)
- .fail(function(err) {
- throw that.normError(err, {
- name: err.name || 'Glossary Parse Error',
- fileName: that.glossaryFile
- });
- });
- });
- })
- .then(function(glossary) {
- that.glossary = glossary;
- });
+// Test if a file is ignored, return true if it is
+Book.prototype.isFileIgnored = function(filename) {
+ return this.ignore.filter([filename]).length == 0;
};
-// Parse a page
-Book.prototype.parsePage = function(filename, options) {
- var that = this, page = {};
- options = _.defaults(options || {}, {
- // Transform svg images
- convertImages: false,
-
- // Interpolate before templating
- interpolateTemplate: _.identity,
-
- // Interpolate after templating
- interpolateContent: _.identity
- });
-
- var interpolate = function(fn) {
- return Q(fn(page))
- .then(function(_page) {
- page = _page || page;
- });
- };
-
- that.log.debug.ln('start parsing file', filename);
-
- var extension = path.extname(filename);
- var filetype = parsers.get(extension);
-
- if (!filetype) return Q.reject(new Error('Can\'t parse file: '+filename));
-
- // Type of parser used
- page.type = filetype.name;
-
- // Path relative to book
- page.path = filename;
-
- // Path absolute in the system
- page.rawPath = path.resolve(that.root, filename);
-
- // Progress in the book
- page.progress = parseProgress(that.navigation, filename);
-
- that.log.debug.ln('render template', filename);
-
- // Read file content
- return that.readFile(page.path)
- .then(function(content) {
- page.content = content;
-
- return interpolate(options.interpolateTemplate);
- })
-
- // Prepare page markup
- .then(function() {
- return filetype.page.prepare(page.content)
- .then(function(content) {
- page.content = content;
- });
- })
-
- // Generate template
- .then(function() {
- return that.template.renderPage(page);
- })
-
- // Prepare and Parse markup
- .then(function(content) {
- page.content = content;
-
- that.log.debug.ln('use file parser', filetype.name, 'for', filename);
- return filetype.page(page.content);
- })
-
- // Post process sections
- .then(function(_page) {
- return _.reduce(_page.sections, function(prev, section) {
- return prev.then(function(_sections) {
- return that.template.postProcess(section.content || '')
- .then(function(content) {
- section.content = content;
- return _sections.concat([section]);
- });
- });
- }, Q([]));
- })
-
- // Prepare html
- .then(function(_sections) {
- return pageUtil.normalize(_sections, {
- book: that,
- convertImages: options.convertImages,
- input: filename,
- navigation: that.navigation,
- base: path.dirname(filename) || './',
- output: path.dirname(filename) || './',
- glossary: that.glossary
- });
- })
-
- // Interpolate output
- .then(function(_sections) {
- page.sections = _sections;
- return interpolate(options.interpolateContent);
- })
-
- .then(function() {
- return page;
- });
+// Read a file in the book, throw error if ignored
+Book.prototype.readFile = function(filename) {
+ if (this.isFileIgnored(filename)) return Q.reject(new Error('File "'+filename+'" is ignored'));
+ return this.fs.readAsString(this.resolve(filename));
};
-// Find file that can be parsed with a specific filename
-Book.prototype.findFile = function(filename) {
+// Find a parsable file using a filename
+Book.prototype.findParsableFile = function(filename) {
var that = this;
var ext = path.extname(filename);
@@ -614,7 +160,7 @@ Book.prototype.findFile = function(filename) {
var filepath = basename+ext;
- return fs.findFile(that.root, filepath)
+ return that.fs.findFile(that.root, filepath)
.then(function(realFilepath) {
if (!realFilepath) return null;
@@ -627,197 +173,15 @@ Book.prototype.findFile = function(filename) {
}, Q(null));
};
-// Format a string using a specific markup language
-Book.prototype.formatString = function(extension, content) {
- return Q()
- .then(function() {
- var filetype = parsers.get(extension);
- if (!filetype) throw new Error('Filetype doesn\'t exist: '+filetype);
-
- return filetype.page(content);
- })
-
- // Merge sections
- .then(function(page) {
- return _.reduce(page.sections, function(content, section) {
- return content + section.content;
- }, '');
- });
-};
-
-// Check if a file exists in the book
-Book.prototype.fileExists = function(filename) {
- return fs.exists(
- 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(
- this.resolve(filename),
- { encoding: 'utf8' }
- );
-};
-
-// Return stat for a file
-Book.prototype.statFile = function(filename) {
- return fs.stat(this.resolve(filename));
-};
-
-// List all files in the book
-Book.prototype.listAllFiles = function() {
- var that = this;
-
- return fs.list(this.root, {
- ignoreFiles: ['.ignore', '.gitignore', '.bookignore'],
- ignoreRules: [
- // Skip Git stuff
- '.git/',
- '.gitignore',
-
- // Skip OS X meta data
- '.DS_Store',
-
- // Skip stuff installed by plugins
- 'node_modules',
-
- // Skip book outputs
- '_book',
- '*.pdf',
- '*.epub',
- '*.mobi',
-
- // Skip config files
- '.ignore',
- '.bookignore',
- 'book.json',
- ]
- })
- .then(function(_files) {
- that.files = _files;
- });
+// Return true if book is associated to a language
+Book.prototype.isLanguageBook = function() {
+ return Boolean(this.parent);
};
+Book.prototype.isSubBook = Book.prototype.isLanguageBook;
-// Return true if the book is a multilingual book
+// Return true if the book is main instance of a multilingual book
Book.prototype.isMultilingual = function() {
- return this.books.length > 0;
-};
-
-// Return root of the parent
-Book.prototype.parentRoot = function() {
- if (this.parent) return this.parent.parentRoot();
- return this.root;
-};
-
-// Return true if it's a sub-book
-Book.prototype.isSubBook = function() {
- return !!this.parent;
-};
-
-// Test if the file is the entry point
-Book.prototype.isEntryPoint = function(fp) {
- return fp == this.readmeFile;
-};
-
-// 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)));
-};
-
-// Resolve a path in the book output
-// Enforce that the output path in the output folder
-Book.prototype.resolveOutput = function() {
- return pathUtil.resolveInRoot.apply(null, [this.options.output].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
-Book.prototype.contentPath = function(link) {
- if (
- path.basename(link, path.extname(link)) == 'README' ||
- link == this.readmeFile
- ) {
- link = path.join(path.dirname(link), 'index'+path.extname(link));
- }
-
- link = links.changeExtension(link, '.html');
- return link;
-};
-
-// Normalize a link to .html and convert README -> index
-Book.prototype.contentLink = function(link) {
- return links.normalize(this.contentPath(link));
-};
-
-// Default structure paths to an extension
-Book.prototype._defaultsStructure = function(filename) {
- var that = this;
- var extension = path.extname(filename);
-
- that.readmeFile = that.readmeFile || that.config.getStructure('readme')+extension;
- that.summaryFile = that.summaryFile || that.config.getStructure('summary')+extension;
- that.glossaryFile = that.glossaryFile || that.config.getStructure('glossary')+extension;
- that.langsFile = that.langsFile || that.config.getStructure('langs')+extension;
-};
-
-// Change output path
-Book.prototype.setOutput = function(p) {
- var that = this;
- this.options.output = path.resolve(p);
-
- _.each(this.books, function(book) {
- book.setOutput(path.join(that.options.output, book.options.language));
- });
-};
-
-// Translate a strign according to the book language
-Book.prototype.i18n = function() {
- var args = Array.prototype.slice.call(arguments);
- return i18n.__.apply({}, [this.config.normalizeLanguage()].concat(args));
-};
-
-// Normalize error
-Book.prototype.normError = function(err, opts, defs) {
- if (_.isString(err)) err = new Error(err);
-
- // Extend err
- _.extend(err, opts || {});
- _.defaults(err, defs || {});
-
- err.lineNumber = err.lineNumber || err.lineno;
- err.columnNumber = err.columnNumber || err.colno;
-
- err.toString = function() {
- var attributes = [];
-
- if (this.fileName) attributes.push('In file \''+this.fileName+'\'');
- if (this.lineNumber) attributes.push('Line '+this.lineNumber);
- if (this.columnNumber) attributes.push('Column '+this.columnNumber);
- return (this.name || 'Error')+': '+this.message+((attributes.length > 0)? ' ('+attributes.join(', ')+')' : '');
- };
-
- return err;
-};
-
-// Call a hook in plugins
-Book.prototype.callHook = function(name, data) {
- return this.plugins.hook(name, data);
+ return this.langs.count() > 0;
};
-module.exports= Book;
+module.exports = Book;
diff --git a/lib/config.js b/lib/config.js
new file mode 100644
index 0000000..c6d05da
--- /dev/null
+++ b/lib/config.js
@@ -0,0 +1,110 @@
+var Q = require('q');
+var _ = require('lodash');
+var semver = require('semver');
+var path = require('path');
+
+var gitbook = require('./gitbook');
+var configDefault = require('./config_default');
+
+/*
+Config is an interface for the book's configuration stored in "book.json" (or "book.js")
+*/
+
+function Config(book, baseConfig) {
+ this.book = book;
+ this.fs = book.fs;
+ this.log = book.log;
+
+ this.replace(baseConfig || {});
+}
+
+// Load configuration of the book
+// and verify that the configuration is satisfying
+Config.prototype.load = function() {
+ var that = this;
+
+ this.log.debug.ln('loading configuration');
+ return this.fs.loadAsObject(this.book.resolve('book'))
+ .fail(function(err) {
+ if (err.code != 'MODULE_NOT_FOUND') throw(err);
+ else return Q({});
+ })
+ .then(function(_config) {
+ return that.replace(_config);
+ })
+ .then(function() {
+ if (!that.book.isLanguageBook()) {
+ if (!gitbook.satisfies(that.options.gitbook)) {
+ throw new Error('GitBook version doesn\'t satisfy version required by the book: '+that.options.gitbook);
+ }
+ if (that.options.gitbook != '*' && !semver.satisfies(semver.inc(gitbook.version, 'patch'), that.options.gitbook)) {
+ that.log.warn.ln('gitbook version specified in your book.json might be too strict for future patches, \''+(_.first(gitbook.version.split('.'))+'.x.x')+'\' is more adequate');
+ }
+ }
+
+ //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 || '', false);
+ //that.options.plugins = _.union(that.options.plugins, that.options.defaultsPlugins);
+ //that.options.plugins = _.uniq(that.options.plugins, 'name');
+
+ // Default value for text direction (from language)
+ /*if (!that.options.direction) {
+ var lang = i18n.getCatalog(that.options.language);
+ if (lang) that.options.direction = lang.direction;
+ }*/
+
+ that.options.gitbook = gitbook.version;
+ });
+};
+
+// Replace the whole configuration
+Config.prototype.replace = function(options) {
+ var that = this;
+
+ this.options = _.cloneDeep(configDefault);
+ this.options = _.merge(this.options, options || {});
+
+ // options.input == book.root
+ Object.defineProperty(this.options, 'input', {
+ get: function () {
+ return that.book.root;
+ }
+ });
+
+ // options.originalInput == book.parent.root
+ Object.defineProperty(this.options, 'originalInput', {
+ get: function () {
+ return that.book.parent? that.book.parent.root : undefined;
+ }
+ });
+
+ // options.originalOutput == book.parent.options.output
+ Object.defineProperty(this.options, 'originalOutput', {
+ get: function () {
+ return that.book.parent? that.book.parent.options.output : undefined;
+ }
+ });
+};
+
+// Return path to a structure file
+// Strip the extension by default
+Config.prototype.getStructure = function(name, dontStripExt) {
+ var filename = this.options.structure[name];
+ if (dontStripExt) return filename;
+
+ filename = filename.split('.').slice(0, -1).join('.');
+ return filename;
+};
+
+// Return a configuration using a key and a default value
+Config.prototype.get = function(key, def) {
+ return _.get(this.options, key, def);
+};
+
+// Update a configuration
+Config.prototype.set = function(key, value) {
+ return _.set(this.options, key, value);
+};
+
+module.exports = Config;
diff --git a/lib/configuration.js b/lib/configuration.js
deleted file mode 100644
index dd95585..0000000
--- a/lib/configuration.js
+++ /dev/null
@@ -1,210 +0,0 @@
-var _ = require('lodash');
-var Q = require('q');
-var path = require('path');
-var semver = require('semver');
-
-var pkg = require('../package.json');
-var i18n = require('./utils/i18n');
-var version = require('./version');
-
-var DEFAULT_CONFIG = require('./config_default');
-
-// Default plugins added to each books
-var DEFAULT_PLUGINS = ['highlight', 'search', 'sharing', 'fontsettings'];
-
-// Check if a plugin is a default plugin
-// Plugin should be in the list
-// And version from book.json specified for this plugin should be satisfied
-function isDefaultPlugin(name, version) {
- if (!_.contains(DEFAULT_PLUGINS, name)) return false;
-
- try {
- var pluginPkg = require('gitbook-plugin-'+name+'/package.json');
- return semver.satisfies(pluginPkg.version, version || '*');
- } catch(e) {
- return false;
- }
-}
-
-// Normalize a list of plugins to use
-function normalizePluginsList(plugins, addDefaults) {
- // Normalize list to an array
- plugins = _.isString(plugins) ? plugins.split(',') : (plugins || []);
-
- // Remove empty parts
- plugins = _.compact(plugins);
-
- // Divide as {name, version} to handle format like 'myplugin@1.0.0'
- plugins = _.map(plugins, function(plugin) {
- if (plugin.name) return plugin;
-
- var parts = plugin.split('@');
- var name = parts[0];
- var version = parts[1];
- return {
- 'name': name,
- 'version': version, // optional
- 'isDefault': isDefaultPlugin(name, version)
- };
- });
-
- // List plugins to remove
- var toremove = _.chain(plugins)
- .filter(function(plugin) {
- return plugin.name.length > 0 && plugin.name[0] == '-';
- })
- .map(function(plugin) {
- return plugin.name.slice(1);
- })
- .value();
-
- // Merge with defaults
- if (addDefaults !== false) {
- _.each(DEFAULT_PLUGINS, function(plugin) {
- if (_.find(plugins, { name: plugin })) {
- return;
- }
-
- plugins.push({
- 'name': plugin,
- 'isDefault': true
- });
- });
- }
-
- // Remove plugin that start with '-'
- plugins = _.filter(plugins, function(plugin) {
- return !_.contains(toremove, plugin.name) && !(plugin.name.length > 0 && plugin.name[0] == '-');
- });
-
- // Remove duplicates
- plugins = _.uniq(plugins, 'name');
-
- return plugins;
-}
-
-var Configuration = function(book, options) {
- this.book = book;
- this.replace(options);
-};
-
-// Read and parse the configuration
-Configuration.prototype.load = function() {
- var that = this;
-
- return Q()
- .then(function() {
- var configPath, _config;
-
- try {
- configPath = require.resolve(
- that.book.resolve(that.options.configFile)
- );
-
- // Invalidate node.js cache for livreloading
- delete require.cache[configPath];
-
- _config = require(configPath);
- that.options = _.merge(
- that.options,
- _.omit(_config, 'configFile', 'defaultsPlugins', 'generator', 'extension')
- );
- }
- catch(err) {
- if (err instanceof SyntaxError) return Q.reject(err);
- return Q();
- }
- })
- .then(function() {
- if (!that.book.isSubBook()) {
- if (!version.satisfies(that.options.gitbook)) {
- throw new Error('GitBook version doesn\'t satisfy version required by the book: '+that.options.gitbook);
- }
- 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'));
- that.options.plugins = normalizePluginsList(that.options.plugins);
- that.options.defaultsPlugins = normalizePluginsList(that.options.defaultsPlugins || '', false);
- that.options.plugins = _.union(that.options.plugins, that.options.defaultsPlugins);
- that.options.plugins = _.uniq(that.options.plugins, 'name');
-
- // Default value for text direction (from language)
- if (!that.options.direction) {
- var lang = i18n.getCatalog(that.options.language);
- if (lang) that.options.direction = lang.direction;
- }
-
- that.options.gitbook = pkg.version;
- });
-};
-
-// Extend the configuration
-Configuration.prototype.extend = function(options) {
- _.extend(this.options, options);
-};
-
-// Replace the whole configuration
-Configuration.prototype.replace = function(options) {
- var that = this;
-
- this.options = _.cloneDeep(DEFAULT_CONFIG);
- this.options = _.merge(this.options, options || {});
-
- // options.input == book.root
- Object.defineProperty(this.options, 'input', {
- get: function () {
- return that.book.root;
- }
- });
-
- // options.originalInput == book.parent.root
- Object.defineProperty(this.options, 'originalInput', {
- get: function () {
- return that.book.parent? that.book.parent.root : undefined;
- }
- });
-
- // options.originalOutput == book.parent.options.output
- Object.defineProperty(this.options, 'originalOutput', {
- get: function () {
- return that.book.parent? that.book.parent.options.output : undefined;
- }
- });
-};
-
-// Dump configuration as json object
-Configuration.prototype.dump = function() {
- return _.cloneDeep(this.options);
-};
-
-// Get structure file
-Configuration.prototype.getStructure = function(name, dontStripExt) {
- var filename = this.options.structure[name];
- if (dontStripExt) return filename;
-
- filename = filename.split('.').slice(0, -1).join('.');
- return filename;
-};
-
-// Return normalized language
-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);
-};
-
-// Update a configuration
-Configuration.prototype.set = function(key, value) {
- return _.set(this.options, key, value);
-};
-
-// Default configuration
-Configuration.DEFAULT = DEFAULT_CONFIG;
-
-module.exports= Configuration;
diff --git a/lib/conrefs_loader.js b/lib/conrefs_loader.js
deleted file mode 100644
index 255bf06..0000000
--- a/lib/conrefs_loader.js
+++ /dev/null
@@ -1,73 +0,0 @@
-var _ = require('lodash');
-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, opts) {
- this.opts = _.defaults(opts || {}, {
- interpolate: _.identity
- });
- 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 that.opts.interpolate(filepath, source.toString());
- })
- .then(function(source) {
- return {
- src: source,
- path: filepath,
-
- // We disable cache sincde content is modified (shortcuts, ...)
- noCache: true
- };
- });
- })
- .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/fs/index.js b/lib/fs/index.js
new file mode 100644
index 0000000..1961b07
--- /dev/null
+++ b/lib/fs/index.js
@@ -0,0 +1,148 @@
+var Q = require('q');
+var _ = require('lodash');
+var path = require('path');
+var Buffer = require('buffer').Buffer;
+var destroy = require('destroy');
+
+/*
+A filesystem is an interface to read/write files
+GitBook can works with a virtual filesystem, for example in the browser.
+*/
+
+// .readdir return files/folder as a list of string, folder ending with '/'
+function pathIsFolder(filename) {
+ return _.last(filename) == '/' || _.last(filename) == '\\';
+}
+
+
+function FS() {
+
+}
+
+// Check if a file exists, run a Promise(true) if that's the case, Promise(false) otherwise
+FS.prototype.exists = function(filename) {
+ // To implement for each fs
+};
+
+// Read a file and returns a promise with the content as a buffer
+FS.prototype.read = function(filename) {
+ // To implement for each fs
+};
+
+// Write a file and returns a promise
+FS.prototype.write = function(filename, buffer) {
+ // To implement for each fs
+};
+
+// List files/directories in a directory
+FS.prototype.readdir = function(folder) {
+ // To implement for each fs
+};
+
+
+// These methods don't require to be redefined, by default it uses .exists, .read, .write, .list
+// For optmization, it can be redefined:
+
+// List files in a directory
+FS.prototype.listFiles = function(folder) {
+ return this.readdir(folder)
+ .then(function(files) {
+ return _.reject(files, pathIsFolder);
+ });
+};
+
+// List all files in the fs
+FS.prototype.listAllFiles = function(folder) {
+ var that = this;
+
+ return this.readdir(folder)
+ .then(function(files) {
+ return _.reduce(files, function(prev, file) {
+ return prev.then(function(output) {
+ var isDirectory = pathIsFolder(file);
+
+ if (!isDirectory) {
+ output.push(file);
+ return output;
+ } else {
+ return that.listAllFiles(path.join(folder, file))
+ .then(function(files) {
+ return output.concat(_.map(files, function(_file) {
+ return path.join(file, _file);
+ }));
+ });
+ }
+ });
+ }, Q([]));
+ });
+};
+
+// Read a file as a string (utf-8)
+FS.prototype.readAsString = function(filename) {
+ return this.read(filename)
+ .then(function(buf) {
+ return buf.toString('utf-8');
+ });
+};
+
+// Write a stream to a file and returns a promise
+FS.prototype.writeStream = function(filename, stream) {
+ var bufs = [];
+ var d = Q.defer();
+
+ var cleanup = function() {
+ destroy(stream);
+ stream.removeAllListeners();
+ };
+
+ stream.on('data', function(d) {
+ bufs.push(d);
+ });
+
+ stream.on('error', function(err) {
+ cleanup();
+
+ d.reject(err);
+ });
+
+ stream.on('end', function(){
+ cleanup();
+
+ var buf = Buffer.concat(bufs);
+ d.resolve(buf);
+ });
+
+ return d.promise;
+};
+
+// Copy a file
+FS.prototype.copy = function(from, to) {
+ var that = this;
+
+ return this.read(from)
+ .then(function(buf) {
+ return that.write(to, buf);
+ });
+};
+
+// Find a file in a folder (case incensitive)
+// Return the real filename
+FS.prototype.findFile = function findFile(root, filename) {
+ return this.listFiles(root)
+ .then(function(files) {
+ return _.find(files, function(file) {
+ return (file.toLowerCase() == filename.toLowerCase());
+ });
+ });
+};
+
+// Load a JSON file
+// By default, fs only supports JSON
+FS.prototype.loadAsObject = function(filename) {
+ return this.readAsString(filename)
+ .then(function(str) {
+ return JSON.parse(str);
+ });
+};
+
+module.exports = FS;
diff --git a/lib/fs/node.js b/lib/fs/node.js
new file mode 100644
index 0000000..0c470d7
--- /dev/null
+++ b/lib/fs/node.js
@@ -0,0 +1,72 @@
+var Q = require('q');
+var _ = require('lodash');
+var util = require('util');
+var path = require('path');
+var fs = require('fs');
+
+var BaseFS = require('./');
+
+function NodeFS() {
+ BaseFS.call(this);
+}
+util.inherits(NodeFS, BaseFS);
+
+// Check if a file exists, run a Promise(true) if that's the case, Promise(false) otherwise
+NodeFS.prototype.exists = function(filename) {
+ var d = Q.defer();
+
+ fs.exists(filename, function(exists) {
+ d.resolve(exists);
+ });
+
+ return d.promise;
+};
+
+// Read a file and returns a promise with the content as a buffer
+NodeFS.prototype.read = function(filename) {
+ return Q.nfcall(fs.readFile, filename);
+};
+
+// Write a file and returns a promise
+NodeFS.prototype.write = function(filename, buffer) {
+ return Q.nfcall(fs.writeFile, filename, buffer);
+};
+
+// List files in a directory
+NodeFS.prototype.readdir = function(folder) {
+ return Q.nfcall(fs.readdir, folder)
+ .then(function(files) {
+ return _.chain(files)
+ .map(function(file) {
+ if (file == '.' || file == '..') return;
+
+ var stat = fs.statSync(path.join(folder, file));
+ if (stat.isDirectory()) file = file + path.sep;
+ return file;
+ })
+ .compact()
+ .value();
+ });
+};
+
+// Load a JSON/JS file
+NodeFS.prototype.loadAsObject = function(filename) {
+ return Q()
+ .then(function() {
+ var jsFile;
+
+ try {
+ jsFile = require.resolve(filename);
+
+ // Invalidate node.js cache for livreloading
+ delete require.cache[jsFile];
+
+ return require(jsFile);
+ }
+ catch(err) {
+ return Q.reject(err);
+ }
+ });
+};
+
+module.exports = NodeFS;
diff --git a/lib/generator.js b/lib/generator.js
deleted file mode 100644
index 4e280d8..0000000
--- a/lib/generator.js
+++ /dev/null
@@ -1,76 +0,0 @@
-var _ = require('lodash');
-var path = require('path');
-var Q = require('q');
-var fs = require('./utils/fs');
-
-var BaseGenerator = function(book) {
- this.book = book;
-
- Object.defineProperty(this, 'options', {
- get: function () {
- return this.book.options;
- }
- });
-
- _.bindAll(this);
-};
-
-BaseGenerator.prototype.callHook = function(name, data) {
- return this.book.callHook(name, data);
-};
-
-// Prepare the genertor
-BaseGenerator.prototype.prepare = function() {
- var that = this;
-
- return that.callHook('init');
-};
-
-// Write a parsed file to the output
-BaseGenerator.prototype.convertFile = function(input) {
- return Q.reject(new Error('Could not convert '+input));
-};
-
-// Copy file to the output (non parsable)
-BaseGenerator.prototype.transferFile = function(input) {
- return fs.copy(
- this.book.resolve(input),
- path.join(this.options.output, input)
- );
-};
-
-// Copy a folder to the output
-BaseGenerator.prototype.transferFolder = function(input) {
- return fs.mkdirp(
- path.join(this.book.options.output, input)
- );
-};
-
-// Copy the cover picture
-BaseGenerator.prototype.copyCover = function() {
- var that = this;
-
- return Q.all([
- 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
- if (!that.book.isSubBook()) return;
-
- return Q.all([
- fs.copy(path.join(that.book.parentRoot(), 'cover.jpg'), path.join(that.options.output, 'cover.jpg')),
- fs.copy(path.join(that.book.parentRoot(), 'cover_small.jpg'), path.join(that.options.output, 'cover_small.jpg'))
- ]);
- })
- .fail(function() {
- return Q();
- });
-};
-
-// At teh end of the generation
-BaseGenerator.prototype.finish = function() {
- return Q.reject(new Error('Could not finish generation'));
-};
-
-module.exports = BaseGenerator;
diff --git a/lib/generators/ebook.js b/lib/generators/ebook.js
deleted file mode 100644
index ff804c6..0000000
--- a/lib/generators/ebook.js
+++ /dev/null
@@ -1,172 +0,0 @@
-var util = require('util');
-var path = require('path');
-var Q = require('q');
-var _ = require('lodash');
-var juice = require('juice');
-var exec = require('child_process').exec;
-
-var fs = require('../utils/fs');
-var stringUtils = require('../utils/string');
-var BaseGenerator = require('./website');
-
-var Generator = function(book, format) {
- BaseGenerator.apply(this, arguments);
-
- // eBook format
- this.ebookFormat = format;
-
- // Resources namespace
- this.namespace = 'ebook';
-
- // Styles to use
- this.styles = _.compact(['print', 'ebook', this.ebookFormat]);
-
- // Convert images (svg -> png)
- this.convertImages = true;
-};
-util.inherits(Generator, BaseGenerator);
-
-Generator.prototype.prepareTemplates = function() {
- this.templates.page = this.book.plugins.template('ebook:page') || path.resolve(this.options.theme, 'templates/ebook/page.html');
- this.templates.summary = this.book.plugins.template('ebook:summary') || path.resolve(this.options.theme, 'templates/ebook/summary.html');
- this.templates.glossary = this.book.plugins.template('ebook:glossary') || path.resolve(this.options.theme, 'templates/ebook/glossary.html');
-
- return Q();
-};
-
-// Generate table of contents
-Generator.prototype.writeSummary = function() {
- var that = this;
-
- that.book.log.info.ln('write SUMMARY.html');
- return this._writeTemplate(this.templates.summary, {}, path.join(this.options.output, 'SUMMARY.html'));
-};
-
-// Return template for footer/header with inlined css
-Generator.prototype.getPDFTemplate = function(id) {
- var tpl = this.options.pdf[id+'Template'];
- var defaultTpl = path.resolve(this.options.theme, 'templates/ebook/'+id+'.html');
- var defaultCSS = path.resolve(this.options.theme, 'assets/ebook/pdf.css');
-
- // Default template from theme
- if (!tpl && fs.existsSync(defaultTpl)) {
- tpl = fs.readFileSync(defaultTpl, { encoding: 'utf-8' });
- }
-
- // Inline CSS using juice
- var stylesheets = [];
-
- // From theme
- if (fs.existsSync(defaultCSS)) {
- stylesheets.push(fs.readFileSync(defaultCSS, { encoding: 'utf-8' }));
- }
-
- // Custom PDF style
- if (this.styles.pdf) {
- stylesheets.push(fs.readFileSync(this.book.resolveOutput(this.styles.pdf), { encoding: 'utf-8' }));
- }
-
- tpl = juice(tpl, {
- extraCss: stylesheets.join('\n\n')
- });
-
- return tpl;
-};
-
-Generator.prototype.finish = function() {
- var that = this;
-
- return Q()
- .then(this.copyAssets)
- .then(this.copyCover)
- .then(this.writeGlossary)
- .then(this.writeSummary)
- .then(function() {
- if (!that.ebookFormat) return Q();
-
- if (!that.options.cover && fs.existsSync(path.join(that.options.output, 'cover.jpg'))) {
- that.options.cover = path.join(that.options.output, 'cover.jpg');
- }
-
- var d = Q.defer();
-
- var _options = {
- '--cover': that.options.cover,
- '--title': that.options.title,
- '--comments': that.options.description,
- '--isbn': that.options.isbn,
- '--authors': that.options.author,
- '--language': that.options.language,
- '--book-producer': 'GitBook',
- '--publisher': 'GitBook',
- '--chapter': 'descendant-or-self::*[contains(concat(\' \', normalize-space(@class), \' \'), \' book-chapter \')]',
- '--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 \')]',
- '--no-chapters-in-toc': true,
- '--max-levels': '1',
- '--breadth-first': true
- };
-
- if (that.ebookFormat == 'pdf') {
- 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),
- '--margin-bottom': String(pdfOptions.margin.bottom),
- '--pdf-default-font-size': String(pdfOptions.fontSize),
- '--pdf-mono-font-size': String(pdfOptions.fontSize),
- '--paper-size': String(pdfOptions.paperSize),
- '--pdf-page-numbers': Boolean(pdfOptions.pageNumbers),
- '--pdf-header-template': that.getPDFTemplate('header'),
- '--pdf-footer-template': that.getPDFTemplate('footer'),
- '--pdf-sans-family': String(pdfOptions.fontFamily)
- });
- } else if (that.ebookFormat == 'epub') {
- _.extend(_options, {
- '--dont-split-on-page-breaks': true
- });
- }
-
- var command = [
- 'ebook-convert',
- path.join(that.options.output, 'SUMMARY.html'),
- path.join(that.options.output, 'index.'+that.ebookFormat),
- stringUtils.optionsToShellArgs(_options)
- ].join(' ');
-
- that.book.log.info('start conversion to', that.ebookFormat, '....');
-
- var child = exec(command, function (error, stdout) {
- if (error) {
- that.book.log.info.fail();
-
- if (error.code == 127) {
- error.message = 'Need to install ebook-convert from Calibre';
- } else {
- error.message = error.message + ' '+stdout;
- }
- return d.reject(error);
- }
-
- that.book.log.info.ok();
- d.resolve();
- });
-
- child.stdout.on('data', function (data) {
- that.book.log.debug(data);
- });
-
- child.stderr.on('data', function (data) {
- that.book.log.debug(data);
- });
-
- return d.promise;
- });
-};
-
-module.exports = Generator;
diff --git a/lib/generators/index.js b/lib/generators/index.js
deleted file mode 100644
index 068d0d9..0000000
--- a/lib/generators/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-var _ = require("lodash");
-var EbookGenerator = require("./ebook");
-
-module.exports = {
- json: require("./json"),
- website: require("./website"),
- ebook: EbookGenerator,
- pdf: _.partialRight(EbookGenerator, "pdf"),
- mobi: _.partialRight(EbookGenerator, "mobi"),
- epub: _.partialRight(EbookGenerator, "epub")
-};
diff --git a/lib/generators/json.js b/lib/generators/json.js
deleted file mode 100644
index 37ffa0b..0000000
--- a/lib/generators/json.js
+++ /dev/null
@@ -1,76 +0,0 @@
-var util = require('util');
-var path = require('path');
-var Q = require('q');
-var _ = require('lodash');
-
-var fs = require('../utils/fs');
-var BaseGenerator = require('../generator');
-var links = require('../utils/links');
-
-var Generator = function() {
- BaseGenerator.apply(this, arguments);
-};
-util.inherits(Generator, BaseGenerator);
-
-// Ignore some methods
-Generator.prototype.transferFile = function() { };
-
-// Convert an input file
-Generator.prototype.convertFile = function(input) {
- var that = this;
-
- return that.book.parsePage(input)
- .then(function(page) {
- var json = {
- progress: page.progress,
- sections: page.sections
- };
-
- var output = links.changeExtension(page.path, '.json');
- output = path.join(that.options.output, output);
-
- return fs.writeFile(
- output,
- JSON.stringify(json, null, 4)
- );
- });
-};
-
-// Finish generation
-Generator.prototype.finish = function() {
- return this.writeReadme();
-};
-
-// Write README.json
-Generator.prototype.writeReadme = function() {
- var that = this;
- var mainLang, langs, readme;
-
- return Q()
- .then(function() {
- langs = that.book.langs;
- mainLang = langs.length > 0? _.first(langs).lang : null;
-
- readme = links.changeExtension(that.book.readmeFile, '.json');
-
- // Read readme from main language
- return fs.readFile(
- mainLang? path.join(that.options.output, mainLang, readme) : path.join(that.options.output, readme)
- );
- })
- .then(function(content) {
- // Extend it with infos about the languages
- var json = JSON.parse(content);
- _.extend(json, {
- langs: langs
- });
-
- // Write it as README.json
- return fs.writeFile(
- path.join(that.options.output, 'README.json'),
- JSON.stringify(json, null, 4)
- );
- });
-};
-
-module.exports = Generator;
diff --git a/lib/generators/website.js b/lib/generators/website.js
deleted file mode 100644
index efb7c0f..0000000
--- a/lib/generators/website.js
+++ /dev/null
@@ -1,268 +0,0 @@
-var util = require('util');
-var path = require('path');
-var Q = require('q');
-var _ = require('lodash');
-
-var nunjucks = require('nunjucks');
-var AutoEscapeExtension = require('nunjucks-autoescape')(nunjucks);
-var FilterExtension = require('nunjucks-filter')(nunjucks);
-
-var fs = require('../utils/fs');
-var BaseGenerator = require('../generator');
-var links = require('../utils/links');
-var i18n = require('../utils/i18n');
-
-var pkg = require('../../package.json');
-
-var Generator = function() {
- BaseGenerator.apply(this, arguments);
-
- // Revision
- this.revision = new Date();
-
- // Resources namespace
- this.namespace = 'website';
-
- // Style to integrates in the output
- this.styles = ['website'];
-
- // Convert images (svg -> png)
- this.convertImages = false;
-
- // Templates
- this.templates = {};
-};
-util.inherits(Generator, BaseGenerator);
-
-// Prepare the genertor
-Generator.prototype.prepare = function() {
- return BaseGenerator.prototype.prepare.apply(this)
- .then(this.prepareStyles)
- .then(this.prepareTemplates)
- .then(this.prepareTemplateEngine);
-};
-
-// Prepare all styles
-Generator.prototype.prepareStyles = function() {
- var that = this;
-
- this.styles = _.chain(this.styles)
- .map(function(style) {
- var stylePath = that.options.styles[style];
- var styleExists = (
- fs.existsSync(that.book.resolveOutput(stylePath)) ||
- fs.existsSync(that.book.resolve(stylePath))
- );
-
- if (stylePath && styleExists) {
- return [style, stylePath];
- }
- return null;
- })
- .compact()
- .object()
- .value();
-
- return Q();
-};
-
-// Prepare templates
-Generator.prototype.prepareTemplates = function() {
- this.templates.page = this.book.plugins.template('site:page') || path.resolve(this.options.theme, 'templates/website/page.html');
- this.templates.langs = this.book.plugins.template('site:langs') || path.resolve(this.options.theme, 'templates/website/langs.html');
- this.templates.glossary = this.book.plugins.template('site:glossary') || path.resolve(this.options.theme, 'templates/website/glossary.html');
-
- return Q();
-};
-
-// Prepare template engine
-Generator.prototype.prepareTemplateEngine = function() {
- var that = this;
-
- return Q()
- .then(function() {
- var language = that.book.config.normalizeLanguage();
-
- if (!i18n.hasLocale(language)) {
- that.book.log.warn.ln('Language "'+language+'" is not available as a layout locales (en, '+i18n.getLocales().join(', ')+')');
- }
-
- var folders = _.chain(that.templates)
- .values()
- .map(path.dirname)
- .uniq()
- .value();
-
- that.env = new nunjucks.Environment(
- new nunjucks.FileSystemLoader(folders),
- {
- autoescape: true
- }
- );
-
- // Add filter
- that.env.addFilter('contentLink', that.book.contentLink.bind(that.book));
- that.env.addFilter('lvl', function(lvl) {
- return lvl.split('.').length;
- });
-
- // Add extension
- that.env.addExtension('AutoEscapeExtension', new AutoEscapeExtension(that.env));
- that.env.addExtension('FilterExtension', new FilterExtension(that.env));
- });
-};
-
-// Finis generation
-Generator.prototype.finish = function() {
- return this.copyAssets()
- .then(this.copyCover)
- .then(this.writeGlossary)
- .then(this.writeLangsIndex);
-};
-
-// Convert an input file
-Generator.prototype.convertFile = function(input) {
- var that = this;
-
- return that.book.parsePage(input, {
- convertImages: that.convertImages,
- interpolateTemplate: function(page) {
- return that.callHook('page:before', page);
- },
- interpolateContent: function(page) {
- return that.callHook('page', page);
- }
- })
- .then(function(page) {
- var relativeOutput = that.book.contentPath(page.path);
- var output = path.join(that.options.output, relativeOutput);
-
- var basePath = path.relative(path.dirname(output), that.options.output) || '.';
- if (process.platform === 'win32') basePath = basePath.replace(/\\/g, '/');
-
- that.book.log.debug.ln('write parsed file', page.path, 'to', relativeOutput);
-
- return that._writeTemplate(that.templates.page, {
- progress: page.progress,
-
- _input: page.path,
- content: page.sections,
-
- basePath: basePath,
- staticBase: links.join(basePath, 'gitbook')
- }, output);
- });
-};
-
-// Write the index for langs
-Generator.prototype.writeLangsIndex = function() {
- if (!this.book.langs.length) return Q();
-
- return this._writeTemplate(this.templates.langs, {
- langs: this.book.langs
- }, path.join(this.options.output, 'index.html'));
-};
-
-// Write glossary
-Generator.prototype.writeGlossary = function() {
- // No glossary
- if (this.book.glossary.length === 0) return Q();
-
- return this._writeTemplate(this.templates.glossary, {}, path.join(this.options.output, 'GLOSSARY.html'));
-};
-
-// Convert a page into a normalized data set
-Generator.prototype.normalizePage = function(page) {
- var that = this;
-
- var _callHook = function(name) {
- return that.callHook(name, page)
- .then(function(_page) {
- page = _page;
- return page;
- });
- };
-
- return Q()
- .then(function() {
- return _callHook('page');
- })
- .then(function() {
- return page;
- });
-};
-
-// Generate a template
-Generator.prototype._writeTemplate = function(tpl, options, output, interpolate) {
- var that = this;
-
- interpolate = interpolate || _.identity;
- return Q()
- .then(function() {
- return that.env.render(
- tpl,
- _.extend({
- gitbook: {
- version: pkg.version
- },
-
- styles: that.styles,
-
- revision: that.revision,
-
- title: that.options.title,
- description: that.options.description,
- language: that.book.config.normalizeLanguage(),
- innerlanguage: that.book.isSubBook()? that.book.config.get('language') : null,
-
- glossary: that.book.glossary,
-
- summary: that.book.summary,
- allNavigation: that.book.navigation,
-
- plugins: {
- resources: that.book.plugins.resources(that.namespace)
- },
- pluginsConfig: JSON.stringify(that.options.pluginsConfig),
- htmlSnippet: _.partial(_.partialRight(that.book.plugins.html, that, options), that.namespace),
-
- options: that.options,
-
- basePath: '.',
- staticBase: path.join('.', 'gitbook'),
-
- '__': that.book.i18n.bind(that.book)
- }, options)
- );
- })
- .then(interpolate)
- .then(function(html) {
- return fs.writeFile(
- output,
- html
- );
- });
-};
-
-// Copy assets
-Generator.prototype.copyAssets = function() {
- var that = this;
-
- // Copy gitbook assets
- return fs.copy(
- path.join(that.options.theme, 'assets/'+this.namespace),
- path.join(that.options.output, 'gitbook')
- )
-
- // Copy plugins assets
- .then(function() {
- return Q.all(
- _.map(that.book.plugins.list, function(plugin) {
- var pluginAssets = path.join(that.options.output, 'gitbook/plugins/', plugin.name);
- return plugin.copyAssets(pluginAssets, that.namespace);
- })
- );
- });
-};
-
-module.exports = Generator;
diff --git a/lib/version.js b/lib/gitbook.js
index f0ae187..cbdcaed 100644
--- a/lib/version.js
+++ b/lib/gitbook.js
@@ -4,7 +4,7 @@ var pkg = require('../package.json');
var VERSION = pkg.version;
var VERSION_STABLE = VERSION.replace(/\-(\S+)/g, '');
-// Test if current current gitbook version satisfies a condition
+// Verify that this gitbook version satisfies a requirement
// We can't directly use samver.satisfies since it will break all plugins when gitbook version is a prerelease (beta, alpha)
function satisfies(condition) {
// Test with real version
@@ -14,6 +14,8 @@ function satisfies(condition) {
return semver.satisfies(VERSION_STABLE, condition);
}
+
module.exports = {
+ version: pkg.version,
satisfies: satisfies
};
diff --git a/lib/index.js b/lib/index.js
index a23ec3f..13a572d 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -1,215 +1,5 @@
-/* eslint no-console: 0 */
-
-var Q = require('q');
-var _ = require('lodash');
-var path = require('path');
-var tinylr = require('tiny-lr');
-var color = require('bash-color');
-
var Book = require('./book');
-var initBook = require('./init');
-var Server = require('./utils/server');
-var stringUtils = require('./utils/string');
-var watch = require('./utils/watch');
-var logger = require('./utils/logger');
-
-var LOG_OPTION = {
- name: 'log',
- description: 'Minimum log level to display',
- values: _.chain(logger.LEVELS).keys().map(stringUtils.toLowerCase).value(),
- defaults: 'info'
-};
-
-var FORMAT_OPTION = {
- name: 'format',
- description: 'Format to build to',
- values: ['website', 'json', 'ebook'],
- defaults: 'website'
-};
-
-// Export init to gitbook library
-Book.init = initBook;
module.exports = {
- Book: Book,
- LOG_LEVELS: logger.LEVELS,
-
- commands: _.flatten([
- {
- name: 'build [book] [output]',
- description: 'build a book',
- options: [
- FORMAT_OPTION,
- LOG_OPTION
- ],
- exec: function(args, kwargs) {
- var input = args[0] || process.cwd();
- var output = args[1] || path.join(input, '_book');
-
- var book = new Book(input, _.extend({}, {
- 'config': {
- 'output': output
- },
- 'logLevel': kwargs.log
- }));
-
- return book.parse()
- .then(function() {
- return book.generate(kwargs.format);
- })
- .then(function(){
- console.log('');
- console.log(color.green('Done, without error'));
- });
- }
- },
-
- _.map(['pdf', 'epub', 'mobi'], function(ebookType) {
- return {
- name: ebookType+' [book] [output]',
- description: 'build a book to '+ebookType,
- options: [
- LOG_OPTION
- ],
- exec: function(args, kwargs) {
- var input = args[0] || process.cwd();
- var output = args[1];
-
- var book = new Book(input, _.extend({}, {
- 'logLevel': kwargs.log
- }));
-
- return book.parse()
- .then(function() {
- return book.generateFile(output, {
- ebookFormat: ebookType
- });
- })
- .then(function(){
- console.log('');
- console.log(color.green('Done, without error'));
- });
- }
- };
- }),
-
- {
- name: 'serve [book]',
- description: 'Build then serve a gitbook from a directory',
- options: [
- {
- name: 'port',
- description: 'Port for server to listen on',
- defaults: 4000
- },
- {
- name: 'lrport',
- description: 'Port for livereload server to listen on',
- defaults: 35729
- },
- {
- name: 'watch',
- description: 'Enable/disable file watcher',
- defaults: true
- },
- FORMAT_OPTION,
- LOG_OPTION
- ],
- exec: function(args, kwargs) {
- var input = args[0] || process.cwd();
- var server = new Server();
-
- // Init livereload server
- var lrServer = tinylr({});
- var lrPath;
-
- var generate = function() {
- if (server.isRunning()) console.log('Stopping server');
-
- return server.stop()
- .then(function() {
- var book = new Book(input, _.extend({}, {
- 'config': {
- 'defaultsPlugins': ['livereload']
- },
- 'logLevel': kwargs.log
- }));
-
- return book.parse()
- .then(function() {
- return book.generate(kwargs.format);
- })
- .thenResolve(book);
- })
- .then(function(book) {
- console.log();
- console.log('Starting server ...');
- return server.start(book.options.output, kwargs.port)
- .then(function() {
- console.log('Serving book on http://localhost:'+kwargs.port);
-
- if (lrPath) {
- // trigger livereload
- lrServer.changed({
- body: {
- files: [lrPath]
- }
- });
- }
-
- if (!kwargs.watch) return;
-
- return watch(book.root)
- .then(function(filepath) {
- // set livereload path
- lrPath = filepath;
- console.log('Restart after change in file', filepath);
- console.log('');
- return generate();
- });
- });
- });
- };
-
- return Q.nfcall(lrServer.listen.bind(lrServer), kwargs.lrport)
- .then(function() {
- console.log('Live reload server started on port:', kwargs.lrport);
- console.log('Press CTRL+C to quit ...');
- console.log('');
- return generate();
- });
- }
- },
-
- {
- name: 'install [book]',
- description: 'install plugins dependencies',
- exec: function(args) {
- var input = args[0] || process.cwd();
-
- var book = new Book(input);
-
- return book.config.load()
- .then(function() {
- return book.plugins.install();
- })
- .then(function(){
- console.log('');
- console.log(color.green('Done, without error'));
- });
- }
- },
-
- {
- name: 'init [directory]',
- description: 'create files and folders based on contents of SUMMARY.md',
- exec: function(args) {
- return initBook(args[0] || process.cwd())
- .then(function(){
- console.log('');
- console.log(color.green('Done, without error'));
- });
- }
- }
- ])
+ Book: Book
};
diff --git a/lib/init.js b/lib/init.js
deleted file mode 100644
index 2fc8016..0000000
--- a/lib/init.js
+++ /dev/null
@@ -1,83 +0,0 @@
-var _ = require('lodash');
-var Q = require('q');
-var path = require('path');
-
-var Book = require('./book');
-var fs = require('./utils/fs');
-
-// Initialize folder structure for a book
-// Read SUMMARY to created the right chapter
-function initBook(root, opts) {
- var book = new Book(root, opts);
- var extensionToUse = '.md';
-
- var chaptersPaths = function(chapters) {
- return _.reduce(chapters || [], function(accu, chapter) {
- var o = {
- title: chapter.title
- };
- if (chapter.path) o.path = chapter.path;
-
- return accu.concat(
- [o].concat(chaptersPaths(chapter.articles))
- );
- }, []);
- };
-
- book.log.info.ln('init book at', root);
- return fs.mkdirp(root)
- .then(function() {
- book.log.info.ln('detect structure from SUMMARY (if it exists)');
- return book.parseSummary();
- })
- .fail(function() {
- return Q();
- })
- .then(function() {
- var summary = book.summaryFile || 'SUMMARY.md';
- var chapters = book.summary.chapters || [];
- extensionToUse = path.extname(summary);
-
- if (chapters.length === 0) {
- chapters = [
- {
- title: 'Summary',
- path: 'SUMMARY'+extensionToUse
- },
- {
- title: 'Introduction',
- path: 'README'+extensionToUse
- }
- ];
- }
-
- return Q(chaptersPaths(chapters));
- })
- .then(function(chapters) {
- // Create files that don't exist
- return Q.all(_.map(chapters, function(chapter) {
- if (!chapter.path) return Q();
- var absolutePath = path.resolve(book.root, chapter.path);
-
- return fs.exists(absolutePath)
- .then(function(exists) {
- if(exists) {
- book.log.info.ln('found', chapter.path);
- return;
- } else {
- book.log.info.ln('create', chapter.path);
- }
-
- return fs.mkdirp(path.dirname(absolutePath))
- .then(function() {
- return fs.writeFile(absolutePath, '# '+chapter.title+'\n');
- });
- });
- }));
- })
- .then(function() {
- book.log.info.ln('initialization is finished');
- });
-}
-
-module.exports = initBook;
diff --git a/lib/logger.js b/lib/logger.js
new file mode 100644
index 0000000..7428180
--- /dev/null
+++ b/lib/logger.js
@@ -0,0 +1,123 @@
+var _ = require('lodash');
+var util = require('util');
+var color = require('bash-color');
+
+var LEVELS = {
+ DEBUG: 0,
+ INFO: 1,
+ WARN: 2,
+ ERROR: 3,
+ DISABLED: 10
+};
+
+var COLORS = {
+ DEBUG: color.purple,
+ INFO: color.cyan,
+ WARN: color.yellow,
+ ERROR: color.red
+};
+
+function Logger(write, logLevel) {
+ if (!(this instanceof Logger)) return new Logger(write, logLevel);
+
+ this._write = write;
+ this.lastChar = '\n';
+
+ // Define log level
+ this.setLevel(logLevel);
+
+ _.bindAll(this);
+
+ // Create easy-to-use method like "logger.debug.ln('....')"
+ _.each(_.omit(LEVELS, 'DISABLED'), function(level, levelKey) {
+ levelKey = levelKey.toLowerCase();
+
+ this[levelKey] = _.partial(this.log, level);
+ this[levelKey].ln = _.partial(this.logLn, level);
+ this[levelKey].ok = _.partial(this.ok, level);
+ this[levelKey].fail = _.partial(this.fail, level);
+ this[levelKey].promise = _.partial(this.promise, level);
+ }, this);
+}
+
+// Change minimum level
+Logger.prototype.setLevel = function(logLevel) {
+ if (_.isString(logLevel)) logLevel = LEVELS[logLevel.toUpperCase()];
+ this.logLevel = logLevel;
+};
+
+// Print a simple string
+Logger.prototype.write = function(msg) {
+ msg = msg.toString();
+ this.lastChar = _.last(msg);
+ return this._write(msg);
+};
+
+// Format a string using the first argument as a printf-like format.
+Logger.prototype.format = function() {
+ return util.format.apply(util, arguments);
+};
+
+// Print a line
+Logger.prototype.writeLn = function(msg) {
+ return this.write((msg || '')+'\n');
+};
+
+// Log/Print a message if level is allowed
+Logger.prototype.log = function(level) {
+ if (level < this.logLevel) return;
+
+ var levelKey = _.findKey(LEVELS, function(v) { return v == level; });
+ var args = Array.prototype.slice.apply(arguments, [1]);
+ var msg = this.format.apply(this, args);
+
+ if (this.lastChar == '\n') {
+ msg = COLORS[levelKey](levelKey.toLowerCase()+':')+' '+msg;
+ }
+
+ return this.write(msg);
+};
+
+// Log/Print a line if level is allowed
+Logger.prototype.logLn = function() {
+ if (this.lastChar != '\n') this.write('\n');
+
+ var args = Array.prototype.slice.apply(arguments);
+ args.push('\n');
+ return this.log.apply(this, args);
+};
+
+// Log a confirmation [OK]
+Logger.prototype.ok = function(level) {
+ var args = Array.prototype.slice.apply(arguments, [1]);
+ var msg = this.format.apply(this, args);
+ if (arguments.length > 1) {
+ this.logLn(level, color.green('>> ') + msg.trim().replace(/\n/g, color.green('\n>> ')));
+ } else {
+ this.log(level, color.green('OK'), '\n');
+ }
+};
+
+// Log a "FAIL"
+Logger.prototype.fail = function(level) {
+ return this.log(level, color.red('ERROR') + '\n');
+};
+
+// Log state of a promise
+Logger.prototype.promise = function(level, p) {
+ var that = this;
+
+ return p.
+ then(function(st) {
+ that.ok(level);
+ return st;
+ }, function(err) {
+ that.fail(level);
+ throw err;
+ });
+};
+
+Logger.LEVELS = LEVELS;
+Logger.COLORS = COLORS;
+
+module.exports = Logger;
diff --git a/lib/page.js b/lib/page.js
new file mode 100644
index 0000000..a6c2caf
--- /dev/null
+++ b/lib/page.js
@@ -0,0 +1,10 @@
+
+
+function Page() {
+ if (!(this instanceof Page)) return new Page();
+
+}
+
+
+
+module.exports = Page;
diff --git a/lib/plugin.js b/lib/plugin.js
deleted file mode 100644
index b7e8260..0000000
--- a/lib/plugin.js
+++ /dev/null
@@ -1,241 +0,0 @@
-var _ = require('lodash');
-var Q = require('q');
-var path = require('path');
-var url = require('url');
-var fs = require('./utils/fs');
-var resolve = require('resolve');
-var mergeDefaults = require('merge-defaults');
-var jsonschema = require('jsonschema');
-var jsonSchemaDefaults = require('json-schema-defaults');
-
-var version = require('./version');
-
-var PLUGIN_PREFIX = 'gitbook-plugin-';
-
-// Return an absolute name for the plugin (the one on NPM)
-function absoluteName(name) {
- if (name.indexOf(PLUGIN_PREFIX) === 0) return name;
- return [PLUGIN_PREFIX, name].join('');
-}
-
-
-var Plugin = function(book, name) {
- this.book = book;
- this.name = absoluteName(name);
- this.packageInfos = {};
- this.infos = {};
-
- // Bind methods
- _.bindAll(this);
-
- _.each([
- absoluteName(name),
- name
- ], function(_name) {
- // Load from the book
- if (this.load(_name, book.root)) return false;
-
- // Load from default plugins
- if (this.load(_name, __dirname)) return false;
- }, this);
-};
-
-// Type of plugins resources
-Plugin.RESOURCES = ['js', 'css'];
-Plugin.HOOKS = [
- 'init', 'finish', 'finish:before', 'config', 'page', 'page:before'
-];
-
-// Return the reduce name for the plugin
-// "gitbook-plugin-test" -> "test"
-// Return a relative name for the plugin (the one on GitBook)
-Plugin.prototype.reducedName = function() {
- return this.name.replace(PLUGIN_PREFIX, '');
-};
-
-// Load from a name
-Plugin.prototype.load = function(name, baseDir) {
- try {
- var res = resolve.sync(name+'/package.json', { basedir: baseDir });
-
- this.baseDir = path.dirname(res);
- this.packageInfos = require(res);
- this.infos = require(resolve.sync(name, { basedir: baseDir }));
- this.name = this.packageInfos.name;
-
- return true;
- } catch (e) {
- this.packageInfos = {};
- this.infos = {};
- return false;
- }
-};
-
-Plugin.prototype.normalizeResource = function(resource) {
- // Parse the resource path
- var parsed = url.parse(resource);
-
- // This is a remote resource
- // so we will simply link to using it's URL
- if (parsed.protocol) {
- return {
- 'url': resource
- };
- }
-
- // This will be copied over from disk
- // and shipped with the book's build
- return { 'path': this.name+'/'+resource };
-};
-
-// Return resources
-Plugin.prototype._getResources = function(base) {
- base = base;
- var book = this.infos[base];
-
- // Compatibility with version 1.x.x
- if (base == 'website') book = book || this.infos.book;
-
- // Nothing specified, fallback to default
- if (!book) {
- return Q({});
- }
-
- // Dynamic function
- if(typeof book === 'function') {
- // Call giving it the context of our book
- return Q().then(book.bind(this.book));
- }
-
- // Plain data object
- return Q(_.cloneDeep(book));
-};
-
-// Normalize resources and return them
-Plugin.prototype.getResources = function(base) {
- var that = this;
-
- return this._getResources(base)
- .then(function(resources) {
-
- _.each(Plugin.RESOURCES, function(resourceType) {
- resources[resourceType] = (resources[resourceType] || []).map(that.normalizeResource);
- });
-
- return resources;
- });
-};
-
-// Normalize filters and return them
-Plugin.prototype.getFilters = function() {
- return this.infos.filters || {};
-};
-
-// Normalize blocks and return them
-Plugin.prototype.getBlocks = function() {
- return this.infos.blocks || {};
-};
-
-// Test if it's a valid plugin
-Plugin.prototype.isValid = function() {
- var that = this;
- var isValid = (
- this.packageInfos &&
- this.packageInfos.name &&
- this.packageInfos.engines &&
- this.packageInfos.engines.gitbook &&
- version.satisfies(this.packageInfos.engines.gitbook)
- );
-
- // Valid hooks
- _.each(this.infos.hooks, function(hook, hookName) {
- if (_.contains(Plugin.HOOKS, hookName)) return;
- that.book.log.warn.ln('Hook "'+hookName+'"" used by plugin "'+that.packageInfos.name+'" has been removed or is deprecated');
- });
-
- return isValid;
-};
-
-// Normalize, validate configuration for this plugin using its schema
-// Throw an error when shcema is not respected
-Plugin.prototype.validateConfig = function(config) {
- var that = this;
-
- return Q()
- .then(function() {
- var schema = that.packageInfos.gitbook || {};
- if (!schema) return config;
-
- // Normalize schema
- schema.id = '/pluginsConfig.'+that.reducedName();
- schema.type = 'object';
-
- // Validate and throw if invalid
- var v = new jsonschema.Validator();
- var result = v.validate(config, schema, {
- propertyName: 'pluginsConfig.'+that.reducedName()
- });
-
- // Throw error
- if (result.errors.length > 0) {
- throw new Error('Configuration Error: '+result.errors[0].stack);
- }
-
- // Insert default values
- var defaults = jsonSchemaDefaults(schema);
- return mergeDefaults(config, defaults);
- });
-};
-
-// Resolve file path
-Plugin.prototype.resolveFile = function(filename) {
- return path.resolve(this.baseDir, filename);
-};
-
-// Resolve file path
-Plugin.prototype.callHook = function(name, data) {
- // Our book will be the context to apply
- var context = this.book;
-
- var hookFunc = this.infos.hooks? this.infos.hooks[name] : null;
- data = data || {};
-
- if (!hookFunc) return Q(data);
-
- this.book.log.debug.ln('call hook', name);
- if (!_.contains(Plugin.HOOKS, name)) this.book.log.warn.ln('hook "'+name+'" used by plugin "'+this.name+'" is deprecated, and will be removed in the coming versions');
-
- return Q()
- .then(function() {
- return hookFunc.apply(context, [data]);
- });
-};
-
-// Copy plugin assets fodler
-Plugin.prototype.copyAssets = function(out, base) {
- var that = this;
-
- return this.getResources(base)
- .get('assets')
- .then(function(assets) {
- // Assets are undefined
- if(!assets) return false;
-
- return fs.copy(
- that.resolveFile(assets),
- out
- ).then(_.constant(true));
- }, _.constant(false));
-};
-
-// Get config from book
-Plugin.prototype.getConfig = function() {
- return this.book.config.get('pluginsConfig.'+this.reducedName(), {});
-};
-
-// Set configuration for this plugin
-Plugin.prototype.setConfig = function(values) {
- return this.book.config.set('pluginsConfig.'+this.reducedName(), values);
-};
-
-module.exports = Plugin;
diff --git a/lib/pluginslist.js b/lib/pluginslist.js
deleted file mode 100644
index 290cd35..0000000
--- a/lib/pluginslist.js
+++ /dev/null
@@ -1,230 +0,0 @@
-var _ = require('lodash');
-var Q = require('q');
-var npmi = require('npmi');
-var npm = require('npm');
-var semver = require('semver');
-
-var Plugin = require('./plugin');
-var version = require('./version');
-
-var initNPM = _.memoize(function() {
- return Q.nfcall(npm.load, { silent: true, loglevel: 'silent' });
-});
-
-
-var PluginsList = function(book, plugins) {
- this.book = book;
- this.log = this.book.log;
-
- // List of Plugin objects
- this.list = [];
-
- // List of names of failed plugins
- this.failed = [];
-
- // Namespaces
- this.namespaces = _.chain(['website', 'ebook'])
- .map(function(namespace) {
- return [
- namespace,
- {
- html: {},
- resources: _.chain(Plugin.RESOURCES)
- .map(function(type) {
- return [type, []];
- })
- .object()
- .value()
- }
- ];
- })
- .object()
- .value();
-
- // Bind methods
- _.bindAll(this);
-
- if (plugins) this.load(plugins);
-};
-
-// return count of plugins
-PluginsList.prototype.count = function() {
- return this.list.length;
-};
-
-// Add and load a plugin
-PluginsList.prototype.load = function(plugin) {
- var that = this;
-
- if (_.isArray(plugin)) {
- return _.reduce(plugin, function(prev, p) {
- return prev.then(function() {
- return that.load(p);
- });
- }, Q());
- }
- if (_.isObject(plugin) && !(plugin instanceof Plugin)) plugin = plugin.name;
- if (_.isString(plugin)) plugin = new Plugin(this.book, plugin);
-
- that.log.info('load plugin', plugin.name, '....');
- if (!plugin.isValid()) {
- that.log.info.fail();
- that.failed.push(plugin.name);
- return Q();
- } else {
- that.log.info.ok();
-
- // Push in the list
- that.list.push(plugin);
- }
-
- return Q()
-
- // Validate and normalize configuration
- .then(function() {
- var config = plugin.getConfig();
- return plugin.validateConfig(config);
- })
- .then(function(config) {
- // Update configuration
- plugin.setConfig(config);
-
- // Extract filters
- that.book.template.addFilters(plugin.getFilters());
-
- // Extract blocks
- that.book.template.addBlocks(plugin.getBlocks());
-
- return _.reduce(_.keys(that.namespaces), function(prev, namespaceName) {
- return prev.then(function() {
- return plugin.getResources(namespaceName)
- .then(function(plResources) {
- var namespace = that.namespaces[namespaceName];
-
- // Extract js and css
- _.each(Plugin.RESOURCES, function(resourceType) {
- namespace.resources[resourceType] = (namespace.resources[resourceType] || []).concat(plResources[resourceType] || []);
- });
-
- // Map of html resources by name added by each plugin
- _.each(plResources.html || {}, function(value, tag) {
- // Turn into function if not one already
- if (!_.isFunction(value)) value = _.constant(value);
-
- namespace.html[tag] = namespace.html[tag] || [];
- namespace.html[tag].push(value);
- });
- });
- });
- }, Q());
- });
-};
-
-// Call a hook
-PluginsList.prototype.hook = function(name, data) {
- return _.reduce(this.list, function(prev, plugin) {
- return prev.then(function(ret) {
- return plugin.callHook(name, ret);
- });
- }, Q(data));
-};
-
-// Return a template from a plugin
-PluginsList.prototype.template = function(name) {
- var withTpl = _.find(this.list, function(plugin) {
- return (
- plugin.infos.templates &&
- plugin.infos.templates[name]
- );
- });
-
- if (!withTpl) return null;
- return withTpl.resolveFile(withTpl.infos.templates[name]);
-};
-
-// Return an html snippet
-PluginsList.prototype.html = function(namespace, tag, context, options) {
- var htmlSnippets = this.namespaces[namespace].html[tag];
- return _.map(htmlSnippets || [], function(code) {
- return code.call(context, options);
- }).join('\n');
-};
-
-// Return a resources map for a namespace
-PluginsList.prototype.resources = function(namespace) {
- return this.namespaces[namespace].resources;
-};
-
-// Install plugins from a book
-PluginsList.prototype.install = function() {
- var that = this;
-
- // Remove defaults (no need to install)
- var plugins = _.reject(that.book.options.plugins, {
- isDefault: true
- });
-
- // Install plugins one by one
- that.book.log.info.ln(plugins.length+' plugins to install');
- return _.reduce(plugins, function(prev, plugin) {
- return prev.then(function() {
- var fullname = 'gitbook-plugin-'+plugin.name;
-
- return Q()
-
- // Resolve version if needed
- .then(function() {
- if (plugin.version) return plugin.version;
-
- that.book.log.info.ln('No version specified, resolve plugin', plugin.name);
- return initNPM()
- .then(function() {
- return Q.nfcall(npm.commands.view, [fullname+'@*', 'engines'], true);
- })
- .then(function(versions) {
- return _.chain(versions)
- .pairs()
- .map(function(v) {
- return {
- version: v[0],
- gitbook: (v[1].engines || {}).gitbook
- };
- })
- .filter(function(v) {
- return v.gitbook && version.satisfies(v.gitbook);
- })
- .sort(function(v1, v2) {
- return semver.lt(v1.version, v2.version)? 1 : -1;
- })
- .pluck('version')
- .first()
- .value();
- });
- })
-
- // Install the plugin with the resolved version
- .then(function(version) {
- if (!version) {
- throw 'Found no satisfactory version for plugin '+plugin.name;
- }
-
- that.book.log.info.ln('install plugin', plugin.name, 'from npm ('+fullname+') with version', version);
- return Q.nfcall(npmi, {
- 'name': fullname,
- 'version': version,
- 'path': that.book.root,
- 'npmLoad': {
- 'loglevel': 'silent',
- 'loaded': true,
- 'prefix': that.book.root
- }
- });
- })
- .then(function() {
- that.book.log.info.ok('plugin', plugin.name, 'installed with success');
- });
- });
- }, Q());
-};
-
-module.exports = PluginsList;
diff --git a/lib/template.js b/lib/template.js
deleted file mode 100644
index dac1201..0000000
--- a/lib/template.js
+++ /dev/null
@@ -1,466 +0,0 @@
-var _ = require('lodash');
-var Q = require('q');
-var path = require('path');
-var nunjucks = require('nunjucks');
-var parsers = require('gitbook-parsers');
-var escapeStringRegexp = require('escape-string-regexp');
-
-var batch = require('./utils/batch');
-var pkg = require('../package.json');
-var defaultBlocks = require('./blocks');
-var BookLoader = require('./conrefs_loader');
-
-// Normalize result from a block
-function normBlockResult(blk) {
- if (_.isString(blk)) blk = { body: blk };
- return blk;
-}
-
-
-var TemplateEngine = function(book) {
- var that = this;
-
- this.book = book;
- this.log = this.book.log;
-
- // Template loader
- this.loader = new BookLoader(book, {
- // Replace shortcuts in imported files
- interpolate: function(filepath, source) {
- var parser = parsers.get(path.extname(filepath));
- var type = parser? parser.name : null;
-
- return that.applyShortcuts(type, source);
- }
- });
-
- // Nunjucks env
- this.env = new nunjucks.Environment(
- this.loader,
- {
- // Escaping is done after by the markdown parser
- autoescape: false,
-
- // Tags
- tags: {
- blockStart: '{%',
- blockEnd: '%}',
- variableStart: '{{',
- variableEnd: '}}',
- commentStart: '{###',
- commentEnd: '###}'
- }
- }
- );
-
- // List of tags shortcuts
- this.shortcuts = [];
-
- // Map of blocks bodies (that requires post-processing)
- this.blockBodies = {};
-
- // Map of added blocks
- this.blocks = {};
-
- // Bind methods
- _.bindAll(this);
-
- // Add default blocks
- this.addBlocks(defaultBlocks);
-};
-
-// Process the result of block in a context
-TemplateEngine.prototype.processBlock = function(blk) {
- blk = _.defaults(blk, {
- parse: false,
- post: undefined
- });
- blk.id = _.uniqueId('blk');
-
- 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+'@%@';
-};
-
-// 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;
- });
-};
-
-// Bind a function to a context
-TemplateEngine.prototype.bindContext = function(func) {
- var that = this;
-
- return function() {
- var ctx = {
- ctx: this.ctx,
- book: that.book,
- generator: that.book.options.generator
- };
-
- return func.apply(ctx, arguments);
- };
-};
-
-// Add filter
-TemplateEngine.prototype.addFilter = function(filterName, func) {
- try {
- this.env.getFilter(filterName);
- this.log.warn.ln('conflict in filters, \''+filterName+'\' is already set');
- return false;
- } catch(e) {
- // Filter doesn't exist
- }
-
- this.log.debug.ln('add filter \''+filterName+'\'');
- this.env.addFilter(filterName, this.bindContext(function() {
- var ctx = this;
- var args = Array.prototype.slice.apply(arguments);
- var callback = _.last(args);
-
- Q()
- .then(function() {
- return func.apply(ctx, args.slice(0, -1));
- })
- .nodeify(callback);
- }), true);
- 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, Ext, extName;
-
- if (_.isFunction(block)) block = { process: block };
-
- block = _.defaults(block || {}, {
- shortcuts: [],
- end: 'end'+name,
- process: _.identity,
- blocks: []
- });
-
- extName = this.blockExtName(name);
-
- if (this.hasBlock(name) && !defaultBlocks[name]) {
- this.log.warn.ln('conflict in blocks, \''+name+'\' is already defined');
- }
-
- // Cleanup previous block
- this.removeBlock(name);
-
- this.log.debug.ln('add block \''+name+'\'');
- this.blocks[name] = block;
-
- Ext = function () {
- this.tags = [name];
-
- this.parse = function(parser, nodes) {
- var body = null;
- var lastBlockName = null;
- var lastBlockArgs = null;
- var allBlocks = block.blocks.concat([block.end]);
- var subbodies = {};
-
- var tok = parser.nextToken();
- var args = parser.parseSignature(null, true);
- parser.advanceAfterBlockEnd(tok.value);
-
- while (1) {
- // Read body
- var currentBody = parser.parseUntilBlocks.apply(parser, allBlocks);
-
- // Handle body with previous block name and args
- if (lastBlockName) {
- subbodies[lastBlockName] = subbodies[lastBlockName] || [];
- subbodies[lastBlockName].push({
- body: currentBody,
- args: lastBlockArgs
- });
- } else {
- body = currentBody;
- }
-
- // Read new block
- lastBlockName = parser.peekToken().value;
- if (lastBlockName == block.end) {
- break;
- }
-
- // Parse signature and move to the end of the block
- lastBlockArgs = parser.parseSignature(null, true);
- parser.advanceAfterBlockEnd(lastBlockName);
- }
- parser.advanceAfterBlockEnd();
-
- var bodies = [body];
- _.each(block.blocks, function(blockName) {
- subbodies[blockName] = subbodies[blockName] || [];
- if (subbodies[blockName].length === 0) {
- subbodies[blockName].push({
- args: new nodes.NodeList(),
- body: new nodes.NodeList()
- });
- }
-
- bodies.push(subbodies[blockName][0].body);
- });
-
- return new nodes.CallExtensionAsync(this, 'run', args, bodies);
- };
-
- this.run = function(context) {
- var args = Array.prototype.slice.call(arguments, 1);
- var callback = args.pop();
-
- // Extract blocks
- var blocks = args
- .concat([])
- .slice(-block.blocks.length);
-
- // Eliminate blocks from list
- if (block.blocks.length > 0) args = args.slice(0, -block.blocks.length);
-
- // Extract main body and kwargs
- var body = args.pop();
- var kwargs = _.isObject(_.last(args))? args.pop() : {};
-
- // Extract blocks body
- var _blocks = _.map(block.blocks, function(blockName, i){
- return {
- name: blockName,
- body: blocks[i]()
- };
- });
-
- Q()
- .then(function() {
- return that.applyBlock(name, {
- body: body(),
- args: args,
- kwargs: kwargs,
- blocks: _blocks
- }, context);
- })
-
- // process the block returned
- .then(that.processBlock)
- .nodeify(callback);
- };
- };
-
- // Add the Extension
- this.env.addExtension(extName, new Ext());
-
- // Add shortcuts
- if (!_.isArray(block.shortcuts)) block.shortcuts = [block.shortcuts];
- _.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,
- tag: {
- start: name,
- end: block.end
- }
- });
- }, 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, ctx) {
- var func, block, 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(ctx || {}, 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;
- var regex = new RegExp(
- escapeStringRegexp(shortcut.start) + '([\\s\\S]*?[^\\$])' + escapeStringRegexp(shortcut.end),
- 'g'
- );
- return content.replace(regex, function(all, match) {
- return '{% '+shortcut.tag.start+' %}'+ match + '{% '+shortcut.tag.end+' %}';
- });
-};
-
-// Apply all shortcuts to some template string
-TemplateEngine.prototype.applyShortcuts = function(type, content) {
- return _.reduce(this.shortcuts, _.partial(this._applyShortcut.bind(this), type), content);
-};
-
-// Render a string from the book
-TemplateEngine.prototype.renderString = function(content, context, options) {
- context = _.extend({}, context, {
- // Variables from book.json
- book: this.book.options.variables,
-
- // Complete book.json
- config: this.book.options,
-
- // infos about gitbook
- gitbook: {
- version: pkg.version,
- generator: this.book.options.generator
- }
- });
- options = _.defaults(options || {}, {
- path: null,
- type: null
- });
- if (options.path) options.path = this.book.resolve(options.path);
- if (!options.type && options.path) {
- var parser = parsers.get(path.extname(options.path));
- options.type = parser? parser.name : null;
- }
-
- // Replace shortcuts
- content = this.applyShortcuts(options.type, content);
-
- return Q.nfcall(this.env.renderString.bind(this.env), content, context, options)
- .fail(function(err) {
- if (_.isString(err)) err = new Error(err);
- err.message = err.message.replace(/^Error: /, '');
-
- throw err;
- });
-};
-
-// Render a file from the book
-TemplateEngine.prototype.renderFile = function(filename) {
- var that = this;
-
- return that.book.readFile(filename)
- .then(function(content) {
- return that.renderString(content, {}, {
- path: filename
- });
- });
-};
-
-// Render a page from the book
-TemplateEngine.prototype.renderPage = function(page) {
- var that = this;
-
- return that.book.statFile(page.path)
- .then(function(stat) {
- var context = {
- // infos about the file
- file: {
- path: page.path,
- mtime: stat.mtime
- }
- };
-
- return that.renderString(page.content, context, {
- path: page.path,
- type: page.type
- });
- });
-};
-
-// Post process content
-TemplateEngine.prototype.postProcess = function(content) {
- var that = this;
-
- return Q(content)
- .then(that.replaceBlocks)
- .then(function(_content) {
- return batch.execEach(that.blockBodies, {
- max: 20,
- fn: function(blk, blkId) {
- return Q()
- .then(function() {
- if (!blk.post) return Q();
- return blk.post();
- })
- .then(function() {
- delete that.blockBodies[blkId];
- });
- }
- })
- .thenResolve(_content);
- });
-};
-
-module.exports = TemplateEngine;
diff --git a/lib/utils/batch.js b/lib/utils/batch.js
deleted file mode 100644
index 9069766..0000000
--- a/lib/utils/batch.js
+++ /dev/null
@@ -1,52 +0,0 @@
-var Q = require("q");
-var _ = require("lodash");
-
-// Execute a method for all element
-function execEach(items, options) {
- if (_.size(items) === 0) return Q();
- var concurrents = 0, d = Q.defer(), pending = [];
-
- options = _.defaults(options || {}, {
- max: 100,
- fn: function() {}
- });
-
-
- function startItem(item, i) {
- if (concurrents >= options.max) {
- pending.push([item, i]);
- return;
- }
-
- concurrents++;
- Q()
- .then(function() {
- return options.fn(item, i);
- })
- .then(function() {
- concurrents--;
-
- // Next pending
- var next = pending.shift();
-
- if (concurrents === 0 && !next) {
- d.resolve();
- } else if (next) {
- startItem.apply(null, next);
- }
- })
- .fail(function(err) {
- pending = [];
- d.reject(err);
- });
- }
-
- _.each(items, startItem);
-
- return d.promise;
-}
-
-module.exports = {
- execEach: execEach
-};
-
diff --git a/lib/utils/fs.js b/lib/utils/fs.js
deleted file mode 100644
index b82701f..0000000
--- a/lib/utils/fs.js
+++ /dev/null
@@ -1,193 +0,0 @@
-var _ = require('lodash');
-var Q = require('q');
-var tmp = require('tmp');
-var path = require('path');
-var fs = require('graceful-fs');
-var fsExtra = require('fs-extra');
-var Ignore = require('fstream-ignore');
-
-var fsUtils = {
- tmp: {
- file: function(opt) {
- return Q.nfcall(tmp.file.bind(tmp), opt).get(0);
- },
- dir: function() {
- return Q.nfcall(tmp.dir.bind(tmp)).get(0);
- }
- },
- list: listFiles,
- stat: Q.denodeify(fs.stat),
- readdir: Q.denodeify(fs.readdir),
- readFile: Q.denodeify(fs.readFile),
- writeFile: writeFile,
- writeStream: writeStream,
- mkdirp: Q.denodeify(fsExtra.mkdirp),
- copy: Q.denodeify(fsExtra.copy),
- remove: Q.denodeify(fsExtra.remove),
- symlink: Q.denodeify(fsExtra.symlink),
- exists: function(path) {
- var d = Q.defer();
- fs.exists(path, d.resolve);
- return d.promise;
- },
- findFile: findFile,
- existsSync: fs.existsSync.bind(fs),
- readFileSync: fs.readFileSync.bind(fs),
- clean: cleanFolder,
- getUniqueFilename: getUniqueFilename
-};
-
-// Write a file
-function writeFile(filename, data, options) {
- var d = Q.defer();
-
- try {
- fs.writeFileSync(filename, data, options);
- } catch(err) {
- d.reject(err);
- }
- d.resolve();
-
-
- return d.promise;
-}
-
-// Write a stream to a file
-function writeStream(filename, st) {
- var d = Q.defer();
-
- var wstream = fs.createWriteStream(filename);
-
- wstream.on('finish', function () {
- d.resolve();
- });
- wstream.on('error', function (err) {
- d.reject(err);
- });
-
- st.on('error', function(err) {
- d.reject(err);
- });
-
- st.pipe(wstream);
-
- return d.promise;
-}
-
-// Find a filename available
-function getUniqueFilename(base, filename) {
- if (!filename) {
- filename = base;
- base = '/';
- }
-
- filename = path.resolve(base, filename);
- var ext = path.extname(filename);
- filename = path.join(path.dirname(filename), path.basename(filename, ext));
-
- var _filename = filename+ext;
-
- var i = 0;
- while (fs.existsSync(filename)) {
- _filename = filename+'_'+i+ext;
- i = i + 1;
- }
-
- return path.relative(base, _filename);
-}
-
-
-// List files in a directory
-function listFiles(root, options) {
- options = _.defaults(options || {}, {
- ignoreFiles: [],
- ignoreRules: []
- });
-
- var d = Q.defer();
-
- // Our list of files
- var files = [];
-
- var ig = Ignore({
- path: root,
- ignoreFiles: options.ignoreFiles
- });
-
- // Add extra rules to ignore common folders
- ig.addIgnoreRules(options.ignoreRules, '__custom_stuff');
-
- // Push each file to our list
- ig.on('child', function (c) {
- files.push(
- c.path.substr(c.root.path.length + 1) + (c.props.Directory === true ? '/' : '')
- );
- });
-
- ig.on('end', function() {
- // Normalize paths on Windows
- if(process.platform === 'win32') {
- return d.resolve(files.map(function(file) {
- return file.replace(/\\/g, '/');
- }));
- }
-
- // Simply return paths otherwise
- return d.resolve(files);
- });
-
- ig.on('error', d.reject);
-
- return d.promise;
-}
-
-// Clean a folder without removing .git and .svn
-// Creates it if non existant
-function cleanFolder(root) {
- if (!fs.existsSync(root)) return fsUtils.mkdirp(root);
-
- return listFiles(root, {
- ignoreFiles: [],
- ignoreRules: [
- // Skip Git and SVN stuff
- '.git/',
- '.svn/'
- ]
- })
- .then(function(files) {
- var d = Q.defer();
-
- _.reduce(files, function(prev, file, i) {
- return prev.then(function() {
- var _file = path.join(root, file);
-
- d.notify({
- i: i+1,
- count: files.length,
- file: _file
- });
- return fsUtils.remove(_file);
- });
- }, Q())
- .then(function() {
- d.resolve();
- }, function(err) {
- d.reject(err);
- });
-
- return d.promise;
- });
-}
-
-// Find a file in a folder (case incensitive)
-// Return the real filename
-function findFile(root, filename) {
- return Q.nfcall(fs.readdir, root)
- .then(function(files) {
- return _.find(files, function(file) {
- return (file.toLowerCase() == filename.toLowerCase());
- });
- });
-}
-
-module.exports = fsUtils;
diff --git a/lib/utils/git.js b/lib/utils/git.js
deleted file mode 100644
index 72c8818..0000000
--- a/lib/utils/git.js
+++ /dev/null
@@ -1,127 +0,0 @@
-var Q = require('q');
-var _ = require('lodash');
-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');
-
-var GIT_PREFIX = 'git+';
-var GIT_TMP = null;
-
-
-// Check if an url is a git dependency url
-function checkGitUrl(giturl) {
- return (giturl.indexOf(GIT_PREFIX) === 0);
-}
-
-// Validates a SHA in hexadecimal
-function validateSha(str) {
- return (/[0-9a-f]{40}/).test(str);
-}
-
-// Parse and extract infos
-function parseGitUrl(giturl) {
- var ref, uri, fileParts, filepath;
-
- if (!checkGitUrl(giturl)) return null;
- giturl = giturl.slice(GIT_PREFIX.length);
-
- uri = new URI(giturl);
- ref = uri.fragment() || 'master';
- uri.fragment(null);
-
- // Extract file inside the repo (after the .git)
- fileParts =uri.path().split('.git');
- filepath = fileParts.length > 1? fileParts.slice(1).join('.git') : '';
- if (filepath[0] == '/') filepath = filepath.slice(1);
-
- // Recreate pathname without the real filename
- uri.path(_.first(fileParts)+'.git');
-
- return {
- host: uri.toString(),
- ref: ref || 'master',
- filepath: filepath
- };
-}
-
-// Clone a git repo from a specific ref
-function cloneGitRepo(host, ref) {
- var isBranch = false;
-
- ref = ref || 'master';
- if (!validateSha(ref)) isBranch = true;
-
- return Q()
-
- // Create temporary folder to store git repos
- .then(function() {
- if (GIT_TMP) return;
- return fs.tmp.dir()
- .then(function(_tmp) {
- GIT_TMP = _tmp;
- });
- })
-
- // Return or clone the git repo
- .then(function() {
- // Unique ID for repo/ref combinaison
- var repoId = crc.crc32(host+'#'+ref).toString(16);
-
- // Absolute path to the folder
- var repoPath = path.resolve(GIT_TMP, repoId);
-
- return fs.exists(repoPath)
- .then(function(doExists) {
- if (doExists) return;
-
- // Clone repo
- 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);
- if (!giturl) return Q(null);
-
- // Clone or get from cache
- return cloneGitRepo(giturl.host, giturl.ref)
- .then(function(repo) {
-
- // Resolve relative path
- return path.resolve(repo, giturl.filepath);
- });
-}
-
-// 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,
- resolveRoot: resolveGitRoot
-};
diff --git a/lib/utils/i18n.js b/lib/utils/i18n.js
deleted file mode 100644
index de64b49..0000000
--- a/lib/utils/i18n.js
+++ /dev/null
@@ -1,80 +0,0 @@
-var _ = require('lodash');
-var path = require('path');
-var fs = require('fs');
-
-var i18n = require('i18n');
-
-var I18N_PATH = path.resolve(__dirname, '../../theme/i18n/');
-var DEFAULT_LANGUAGE = 'en';
-var LOCALES = _.map(fs.readdirSync(I18N_PATH), function(lang) {
- return path.basename(lang, '.json');
-});
-
-i18n.configure({
- locales: LOCALES,
- directory: I18N_PATH,
- defaultLocale: DEFAULT_LANGUAGE,
- updateFiles: false
-});
-
-function compareLocales(lang, locale) {
- var langMain = _.first(lang.split('-'));
- var langSecond = _.last(lang.split('-'));
-
- var localeMain = _.first(locale.split('-'));
- var localeSecond = _.last(locale.split('-'));
-
- if (locale == lang) return 100;
- if (localeMain == langMain) return 50;
- if (localeSecond == langSecond) return 20;
- return 0;
-}
-
-var normalizeLanguage = _.memoize(function(lang) {
- var language = _.chain(LOCALES)
- .values()
- .map(function(locale) {
- return {
- locale: locale,
- score: compareLocales(lang, locale)
- };
- })
- .filter(function(lang) {
- return lang.score > 0;
- })
- .sortBy('score')
- .pluck('locale')
- .last()
- .value();
- return language || lang;
-});
-
-function translate(locale, phrase) {
- var args = Array.prototype.slice.call(arguments, 2);
-
- return i18n.__.apply({}, [{
- locale: locale,
- phrase: phrase
- }].concat(args));
-}
-
-function getCatalog(locale) {
- locale = normalizeLanguage(locale);
- return i18n.getCatalog(locale);
-}
-
-function getLocales() {
- return LOCALES;
-}
-
-function hasLocale(locale) {
- return _.contains(LOCALES, locale);
-}
-
-module.exports = {
- __: translate,
- normalizeLanguage: normalizeLanguage,
- getCatalog: getCatalog,
- getLocales: getLocales,
- hasLocale: hasLocale
-};
diff --git a/lib/utils/images.js b/lib/utils/images.js
deleted file mode 100644
index a82b0a1..0000000
--- a/lib/utils/images.js
+++ /dev/null
@@ -1,37 +0,0 @@
-var _ = require("lodash");
-var Q = require("q");
-var fs = require("./fs");
-var spawn = require("spawn-cmd").spawn;
-
-// Convert a svg file
-var convertSVG = function(source, dest, options) {
- if (!fs.existsSync(source)) return Q.reject(new Error("File doesn't exist: "+source));
- var d = Q.defer();
-
- options = _.defaults(options || {}, {
-
- });
-
- //var command = shellescape(["svgexport", source, dest]);
- var child = spawn("svgexport", [source, dest]);
-
- child.on("error", function(error) {
- if (error.code == "ENOENT") error = new Error("Need to install \"svgexport\" using \"npm install svgexport -g\"");
- return d.reject(error);
- });
-
- child.on("close", function(code) {
- if (code === 0 && fs.existsSync(dest)) {
- d.resolve();
- } else {
- d.reject(new Error("Error converting "+source+" into "+dest));
- }
- });
-
- return d.promise;
-};
-
-module.exports = {
- convertSVG: convertSVG,
- INVALID: [".svg"]
-};
diff --git a/lib/utils/links.js b/lib/utils/links.js
deleted file mode 100644
index 5122396..0000000
--- a/lib/utils/links.js
+++ /dev/null
@@ -1,81 +0,0 @@
-var url = require('url');
-var path = require('path');
-
-// Is the link an external link
-function isExternal(href) {
- try {
- return Boolean(url.parse(href).protocol);
- } catch(err) {
- return false;
- }
-}
-
-// Return true if the link is relative
-function isRelative(href) {
- try {
- var parsed = url.parse(href);
-
- return !!(!parsed.protocol && parsed.path);
- } catch(err) {
- return true;
- }
-}
-
-// Return true if the link is an achor
-function isAnchor(href) {
- try {
- var parsed = url.parse(href);
- return !!(!parsed.protocol && !parsed.path && parsed.hash);
- } catch(err) {
- return false;
- }
-}
-
-// Normalize a path to be a link
-function normalizeLink(s) {
- return s.replace(/\\/g, '/');
-}
-
-// Relative to absolute path
-// dir: directory parent of the file currently in rendering process
-// outdir: directory parent from the html output
-function toAbsolute(_href, dir, outdir) {
- if (isExternal(_href)) return _href;
-
- // Path "_href" inside the base folder
- var hrefInRoot = path.normalize(path.join(dir, _href));
- if (_href[0] == '/') hrefInRoot = path.normalize(_href.slice(1));
-
- // Make it relative to output
- _href = path.relative(outdir, hrefInRoot);
-
- // Normalize windows paths
- _href = normalizeLink(_href);
-
- return _href;
-}
-
-// Join links
-function join() {
- var _href = path.join.apply(path, arguments);
-
- return normalizeLink(_href);
-};
-
-// Change extension
-function changeExtension(filename, newext) {
- return path.join(
- path.dirname(filename),
- path.basename(filename, path.extname(filename))+newext
- );
-}
-
-module.exports = {
- isAnchor: isAnchor,
- isRelative: isRelative,
- isExternal: isExternal,
- toAbsolute: toAbsolute,
- join: join,
- changeExtension: changeExtension,
- normalize: normalizeLink
-};
diff --git a/lib/utils/logger.js b/lib/utils/logger.js
deleted file mode 100644
index db3d90e..0000000
--- a/lib/utils/logger.js
+++ /dev/null
@@ -1,102 +0,0 @@
-var _ = require("lodash");
-var util = require("util");
-var color = require("bash-color");
-
-var LEVELS = {
- DEBUG: 0,
- INFO: 1,
- WARN: 2,
- ERROR: 3,
- DISABLED: 10
-};
-
-var COLORS = {
- DEBUG: color.purple,
- INFO: color.cyan,
- WARN: color.yellow,
- ERROR: color.red
-};
-
-module.exports = function(_write, logLevel) {
- var logger = {};
- var lastChar = "\n";
- if (_.isString(logLevel)) logLevel = LEVELS[logLevel.toUpperCase()];
-
- // Write a simple message
- logger.write = function(msg) {
- msg = msg.toString();
- lastChar = _.last(msg);
- return _write(msg);
- };
-
- // Format a message
- logger.format = function() {
- return util.format.apply(util, arguments);
- };
-
- // Write a line
- logger.writeLn = function(msg) {
- return this.write((msg || "")+"\n");
- };
-
- // Write a message with a certain level
- logger.log = function(level) {
- if (level < logLevel) return;
-
- var levelKey = _.findKey(LEVELS, function(v) { return v == level; });
- var args = Array.prototype.slice.apply(arguments, [1]);
- var msg = logger.format.apply(logger, args);
-
- if (lastChar == "\n") {
- msg = COLORS[levelKey](levelKey.toLowerCase()+":")+" "+msg;
- }
-
- return logger.write(msg);
- };
- logger.logLn = function() {
- if (lastChar != "\n") logger.write("\n");
-
- var args = Array.prototype.slice.apply(arguments);
- args.push("\n");
- logger.log.apply(logger, args);
- };
-
- // Write a OK
- logger.ok = function(level) {
- var args = Array.prototype.slice.apply(arguments, [1]);
- var msg = logger.format.apply(logger, args);
- if (arguments.length > 1) {
- logger.logLn(level, color.green(">> ") + msg.trim().replace(/\n/g, color.green("\n>> ")));
- } else {
- logger.log(level, color.green("OK"), "\n");
- }
- };
-
- // Write an "FAIL"
- logger.fail = function(level) {
- return logger.log(level, color.red("ERROR")+"\n");
- };
-
- _.each(_.omit(LEVELS, "DISABLED"), function(level, levelKey) {
- levelKey = levelKey.toLowerCase();
-
- logger[levelKey] = _.partial(logger.log, level);
- logger[levelKey].ln = _.partial(logger.logLn, level);
- logger[levelKey].ok = _.partial(logger.ok, level);
- logger[levelKey].fail = _.partial(logger.fail, level);
- logger[levelKey].promise = function(p) {
- return p.
- then(function(st) {
- logger[levelKey].ok();
- return st;
- }, function(err) {
- logger[levelKey].fail();
- throw err;
- });
- };
- });
-
- return logger;
-};
-module.exports.LEVELS = LEVELS;
-module.exports.COLORS = COLORS;
diff --git a/lib/utils/navigation.js b/lib/utils/navigation.js
deleted file mode 100644
index d07eb35..0000000
--- a/lib/utils/navigation.js
+++ /dev/null
@@ -1,79 +0,0 @@
-var _ = require("lodash");
-
-// Cleans up an article/chapter object
-// remove "articles" attributes
-function clean(obj) {
- return obj && _.omit(obj, ["articles"]);
-}
-
-function flattenChapters(chapters) {
- return _.reduce(chapters, function(accu, chapter) {
- return accu.concat([clean(chapter)].concat(flattenChapters(chapter.articles)));
- }, []);
-}
-
-// Returns from a summary a map of
-/*
- {
- "file/path.md": {
- prev: ...,
- next: ...,
- },
- ...
- }
-*/
-function navigation(summary, files) {
- // Support single files as well as list
- files = _.isArray(files) ? files : (_.isString(files) ? [files] : null);
-
- // List of all navNodes
- // Flatten chapters
- var navNodes = flattenChapters(summary.chapters);
-
- // Mapping of prev/next for a give path
- var mapping = _.chain(navNodes)
- .map(function(current, i) {
- var prev = null, next = null;
-
- // Skip if no path
- if(!current.exists) return null;
-
- // Find prev
- prev = _.chain(navNodes.slice(0, i))
- .reverse()
- .find(function(node) {
- return node.exists && !node.external;
- })
- .value();
-
- // Find next
- next = _.chain(navNodes.slice(i+1))
- .find(function(node) {
- return node.exists && !node.external;
- })
- .value();
-
- return [current.path, {
- index: i,
- title: current.title,
- introduction: current.introduction,
- prev: prev,
- next: next,
- level: current.level,
- }];
- })
- .compact()
- .object()
- .value();
-
- // Filter for only files we want
- if(files) {
- return _.pick(mapping, files);
- }
-
- return mapping;
-}
-
-
-// Exports
-module.exports = navigation;
diff --git a/lib/utils/page.js b/lib/utils/page.js
deleted file mode 100644
index 010d703..0000000
--- a/lib/utils/page.js
+++ /dev/null
@@ -1,397 +0,0 @@
-var Q = require('q');
-var _ = require('lodash');
-var url = require('url');
-var path = require('path');
-var cheerio = require('cheerio');
-var domSerializer = require('dom-serializer');
-var request = require('request');
-var crc = require('crc');
-var slug = require('github-slugid');
-
-var links = require('./links');
-var imgUtils = require('./images');
-var fs = require('./fs');
-var batch = require('./batch');
-
-var parsableExtensions = require('gitbook-parsers').extensions;
-
-// Map of images that have been converted
-var imgConversionCache = {};
-
-// Render a cheerio dom as html
-function renderDom($, dom, options) {
- if (!dom && $._root && $._root.children) {
- dom = $._root.children;
- }
-
- options = options|| dom.options || $._options;
- return domSerializer(dom, options);
-}
-
-function replaceText($, el, search, replace, text_only ) {
- return $(el).each(function(){
- var node = this.firstChild,
- val,
- new_val,
-
- // Elements to be removed at the end.
- remove = [];
-
- // Only continue if firstChild exists.
- if ( node ) {
-
- // Loop over all childNodes.
- while (node) {
-
- // Only process text nodes.
- if ( node.nodeType === 3 ) {
-
- // The original node value.
- val = node.nodeValue;
-
- // The new value.
- new_val = val.replace( search, replace );
-
- // Only replace text if the new value is actually different!
- if ( new_val !== val ) {
-
- if ( !text_only && /</.test( new_val ) ) {
- // The new value contains HTML, set it in a slower but far more
- // robust way.
- $(node).before( new_val );
-
- // Don't remove the node yet, or the loop will lose its place.
- remove.push( node );
- } else {
- // The new value contains no HTML, so it can be set in this
- // very fast, simple way.
- node.nodeValue = new_val;
- }
- }
- }
-
- node = node.nextSibling;
- }
- }
-
- // Time to remove those elements!
- if (remove.length) $(remove).remove();
- });
-}
-
-function pregQuote( str ) {
- return (str+'').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, '\\$1');
-}
-
-
-// Adapt an html snippet to be relative to a base folder
-function normalizeHtml(src, options) {
- var $ = cheerio.load(src, {
- // We should parse html without trying to normalize too much
- xmlMode: false,
-
- // SVG need some attributes to use uppercases
- lowerCaseAttributeNames: false,
- lowerCaseTags: false
- });
- var toConvert = [];
- var svgContent = {};
- var outputRoot = options.book.options.output;
-
- imgConversionCache[outputRoot] = imgConversionCache[outputRoot] || {};
-
- // Find svg images to extract and process
- if (options.convertImages) {
- $('svg').each(function() {
- var content = renderDom($, $(this));
- var svgId = _.uniqueId('svg');
- var dest = svgId+'.svg';
-
- // Generate filename
- dest = '/'+fs.getUniqueFilename(outputRoot, dest);
-
- svgContent[dest] = '<?xml version="1.0" encoding="UTF-8"?>'+content;
- $(this).replaceWith($('<img>').attr('src', dest));
- });
- }
-
- // Generate ID for headings
- $('h1,h2,h3,h4,h5,h6').each(function() {
- if ($(this).attr('id')) return;
-
- $(this).attr('id', slug($(this).text()));
- });
-
- // Find images to normalize
- $('img').each(function() {
- var origin;
- var src = $(this).attr('src');
-
- if (!src) return;
- var isExternal = links.isExternal(src);
-
- // Transform as relative to the bases
- if (links.isRelative(src)) {
- src = links.toAbsolute(src, options.base, options.output);
- }
-
- // Convert if needed
- if (options.convertImages) {
- // If image is external and ebook, then downlaod the images
- if (isExternal) {
- origin = src;
- src = '/'+crc.crc32(origin).toString(16)+path.extname(url.parse(origin).pathname);
- src = links.toAbsolute(src, options.base, options.output);
- isExternal = false;
- }
-
- var ext = path.extname(src);
- var srcAbs = links.join('/', options.base, src);
-
- // Test image extension
- if (_.contains(imgUtils.INVALID, ext)) {
- if (imgConversionCache[outputRoot][srcAbs]) {
- // Already converted
- src = imgConversionCache[outputRoot][srcAbs];
- } else {
- // Not converted yet
- var dest = '';
-
- // Replace extension
- dest = links.join(path.dirname(srcAbs), path.basename(srcAbs, ext)+'.png');
- dest = dest[0] == '/'? dest.slice(1) : dest;
-
- // Get a name that doesn't exists
- dest = fs.getUniqueFilename(outputRoot, dest);
-
- options.book.log.debug.ln('detect invalid image (will be converted to png):', srcAbs);
-
- // Add to cache
- imgConversionCache[outputRoot][srcAbs] = '/'+dest;
-
- // Push to convert
- toConvert.push({
- origin: origin,
- content: svgContent[srcAbs],
- source: isExternal? srcAbs : path.join('./', srcAbs),
- dest: path.join('./', dest)
- });
-
- src = links.join('/', dest);
- }
-
- // Reset as relative to output
- src = links.toAbsolute(src, options.base, options.output);
- }
-
- else if (origin) {
- // Need to downlaod image
- toConvert.push({
- origin: origin,
- source: path.join('./', srcAbs)
- });
- }
- }
-
- $(this).attr('src', src);
- });
-
- // Normalize links
- $('a').each(function() {
- var href = $(this).attr('href');
- if (!href) return;
-
- if (links.isAnchor(href)) {
- // Keep it as it is
- } else if (links.isRelative(href)) {
- var parts = url.parse(href);
-
- var pathName = decodeURIComponent(parts.pathname);
- var anchor = parts.hash || '';
-
- // Calcul absolute path for this file (without the anchor)
- var absolutePath = links.join(options.base, pathName);
-
- // 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 {
- // External links
- $(this).attr('target', '_blank');
- }
-
- // Transform extension
- $(this).attr('href', href);
- });
-
- // Highlight code blocks
- $('code').each(function() {
- // 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();
-
- var source = $(this).text();
- var blk = options.book.template.applyBlock('code', {
- body: source,
- kwargs: {
- language: lang
- }
- });
-
- if (blk.html === false) $(this).text(blk.body);
- else $(this).html(blk.body);
- });
-
- // Replace glossary terms
- var glossary = _.sortBy(options.glossary, function(term) {
- return -term.name.length;
- });
-
- _.each(glossary, function(term) {
- var r = new RegExp( '\\b(' + pregQuote(term.name.toLowerCase()) + ')\\b' , 'gi' );
- var includedInFiles = false;
-
- $('*').each(function() {
- // Ignore codeblocks
- if (_.contains(['code', 'pre', 'a', 'script'], this.name.toLowerCase())) return;
-
- replaceText($, this, r, function(match) {
- // Add to files index in glossary
- if (!includedInFiles) {
- includedInFiles = true;
- term.files = term.files || [];
- term.files.push(options.navigation[options.input]);
- }
- return '<a href=\''+links.toAbsolute('/GLOSSARY.html', options.base, options.output) + '#' + term.id+'\' class=\'glossary-term\' title=\''+_.escape(term.description)+'\'>'+match+'</a>';
- });
- });
- });
-
- return {
- html: renderDom($),
- images: toConvert
- };
-}
-
-// Convert svg images to png
-function convertImages(images, options) {
- if (!options.convertImages) return Q();
-
- var downloaded = [];
- options.book.log.debug.ln('convert ', images.length, 'images to png');
-
- return batch.execEach(images, {
- max: 100,
- fn: function(image) {
- var imgin = path.resolve(options.book.options.output, image.source);
-
- return Q()
-
- // Write image if need to be download
- .then(function() {
- 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)))
- .fail(function(err) {
- if (!_.isError(err)) err = new Error(err);
-
- err.message = 'Fail downloading '+image.origin+': '+err.message;
- throw err;
- });
- })
-
- // Write svg if content
- .then(function() {
- if (!image.content) return;
- return fs.writeFile(imgin, image.content);
- })
-
- // Convert
- .then(function() {
- if (!image.dest) return;
- var imgout = path.resolve(options.book.options.output, image.dest);
- options.book.log.debug('convert image', image.source, 'to', image.dest, '...');
- return options.book.log.debug.promise(imgUtils.convertSVG(imgin, imgout));
- });
- }
- })
- .then(function() {
- options.book.log.debug.ok(images.length+' images converted with success');
- });
-}
-
-// Adapt page content to be relative to a base folder
-function normalizePage(sections, options) {
- options = _.defaults(options || {}, {
- // Current book
- book: null,
-
- // Do we need to convert svg?
- convertImages: false,
-
- // Current file path
- input: '.',
-
- // Navigation to use to transform path
- navigation: {},
-
- // Directory parent of the file currently in rendering process
- base: './',
-
- // Directory parent from the html output
- output: './',
-
- // Glossary terms
- glossary: []
- });
-
- // List of images to convert
- var toConvert = [];
-
- sections = _.map(sections, function(section) {
- if (section.type != 'normal') return section;
-
- var out = normalizeHtml(section.content, options);
-
- toConvert = toConvert.concat(out.images);
- section.content = out.html;
- return section;
- });
-
- return Q()
- .then(function() {
- toConvert = _.uniq(toConvert, 'source');
- return convertImages(toConvert, options);
- })
- .thenResolve(sections);
-}
-
-
-module.exports = {
- normalize: normalizePage
-};
diff --git a/lib/utils/path.js b/lib/utils/path.js
index 5285896..dc97d5d 100644
--- a/lib/utils/path.js
+++ b/lib/utils/path.js
@@ -1,5 +1,5 @@
-var _ = require("lodash");
-var path = require("path");
+var _ = require('lodash');
+var path = require('path');
// Return true if file path is inside a folder
function isInRoot(root, filename) {
@@ -26,8 +26,8 @@ function resolveInRoot(root) {
result = path.resolve(root, input);
if (!isInRoot(root, result)) {
- err = new Error("EACCESS: \"" + result + "\" not in \"" + root + "\"");
- err.code = "EACCESS";
+ err = new Error('EACCESS: "' + result + '" not in "' + root + '"');
+ err.code = 'EACCESS';
throw err;
}
diff --git a/lib/utils/progress.js b/lib/utils/progress.js
deleted file mode 100644
index 8dda892..0000000
--- a/lib/utils/progress.js
+++ /dev/null
@@ -1,55 +0,0 @@
-var _ = require('lodash');
-
-// Returns from a navigation and a current file, a snapshot of current detailed state
-function calculProgress(navigation, current) {
- var n = _.size(navigation);
- var percent = 0, prevPercent = 0, currentChapter = null;
- var done = true;
-
- var chapters = _.chain(navigation)
-
- // Transform as array
- .map(function(nav, path) {
- nav.path = path;
- return nav;
- })
-
- // Sort entries
- .sortBy(function(nav) {
- return nav.index;
- })
-
- .map(function(nav, i) {
- // Calcul percent
- nav.percent = (i * 100) / Math.max((n - 1), 1);
-
- // Is it done
- nav.done = done;
- if (nav.path == current) {
- currentChapter = nav;
- percent = nav.percent;
- done = false;
- } else if (done) {
- prevPercent = nav.percent;
- }
-
- return nav;
- })
- .value();
-
- return {
- // Previous percent
- prevPercent: prevPercent,
-
- // Current percent
- percent: percent,
-
- // List of chapter with progress
- chapters: chapters,
-
- // Current chapter
- current: currentChapter
- };
-}
-
-module.exports = calculProgress;
diff --git a/lib/utils/server.js b/lib/utils/server.js
deleted file mode 100644
index 1d6822f..0000000
--- a/lib/utils/server.js
+++ /dev/null
@@ -1,94 +0,0 @@
-var Q = require("q");
-var events = require("events");
-var http = require("http");
-var send = require("send");
-var util = require("util");
-var url = require("url");
-
-var Server = function() {
- this.running = null;
- this.dir = null;
- this.port = 0;
- this.sockets = [];
-};
-util.inherits(Server, events.EventEmitter);
-
-// Return true if the server is running
-Server.prototype.isRunning = function() {
- return !!this.running;
-};
-
-// Stop the server
-Server.prototype.stop = function() {
- var that = this;
- if (!this.isRunning()) return Q();
-
- var d = Q.defer();
- this.running.close(function(err) {
- that.running = null;
- that.emit("state", false);
-
- if (err) d.reject(err);
- else d.resolve();
- });
-
- for (var i = 0; i < this.sockets.length; i++) {
- this.sockets[i].destroy();
- }
-
- return d.promise;
-};
-
-Server.prototype.start = function(dir, port) {
- var that = this, pre = Q();
- port = port || 8004;
-
- if (that.isRunning()) pre = this.stop();
- return pre
- .then(function() {
- var d = Q.defer();
-
- that.running = http.createServer(function(req, res){
- // Render error
- function error(err) {
- res.statusCode = err.status || 500;
- res.end(err.message);
- }
-
- // Redirect to directory"s index.html
- function redirect() {
- res.statusCode = 301;
- res.setHeader("Location", req.url + "/");
- res.end("Redirecting to " + req.url + "/");
- }
-
- // Send file
- send(req, url.parse(req.url).pathname)
- .root(dir)
- .on("error", error)
- .on("directory", redirect)
- .pipe(res);
- });
-
- that.running.on("connection", function (socket) {
- that.sockets.push(socket);
- socket.setTimeout(4000);
- socket.on("close", function () {
- that.sockets.splice(that.sockets.indexOf(socket), 1);
- });
- });
-
- that.running.listen(port, function(err) {
- if (err) return d.reject(err);
-
- that.port = port;
- that.dir = dir;
- that.emit("state", true);
- d.resolve();
- });
-
- return d.promise;
- });
-};
-
-module.exports = Server;
diff --git a/lib/utils/string.js b/lib/utils/string.js
deleted file mode 100644
index caa2364..0000000
--- a/lib/utils/string.js
+++ /dev/null
@@ -1,27 +0,0 @@
-var _ = require("lodash");
-
-function escapeShellArg(arg) {
- var ret = "";
-
- ret = arg.replace(/"/g, '\\"');
-
- return "\"" + ret + "\"";
-}
-
-function optionsToShellArgs(options) {
- return _.chain(options)
- .map(function(value, key) {
- if (value === null || value === undefined || value === false) return null;
- if (value === true) return key;
- return key+"="+escapeShellArg(value);
- })
- .compact()
- .value()
- .join(" ");
-}
-
-module.exports = {
- escapeShellArg: escapeShellArg,
- optionsToShellArgs: optionsToShellArgs,
- toLowerCase: String.prototype.toLowerCase.call.bind(String.prototype.toLowerCase)
-};
diff --git a/lib/utils/watch.js b/lib/utils/watch.js
deleted file mode 100644
index 4d1a752..0000000
--- a/lib/utils/watch.js
+++ /dev/null
@@ -1,40 +0,0 @@
-var Q = require("q");
-var _ = require("lodash");
-var path = require("path");
-var chokidar = require("chokidar");
-
-var parsers = require("gitbook-parsers");
-
-function watch(dir) {
- var d = Q.defer();
- dir = path.resolve(dir);
-
- var toWatch = [
- "book.json", "book.js"
- ];
-
- _.each(parsers.extensions, function(ext) {
- toWatch.push("**/*"+ext);
- });
-
- var watcher = chokidar.watch(toWatch, {
- cwd: dir,
- ignored: "_book/**",
- ignoreInitial: true
- });
-
- watcher.once("all", function(e, filepath) {
- watcher.close();
-
- d.resolve(filepath);
- });
- watcher.once("error", function(err) {
- watcher.close();
-
- d.reject(err);
- });
-
- return d.promise;
-}
-
-module.exports = watch;