Source: lib/post_processing.js

/**
 * **Utilities leveraged in several places while processing the book.**

 * All of them are bound to the `book` instance at runtime.

 * @module post-processing
 */

var fs = require('fs');
var _ = require('lodash');
var Q = require('q');
var path = require('path');

// General purpose helpers controlling the post-processing workflow.
var _workflow = require('./workflow');

// Available post-processing units, as an ordered stack.
// NOTE: could probably be implemented as a middleware pipeline, with the last
// rendering's path flowing through the post-processing units. Let's KISS though.
var _pipeline = [
  {name: 'background', factory: require('./units/background')}
  // {name: 'foreground', require('./units/foreground')}
];



/*** Private API ***/

/**
 * Perform some high-level checks prior to committing to post-processing.
 */
function preflight() {
  var that = this;

  return Q()
  .then(function() {
    if (that.book.options.generator != 'pdf') {
      throw new Error('invalid book format');
    }
  })
  .then(function() {
    that.book.log.info.ln('start pdf post-processing');
  });
}

/**
 * Extract relevant conf about background and foreground post-processing, from
 * the book's configuration.
 */
function readConfiguration() {
  var that = this;

  return Q()
  .then(function() {
    var cfg = that.book.config.get('pluginsConfig.pdf-styling', {});
    that.cfg = _.chain({
      background: cfg.background,
      foreground: cfg.foreground
    }).pick(function(unitCfg) {
      // Some validation already took place using JSON-schema, but it might be
      // worth doing some more here at some point.
      return !(_.isUndefined(unitCfg) || _.isEmpty(unitCfg));
    })
    .value();
  });
}

/**
 * Check first if any post-processing unit is actually registered.
 *
 * Exit early if none reported. Otherwise, start with moving the book to a
 * temporary location before further processing (solves path issues with
 * `pdftk`, among other tools, where the input path must differ from the output
 * path).
 */
function beforeRun() {
  var that = this;

  return Q()
  .then(function() {
    if (_.chain(that.cfg).values().isEmpty().value()) {
      throw new Error("nothing to do (bad configuration?)");
    }
  })
  .then(function() {
    // We'll need to track some paths. Not sure where to store that information
    // at runtime yet, book's options hash will do for now.
    that.book.options.pdfStyling = {};
    return _workflow.createTmp.call(that.book);
  })
}

/**
 * Run all registered post-processing units, in order, to completion.
 */
function runRegisteredUnits() {
  var that = this;

  var sequence = _.chain(_pipeline)
                  .map(function(unit) {
                    if (that.cfg[unit.name])
                      var unitt = new unit.factory(that.cfg[unit.name], that.book);
                      return unitt.run;
                  })
                  .compact()
                  .value();

  return sequence.reduce(Q.when, Q());
}

function afterRun() {
  var that = this;

  return Q()
  .then(function() {
    return _workflow.cleanupTmp.call(that.book);
  })
  .then(function(){
    delete that.book.options.pdfStyling;
  })
  .then(function() {
    that.book.log.info.ln('completed pdf post-processing');
  });
}

/**
 * Log error, then abort.
 */
function handleError(err) {
  this.book.log.error.ln('error with pdf-styling: ', err.stack || err.message || err);
  this.book.log.warn.ln('aborting post-processing of pdf');

  return Q();
}



/*** Public API ***/

/**
 * Perform initial checks.
 */
var PostProcessing = function(book) {
  this.book = book;
  this.units = [];
  _.bindAll(this);
};

/**
 * Trigger all post-processing units.
 */
PostProcessing.prototype.run = function() {
  return Q()
  .then(_.bind(preflight, this))
  .then(_.bind(readConfiguration, this))
  .then(_.bind(beforeRun, this))
  .then(_.bind(runRegisteredUnits, this))
  .then(_.bind(afterRun, this))
  .fail(_.bind(handleError, this));
};

module.exports = PostProcessing;