summaryrefslogtreecommitdiffstats
path: root/lib/book.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/book.js')
-rw-r--r--lib/book.js339
1 files changed, 123 insertions, 216 deletions
diff --git a/lib/book.js b/lib/book.js
index 514a719..72f0ec2 100644
--- a/lib/book.js
+++ b/lib/book.js
@@ -1,23 +1,22 @@
-var Q = require("q");
-var _ = require("lodash");
-var path = require("path");
-var lunr = require("lunr");
-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 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");
-
-var generators = require("./generators");
+var Q = require('q');
+var _ = require('lodash');
+var path = require('path');
+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 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');
+
+var generators = require('./generators');
var Book = function(root, context, parent) {
this.context = _.defaults(context || {}, {
@@ -30,7 +29,7 @@ var Book = function(root, context, parent) {
},
// Log level
- logLevel: "info"
+ logLevel: 'info'
});
// Log
@@ -44,7 +43,7 @@ var Book = function(root, context, parent) {
// Configuration
this.config = new Configuration(this, this.context.config);
- Object.defineProperty(this, "options", {
+ Object.defineProperty(this, 'options', {
get: function () {
return this.config.options;
}
@@ -78,23 +77,13 @@ var Book = function(root, context, parent) {
this.readmeFile = null;
this.langsFile = null;
- // Search Index
- this.searchIndexEnabled = true;
- this.searchIndexSize = 0;
- this.searchIndex = lunr(function () {
- this.ref("url");
-
- this.field("title", { boost: 10 });
- this.field("body");
- });
-
// Bind methods
_.bindAll(this);
};
// Return string representation
Book.prototype.toString = function() {
- return "[Book "+this.root+"]";
+ return '[Book '+this.root+']';
};
// Initialize and parse the book: config, summary, glossary
@@ -112,17 +101,17 @@ Book.prototype.parse = 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");
+ 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);
+ 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
+ 'output': path.join(that.options.output, lang.lang),
+ 'language': lang.lang
})
}),
that
@@ -165,15 +154,15 @@ Book.prototype.generate = function(generator) {
var that = this;
that.options.generator = generator || that.options.generator;
- that.log.info.ln("start generation with", that.options.generator, "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");
+ 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+")");
+ that.log.debug.ln('remove', p.file, '('+p.i+'/'+p.count+')');
})
.then(function() {
that.log.info.ok();
@@ -183,7 +172,7 @@ Book.prototype.generate = function(generator) {
// Create generator
.then(function() {
var Generator = generators[generator];
- if (!Generator) throw "Generator \""+that.options.generator+"\" doesn't exist";
+ if (!Generator) throw 'Generator \''+that.options.generator+'\' doesn\'t exist';
generator = new Generator(that);
return generator.prepare();
@@ -196,43 +185,43 @@ Book.prototype.generate = function(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";
+ if (file[file.length -1] == '/') {
+ return 'directories';
} else if (_.contains(parsers.extensions, path.extname(file)) && that.navigation[file]) {
- return "content";
+ return 'content';
} else {
- return "files";
+ return 'files';
}
});
return Q()
- // First, let"s create folder
+ // 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);
+ that.log.debug.ln('transferring folder', folder);
return Q(generator.transferFolder(folder));
});
}, Q());
})
- // Then, let"s copy other files
+ // Then, let's copy other files
.then(function() {
return Q.all(_.map(ops.files || [], function(file) {
- that.log.debug.ln("transferring file", file);
+ that.log.debug.ln('transferring file', file);
return Q(generator.transferFile(file));
}));
})
- // Finally let"s generate content
+ // 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);
+ var p = ((i*100)/nFiles).toFixed(0)+'%';
+ that.log.debug.ln('processing', file, p);
return Q(generator.convertFile(file))
.fail(function(err) {
@@ -249,16 +238,16 @@ Book.prototype.generate = function(generator) {
// Finish generation
.then(function() {
- return generator.callHook("finish:before");
+ return generator.callHook('finish:before');
})
.then(function() {
return generator.finish();
})
.then(function() {
- return generator.callHook("finish");
+ return generator.callHook('finish');
})
.then(function() {
- that.log.info.ln("generation is finished");
+ that.log.info.ln('generation is finished');
});
};
@@ -284,7 +273,7 @@ Book.prototype.generateFile = function(output, options) {
options = _.defaults(options || {}, {
ebookFormat: path.extname(output).slice(1)
});
- output = output || path.resolve(book.root, "book."+options.ebookFormat);
+ output = output || path.resolve(book.root, 'book.'+options.ebookFormat);
return fs.tmp.dir()
.then(function(tmpDir) {
@@ -297,13 +286,13 @@ Book.prototype.generateFile = function(output, options) {
var _tmpDir = tmpDir;
if (lang) {
- _outputFile = _outputFile.slice(0, -path.extname(_outputFile).length)+"_"+lang+path.extname(_outputFile);
+ _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);
+ book.log.debug.ln('copy ebook to', _outputFile);
return fs.copy(
- path.join(_tmpDir, "index."+options.ebookFormat),
+ path.join(_tmpDir, 'index.'+options.ebookFormat),
_outputFile
);
};
@@ -323,7 +312,7 @@ Book.prototype.generateFile = function(output, options) {
}
})
.then(function(n) {
- book.log.info.ok(n+" file(s) generated");
+ book.log.info.ok(n+' file(s) generated');
return fs.remove(tmpDir);
});
@@ -335,7 +324,7 @@ Book.prototype.generateFile = function(output, options) {
Book.prototype.parseConfig = function() {
var that = this;
- that.log.info("loading book configuration....");
+ that.log.info('loading book configuration....');
return that.config.load()
.then(function() {
that.log.info.ok();
@@ -349,34 +338,34 @@ Book.prototype.parsePlugins = function() {
// 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."));
+ 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");
+ 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 structure = that.config.getStructure("readme");
- that.log.debug.ln("start parsing readme:", structure);
+ var structure = that.config.getStructure('readme');
+ that.log.debug.ln('start parsing readme:', structure);
return that.findFile(structure)
.then(function(readme) {
- if (!readme) throw "No README file";
- if (!_.contains(that.files, readme.path)) throw "README file is ignored";
+ 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);
+ 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",
+ name: err.name || 'Readme Parse Error',
fileName: that.readmeFile
});
});
@@ -393,8 +382,8 @@ Book.prototype.parseReadme = function() {
Book.prototype.parseLangs = function() {
var that = this;
- var structure = that.config.getStructure("langs");
- that.log.debug.ln("start parsing languages index:", structure);
+ var structure = that.config.getStructure('langs');
+ that.log.debug.ln('start parsing languages index:', structure);
return that.findFile(structure)
.then(function(langs) {
@@ -403,13 +392,13 @@ Book.prototype.parseLangs = function() {
that.langsFile = langs.path;
that._defaultsStructure(that.langsFile);
- that.log.debug.ln("languages index located at", 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",
+ name: err.name || 'Langs Parse Error',
fileName: that.langsFile
});
});
@@ -424,29 +413,29 @@ Book.prototype.parseLangs = function() {
Book.prototype.parseSummary = function() {
var that = this;
- var structure = that.config.getStructure("summary");
- that.log.debug.ln("start parsing summary:", structure);
+ var structure = that.config.getStructure('summary');
+ that.log.debug.ln('start parsing summary:', structure);
return that.findFile(structure)
.then(function(summary) {
- if (!summary) throw "No SUMMARY file";
+ if (!summary) throw 'No SUMMARY file';
// 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);
- that.log.debug.ln("summary located at", that.summaryFile);
+ 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"),
+ entryPointTitle: that.i18n('SUMMARY_INTRODUCTION'),
files: that.files
})
.fail(function(err) {
throw that.normError(err, {
- name: err.name || "Summary Parse Error",
+ name: err.name || 'Summary Parse Error',
fileName: that.summaryFile
});
});
@@ -462,8 +451,8 @@ Book.prototype.parseSummary = function() {
Book.prototype.parseGlossary = function() {
var that = this;
- var structure = that.config.getStructure("glossary");
- that.log.debug.ln("start parsing glossary: ", structure);
+ var structure = that.config.getStructure('glossary');
+ that.log.debug.ln('start parsing glossary: ', structure);
return that.findFile(structure)
.then(function(glossary) {
@@ -474,13 +463,13 @@ Book.prototype.parseGlossary = function() {
that._defaultsStructure(that.glossaryFile);
that.files = _.without(that.files, that.glossaryFile);
- that.log.debug.ln("glossary located at", 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",
+ name: err.name || 'Glossary Parse Error',
fileName: that.glossaryFile
});
});
@@ -512,12 +501,12 @@ Book.prototype.parsePage = function(filename, options) {
});
};
- that.log.debug.ln("start parsing file", filename);
+ 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));
+ if (!filetype) return Q.reject(new Error('Can\'t parse file: '+filename));
// Type of parser used
page.type = filetype.name;
@@ -531,7 +520,7 @@ Book.prototype.parsePage = function(filename, options) {
// Progress in the book
page.progress = parseProgress(that.navigation, filename);
- that.log.debug.ln("render template", filename);
+ that.log.debug.ln('render template', filename);
// Read file content
return that.readFile(page.path)
@@ -558,7 +547,7 @@ Book.prototype.parsePage = function(filename, options) {
.then(function(content) {
page.content = content;
- that.log.debug.ln("use file parser", filetype.name, "for", filename);
+ that.log.debug.ln('use file parser', filetype.name, 'for', filename);
return filetype.page(page.content);
})
@@ -566,7 +555,7 @@ Book.prototype.parsePage = function(filename, options) {
.then(function(_page) {
return _.reduce(_page.sections, function(prev, section) {
return prev.then(function(_sections) {
- return that.template.postProcess(section.content || "")
+ return that.template.postProcess(section.content || '')
.then(function(content) {
section.content = content;
return _sections.concat([section]);
@@ -582,8 +571,8 @@ Book.prototype.parsePage = function(filename, options) {
convertImages: options.convertImages,
input: filename,
navigation: that.navigation,
- base: path.dirname(filename) || "./",
- output: path.dirname(filename) || "./",
+ base: path.dirname(filename) || './',
+ output: path.dirname(filename) || './',
glossary: that.glossary
});
})
@@ -595,7 +584,6 @@ Book.prototype.parsePage = function(filename, options) {
})
.then(function() {
- that.indexPage(page);
return page;
});
};
@@ -623,6 +611,24 @@ 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(
@@ -639,7 +645,7 @@ Book.prototype.fileIsInBook = function(filename) {
Book.prototype.readFile = function(filename) {
return fs.readFile(
this.resolve(filename),
- { encoding: "utf8" }
+ { encoding: 'utf8' }
);
};
@@ -653,28 +659,28 @@ Book.prototype.listAllFiles = function() {
var that = this;
return fs.list(this.root, {
- ignoreFiles: [".ignore", ".gitignore", ".bookignore"],
+ ignoreFiles: ['.ignore', '.gitignore', '.bookignore'],
ignoreRules: [
// Skip Git stuff
- ".git/",
- ".gitignore",
+ '.git/',
+ '.gitignore',
// Skip OS X meta data
- ".DS_Store",
+ '.DS_Store',
// Skip stuff installed by plugins
- "node_modules",
+ 'node_modules',
// Skip book outputs
- "_book",
- "*.pdf",
- "*.epub",
- "*.mobi",
+ '_book',
+ '*.pdf',
+ '*.epub',
+ '*.mobi',
// Skip config files
- ".ignore",
- ".bookignore",
- "book.json",
+ '.ignore',
+ '.bookignore',
+ 'book.json',
]
})
.then(function(_files) {
@@ -693,7 +699,7 @@ Book.prototype.parentRoot = function() {
return this.root;
};
-// Return true if it"s a sub-book
+// Return true if it's a sub-book
Book.prototype.isSubBook = function() {
return !!this.parent;
};
@@ -722,13 +728,13 @@ Book.prototype.relative = function(p) {
// Normalize a path to .html and convert README -> index
Book.prototype.contentPath = function(link) {
if (
- path.basename(link, path.extname(link)) == "README" ||
+ path.basename(link, path.extname(link)) == 'README' ||
link == this.readmeFile
) {
- link = path.join(path.dirname(link), "index"+path.extname(link));
+ link = path.join(path.dirname(link), 'index'+path.extname(link));
}
- link = links.changeExtension(link, ".html");
+ link = links.changeExtension(link, '.html');
return link;
};
@@ -737,40 +743,15 @@ Book.prototype.contentLink = function(link) {
return links.normalize(this.contentPath(link));
};
-// Index a page into the search index
-Book.prototype.indexPage = function(page) {
- var nav = this.navigation[page.path];
- if (!nav || !this.searchIndexEnabled) return;
-
- this.log.debug.ln("index page", page.path);
-
- // Extract text from the page
- var text = pageUtil.extractText(page.sections);
-
- // Limit size of index (to avoid #941)
- this.searchIndexSize = this.searchIndexSize + text.length;
- if (this.searchIndexSize > this.config.get('search.maxIndexSize')) {
- this.log.warn.ln("search index is too big, indexing is now disabled");
- this.searchIndexEnabled = false;
- return;
- }
-
- this.searchIndex.add({
- url: this.contentLink(page.path),
- title: nav.title,
- body: text
- });
-};
-
// 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;
+ 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
@@ -803,87 +784,13 @@ Book.prototype.normError = function(err, opts, defs) {
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(", ")+")" : "");
+ 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;
};
-// Init and return a book
-Book.init = function(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= Book;