summaryrefslogtreecommitdiffstats
path: root/lib/output/base.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/output/base.js')
-rw-r--r--lib/output/base.js274
1 files changed, 274 insertions, 0 deletions
diff --git a/lib/output/base.js b/lib/output/base.js
new file mode 100644
index 0000000..a1d8804
--- /dev/null
+++ b/lib/output/base.js
@@ -0,0 +1,274 @@
+var _ = require('lodash');
+var Ignore = require('ignore');
+var path = require('path');
+
+var Promise = require('../utils/promise');
+var pathUtil = require('../utils/path');
+var location = require('../utils/location');
+var PluginsManager = require('../plugins');
+var TemplateEngine = require('../template');
+
+/*
+Output is like a stream interface for a parsed book
+to output "something".
+
+The process is mostly on the behavior of "onPage" and "onAsset"
+*/
+
+function Output(book, opts, parent) {
+ _.bindAll(this);
+ this.parent = parent;
+
+ this.opts = _.defaults({}, opts || {}, {
+ directoryIndex: true
+ });
+
+ this.book = book;
+ this.log = this.book.log;
+
+ // Create plugins manager
+ this.plugins = new PluginsManager(this.book);
+
+ // Create template engine
+ this.template = new TemplateEngine(this);
+
+ // Files to ignore in output
+ this.ignore = Ignore();
+}
+
+// Default extension for output
+Output.prototype.defaultExtension = '.html';
+
+// Start the generation, for a parsed book
+Output.prototype.generate = function() {
+ var that = this;
+ var isMultilingual = this.book.isMultilingual();
+
+ return Promise()
+
+ // Load all plugins
+ .then(function() {
+ return that.plugins.loadAll()
+ .then(function() {
+ that.template.addFilters(that.plugins.getFilters());
+ that.template.addBlocks(that.plugins.getBlocks());
+ });
+ })
+
+ // Transform the configuration
+ .then(function() {
+ return that.plugins.hook('config', that.book.config.dump())
+ .then(function(cfg) {
+ that.book.config.replace(cfg);
+ });
+ })
+
+ // Initialize the generation
+ .then(function() {
+ return that.plugins.hook('init');
+ })
+ .then(function() {
+ that.log.info.ln('preparing the generation');
+ return that.prepare();
+ })
+
+ // Process all files
+ .then(function() {
+ that.log.debug.ln('listing files');
+ return that.book.fs.listAllFiles(that.book.root);
+ })
+
+ // We want to process assets first, then pages
+ // Since pages can have logic based on existance of assets
+ .then(function(files) {
+ // Split into pages/assets
+ var byTypes = _.chain(files)
+ .filter(that.ignore.createFilter())
+
+ // Ignore file present in a language book
+ .filter(function(filename) {
+ return !(isMultilingual && that.book.isInLanguageBook(filename));
+ })
+
+ .groupBy(function(filename) {
+ return (that.book.hasPage(filename)? 'page' : 'asset');
+ })
+
+ .value();
+
+ return Promise.serie(byTypes.asset, function(filename) {
+ that.log.debug.ln('copy asset', filename);
+ return that.onAsset(filename);
+ })
+ .then(function() {
+ return Promise.serie(byTypes.page, function(filename) {
+ that.log.debug.ln('process page', filename);
+ return that.onPage(that.book.getPage(filename));
+ });
+ });
+ })
+
+ // Generate sub-books
+ .then(function() {
+ if (!that.book.isMultilingual()) return;
+
+ return Promise.serie(that.book.books, function(subbook) {
+ that.log.info.ln('');
+ that.log.info.ln('start generation of language "' + path.relative(that.book.root, subbook.root) + '"');
+
+ var out = that.onLanguageBook(subbook);
+ return out.generate();
+ });
+ })
+
+ // Finish the generation
+ .then(function() {
+ return that.plugins.hook('finish:before');
+ })
+ .then(function() {
+ that.log.debug.ln('finishing the generation');
+ return that.finish();
+ })
+ .then(function() {
+ return that.plugins.hook('finish');
+ })
+
+ .then(function() {
+ if (!that.book.isLanguageBook()) that.log.info.ln('');
+ that.log.info.ok('generation finished with success!');
+ });
+};
+
+// Prepare the generation
+Output.prototype.prepare = function() {
+ this.ignore.addPattern(_.compact([
+ '.gitignore',
+ '.ignore',
+ '.bookignore',
+ 'node_modules',
+
+ // The configuration file should not be copied in the output
+ this.book.config.path,
+
+ // Structure file to ignore
+ this.book.summary.path,
+ this.book.langs.path
+ ]));
+};
+
+// Write a page (parsable file), ex: markdown, etc
+Output.prototype.onPage = function(page) {
+ return page.toHTML(this);
+};
+
+// Copy an asset file (non-parsable), ex: images, etc
+Output.prototype.onAsset = function(filename) {
+
+};
+
+// Finish the generation
+Output.prototype.finish = function() {
+
+};
+
+// Resolve an HTML link
+Output.prototype.onRelativeLink = function(currentPage, href) {
+ var to = currentPage.followPage(href);
+
+ // Replace by an .html link
+ if (to) {
+ href = to.path;
+
+ // Recalcul as relative link
+ href = currentPage.relative(href);
+
+ // Replace .md by .html
+ href = this.outputUrl(href);
+ }
+
+ return href;
+};
+
+// Output a SVG buffer as a file
+Output.prototype.onOutputSVG = function(page, svg) {
+ return null;
+};
+
+// Output an image as a file
+// Normalize the relative link
+Output.prototype.onOutputImage = function(page, imgFile) {
+ imgFile = page.resolveLocal(imgFile);
+ return page.relative(imgFile);
+};
+
+// Read a template by its source URL
+Output.prototype.onGetTemplate = function(sourceUrl) {
+ throw new Error('template not found '+sourceUrl);
+};
+
+// Generate a source URL for a template
+Output.prototype.onResolveTemplate = function(from, to) {
+ return path.resolve(path.dirname(from), to);
+};
+
+// Prepare output for a language book
+Output.prototype.onLanguageBook = function(book) {
+ return new this.constructor(book, this.opts, this);
+};
+
+
+// ---- Utilities ----
+
+// Return a default context for templates
+Output.prototype.getContext = function() {
+ return _.extend(
+ {},
+ this.book.getContext(),
+ this.book.langs.getContext(),
+ this.book.summary.getContext(),
+ this.book.glossary.getContext(),
+ this.book.config.getContext()
+ );
+};
+
+// Resolve a file path in the context of a specific page
+// Result is an "absolute path relative to the output folder"
+Output.prototype.resolveForPage = function(page, href) {
+ if (_.isString(page)) page = this.book.getPage(page);
+
+ href = page.relative(href);
+ return this.onRelativeLink(page, href);
+};
+
+// Filename for output
+// READMEs are replaced by index.html
+// /test/README.md -> /test/index.html
+Output.prototype.outputPath = function(filename, ext) {
+ ext = ext || this.defaultExtension;
+ var output = filename;
+
+ if (
+ path.basename(filename, path.extname(filename)) == 'README' ||
+ output == this.book.readme.path
+ ) {
+ output = path.join(path.dirname(output), 'index'+ext);
+ } else {
+ output = pathUtil.setExtension(output, ext);
+ }
+
+ return output;
+};
+
+// Filename for output
+// /test/index.html -> /test/
+Output.prototype.outputUrl = function(filename, ext) {
+ var href = this.outputPath(filename, ext);
+
+ if (path.basename(href) == 'index.html' && this.opts.directoryIndex) {
+ href = path.dirname(href) + '/';
+ }
+
+ return location.normalize(href);
+};
+
+module.exports = Output;