diff options
author | Samy Pesse <samypesse@gmail.com> | 2016-04-24 19:12:03 +0200 |
---|---|---|
committer | Samy Pesse <samypesse@gmail.com> | 2016-04-24 19:12:03 +0200 |
commit | 93b257314aa9a6ba240ff7d9534ddb1a6ded6bc0 (patch) | |
tree | fdb9103fe3d32c34326b7a13746668e2056a40de /lib | |
parent | 19a1db8cb43431352d30dbc48aebb9cc8bf58eec (diff) | |
download | gitbook-93b257314aa9a6ba240ff7d9534ddb1a6ded6bc0.zip gitbook-93b257314aa9a6ba240ff7d9534ddb1a6ded6bc0.tar.gz gitbook-93b257314aa9a6ba240ff7d9534ddb1a6ded6bc0.tar.bz2 |
Add default filters and blocks
Diffstat (limited to 'lib')
-rw-r--r-- | lib/book.js | 396 | ||||
-rw-r--r-- | lib/constants/defaultBlocks.js | 51 | ||||
-rw-r--r-- | lib/constants/defaultFilters.js | 15 | ||||
-rw-r--r-- | lib/output/createTemplateEngine.js | 14 | ||||
-rw-r--r-- | lib/output/loaders/conrefs.js | 8 |
5 files changed, 87 insertions, 397 deletions
diff --git a/lib/book.js b/lib/book.js deleted file mode 100644 index 77e973a..0000000 --- a/lib/book.js +++ /dev/null @@ -1,396 +0,0 @@ -var _ = require('lodash'); -var path = require('path'); -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 error = require('./utils/error'); -var Promise = require('./utils/promise'); -var Logger = require('./utils/logger'); -var parsers = require('./parsers'); -var initBook = require('./init'); - - -/* -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); - - this.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 error.ParsingError(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; - if (this.parent) { - this.language = path.relative(this.parent.root, this.root); - } - - // 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([ - // Skip Git stuff - '.git/', - - // Skip OS X meta data - '.DS_Store', - - // Skip stuff installed by plugins - 'node_modules', - - // Skip book outputs - '_book', - '*.pdf', - '*.epub', - '*.mobi' - ]); - - // 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); - - // Multilinguals book - this.langs = new Langs(this); - this.books = []; - - // List of page in the book - this.pages = {}; - - // Deprecation for templates - Object.defineProperty(this, 'options', { - get: function () { - this.log.warn.ln('"options" property is deprecated, use config.get(key) instead'); - return this.config.options; - } - }); - - _.bindAll(this); - - // Loop for template filters/blocks - error.deprecateField(this, 'book', this, '"book" property is deprecated, use "this" directly instead'); -} - -// Return templating context for the book -Book.prototype.getContext = function() { - var variables = this.config.get('variables', {}); - - return { - book: _.extend({ - language: this.language - }, variables) - }; -}; - -// Parse and prepare the configuration, fail if invalid -Book.prototype.prepareConfig = function() { - var that = this; - - return this.config.load() - .then(function() { - var rootFolder = that.config.get('root'); - if (!rootFolder) return; - - that.originalRoot = that.root; - that.root = path.resolve(that.root, rootFolder); - }); -}; - -// 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 - }); - } - - return filename; -}; - -// Return false if a file is outside the book' scope -Book.prototype.isFileInScope = function(filename) { - filename = path.resolve(this.root, filename); - - // 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); -}; - -// Parse .gitignore, etc to extract rules -Book.prototype.parseIgnoreRules = function() { - var that = this; - - 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(); - }); - }); -}; - -// Parse the whole book -Book.prototype.parse = function() { - var that = this; - - return Promise() - .then(this.prepareConfig) - .then(this.parseIgnoreRules) - - // Parse languages - .then(function() { - return that.langs.load(); - }) - - .then(function() { - if (that.isMultilingual()) { - 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) - })); - - that.books.push(langBook); - - return langBook.parse(); - }); - } - - return Promise() - - // Parse the readme - .then(that.readme.load) - .then(function() { - if (!that.readme.exists()) { - throw new error.FileNotFoundError({ filename: 'README' }); - } - - // 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); - }) - - // 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); - }); - }) - - // Parse the glossary - .then(that.glossary.load) - - // Add the glossary as a page - .then(function() { - if (!that.glossary.exists()) return; - that.addPage(that.glossary.path); - }); - }); -}; - -// Mark a filename as being parsable -Book.prototype.addPage = function(filename) { - if (this.hasPage(filename)) return this.getPage(filename); - - filename = pathUtil.normalize(filename); - this.pages[filename] = new Page(this, filename); - return this.pages[filename]; -}; - -// Return a page by its filename (or undefined) -Book.prototype.getPage = function(filename) { - filename = pathUtil.normalize(filename); - return this.pages[filename]; -}; - - -// Return true, if has a specific page -Book.prototype.hasPage = function(filename) { - return Boolean(this.getPage(filename)); -}; - -// 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 Promise.reject(new error.FileNotFoundError({ filename: filename })); - return this.fs.readAsString(this.resolve(filename)); -}; - -// 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 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.getByExt(ext), - path: realFilepath - }; - }); - }); - }, Promise(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; -}; - -// Return true if file is in the scope of this book -Book.prototype.isInBook = function(filename) { - return pathUtil.isInRoot( - this.root, - filename - ); -}; - -// Return true if file is in the scope of a child book -Book.prototype.isInLanguageBook = function(filename) { - var that = this; - - return _.some(this.langs.list(), function(lang) { - return pathUtil.isInRoot( - that.resolve(lang.id), - that.resolve(filename) - ); - }); -}; - -// ----- Parser Methods - -// Render a markup string in inline mode -Book.prototype.renderInline = function(type, src) { - var parser = parsers.get(type); - return parser.inline(src) - .get('content'); -}; - -// Render a markup string in block mode -Book.prototype.renderBlock = function(type, src) { - var parser = parsers.get(type); - return parser.page(src) - .get('content'); -}; - - -// ----- DEPRECATED METHODS - -Book.prototype.contentLink = error.deprecateMethod(function(s) { - return this.output.toURL(s); -}, '.contentLink() is deprecated, use ".output.toURL()" instead'); - -Book.prototype.contentPath = error.deprecateMethod(function(s) { - return this.output.toURL(s); -}, '.contentPath() is deprecated, use ".output.toURL()" instead'); - -Book.prototype.isSubBook = error.deprecateMethod(function() { - return this.isLanguageBook(); -}, '.isSubBook() is deprecated, use ".isLanguageBook()" instead'); - - -// Initialize a book -Book.init = function(fs, root, opts) { - var book = new Book(_.extend(opts || {}, { - root: root, - fs: fs - })); - - return initBook(book); -}; - - -module.exports = Book; diff --git a/lib/constants/defaultBlocks.js b/lib/constants/defaultBlocks.js new file mode 100644 index 0000000..5d91359 --- /dev/null +++ b/lib/constants/defaultBlocks.js @@ -0,0 +1,51 @@ +var Immutable = require('immutable'); +var TemplateBlock = require('../models/templateBlock'); + +module.exports = Immutable.List([ + TemplateBlock({ + name: 'html', + process: function(blk) { + return blk; + } + }), + + TemplateBlock({ + name: 'code', + process: function(blk) { + return { + html: false, + body: blk.body + }; + } + }), + + TemplateBlock({ + name: 'markdown', + process: function(blk) { + return this.book.renderInline('markdown', blk.body) + .then(function(out) { + return { body: out }; + }); + } + }), + + TemplateBlock({ + name: 'asciidoc', + process: function(blk) { + return this.book.renderInline('asciidoc', blk.body) + .then(function(out) { + return { body: out }; + }); + } + }), + + TemplateBlock({ + name: 'markup', + process: function(blk) { + return this.book.renderInline(this.ctx.file.type, blk.body) + .then(function(out) { + return { body: out }; + }); + } + }), +]); diff --git a/lib/constants/defaultFilters.js b/lib/constants/defaultFilters.js new file mode 100644 index 0000000..35025cc --- /dev/null +++ b/lib/constants/defaultFilters.js @@ -0,0 +1,15 @@ +var Immutable = require('immutable'); +var moment = require('moment'); + +module.exports = Immutable.Map({ + // Format a date + // ex: 'MMMM Do YYYY, h:mm:ss a + date: function(time, format) { + return moment(time).format(format); + }, + + // Relative Time + dateFromNow: function(time) { + return moment(time).fromNow(); + } +}); diff --git a/lib/output/createTemplateEngine.js b/lib/output/createTemplateEngine.js index 810c41e..27dbeba 100644 --- a/lib/output/createTemplateEngine.js +++ b/lib/output/createTemplateEngine.js @@ -1,5 +1,10 @@ var Immutable = require('immutable'); + var TemplateEngine = require('../models/templateEngine'); +var ConrefsLoader = require('./loaders/conrefs'); + +var defaultBlocks = require('../constants/defaultBlocks'); +var defaultFilters = require('../constants/defaultFilters'); /** Create template engine for an output. @@ -22,9 +27,16 @@ function createTemplateEngine(output) { }) .flatten(); + // Extend with default + blocks = defaultBlocks.concat(blocks); + filters = defaultFilters.merge(filters); + + var loader = new ConrefsLoader(); + return new TemplateEngine({ filters: filters, - blocks: blocks + blocks: blocks, + loader: loader }); } diff --git a/lib/output/loaders/conrefs.js b/lib/output/loaders/conrefs.js new file mode 100644 index 0000000..7580826 --- /dev/null +++ b/lib/output/loaders/conrefs.js @@ -0,0 +1,8 @@ +var nunjucks = require('nunjucks'); + +var Loader = nunjucks.Loader.extend({ + async: true, + +}); + +module.exports = Loader; |