diff options
Diffstat (limited to 'lib/book.js')
-rw-r--r-- | lib/book.js | 929 |
1 files changed, 232 insertions, 697 deletions
diff --git a/lib/book.js b/lib/book.js index 9ba27f3..400296e 100644 --- a/lib/book.js +++ b/lib/book.js @@ -1,25 +1,34 @@ -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 Ignore = require('ignore'); + +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 error = require('./utils/error'); +var Promise = require('./utils/promise'); +var Logger = require('./utils/logger'); +var parsers = require('./parsers'); + + +/* +The Book class is an interface for parsing books content. +It does not require to run on Node.js, isnce it only depends on the fs implementation +*/ + +function Book(opts) { + if (!(this instanceof Book)) return new Book(opts); -var Configuration = require('./configuration'); -var TemplateEngine = require('./template'); -var PluginsList = require('./pluginslist'); + this.opts = _.defaults(opts || {}, { + fs: null, -var generators = require('./generators'); + // Root path for the book + root: '', -var Book = function(root, context, parent) { - this.context = _.defaults(context || {}, { // Extend book configuration config: {}, @@ -32,572 +41,234 @@ var Book = function(root, context, parent) { logLevel: 'info' }); - // Log - this.log = logger(this.context.log, this.context.logLevel); + if (!opts.fs) throw error.ParsingError(new Error('Book requires a fs instance')); - // Root folder of the book - this.root = path.resolve(root); + // Root path for the book + this.root = opts.root; - // Parent book - this.parent = parent; + // If multi-lingual, book can have a parent + this.parent = opts.parent; + if (this.parent) { + this.language = path.relative(this.parent.root, this.root); + } - // Configuration - this.config = new Configuration(this, this.context.config); - Object.defineProperty(this, 'options', { - get: function () { - return this.config.options; - } - }); + // A book is linked to an fs, to access its content + this.fs = opts.fs; - // Template - this.template = new TemplateEngine(this); + // Rules to ignore some files + this.ignore = Ignore(); + this.ignore.addPattern([ + // Skip Git stuff + '.git/', - // Summary - this.summary = {}; - this.navigation = []; + // Skip OS X meta data + '.DS_Store', - // Glossary - this.glossary = []; + // Skip stuff installed by plugins + 'node_modules', - // Langs - this.langs = []; + // Skip book outputs + '_book', + '*.pdf', + '*.epub', + '*.mobi' + ]); - // Sub-books - this.books = []; + // Create a logger for the book + this.log = new Logger(opts.log, opts.logLevel); - // Files in the book - this.files = []; + // Create an interface to access the configuration + this.config = new Config(this, opts.config); - // List of plugins - this.plugins = new PluginsList(this); + // Interfaces for the book structure + this.readme = new Readme(this); + this.summary = new Summary(this); + this.glossary = new Glossary(this); - // Structure files - this.summaryFile = null; - this.glossaryFile = null; - this.readmeFile = null; - this.langsFile = null; + // Multilinguals book + this.langs = new Langs(this); + this.books = []; - // Bind methods - _.bindAll(this); -}; + // List of page in the book + this.pages = {}; -// Return string representation -Book.prototype.toString = function() { - return '[Book '+this.root+']'; -}; + _.bindAll(this); +} -// Initialize and parse the book: config, summary, glossary -Book.prototype.parse = function() { - var that = this; - var multilingual = false; +// Return templating context for the book +Book.prototype.getContext = function() { + var variables = this.config.get('variables', {}); - return this.parseConfig() + return { + book: _.extend({ + language: this.language + }, variables) + }; +}; - .then(function() { - return that.parsePlugins(); - }) +// Parse and prepare the configuration, fail if invalid +Book.prototype.prepareConfig = function() { + return this.config.load(); +}; - .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 - ); - }); +// Resolve a path in the book source +// Enforce that the output path is in the scope +Book.prototype.resolve = function() { + var filename = path.resolve.apply(path, [this.root].concat(_.toArray(arguments))); + if (!this.isFileInScope(filename)) { + throw error.FileOutOfScopeError({ + filename: filename, + root: this.root }); - }) + } - .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(); - }) + return filename; +}; - .then(function() { - // Init sub-books - return _.reduce(that.books, function(prev, book) { - return prev.then(function() { - return book.parse(); - }); - }, Q()); - }) +// Return false if a file is outside the book' scope +Book.prototype.isFileInScope = function(filename) { + filename = path.resolve(this.root, filename); - .thenResolve(this); + // Is the file in the scope of the parent? + if (this.parent && this.parent.isFileInScope(filename)) return true; + + // Is file in the root folder? + return pathUtil.isInRoot(this.root, filename); }; -// 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+')'); - }) - .then(function() { - that.log.info.ok(); + return Promise.serie([ + '.ignore', + '.gitignore', + '.bookignore' + ], function(filename) { + return that.readFile(filename) + .then(function(content) { + that.ignore.addPattern(content.toString().split(/\r?\n/)); + }, function() { + return Promise(); }); - }) + }); +}; - // Create generator - .then(function() { - var Generator = generators[generator]; - if (!Generator) throw 'Generator \''+that.options.generator+'\' doesn\'t exist'; - generator = new Generator(that); +// Parse the whole book +Book.prototype.parse = function() { + var that = this; - return generator.prepare(); - }) + return Promise() + .then(this.prepareConfig) + .then(this.parseIgnoreRules) - // Transform configuration + // Parse languages .then(function() { - return that.callHook('config', that.config.dump()) - .then(function(newConfig) { - that.config.replace(newConfig); - }); + return that.langs.load(); }) - // 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() + if (that.isLanguageBook()) { + throw error.ParsingError(new Error('A multilingual book as a language book is forbidden')); + } + + that.log.info.ln('Parsing multilingual book, with', that.langs.count(), 'languages'); + + // Create a new book for each language and parse it + return Promise.serie(that.langs.list(), function(lang) { + that.log.debug.ln('Preparing book for language', lang.id); + var langBook = new Book(_.extend({}, that.opts, { + parent: that, + config: that.config.dump(), + root: that.resolve(lang.id) + })); - // 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()); - }) + that.books.push(langBook); - // 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()); + return langBook.parse(); }); } - }) - // 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'); - }); -}; + return Promise() -// Generate the output for a multilingual book -Book.prototype.generateMultiLingual = 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()); - }); -}; + // Parse the readme + .then(that.readme.load) + .then(function() { + if (!that.readme.exists()) { + throw new error.FileNotFoundError({ filename: 'README' }); + } -// Extract files from ebook generated -Book.prototype.generateFile = function(output, options) { - var book = this; + // Default configuration to infos extracted from readme + if (!that.config.get('title')) that.config.set('title', that.readme.title); + if (!that.config.get('description')) that.config.set('description', that.readme.description); + }) - options = _.defaults(options || {}, { - ebookFormat: path.extname(output).slice(1) - }); - output = output || path.resolve(book.root, 'book.'+options.ebookFormat); + // Parse the summary + .then(that.summary.load) + .then(function() { + if (!that.summary.exists()) { + that.log.warn.ln('no summary file in this book'); + } + + // Index summary's articles + that.summary.walk(function(article) { + if (!article.hasLocation() || article.isExternal()) return; + that.addPage(article.path); + }); + }) - return fs.tmp.dir() - .then(function(tmpDir) { - book.setOutput(tmpDir); + // Parse the glossary + .then(that.glossary.load) - return book.generate(options.ebookFormat) + // Add the glossary as a page .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); - }); + if (!that.glossary.exists()) return; + that.addPage(that.glossary.path); }); }); }; -// Parse configuration -Book.prototype.parseConfig = function() { - var that = this; +// Mark a filename as being parsable +Book.prototype.addPage = function(filename) { + if (this.hasPage(filename)) return this.getPage(filename); - that.log.info('loading book configuration....'); - return that.config.load() - .then(function() { - that.log.info.ok(); - }); + filename = pathUtil.normalize(filename); + this.pages[filename] = new Page(this, filename); + return this.pages[filename]; }; -// 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'); - }); +// Return a page by its filename (or undefined) +Book.prototype.getPage = function(filename) { + filename = pathUtil.normalize(filename); + return this.pages[filename]; }; -// 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 - }); - }); - }); - }) - .then(function(readme) { - that.options.title = that.options.title || readme.title; - that.options.description = that.options.description || readme.description; - }); +// Return true, if has a specific page +Book.prototype.hasPage = function(filename) { + return Boolean(this.getPage(filename)); }; - -// 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; - }); +// Test if a file is ignored, return true if it is +Book.prototype.isFileIgnored = function(filename) { + return this.ignore.filter([filename]).length == 0; }; -// Parse summary to extract list of chapters -Book.prototype.parseSummary = function() { - var that = this; - - var filename = that.config.getStructure('summary', true); - that.log.debug.ln('start parsing summary:', filename); - - return that.findFile(filename) - .then(function(summary) { - 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); - 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); - }); -}; - -// 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; - }); +// Read a file in the book, throw error if ignored +Book.prototype.readFile = function(filename) { + if (this.isFileIgnored(filename)) return Promise.reject(new error.FileNotFoundError({ filename: filename })); + return this.fs.readAsString(this.resolve(filename)); }; -// 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; - }); +// Get stat infos about a file +Book.prototype.statFile = function(filename) { + if (this.isFileIgnored(filename)) return Promise.reject(new error.FileNotFoundError({ filename: filename })); + return this.fs.stat(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 +285,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; @@ -624,200 +295,64 @@ Book.prototype.findFile = function(filename) { }; }); }); - }, Q(null)); + }, Promise(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) - ); +// Return true if book is associated to a language +Book.prototype.isLanguageBook = function() { + return Boolean(this.parent); }; +Book.prototype.isSubBook = Book.prototype.isLanguageBook; -// Check if a file path is inside the book -Book.prototype.fileIsInBook = function(filename) { - return pathUtil.isInRoot(this.root, filename); +// Return true if the book is main instance of a multilingual book +Book.prototype.isMultilingual = function() { + return this.langs.count() > 0; }; -// Read a file -Book.prototype.readFile = function(filename) { - return fs.readFile( - this.resolve(filename), - { encoding: 'utf8' } +// Return true if file is in the scope of this book +Book.prototype.isInBook = function(filename) { + return pathUtil.isInRoot( + this.root, + filename ); }; -// 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() { +// Return true if file is in the scope of a child book +Book.prototype.isInLanguageBook = function(filename) { 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 _.some(this.langs.list(), function(lang) { + return pathUtil.isInRoot( + that.resolve(lang.id), + that.resolve(filename) + ); }); }; -// Return true if the book is 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)); +// Locate a book in a folder +// - Read the ".gitbook" is exists +// - Try the folder itself +// - Try a "docs" folder +Book.locate = function(fs, root) { + return fs.readAsString(path.join(root, '.gitbook')) + .then(function(content) { + return path.join(root, content); + }, function() { + // .gitbook doesn't exists, fall back to the root folder + return Promise(root); }); }; -// 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; +// Locate and setup a book +Book.setup = function(fs, root, opts) { + return Book.locate(fs, root) + .then(function(_root) { + return new Book(_.extend(opts || {}, { + root: _root, + fs: fs + })); + }); }; -// Call a hook in plugins -Book.prototype.callHook = function(name, data) { - return this.plugins.hook(name, data); -}; -module.exports= Book; +module.exports = Book; |