diff options
author | Samy Pessé <samypesse@gmail.com> | 2016-04-22 11:00:21 +0200 |
---|---|---|
committer | Samy Pessé <samypesse@gmail.com> | 2016-04-22 11:00:21 +0200 |
commit | 4336fdb2414d460ffee68a0cc87c0cb0c85cf56e (patch) | |
tree | 279f711ab98666c892c19a7b9e4073a094f03f98 /lib/output | |
parent | 87db7cf1d412fa6fbd18e9a7e4f4755f2c0c5547 (diff) | |
download | gitbook-4336fdb2414d460ffee68a0cc87c0cb0c85cf56e.zip gitbook-4336fdb2414d460ffee68a0cc87c0cb0c85cf56e.tar.gz gitbook-4336fdb2414d460ffee68a0cc87c0cb0c85cf56e.tar.bz2 |
Base
Diffstat (limited to 'lib/output')
-rw-r--r-- | lib/output/assets-inliner.js | 140 | ||||
-rw-r--r-- | lib/output/base.js | 309 | ||||
-rw-r--r-- | lib/output/conrefs.js | 67 | ||||
-rw-r--r-- | lib/output/ebook.js | 193 | ||||
-rw-r--r-- | lib/output/folder.js | 152 | ||||
-rw-r--r-- | lib/output/generateBook.js | 42 | ||||
-rw-r--r-- | lib/output/generatePage.js | 9 | ||||
-rw-r--r-- | lib/output/generators/json.js | 26 | ||||
-rw-r--r-- | lib/output/index.js | 11 | ||||
-rw-r--r-- | lib/output/json.js | 47 | ||||
-rw-r--r-- | lib/output/modifiers/addHeadingId.js | 9 | ||||
-rw-r--r-- | lib/output/modifiers/htmlTransform.js | 16 | ||||
-rw-r--r-- | lib/output/modifiers/index.js | 9 | ||||
-rw-r--r-- | lib/output/modifiers/inlineAssets.js | 11 | ||||
-rw-r--r-- | lib/output/modifiers/svgToImg.js | 28 | ||||
-rw-r--r-- | lib/output/website/index.js | 225 | ||||
-rw-r--r-- | lib/output/website/templateEnv.js | 95 | ||||
-rw-r--r-- | lib/output/website/themeLoader.js | 127 |
18 files changed, 161 insertions, 1355 deletions
diff --git a/lib/output/assets-inliner.js b/lib/output/assets-inliner.js deleted file mode 100644 index 6f1f02d..0000000 --- a/lib/output/assets-inliner.js +++ /dev/null @@ -1,140 +0,0 @@ -var util = require('util'); -var path = require('path'); -var crc = require('crc'); - -var FolderOutput = require('./folder')(); -var Promise = require('../utils/promise'); -var fs = require('../utils/fs'); -var imagesUtil = require('../utils/images'); -var location = require('../utils/location'); - -var DEFAULT_ASSETS_FOLDER = 'assets'; - -/* -Mixin to inline all the assets in a book: - - Outline <svg> tags - - Download remote images - - Convert .svg images as png -*/ - -module.exports = function assetsInliner(Base) { - Base = Base || FolderOutput; - - function AssetsInliner() { - Base.apply(this, arguments); - - // Map of svg already converted - this.svgs = {}; - this.inlineSvgs = {}; - - // Map of images already downloaded - this.downloaded = {}; - } - util.inherits(AssetsInliner, Base); - - // Output a SVG buffer as a file - AssetsInliner.prototype.onOutputSVG = function(page, svg) { - this.log.debug.ln('output svg from', page.path); - - // Convert svg buffer to a png file - return this.convertSVGBuffer(svg) - - // Return relative path from the page - .then(function(filename) { - return page.relative('/' + filename); - }); - }; - - - // Output an image as a file - AssetsInliner.prototype.onOutputImage = function(page, src) { - var that = this; - - return Promise() - - // Download file if external - .then(function() { - if (!location.isExternal(src)) return; - - return that.downloadAsset(src) - .then(function(_asset) { - src = '/' + _asset; - }); - - }) - .then(function() { - // Resolve src to a relative filepath to the book's root - src = page.resolveLocal(src); - - // Already a PNG/JPG/.. ? - if (path.extname(src).toLowerCase() != '.svg') { - return src; - } - - // Convert SVG to PNG - return that.convertSVGFile(that.resolve(src)); - }) - - // Return relative path from the page - .then(function(filename) { - return page.relative(filename); - }); - }; - - // Download an asset if not already download; returns the output file - AssetsInliner.prototype.downloadAsset = function(src) { - if (this.downloaded[src]) return Promise(this.downloaded[src]); - - var that = this; - var ext = path.extname(src); - var hash = crc.crc32(src).toString(16); - - // Create new file - return this.createNewFile(DEFAULT_ASSETS_FOLDER, hash + ext) - .then(function(filename) { - that.downloaded[src] = filename; - - that.log.debug.ln('downloading asset', src); - return fs.download(src, that.resolve(filename)) - .thenResolve(filename); - }); - }; - - // Convert a .svg into an .png - // Return the output filename for the .png - AssetsInliner.prototype.convertSVGFile = function(src) { - if (this.svgs[src]) return Promise(this.svgs[src]); - - var that = this; - var hash = crc.crc32(src).toString(16); - - // Create new file - return this.createNewFile(DEFAULT_ASSETS_FOLDER, hash + '.png') - .then(function(filename) { - that.svgs[src] = filename; - - return imagesUtil.convertSVGToPNG(src, that.resolve(filename)) - .thenResolve(filename); - }); - }; - - // Convert an inline svg into an .png - // Return the output filename for the .png - AssetsInliner.prototype.convertSVGBuffer = function(buf) { - var that = this; - var hash = crc.crc32(buf).toString(16); - - // Already converted? - if (this.inlineSvgs[hash]) return Promise(this.inlineSvgs[hash]); - - return this.createNewFile(DEFAULT_ASSETS_FOLDER, hash + '.png') - .then(function(filename) { - that.inlineSvgs[hash] = filename; - - return imagesUtil.convertSVGBufferToPNG(buf, that.resolve(filename)) - .thenResolve(filename); - }); - }; - - return AssetsInliner; -}; diff --git a/lib/output/base.js b/lib/output/base.js deleted file mode 100644 index 868b85b..0000000 --- a/lib/output/base.js +++ /dev/null @@ -1,309 +0,0 @@ -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 error = require('../utils/error'); -var PluginsManager = require('../plugins'); -var TemplateEngine = require('../template'); -var gitbook = require('../gitbook'); - -/* -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; - book.output = this; - 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(); - - // Hack to inherits from rules of the book - this.ignore.add(this.book.ignore); -} - -// Default name for generator -Output.prototype.name = 'base'; - -// 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', - '_layouts', - - // 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; - - // Change README path to be "index.html" - if (href == this.book.readme.path) { - href = 'index.html'; - } - - // Recalcul as relative link - href = currentPage.relative(href); - - // Replace .md by .html - href = this.toURL(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) { - if (location.isExternal(imgFile)) { - return 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 conetxt for the output itself -Output.prototype.getSelfContext = function() { - return { - name: this.name - }; -}; - -// Return a default context for templates -Output.prototype.getContext = function() { - var ctx = _.extend( - { - output: this.getSelfContext() - }, - this.book.getContext(), - (this.book.isLanguageBook()? this.book.parent: this.book).langs.getContext(), - this.book.readme.getContext(), - this.book.summary.getContext(), - this.book.glossary.getContext(), - this.book.config.getContext(), - gitbook.getContext() - ); - - // Deprecated fields - error.deprecateField(ctx.gitbook, 'generator', this.name, '"gitbook.generator" property is deprecated, use "output.name" instead'); - - return ctx; -}; - -// 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.toURL = 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; diff --git a/lib/output/conrefs.js b/lib/output/conrefs.js deleted file mode 100644 index e58f836..0000000 --- a/lib/output/conrefs.js +++ /dev/null @@ -1,67 +0,0 @@ -var path = require('path'); -var util = require('util'); - -var folderOutput = require('./folder'); -var Git = require('../utils/git'); -var fs = require('../utils/fs'); -var pathUtil = require('../utils/path'); -var location = require('../utils/location'); - -/* -Mixin for output to resolve git conrefs -*/ - -module.exports = function conrefsLoader(Base) { - Base = folderOutput(Base); - - function ConrefsLoader() { - Base.apply(this, arguments); - - this.git = new Git(); - } - util.inherits(ConrefsLoader, Base); - - // Read a template by its source URL - ConrefsLoader.prototype.onGetTemplate = function(sourceURL) { - var that = this; - - return this.git.resolve(sourceURL) - .then(function(filepath) { - // Is local file - if (!filepath) { - filepath = that.book.resolve(sourceURL); - } else { - that.book.log.debug.ln('resolve from git', sourceURL, 'to', filepath); - } - - // Read file from absolute path - return fs.readFile(filepath) - .then(function(source) { - return { - src: source.toString('utf8'), - path: filepath - }; - }); - }); - }; - - // Generate a source URL for a template - ConrefsLoader.prototype.onResolveTemplate = function(from, to) { - // If origin is in the book, we enforce result file to be in the book - if (this.book.isInBook(from)) { - var href = location.toAbsolute(to, path.dirname(from), ''); - return this.book.resolve(href); - } - - // If origin is in a git repository, we resolve file in the git repository - var gitRoot = this.git.resolveRoot(from); - if (gitRoot) { - return pathUtil.resolveInRoot(gitRoot, to); - } - - // If origin is not in the book (include from a git content ref) - return path.resolve(path.dirname(from), to); - }; - - return ConrefsLoader; -}; diff --git a/lib/output/ebook.js b/lib/output/ebook.js deleted file mode 100644 index 2b8fac9..0000000 --- a/lib/output/ebook.js +++ /dev/null @@ -1,193 +0,0 @@ -var _ = require('lodash'); -var util = require('util'); -var juice = require('juice'); - -var command = require('../utils/command'); -var fs = require('../utils/fs'); -var Promise = require('../utils/promise'); -var error = require('../utils/error'); -var WebsiteOutput = require('./website'); -var assetsInliner = require('./assets-inliner'); - -function _EbookOutput() { - WebsiteOutput.apply(this, arguments); - - // ebook-convert does not support link like "./" - this.opts.directoryIndex = false; -} -util.inherits(_EbookOutput, WebsiteOutput); - -var EbookOutput = assetsInliner(_EbookOutput); - -EbookOutput.prototype.name = 'ebook'; - -// Return context for templating -// Incldue type of ebbook generated -EbookOutput.prototype.getSelfContext = function() { - var ctx = EbookOutput.super_.prototype.getSelfContext.apply(this); - ctx.format = this.opts.format; - - return ctx; -}; - -// Finish generation, create ebook using ebook-convert -EbookOutput.prototype.finish = function() { - var that = this; - if (that.book.isMultilingual()) { - return EbookOutput.super_.prototype.finish.apply(that); - } - - return Promise() - .then(function() { - return EbookOutput.super_.prototype.finish.apply(that); - }) - - // Generate SUMMARY.html - .then(function() { - return that.render('summary', 'SUMMARY.html', that.getContext()); - }) - - // Start ebook-convert - .then(function() { - return that.ebookConvertOption(); - }) - - .then(function(options) { - if (!that.opts.format) return; - - var cmd = [ - 'ebook-convert', - that.resolve('SUMMARY.html'), - that.resolve('index.'+that.opts.format), - command.optionsToShellArgs(options) - ].join(' '); - - return command.exec(cmd) - .progress(function(data) { - that.book.log.debug(data); - }) - .fail(function(err) { - if (err.code == 127) { - throw error.RequireInstallError({ - cmd: 'ebook-convert', - install: 'Install it from Calibre: https://calibre-ebook.com' - }); - } - - throw error.EbookError(err); - }); - }); -}; - -// Generate header/footer for PDF -EbookOutput.prototype.getPDFTemplate = function(tpl) { - var that = this; - var context = _.extend( - { - // Nunjucks context mapping to ebook-convert templating - page: { - num: '_PAGENUM_', - title: '_TITLE_', - section: '_SECTION_' - } - }, - this.getContext() - ); - - return this.renderAsString('pdf_'+tpl, context) - - // Inline css, include css relative to the output folder - .then(function(output) { - return Promise.nfcall(juice.juiceResources, output, { - webResources: { - relativeTo: that.root() - } - }); - }); -}; - -// Locate the cover file to use -// Use configuration or search a "cover.jpg" file -// For multi-lingual book, it can use the one from the main book -EbookOutput.prototype.locateCover = function() { - var cover = this.book.config.get('cover', 'cover.jpg'); - - // Resolve to absolute - cover = this.resolve(cover); - - // Cover doesn't exist and multilingual? - if (!fs.existsSync(cover)) { - if (this.parent) return this.parent.locateCover(); - else return undefined; - } - - return cover; -}; - -// Generate options for ebook-convert -EbookOutput.prototype.ebookConvertOption = function() { - var that = this; - - var options = { - '--cover': this.locateCover(), - '--title': that.book.config.get('title'), - '--comments': that.book.config.get('description'), - '--isbn': that.book.config.get('isbn'), - '--authors': that.book.config.get('author'), - '--language': that.book.config.get('language'), - '--book-producer': 'GitBook', - '--publisher': 'GitBook', - '--chapter': 'descendant-or-self::*[contains(concat(\' \', normalize-space(@class), \' \'), \' book-chapter \')]', - '--level1-toc': 'descendant-or-self::*[contains(concat(\' \', normalize-space(@class), \' \'), \' book-chapter-1 \')]', - '--level2-toc': 'descendant-or-self::*[contains(concat(\' \', normalize-space(@class), \' \'), \' book-chapter-2 \')]', - '--level3-toc': 'descendant-or-self::*[contains(concat(\' \', normalize-space(@class), \' \'), \' book-chapter-3 \')]', - '--no-chapters-in-toc': true, - '--max-levels': '1', - '--breadth-first': true - }; - - if (that.opts.format == 'epub') { - options = _.extend(options, { - '--dont-split-on-page-breaks': true - }); - } - - if (that.opts.format != 'pdf') return Promise(options); - - var pdfOptions = that.book.config.get('pdf'); - - options = _.extend(options, { - '--chapter-mark': String(pdfOptions.chapterMark), - '--page-breaks-before': String(pdfOptions.pageBreaksBefore), - '--margin-left': String(pdfOptions.margin.left), - '--margin-right': String(pdfOptions.margin.right), - '--margin-top': String(pdfOptions.margin.top), - '--margin-bottom': String(pdfOptions.margin.bottom), - '--pdf-default-font-size': String(pdfOptions.fontSize), - '--pdf-mono-font-size': String(pdfOptions.fontSize), - '--paper-size': String(pdfOptions.paperSize), - '--pdf-page-numbers': Boolean(pdfOptions.pageNumbers), - '--pdf-header-template': that.getPDFTemplate('header'), - '--pdf-footer-template': that.getPDFTemplate('footer'), - '--pdf-sans-family': String(pdfOptions.fontFamily) - }); - - return that.getPDFTemplate('header') - .then(function(tpl) { - options['--pdf-header-template'] = tpl; - - return that.getPDFTemplate('footer'); - }) - .then(function(tpl) { - options['--pdf-footer-template'] = tpl; - - return options; - }); -}; - -// Don't write multi-lingual index for wbook -EbookOutput.prototype.outputMultilingualIndex = function() { - -}; - -module.exports = EbookOutput; diff --git a/lib/output/folder.js b/lib/output/folder.js deleted file mode 100644 index 8303ed2..0000000 --- a/lib/output/folder.js +++ /dev/null @@ -1,152 +0,0 @@ -var _ = require('lodash'); -var util = require('util'); -var path = require('path'); - -var Output = require('./base'); -var fs = require('../utils/fs'); -var pathUtil = require('../utils/path'); -var Promise = require('../utils/promise'); - -/* -This output requires the native fs module to output -book as a directory (mapping assets and pages) -*/ - -module.exports = function folderOutput(Base) { - Base = Base || Output; - - function FolderOutput() { - Base.apply(this, arguments); - - this.opts.root = path.resolve(this.opts.root || this.book.resolve('_book')); - } - util.inherits(FolderOutput, Base); - - // Copy an asset file (non-parsable), ex: images, etc - FolderOutput.prototype.onAsset = function(filename) { - return this.copyFile( - this.book.resolve(filename), - filename - ); - }; - - // Prepare the generation by creating the output folder - FolderOutput.prototype.prepare = function() { - var that = this; - - return Promise() - .then(function() { - return FolderOutput.super_.prototype.prepare.apply(that); - }) - - // Cleanup output folder - .then(function() { - that.log.debug.ln('removing previous output directory'); - return fs.rmDir(that.root()) - .fail(function() { - return Promise(); - }); - }) - - // Create output folder - .then(function() { - that.log.debug.ln('creating output directory'); - return fs.mkdirp(that.root()); - }) - - // Add output folder to ignored files - .then(function() { - that.ignore.addPattern([ - path.relative(that.book.root, that.root()) - ]); - }); - }; - - // Prepare output for a language book - FolderOutput.prototype.onLanguageBook = function(book) { - return new this.constructor(book, _.extend({}, this.opts, { - - // Language output should be output in sub-directory of output - root: path.resolve(this.root(), book.language) - }), this); - }; - - // ----- Utility methods ----- - - // Return path to the root folder - FolderOutput.prototype.root = function() { - return this.opts.root; - }; - - // Resolve a file in the output directory - FolderOutput.prototype.resolve = function(filename) { - return pathUtil.resolveInRoot.apply(null, [this.root()].concat(_.toArray(arguments))); - }; - - // Copy a file to the output - FolderOutput.prototype.copyFile = function(from, to) { - var that = this; - - return Promise() - .then(function() { - to = that.resolve(to); - var folder = path.dirname(to); - - // Ensure folder exists - return fs.mkdirp(folder); - }) - .then(function() { - return fs.copy(from, to); - }); - }; - - // Write a file/buffer to the output folder - FolderOutput.prototype.writeFile = function(filename, buf) { - var that = this; - - return Promise() - .then(function() { - filename = that.resolve(filename); - var folder = path.dirname(filename); - - // Ensure folder exists - return fs.mkdirp(folder); - }) - - // Write the file - .then(function() { - return fs.writeFile(filename, buf); - }); - }; - - // Return true if a file exists in the output folder - FolderOutput.prototype.hasFile = function(filename) { - var that = this; - - return Promise() - .then(function() { - return fs.exists(that.resolve(filename)); - }); - }; - - // Create a new unique file - // Returns its filename - FolderOutput.prototype.createNewFile = function(base, filename) { - var that = this; - - if (!filename) { - filename = path.basename(filename); - base = path.dirname(base); - } - - return fs.uniqueFilename(this.resolve(base), filename) - .then(function(out) { - out = path.join(base, out); - - return fs.ensure(that.resolve(out)) - .thenResolve(out); - }); - }; - - return FolderOutput; -}; diff --git a/lib/output/generateBook.js b/lib/output/generateBook.js new file mode 100644 index 0000000..3f04875 --- /dev/null +++ b/lib/output/generateBook.js @@ -0,0 +1,42 @@ +var Parse = require('../parse'); + +/** + List all assets for a book + + @param {Book} book + @param {Map<String:Page>} pages + @param +*/ +function listAssets(book, pages) { + var fs = book.getContentFS(); + + return fs.listAllFiles() + .then(function(files) { + return files.filterNot(function(file) { + return ( + book.isContentFileIgnored(file) || + pages.has(file) + ); + }); + }); +} + + +/** + Generate a book using a generator + + @param {Generator} generator + @param {Book} book + + @return {Promise} +*/ +function generateBook(generator, book) { + // List all parsable pages + return Parse.parsePagesList(book) + .then(function(pages) { + return listAssets(book, pages); + }); +} + + +module.exports = generateBook; diff --git a/lib/output/generatePage.js b/lib/output/generatePage.js new file mode 100644 index 0000000..9afb50a --- /dev/null +++ b/lib/output/generatePage.js @@ -0,0 +1,9 @@ +/** + Generate a page using a generator +*/ +function generatePage(generator, book, page) { + +} + + +module.exports = generatePage; diff --git a/lib/output/generators/json.js b/lib/output/generators/json.js new file mode 100644 index 0000000..e75ae7c --- /dev/null +++ b/lib/output/generators/json.js @@ -0,0 +1,26 @@ +var Promise = require('../../utils/promise'); + +var Modifier = require('../'); + + +function JSONGenerator(book) { + this.book = book; +} + + +JSONGenerator.prototype.onPage = function(page) { + return Modifier.HTMLTransformations(page, [ + Modifier.svgToImg(), + Modifier.svgToPng() + ]) + .then(function() { + + + }); +}; + +JSONGenerator.prototype.onAsset = function(file) { + +}; + +module.exports = JSONGenerator;
\ No newline at end of file diff --git a/lib/output/index.js b/lib/output/index.js new file mode 100644 index 0000000..67ca5ee --- /dev/null +++ b/lib/output/index.js @@ -0,0 +1,11 @@ + + +function generate(book, output) { + +} + + + +module.exports = { + generate: generate +}; diff --git a/lib/output/json.js b/lib/output/json.js deleted file mode 100644 index 7061141..0000000 --- a/lib/output/json.js +++ /dev/null @@ -1,47 +0,0 @@ -var conrefsLoader = require('./conrefs'); - -var JSONOutput = conrefsLoader(); - -JSONOutput.prototype.name = 'json'; - -// Don't copy asset on JSON output -JSONOutput.prototype.onAsset = function(filename) {}; - -// Write a page (parsable file) -JSONOutput.prototype.onPage = function(page) { - var that = this; - - // Parse the page - return page.toHTML(this) - - // Write as json - .then(function() { - var json = page.getOutputContext(that); - - // Delete some private properties - delete json.config; - - // Specify JSON output version - json.version = '3'; - - return that.writeFile( - page.withExtension('.json'), - JSON.stringify(json, null, 4) - ); - }); -}; - -// At the end of generation, generate README.json for multilingual books -JSONOutput.prototype.finish = function() { - if (!this.book.isMultilingual()) return; - - // Copy README.json from main book - var mainLanguage = this.book.langs.getDefault().id; - return this.copyFile( - this.resolve(mainLanguage, 'README.json'), - 'README.json' - ); -}; - - -module.exports = JSONOutput; diff --git a/lib/output/modifiers/addHeadingId.js b/lib/output/modifiers/addHeadingId.js new file mode 100644 index 0000000..751f4b8 --- /dev/null +++ b/lib/output/modifiers/addHeadingId.js @@ -0,0 +1,9 @@ +var slug = require('github-slugid'); +var HTMLModifier = require('./html'); + +var addHeadingID = HTMLModifier('h1,h2,h3,h4,h5,h6', function(heading) { + if (heading.attr('id')) return; + heading.attr('id', slug(heading.text())); +}); + +module.exports = addHeadingID; diff --git a/lib/output/modifiers/htmlTransform.js b/lib/output/modifiers/htmlTransform.js new file mode 100644 index 0000000..528b08d --- /dev/null +++ b/lib/output/modifiers/htmlTransform.js @@ -0,0 +1,16 @@ +var Promise = require('../../utils/promise'); + +/** + + +*/ +function transformTags() { + var $elements = $(query); + + return Promise.serie($elements, function(el) { + var $el = that.$(el); + return fn.call(that, $el); + }); +} + +module.exports = transformTags; diff --git a/lib/output/modifiers/index.js b/lib/output/modifiers/index.js new file mode 100644 index 0000000..76ce3c2 --- /dev/null +++ b/lib/output/modifiers/index.js @@ -0,0 +1,9 @@ + + +function modifyPage() { + + +} + + +module.exports = modifyPage; diff --git a/lib/output/modifiers/inlineAssets.js b/lib/output/modifiers/inlineAssets.js new file mode 100644 index 0000000..190a945 --- /dev/null +++ b/lib/output/modifiers/inlineAssets.js @@ -0,0 +1,11 @@ + + +/** + +*/ +function inlineAssets() { + + +} + +module.exports = inlineAssets; diff --git a/lib/output/modifiers/svgToImg.js b/lib/output/modifiers/svgToImg.js new file mode 100644 index 0000000..b36770a --- /dev/null +++ b/lib/output/modifiers/svgToImg.js @@ -0,0 +1,28 @@ +var cheerio = require('cheerio'); +var domSerializer = require('dom-serializer'); + +// Render a cheerio DOM as html +function renderDOM($, dom, options) { + if (!dom && $._root && $._root.children) { + dom = $._root.children; + } + options = options|| dom.options || $._options; + return domSerializer(dom, options); +} + +/** + +*/ +var svgToImg = HTMLModifier('svg', function($svg, $) { + var content = '<?xml version="1.0" encoding="UTF-8"?>' + renderDOM($, $svg); + + + +}); + +function svgToImg(page) { + var $ = cheerio.load(page.content); + +} + +module.exports = svgToImg; diff --git a/lib/output/website/index.js b/lib/output/website/index.js deleted file mode 100644 index 0a8618c..0000000 --- a/lib/output/website/index.js +++ /dev/null @@ -1,225 +0,0 @@ -var _ = require('lodash'); -var path = require('path'); -var util = require('util'); -var I18n = require('i18n-t'); - -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); - - // Nunjucks environment - this.env; - - // Plugin instance for the main theme - this.theme; - - // Plugin instance for the default theme - this.defaultTheme; - - // Resources loaded from plugins - this.resources; - - // i18n for themes - this.i18n = new I18n(); -} -util.inherits(_WebsiteOutput, Output); - -var WebsiteOutput = conrefsLoader(_WebsiteOutput); - -// Name of the generator -// It's being used as a prefix for templates -WebsiteOutput.prototype.name = 'website'; - -// Load and setup the theme -WebsiteOutput.prototype.prepare = function() { - var that = this; - - return Promise() - .then(function() { - return WebsiteOutput.super_.prototype.prepare.apply(that); - }) - - .then(function() { - // This list is ordered to give priority to templates in the book - var searchPaths = _.pluck(that.plugins.list(), 'root'); - - // The book itself can contains a "_layouts" folder - searchPaths.unshift(that.book.root); - - // Load i18n - _.each(searchPaths.concat().reverse(), function(searchPath) { - var i18nRoot = path.resolve(searchPath, '_i18n'); - - if (!fs.existsSync(i18nRoot)) return; - that.i18n.load(i18nRoot); - }); - - that.searchPaths = searchPaths; - }) - - // Copy assets from themes before copying files from book - .then(function() { - if (that.book.isLanguageBook()) return; - - // Assets from the book are already copied - // Copy assets from plugins (start with default plugins) - return Promise.serie(that.plugins.list().reverse(), function(plugin) { - // Copy assets only if exists (don't fail otherwise) - var assetFolder = path.join(plugin.root, '_assets', that.name); - if (!fs.existsSync(assetFolder)) return; - - that.log.debug.ln('copy assets from theme', assetFolder); - return fs.copyDir( - assetFolder, - that.resolve('gitbook'), - { - deleteFirst: false, - overwrite: true, - confirm: true - } - ); - }); - }) - - // Load resources for plugins - .then(function() { - return that.plugins.getResources(that.name) - .then(function(resources) { - that.resources = resources; - }); - }); -}; - -// Write a page (parsable file) -WebsiteOutput.prototype.onPage = function(page) { - var that = this; - - // Parse the page - return page.toHTML(this) - - // Render the page template with the same context as the json output - .then(function() { - return that.render('page', that.outputPath(page.path), page.getOutputContext(that)); - }); -}; - -// Finish generation, create ebook using ebook-convert -WebsiteOutput.prototype.finish = function() { - var that = this; - - return Promise() - .then(function() { - return WebsiteOutput.super_.prototype.finish.apply(that); - }) - - // Copy assets from plugins - .then(function() { - if (that.book.isLanguageBook()) return; - return that.plugins.copyResources(that.name, that.resolve('gitbook')); - }) - - // Generate homepage to select languages - .then(function() { - if (!that.book.isMultilingual()) return; - return that.outputMultilingualIndex(); - }); -}; - -// ----- Utilities ---- - -// Write multi-languages index -WebsiteOutput.prototype.outputMultilingualIndex = function() { - var that = this; - - 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); - - 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 - - - @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; - - // 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: { - getJSContext: function() { - return { - page: _.omit(context.page, 'content'), - config: context.config, - file: context.file, - gitbook: context.gitbook, - basePath: basePath, - book: { - language: context.book.language - } - }; - } - } - }); - - return this.renderAsString(tpl, context) - .then(function(html) { - return that.writeFile( - outputFile, - html - ); - }); -}; - -// Return a complete name for a template -WebsiteOutput.prototype.templateName = function(name) { - return path.join(this.name, name+'.html'); -}; - -module.exports = WebsiteOutput; - - - diff --git a/lib/output/website/templateEnv.js b/lib/output/website/templateEnv.js deleted file mode 100644 index d385108..0000000 --- a/lib/output/website/templateEnv.js +++ /dev/null @@ -1,95 +0,0 @@ -var _ = require('lodash'); -var nunjucks = require('nunjucks'); -var path = require('path'); -var fs = require('fs'); -var DoExtension = require('nunjucks-do')(nunjucks); - - -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) { - context = _.defaults(context || {}, { - // Required by ThemeLoader - template: {} - }); - - var loader = new ThemeLoader( - _.map(output.searchPaths, templatesPath) - ); - var env = new nunjucks.Environment(loader); - - env.addExtension('DoExtension', new DoExtension()); - - // 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 deleted file mode 100644 index 774a39e..0000000 --- a/lib/output/website/themeLoader.js +++ /dev/null @@ -1,127 +0,0 @@ -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); - var templateName = this.getTemplateName(fullpath); - - if(!fullpath) { - return null; - } - - var src = fs.readFileSync(fullpath, 'utf-8'); - - src = '{% do %}var template = template || {}; template.stack = template.stack || []; template.stack.push(template.self); template.self = ' + JSON.stringify(templateName) + '{% enddo %}\n' + - src + - '\n{% do %}template.self = template.stack.pop();{% enddo %}'; - - return { - src: src, - 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; - }, - - /* - Get original search path containing a template - - @param {String} filepath - @return {String} searchPath - */ - getSearchPath: function(filepath) { - return _.chain(this.searchPaths) - .sortBy(function(s) { - return -s.length; - }) - .find(function(basePath) { - return (filepath && filepath.indexOf(basePath) === 0); - }) - .value(); - }, - - /* - Get template name from a filepath - - @param {String} filepath - @return {String} name - */ - getTemplateName: function(filepath) { - var originalSearchPath = this.getSearchPath(filepath); - return originalSearchPath? path.relative(originalSearchPath, filepath) : null; - }, - - /* - 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 = this.getSearchPath(from); - var originalFilename = this.getTemplateName(from); - - // 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; |