Files
gitbook/lib/book.js
T
2016-01-22 21:04:36 +01:00

188 lines
4.8 KiB
JavaScript

var _ = require('lodash');
var Q = require('q');
var path = require('path');
var Ignore = require('ignore');
var parsers = require('gitbook-parsers');
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');
function Book(opts) {
if (!(this instanceof Book)) return new Book(opts);
opts = _.defaults(opts || {}, {
fs: null,
// Root path for the book
root: '',
// Extend book configuration
config: {},
// Log function
log: function(msg) {
process.stdout.write(msg);
},
// Log level
logLevel: 'info'
});
if (!opts.fs) throw new Error('Book requires a fs instance');
// Root path for the book
this.root = opts.root;
// If multi-lingual, book can have a parent
this.parent = opts.parent;
// A book is linked to an fs, to access its content
this.fs = opts.fs;
// Rules to ignore some files
this.ignore = Ignore();
this.ignore.addPattern(['.git', '.svn', '.DS_Store']);
// Create a logger for the book
this.log = new Logger(opts.log, opts.logLevel);
// Create an interface to access the configuration
this.config = new Config(this, opts.config);
// 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);
// Map of pages in the bok
this.pages = {};
_.bindAll(this);
}
// Parse and prepare the configuration, fail if invalid
Book.prototype.prepareConfig = function() {
return this.config.load();
};
// 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)));
};
// Parse .gitignore, etc to extract rules
Book.prototype.parseIgnoreRules = function() {
var that = this;
return _.reduce([
'.ignore',
'.gitignore',
'.bookignore'
], function(prev, filename) {
return prev.then(function() {
return that.readFile(filename);
})
.then(function(content) {
that.ignore.addPattern(content.toString().split(/\r?\n/));
});
}, Q());
};
// Parse the whole book
Book.prototype.parse = function() {
var that = this;
return Q()
.then(this.prepareConfig)
.then(this.parseIgnoreRules)
// Parse languages
.then(function() {
return that.langs.load();
})
.then(function() {
if (that.isMultilingual()) return;
return Q()
.then(that.readme.load)
.then(function() {
if (that.readme.exists()) return;
throw new Error('No README file (or is ignored)');
})
.then(that.summary.load)
.then(function() {
if (that.summary.exists()) return;
throw new Error('No SUMMARY file (or is ignored)');
})
.then(that.glossary.load);
});
};
// Test if a file is ignored, return true if it is
Book.prototype.isFileIgnored = function(filename) {
return this.ignore.filter([filename]).length == 0;
};
// 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 a parsable file using a filename
Book.prototype.findParsableFile = function(filename) {
var that = this;
var ext = path.extname(filename);
var basename = path.basename(filename, ext);
// Ordered list of extensions to test
var exts = parsers.extensions;
if (ext) exts = _.uniq([ext].concat(exts));
return _.reduce(exts, function(prev, ext) {
return prev.then(function(output) {
// Stop if already find a parser
if (output) return output;
var filepath = basename+ext;
return that.fs.findFile(that.root, filepath)
.then(function(realFilepath) {
if (!realFilepath) return null;
return {
parser: parsers.get(ext),
path: realFilepath
};
});
});
}, Q(null));
};
// 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 main instance of a multilingual book
Book.prototype.isMultilingual = function() {
return this.langs.count() > 0;
};
module.exports = Book;