Complete generator for ebooks

This commit is contained in:
Samy Pessé
2016-02-21 15:52:04 +01:00
parent e5dd987d03
commit a446deaff3
4 changed files with 181 additions and 6 deletions
+16 -2
View File
@@ -7,7 +7,6 @@ var pathUtil = require('../utils/path');
var location = require('../utils/location');
var PluginsManager = require('../plugins');
var TemplateEngine = require('../template');
var gitbook = require('../gitbook');
/*
Output is like a stream interface for a parsed book
@@ -16,9 +15,13 @@ to output "something".
The process is mostly on the behavior of "onPage" and "onAsset"
*/
function Output(book) {
function Output(book, opts) {
_.bindAll(this);
this.opts = _.defaults(opts || {}, {
});
this.book = book;
this.log = this.book.log;
@@ -175,6 +178,17 @@ Output.prototype.onResolveTemplate = function(from, to) {
// ---- Utilities ----
// Return a default context for templates
Output.prototype.getContext = function() {
return _.extend(
{},
this.book.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) {
+143 -2
View File
@@ -1,14 +1,155 @@
var _ = require('lodash');
var util = require('util');
var juice = require('juice');
var command = require('../utils/command');
var fs = require('../utils/fs');
var WebsiteOutput = require('./website');
var assetsInliner = require('./assets-inliner');
function EbookOutput() {
function _EbookOutput() {
WebsiteOutput.apply(this, arguments);
}
util.inherits(EbookOutput, WebsiteOutput);
var EbookOutput = assetsInliner(_EbookOutput);
EbookOutput.prototype.name = 'ebook';
// Finish generation, create ebook using ebook-convert
EbookOutput.prototype.finish = function() {
var that = this;
module.exports = assetsInliner(EbookOutput);
return Promise()
.then(function() {
return EbookOutput.super_.prototype.finish.apply(that);
})
// Generate SUMMARY.html
.then(function() {
return that.render('summary', that.getContext())
.then(function(html) {
return that.writeFile(
that.resolve('SUMMARY.html'),
html
);
});
})
// Start ebook-convert
.then(function() {
return that.ebookConvertOption();
})
.then(function(options) {
var cmd = [
'ebook-convert',
that.resolve('SUMMARY.html'),
that.resolve('index.'+that.opts.format),
command.optionsToShellArgs(options)
].join(' ');
return command.exec(cmd)
.fail(function(err) {
if (err.code == 127) {
err = new Error('Need to install ebook-convert from Calibre');
}
throw 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_'
}
},
this.getContext()
);
return this.render('pdf_'+tpl, context)
// Inline css, include css relative to the output folder
.then(function(output) {
return Promise.nfcall(juice.juiceResources, tpl, {
webResources: {
relativeTo: that.root()
}
});
});
};
// Generate options for ebook-convert
EbookOutput.prototype.ebookConvertOption = function() {
var that = this;
var cover = this.book.config.get('cover');
if (!cover && fs.existsSync(this.resolve('cover.jpg'))) {
cover = this.resolve('cover.jpg');
}
var options = {
'--cover': cover,
'--title': this.book.config.get('title'),
'--comments': this.book.config.get('description'),
'--isbn': this.book.config.get('isbn'),
'--authors': this.book.config.get('author'),
'--language': this.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 (this.opts.format == 'epub') {
options = _.extend(options, {
'--dont-split-on-page-breaks': true
});
}
if (this.opts.format != 'pdf') return Promise(options);
var pdfOptions = this.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 this.getPDFTemplate('header')
.then(function(tpl) {
options['--pdf-header-template'] = tpl;
return that.getPDFTemplate('footer');
})
.then(function(tpl) {
options['--pdf-footer-template'] = tpl;
return options;
});
};
module.exports = EbookOutput;
+21 -1
View File
@@ -1,3 +1,4 @@
var _ = require('lodash');
var childProcess = require('child_process');
var Promise = require('./promise');
@@ -37,8 +38,27 @@ function spawn(command, args, options) {
return d.promise;
}
// Transform an option object to a command line string
function escapeShellArg(s) {
s = s.replace(/"/g, '\\"');
return '"' + s + '"';
}
function optionsToShellArgs(options) {
return _.chain(options)
.map(function(value, key) {
if (value === null || value === undefined || value === false) return null;
if (value === true) return key;
return key + '=' + escapeShellArg(value);
})
.compact()
.value()
.join(' ');
}
module.exports = {
isAvailable: isAvailable,
exec: exec,
spawn: spawn
spawn: spawn,
optionsToShellArgs: optionsToShellArgs
};
+1 -1
View File
@@ -37,7 +37,7 @@
"dom-serializer": "0.1.0",
"spawn-cmd": "0.0.2",
"escape-string-regexp": "1.0.3",
"juice": "1.5.0",
"juice": "1.9.0",
"jsonschema": "1.0.2",
"json-schema-defaults": "0.1.1",
"merge-defaults": "0.2.1",