diff options
Diffstat (limited to 'lib/output')
-rw-r--r-- | lib/output/ebook.js | 10 | ||||
-rw-r--r-- | lib/output/website/index.js (renamed from lib/output/website.js) | 167 | ||||
-rw-r--r-- | lib/output/website/templateEnv.js | 87 | ||||
-rw-r--r-- | lib/output/website/themeLoader.js | 99 |
4 files changed, 260 insertions, 103 deletions
diff --git a/lib/output/ebook.js b/lib/output/ebook.js index b0b05ca..2b8fac9 100644 --- a/lib/output/ebook.js +++ b/lib/output/ebook.js @@ -44,13 +44,7 @@ EbookOutput.prototype.finish = function() { // Generate SUMMARY.html .then(function() { - return that.render('summary', that.getContext()) - .then(function(html) { - return that.writeFile( - 'SUMMARY.html', - html - ); - }); + return that.render('summary', 'SUMMARY.html', that.getContext()); }) // Start ebook-convert @@ -100,7 +94,7 @@ EbookOutput.prototype.getPDFTemplate = function(tpl) { this.getContext() ); - return this.render('pdf_'+tpl, context) + return this.renderAsString('pdf_'+tpl, context) // Inline css, include css relative to the output folder .then(function(output) { diff --git a/lib/output/website.js b/lib/output/website/index.js index 1eaf98a..9ef87b6 100644 --- a/lib/output/website.js +++ b/lib/output/website/index.js @@ -1,22 +1,14 @@ var _ = require('lodash'); var path = require('path'); var util = require('util'); -var nunjucks = require('nunjucks'); var I18n = require('i18n-t'); -var Promise = require('../utils/promise'); -var location = require('../utils/location'); -var fs = require('../utils/fs'); -var defaultFilters = require('../template/filters'); -var FSLoader = require('../template/fs-loader'); -var conrefsLoader = require('./conrefs'); -var Output = require('./base'); - - -// Directory for a theme with the templates -function templatesPath(dir) { - return path.join(dir, '_layouts'); -} +var Promise = require('../../utils/promise'); +var location = require('../../utils/location'); +var fs = require('../../utils/fs'); +var conrefsLoader = require('../conrefs'); +var Output = require('../base'); +var setupTemplateEnv = require('./templateEnv'); function _WebsiteOutput() { Output.apply(this, arguments); @@ -68,58 +60,7 @@ WebsiteOutput.prototype.prepare = function() { that.i18n.load(i18nRoot); }); - that.env = new nunjucks.Environment(new FSLoader(_.map(searchPaths, templatesPath))); - - // Add GitBook default filters - _.each(defaultFilters, function(fn, filter) { - that.env.addFilter(filter, fn); - }); - - // Translate using _i18n locales - that.env.addFilter('t', function(s) { - return that.i18n.t(that.book.config.get('language'), s); - }); - - // Transform an absolute path into a relative path - // using this.ctx.page.path - that.env.addFilter('resolveFile', function(href) { - return location.normalize(that.resolveForPage(this.ctx.file.path, href)); - }); - - // Test if a file exists - that.env.addFilter('fileExists', function(href) { - return fs.existsSync(that.resolve(href)); - }); - - // Transform a '.md' into a '.html' (README -> index) - that.env.addFilter('contentURL', function(s) { - return that.toURL(s); - }); - - // Get an article using its path - that.env.addFilter('getArticleByPath', function(s) { - var article = that.book.summary.getArticle(s); - if (!article) return undefined; - - return article.getContext(); - }); - - // Relase path to an asset - that.env.addFilter('resolveAsset', function(href) { - href = path.join('gitbook', href); - - // Resolve for current file - if (this.ctx.file) { - href = that.resolveForPage(this.ctx.file.path, '/' + href); - } - - // Use assets from parent - if (that.book.isLanguageBook()) { - href = path.join('../', href); - } - - return location.normalize(href); - }); + that.searchPaths = searchPaths; }) // Copy assets from themes before copying files from book @@ -164,15 +105,7 @@ WebsiteOutput.prototype.onPage = function(page) { // Render the page template with the same context as the json output .then(function() { - return that.render('page', page.getOutputContext(that)); - }) - - // Write the HTML file - .then(function(html) { - return that.writeFile( - that.outputPath(page.path), - html - ); + return that.render('page', that.outputPath(page.path), page.getOutputContext(that)); }); }; @@ -204,46 +137,87 @@ WebsiteOutput.prototype.finish = function() { WebsiteOutput.prototype.outputMultilingualIndex = function() { var that = this; - return that.render('languages', that.getContext()) - .then(function(html) { - return that.writeFile( - 'index.html', - html - ); + return that.render('languages', 'index.html', that.getContext()); +}; + +/* + Render a template as an HTML string + Templates are stored in `_layouts` folders + + + @param {String} tpl: template name (ex: "page") + @param {String} outputFile: filename to write, relative to the output + @param {Object} context: context for the page + @return {Promise} +*/ +WebsiteOutput.prototype.renderAsString = function(tpl, context) { + // Calcul template name + var filename = this.templateName(tpl); + + // Extend context + // Setup complete context + context.template = _.extend(context.template || {}, { + self: filename }); + + context = _.extend(context, { + plugins: { + resources: this.resources + }, + + options: this.opts + }); + + // Create environment + var env = setupTemplateEnv(this, context); + + return Promise.nfcall(env.render.bind(env), filename, context); }; -// Render a template using nunjucks -// Templates are stored in `_layouts` folders -WebsiteOutput.prototype.render = function(tpl, context) { +/* + Render a template using nunjucks + Templates are stored in `_layouts` folders + + + @param {String} tpl: template name (ex: "page") + @param {String} outputFile: filename to write, relative to the output + @param {Object} context: context for the page + @return {Promise} +*/ +WebsiteOutput.prototype.render = function(tpl, outputFile, context) { var that = this; - var filename = this.templateName(tpl); + // Calcul relative path to the root + var outputDirName = path.dirname(outputFile); + var basePath = location.normalize(path.relative(outputDirName, './')); + + // Setup complete context context = _.extend(context, { + basePath: basePath, + template: { - self: filename, getJSContext: function() { return { page: _.omit(context.page, 'content'), config: context.config, file: context.file, gitbook: context.gitbook, - basePath: location.normalize(that.resolveForPage(context.file.path, './')), + basePath: basePath, book: { language: context.book.language } }; } - }, - - plugins: { - resources: this.resources - }, - - options: this.opts + } }); - return Promise.nfcall(this.env.render.bind(this.env), filename, context); + return this.renderAsString(tpl, context) + .then(function(html) { + return that.writeFile( + outputFile, + html + ); + }); }; // Return a complete name for a template @@ -252,3 +226,6 @@ WebsiteOutput.prototype.templateName = function(name) { }; module.exports = WebsiteOutput; + + + diff --git a/lib/output/website/templateEnv.js b/lib/output/website/templateEnv.js new file mode 100644 index 0000000..ea2b521 --- /dev/null +++ b/lib/output/website/templateEnv.js @@ -0,0 +1,87 @@ +var _ = require('lodash'); +var nunjucks = require('nunjucks'); +var path = require('path'); +var fs = require('fs'); + +var location = require('../../utils/location'); +var defaultFilters = require('../../template/filters'); + +var ThemeLoader = require('./themeLoader'); + +// Directory for a theme with the templates +function templatesPath(dir) { + return path.join(dir, '_layouts'); +} + +/* + Create and setup at Nunjucks template environment + + @return {Nunjucks.Environment} +*/ +function setupTemplateEnv(output, context) { + var loader = new ThemeLoader( + _.map(output.searchPaths, templatesPath) + ); + var env = new nunjucks.Environment(loader); + + // Add context as global + _.each(context, function(value, key) { + env.addGlobal(key, value); + }); + + // Add GitBook default filters + _.each(defaultFilters, function(fn, filter) { + env.addFilter(filter, fn); + }); + + // Translate using _i18n locales + env.addFilter('t', function t(s) { + return output.i18n.t(output.book.config.get('language'), s); + }); + + // Transform an absolute path into a relative path + // using this.ctx.page.path + env.addFilter('resolveFile', function resolveFile(href) { + return location.normalize(output.resolveForPage(context.file.path, href)); + }); + + // Test if a file exists + env.addFilter('fileExists', function fileExists(href) { + return fs.existsSync(output.resolve(href)); + }); + + // Transform a '.md' into a '.html' (README -> index) + env.addFilter('contentURL', function contentURL(s) { + return output.toURL(s); + }); + + // Get an article using its path + env.addFilter('getArticleByPath', function getArticleByPath(s) { + var article = output.book.summary.getArticle(s); + if (!article) return undefined; + + return article.getContext(); + }); + + // Relase path to an asset + env.addFilter('resolveAsset', function resolveAsset(href) { + href = path.join('gitbook', href); + + // Resolve for current file + if (context.file) { + href = output.resolveForPage(context.file.path, '/' + href); + } + + // Use assets from parent + if (output.book.isLanguageBook()) { + href = path.join('../', href); + } + + return location.normalize(href); + }); + + + return env; +} + +module.exports = setupTemplateEnv; diff --git a/lib/output/website/themeLoader.js b/lib/output/website/themeLoader.js new file mode 100644 index 0000000..013b81c --- /dev/null +++ b/lib/output/website/themeLoader.js @@ -0,0 +1,99 @@ +var _ = require('lodash'); +var fs = require('fs'); +var path = require('path'); +var nunjucks = require('nunjucks'); + +/* + Nunjucks loader similar to FileSystemLoader, but avoid infinite looping +*/ + +/* + Return true if a filename is relative. +*/ +function isRelative(filename) { + return (filename.indexOf('./') === 0 || filename.indexOf('../') === 0); +} + +var ThemeLoader = nunjucks.Loader.extend({ + init: function(searchPaths) { + this.searchPaths = _.map(searchPaths, path.normalize); + }, + + /* + Read source of a resolved filepath + + @param {String} + @return {Object} + */ + getSource: function(fullpath) { + if (!fullpath) return null; + + fullpath = this.resolve(null, fullpath); + + if(!fullpath) { + return null; + } + + return { + src: fs.readFileSync(fullpath, 'utf-8'), + path: fullpath, + noCache: true + }; + }, + + /* + Nunjucks calls "isRelative" to determine when to call "resolve". + We handle absolute paths ourselves in ".resolve" so we always return true + */ + isRelative: function() { + return true; + }, + + /* + Resolve a template from a current template + + @param {String|null} from + @param {String} to + @return {String|null} + */ + resolve: function(from, to) { + var searchPaths = this.searchPaths; + + // Relative template like "./test.html" + if (isRelative(to) && from) { + return path.resolve(path.dirname(from), to); + } + + // Determine in which search folder we currently are + var originalSearchPath = _.chain(this.searchPaths) + .sortBy(function(s) { + return -s.length; + }) + .find(function(basePath) { + return (from && from.indexOf(basePath) === 0); + }) + .value(); + var originalFilename = originalSearchPath? path.relative(originalSearchPath, from) : null; + + // If we are including same file from a different search path + // Slice the search paths to avoid including from previous ones + if (originalFilename == to) { + var currentIndex = searchPaths.indexOf(originalSearchPath); + searchPaths = searchPaths.slice(currentIndex + 1); + } + + // Absolute template to resolve in root folder + var resultFolder = _.find(searchPaths, function(basePath) { + var p = path.resolve(basePath, to); + + return ( + p.indexOf(basePath) === 0 + && fs.existsSync(p) + ); + }); + if (!resultFolder) return null; + return path.resolve(resultFolder, to); + } +}); + +module.exports = ThemeLoader; |