<commit_before>/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright (c) 2015, Joyent, Inc.
 */

module.exports = FlagFilter;

var stream = require('stream');
var util = require('util');
var assert = require('assert-plus');
var utils = require('./utils');
var bunyan = require('bunyan');

var consts = require('./consts');
var SERVICES_TAG = consts.SERVICES_TAG;
var DOCKER_PREFIX = consts.DOCKER_PREFIX;
var USER_EN_FLAG = consts.USER_EN_FLAG;
var INST_EN_FLAG = consts.INST_EN_FLAG;
var INST_EN_TAG = consts.INST_EN_TAG;
var INST_PTR_TAG = consts.INST_PTR_TAG;

/*JSSTYLED*/
var PTR_REGEX = /^[a-z0-9][a-z0-9\-]{0,62}(?:\.[a-z0-9][a-z0-9\-]{0,62})*$/i;

/*
 * The FlagFilter transform stream takes in a VM object that has been annotated
 * with "owner" and "server" objects (by UfdsFilter, CnFilter and NetFilter),
 * and determines whether the object will result in the addition or removal of
 * DNS records.
 *
 * It also processes the SERVICES_TAG metadata to decide which services the
 * given VM record will be listed in. This is not so easy to decouple, as
 * some flags will result in the services list being emptied without the whole
 * VM being removed from DNS.
 */
function FlagFilter(opts) {
	assert.object(opts, 'options');

	assert.optionalObject(opts.log, 'options.log');
	var log = opts.log || bunyan.createLogger({name: 'cns'});
	this.log = log.child({stage: 'FlagFilter'});

	var xformOpts = {
		readableObjectMode: true,
		writableObjectMode: true
	};
	stream.Transform.call(this, xformOpts);
}
util.inherits(FlagFilter, stream.Transform);

FlagFilter.prototype._transform = function (vm, enc, cb) {
	assert.object(vm, 'vm');
	assert.object(vm.owner, 'vm.owner');
	assert.object(vm.server, 'vm.server');
	assert.string(vm.uuid, 'vm.uuid');
	assert.object(vm.tags, 'vm.tags');
	assert.object(vm.customer_metadata, 'vm.customer_metadata');

	vm.services = [];
	var svcIdx = {};
	if (vm.tags) {
		var svcs = vm.tags[SERVICES_TAG];
		if (svcs === undefined)
			svcs = vm.tags[DOCKER_PREFIX + SERVICES_TAG];
		if (svcs !== undefined) {
			svcs.split(',').forEach(function (svcTag) {
				/*
				 * For future-proofing purposes, we grab just
				 * the part before the first colon of the tag.
				 * If we need to add options in future they
				 * will be after this colon.
				 */
				svcTag = svcTag.trim().split(':');
				if (svcTag[0].length > 0) {
					if (svcIdx[svcTag[0]] === undefined) {
						svcIdx[svcTag[0]] = {
							name: svcTag[0],
							ports: []
						};
						vm.services.push(
						    svcIdx[svcTag[0]]);
					}
					var port;
					if (svcTag[1] && svcTag[1].length > 0)
						port = parseInt(svcTag[1], 10);
					if (port && isFinite(port))
						svcIdx[svcTag[0]].ports.
						    push(port);
				}
			});
		}
	}

	/* Set default: list in both instance and service records. */
	vm.listInstance = true;
	vm.listServices = true;
	/*
	 * vm.reasons is an array of strings identifying the reasoning for
	 * why the two flags above are set as they are.
	 */
	vm.reasons = [];

	/*
	 * A zone that isn't running, or is on a CN that is not running, or
	 * a zone that has its metadata status flag set to non-"up" should
	 * be excluded from any services, but will still have instance records.
	 *
	 * "CN that is not running" includes CNs that have not heartbeated in
	 * the last 30 seconds, and CNs that have only booted up in the last
	 * 2 minutes.
	 */
	if (vm.customer_metadata[INST_EN_FLAG] !== undefined &&
	    vm.customer_metadata[INST_EN_FLAG] !== 'up') {
		vm.listServices = false;
		vm.reasons.push('inst_en_flag');

	} else if (vm.server.status !== 'running' ||
	    vm.server.heartbeat_age > 60000 ||
	    vm.server.last_boot_age < 120000) {
		vm.listServices = false;
		vm.reasons.push('cn_down');

	} else if (vm.state !== 'running') {
		vm.listServices = false;
		vm.reasons.push('vm_down');
	}

	/* Lowest priority: user enable flags. */
	if (!vm.owner[USER_EN_FLAG] ||
	    vm.owner[USER_EN_FLAG] === 'false') {
		vm.listServices = false;
		vm.listInstance = false;
		vm.reasons.push('user_en_flag');

	} else if (!vm.owner.approved_for_provisioning ||
	    vm.owner.approved_for_provisioning === 'false') {
		vm.listServices = false;
		vm.listInstance = false;
		vm.reasons.push('user_not_approved');
	}

	/*
	 * Enable all VMs that have a PTR tag, even if their owner doesn't
	 * have CNS enabled globally.
	 */
	if (vm.tags && vm.tags[INST_PTR_TAG] &&
	    PTR_REGEX.test(vm.tags[INST_PTR_TAG])) {
		vm.listInstance = true;
		vm.reasons.push('ptr');
		vm.ptrname = vm.tags[INST_PTR_TAG];

	} else if (vm.tags && vm.tags[DOCKER_PREFIX + INST_PTR_TAG] &&
	    PTR_REGEX.test(vm.tags[DOCKER_PREFIX + INST_PTR_TAG])) {
		vm.listInstance = true;
		vm.reasons.push('ptr');
		vm.ptrname = vm.tags[DOCKER_PREFIX + INST_PTR_TAG]
	}

	/*
	 * Finally, the top priority: explicit VM disable tags, and VMs that
	 * have been destroyed.
	 */
	if (vm.tags && vm.tags[INST_EN_TAG] !== undefined &&
	    vm.tags[INST_EN_TAG] !== false) {
		vm.listServices = false;
		vm.listInstance = false;
		vm.reasons.push('inst_en_tag');

	} else if (vm.tags &&
	    vm.tags[DOCKER_PREFIX + INST_EN_TAG] !== undefined &&
	    vm.tags[DOCKER_PREFIX + INST_EN_TAG] !== false &&
	    vm.tags[DOCKER_PREFIX + INST_EN_TAG] !== 'false') {
		vm.listServices = false;
		vm.listInstance = false;
		vm.reasons.push('inst_en_tag');

	} else if (vm.destroyed || vm.state === 'destroyed' ||
	    vm.state === 'failed') {
		vm.listServices = false;
		vm.listInstance = false;
		vm.reasons.push('destroyed');
	}

	this.push(vm);
	cb();
};
<commit_msg>CNS-135 vm.operation is confusing (style)<commit_after>/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright (c) 2015, Joyent, Inc.
 */

module.exports = FlagFilter;

var stream = require('stream');
var util = require('util');
var assert = require('assert-plus');
var utils = require('./utils');
var bunyan = require('bunyan');

var consts = require('./consts');
var SERVICES_TAG = consts.SERVICES_TAG;
var DOCKER_PREFIX = consts.DOCKER_PREFIX;
var USER_EN_FLAG = consts.USER_EN_FLAG;
var INST_EN_FLAG = consts.INST_EN_FLAG;
var INST_EN_TAG = consts.INST_EN_TAG;
var INST_PTR_TAG = consts.INST_PTR_TAG;

/*JSSTYLED*/
var PTR_REGEX = /^[a-z0-9][a-z0-9\-]{0,62}(?:\.[a-z0-9][a-z0-9\-]{0,62})*$/i;

/*
 * The FlagFilter transform stream takes in a VM object that has been annotated
 * with "owner" and "server" objects (by UfdsFilter, CnFilter and NetFilter),
 * and determines whether the object will result in the addition or removal of
 * DNS records.
 *
 * It also processes the SERVICES_TAG metadata to decide which services the
 * given VM record will be listed in. This is not so easy to decouple, as
 * some flags will result in the services list being emptied without the whole
 * VM being removed from DNS.
 */
function FlagFilter(opts) {
	assert.object(opts, 'options');

	assert.optionalObject(opts.log, 'options.log');
	var log = opts.log || bunyan.createLogger({name: 'cns'});
	this.log = log.child({stage: 'FlagFilter'});

	var xformOpts = {
		readableObjectMode: true,
		writableObjectMode: true
	};
	stream.Transform.call(this, xformOpts);
}
util.inherits(FlagFilter, stream.Transform);

FlagFilter.prototype._transform = function (vm, enc, cb) {
	assert.object(vm, 'vm');
	assert.object(vm.owner, 'vm.owner');
	assert.object(vm.server, 'vm.server');
	assert.string(vm.uuid, 'vm.uuid');
	assert.object(vm.tags, 'vm.tags');
	assert.object(vm.customer_metadata, 'vm.customer_metadata');

	vm.services = [];
	var svcIdx = {};
	if (vm.tags) {
		var svcs = vm.tags[SERVICES_TAG];
		if (svcs === undefined)
			svcs = vm.tags[DOCKER_PREFIX + SERVICES_TAG];
		if (svcs !== undefined) {
			svcs.split(',').forEach(function (svcTag) {
				/*
				 * For future-proofing purposes, we grab just
				 * the part before the first colon of the tag.
				 * If we need to add options in future they
				 * will be after this colon.
				 */
				svcTag = svcTag.trim().split(':');
				if (svcTag[0].length > 0) {
					if (svcIdx[svcTag[0]] === undefined) {
						svcIdx[svcTag[0]] = {
							name: svcTag[0],
							ports: []
						};
						vm.services.push(
						    svcIdx[svcTag[0]]);
					}
					var port;
					if (svcTag[1] && svcTag[1].length > 0)
						port = parseInt(svcTag[1], 10);
					if (port && isFinite(port))
						svcIdx[svcTag[0]].ports.
						    push(port);
				}
			});
		}
	}

	/* Set default: list in both instance and service records. */
	vm.listInstance = true;
	vm.listServices = true;
	/*
	 * vm.reasons is an array of strings identifying the reasoning for
	 * why the two flags above are set as they are.
	 */
	vm.reasons = [];

	/*
	 * A zone that isn't running, or is on a CN that is not running, or
	 * a zone that has its metadata status flag set to non-"up" should
	 * be excluded from any services, but will still have instance records.
	 *
	 * "CN that is not running" includes CNs that have not heartbeated in
	 * the last 30 seconds, and CNs that have only booted up in the last
	 * 2 minutes.
	 */
	if (vm.customer_metadata[INST_EN_FLAG] !== undefined &&
	    vm.customer_metadata[INST_EN_FLAG] !== 'up') {
		vm.listServices = false;
		vm.reasons.push('inst_en_flag');

	} else if (vm.server.status !== 'running' ||
	    vm.server.heartbeat_age > 60000 ||
	    vm.server.last_boot_age < 120000) {
		vm.listServices = false;
		vm.reasons.push('cn_down');

	} else if (vm.state !== 'running') {
		vm.listServices = false;
		vm.reasons.push('vm_down');
	}

	/* Lowest priority: user enable flags. */
	if (!vm.owner[USER_EN_FLAG] ||
	    vm.owner[USER_EN_FLAG] === 'false') {
		vm.listServices = false;
		vm.listInstance = false;
		vm.reasons.push('user_en_flag');

	} else if (!vm.owner.approved_for_provisioning ||
	    vm.owner.approved_for_provisioning === 'false') {
		vm.listServices = false;
		vm.listInstance = false;
		vm.reasons.push('user_not_approved');
	}

	/*
	 * Enable all VMs that have a PTR tag, even if their owner doesn't
	 * have CNS enabled globally.
	 */
	if (vm.tags && vm.tags[INST_PTR_TAG] &&
	    PTR_REGEX.test(vm.tags[INST_PTR_TAG])) {
		vm.listInstance = true;
		vm.reasons.push('ptr');
		vm.ptrname = vm.tags[INST_PTR_TAG];

	} else if (vm.tags && vm.tags[DOCKER_PREFIX + INST_PTR_TAG] &&
	    PTR_REGEX.test(vm.tags[DOCKER_PREFIX + INST_PTR_TAG])) {
		vm.listInstance = true;
		vm.reasons.push('ptr');
		vm.ptrname = vm.tags[DOCKER_PREFIX + INST_PTR_TAG];
	}

	/*
	 * Finally, the top priority: explicit VM disable tags, and VMs that
	 * have been destroyed.
	 */
	if (vm.tags && vm.tags[INST_EN_TAG] !== undefined &&
	    vm.tags[INST_EN_TAG] !== false) {
		vm.listServices = false;
		vm.listInstance = false;
		vm.reasons.push('inst_en_tag');

	} else if (vm.tags &&
	    vm.tags[DOCKER_PREFIX + INST_EN_TAG] !== undefined &&
	    vm.tags[DOCKER_PREFIX + INST_EN_TAG] !== false &&
	    vm.tags[DOCKER_PREFIX + INST_EN_TAG] !== 'false') {
		vm.listServices = false;
		vm.listInstance = false;
		vm.reasons.push('inst_en_tag');

	} else if (vm.destroyed || vm.state === 'destroyed' ||
	    vm.state === 'failed') {
		vm.listServices = false;
		vm.listInstance = false;
		vm.reasons.push('destroyed');
	}

	this.push(vm);
	cb();
};
<|endoftext|>


<commit_before>var express = require('express');
var cookieParser = require('cookie-parser');
var app = express();

app.use(cookieParser());

app.use('/tests', express.static(__dirname+"/tests"));
app.use( express.static(__dirname+"/js"));

app.get('/tests/cookie_included/:cookieName', function (req, res) {
    if (req.cookies && req.cookies[req.params.cookieName]){
        res.json(1);
    }
    else{
        res.json(0);
    }
});

var server = app.listen(3000, function () {

  var host = server.address().address;
  var port = server.address().port;

  console.log('Mixpanel test app listening at http://%s:%s', host, port);

});<commit_msg>minor test server cleanup<commit_after>'use strict';

var express      = require('express');
var cookieParser = require('cookie-parser');
var logger       = require('morgan');

var app = express();

app.use(cookieParser());
app.use(logger('dev'));

app.use('/tests', express.static(__dirname + "/tests"));
app.get('/tests/cookie_included/:cookieName', function (req, res) {
    if (req.cookies && req.cookies[req.params.cookieName]) {
        res.json(1);
    } else {
        res.json(0);
    }
});
app.use(express.static(__dirname));

var server = app.listen(3000, function () {
  console.log('Mixpanel test app listening on port %s', server.address().port);
});
<|endoftext|>


<commit_before>var Stream = require('stream');

// writable
exports.Event = function (stdout, stderr) {

    // TODO set local stream to object mode
    // TODO net streams default mode

    stdout = stdout || {
        write: function () {},
        end: function () {}
    };

    // write errors to stdout if stderr dont exists
    stderr = stderr || stdout;

    var stdin = Stream.Duplex({
        readableObjectMode: true,
        writableObjectMode: true
    });
    stdin._buf = [];
    stdin._write = function (chunk, enc, next) {

        if (!stdin._fe) {
            return stdin.out.end(new Error('No handlers for event.'));
        }
        stdin.next = next;
        stdin._pos = -1;
        stdin.out.next(null, chunk);
    };
    stdin._read = function () {
        if (stdin._fe) {
            stdin.uncork();
        }
    },
    stdin.stdout = stdout;
    stdin.stderr = stderr;
    stdin.out = {
        write: function (err, data) {

            if (err) {
                //err = typeof err !== 'string' ? err.toString() : err;
                return stdin.stderr.write(err);
            }

            // TODO how to pipe object streams to non-object streams?
            //if (data && typeof data !== 'string' && data.constructor !== Buffer) {
            //    data = JSON.stringify(data);
            //}

            stdin.stdout.write(data);
        },
        end: function (err, data) {
            stdin.out.write(err, data);
            stdin.stdout.end();
            stdin.stderr.end();

            // TODO what to do on event stream end?
            stdin.push(null);
            stdin.end();
        },
        next: function (err, data) {

            // emit error to event stream: this.flow(...).on('error', fn);
            // TODO test error handling. also: stdin.emit('error', err);
            if (err) {
                stdin.next(err);
                data == null;
            }

            // handle EOF
            // TODO test stream piping/unpigin, what happens on EOF?
            if (data === null) {
                // end input and output streams
                stdin.out.end();
                return;
            }

            // update handler ref
            var dh = stdin._fe.d[++stdin._pos];

            if (!dh) {

                // push data to readable
                if ((!stdin.push(data))) {
                    // buffer all writes
                    stdin.cork()
                }

                // request the next data chunk
                stdin.next();

                return;
            }

            // call next data handler
            dh[1].call(dh[0], stdin.out, dh[2], data);
        }
    };

    return stdin;
};
<commit_msg>auto convert object to strings for network streams<commit_after>var Stream = require('stream');

// writable
exports.Event = function (options) {

    // TODO set local stream to object mode
    // TODO net streams default mode

    var stdout = options.std;
    var stderr = options.err;
    stdout = stdout || {
        write: function () {},
        end: function () {}
    };

    // write errors to stdout if stderr dont exists
    stderr = stderr || stdout;

    var stdin = Stream.Duplex({
        readableObjectMode: true,
        writableObjectMode: true
    });
    stdin._buf = [];
    stdin._write = function (chunk, enc, next) {

        if (!stdin._fe) {
            return stdin.out.end(new Error('No handlers for event.'));
        }

        stdin.next = next;
        stdin._pos = -1;
        stdin.out.next(null, chunk);
    };
    stdin._read = function () {
        if (stdin._fe) {
            stdin.uncork();
        }
    },
    stdin.stdout = stdout;
    stdin.stderr = stderr;
    stdin.out = {
        write: function (err, data) {

            if (typeof err !== 'undefined') {
                if (options.net !== 'flow') {
                    err = typeof err !== 'string' ? err.toString() : err;
                }
                return stdin.stderr.write(err);
            }

            if (typeof data !== 'undefined') {

                if (options.net !== 'flow' && typeof data !== 'string' && data.constructor !== Buffer) {
                    data = JSON.stringify(data);
                }

                stdin.stdout.write(data);
            }

        },
        end: function (err, data) {

            stdin.out.write(err, data);
            stdin.stdout.end();
            stdin.stderr.end();

            // TODO what to do on event stream end?
            stdin.push(null);
            stdin.end();
        },
        next: function (err, data) {

            // emit error to event stream: this.flow(...).on('error', fn);
            if (err) {
                // TODO call error handlers

                stdin.next(err);
                data = null;
            }

            // handle EOF
            // TODO test stream piping/unpigin, what happens on EOF?
            if (data === null) {
                // end input and output streams
                stdin.out.end();
                return;
            }

            // update handler ref
            var dh = stdin._fe.d[++stdin._pos];

            if (!dh) {

                // push data to readable
                if ((!stdin.push(data))) {
                    // buffer all writes
                    stdin.cork()
                }

                // request the next data chunk
                stdin.next();

                return;
            }

            // call next data handler
            dh[1].call(dh[0], stdin.out, dh[2], data);
        }
    };

    return stdin;
};
<|endoftext|>


<commit_before>var path = require('path');
var fs = require('fs-extra');
var _ = require('lodash');
var StorageBase = require('../storage-base');

module.exports = StorageFs;

function StorageFs(options) {
  StorageBase.apply(this, arguments);
}

var p = StorageFs.prototype = new StorageBase();

p.fetch = function(options, originalPath, stepsHash, cb) {
  var filename = originalPath;
  if (stepsHash) {
    filename += '-' + stepsHash;
  }

  filename = path.resolve(options.path || './', filename);

  fs.readFile(filename + '.json', 'utf8', function(err, data) {
    var info = { path: originalPath, stepsHash: stepsHash };
    if (data) {
      try {
        info = _.merge(info, JSON.parse(data.toString()));
      catch (err) {
        fs.unlink(filename + '.json', function(err){});
      }
    }

    fs.readFile(filename, function(err, data) {
      if (err) {
        return void cb(err);
      }
      cb(null, info, data);
    });
  });
};

function checkDir(filename, cb) {
  var folder = path.dirname(filename);
  fs.stat(folder, function(err, data) {
    if(!err) return cb();
    if(err.code === 'ENOENT') {
      fs.mkdirp(folder, function(err){
        if (err) {
          return void cb(err);
        }
        cb();
      });
    } else {
      return void cb(err);
    }
  });
}

p.store = function(options, originalPath, stepsHash, image, cb) {
  var filename = originalPath;
  if (!stepsHash) {
    return void cb(new Error('Cannot store an image over the original'));
  }
  filename += ('-' + stepsHash);
  filename = path.resolve(options.path || './', filename);

  image.info.stepsHash = stepsHash;

  checkDir(filename, function(err){
    if (err) {
      return void cb(err);
    }
    fs.writeFile(filename + '.json', new Buffer(JSON.stringify(image.info)), 'utf8', function(err) {
      // do nothing
    });
    fs.writeFile(filename, image.buffer, cb);

  });
};
<commit_msg>Return error instead remove file.<commit_after>var path = require('path');
var fs = require('fs-extra');
var _ = require('lodash');
var StorageBase = require('../storage-base');

module.exports = StorageFs;

function StorageFs(options) {
  StorageBase.apply(this, arguments);
}

var p = StorageFs.prototype = new StorageBase();

p.fetch = function(options, originalPath, stepsHash, cb) {
  var filename = originalPath;
  if (stepsHash) {
    filename += '-' + stepsHash;
  }

  filename = path.resolve(options.path || './', filename);

  fs.readFile(filename + '.json', 'utf8', function(err, data) {
    var info = { path: originalPath, stepsHash: stepsHash };
    if (data) {
      try {
        info = _.merge(info, JSON.parse(data.toString()));
      catch (err) {
        cb(err);
      }
    }

    fs.readFile(filename, function(err, data) {
      if (err) {
        return void cb(err);
      }
      cb(null, info, data);
    });
  });
};

function checkDir(filename, cb) {
  var folder = path.dirname(filename);
  fs.stat(folder, function(err, data) {
    if(!err) return cb();
    if(err.code === 'ENOENT') {
      fs.mkdirp(folder, function(err){
        if (err) {
          return void cb(err);
        }
        cb();
      });
    } else {
      return void cb(err);
    }
  });
}

p.store = function(options, originalPath, stepsHash, image, cb) {
  var filename = originalPath;
  if (!stepsHash) {
    return void cb(new Error('Cannot store an image over the original'));
  }
  filename += ('-' + stepsHash);
  filename = path.resolve(options.path || './', filename);

  image.info.stepsHash = stepsHash;

  checkDir(filename, function(err){
    if (err) {
      return void cb(err);
    }
    fs.writeFile(filename + '.json', new Buffer(JSON.stringify(image.info)), 'utf8', function(err) {
      // do nothing
    });
    fs.writeFile(filename, image.buffer, cb);

  });
};
<|endoftext|>


<commit_before>var CHANGE_EVENTS = {
  hash: 'hashchange',
  history: 'popstate'
};

var _location;

/**
 * Returns the type of navigation that is currently being used.
 */
exports.getLocation = getLocation;
function getLocation() {
  return _location || 'hash';
}

/**
 * Setup the URL store to get the value of the current path from window.location
 * as it changes. The location argument may be either "hash" (the default) or
 * "history" and specifies the type of change that should be listened for, hashchange
 * or popstate respectively.
 */
exports.setup = setup;
function setup(location) {
  if (_location)
    throw new Error('Cannot setup URL store twice');

  var changeEvent = CHANGE_EVENTS[location];

  if (!changeEvent)
    throw new Error('Invalid URL location: ' + location);

  _location = location;

  if (location === 'hash' && getCurrentPathUsingLocation(location) === '')
    pushHash('/');

  window.addEventListener(changeEvent, handleStateChange, false);

  handleStateChange();
}

/**
 * Stops listening for changes to window.location.
 */
exports.teardown = teardown;
function teardown() {
  if (!_location)
    return;

  window.removeEventListener(CHANGE_EVENTS[_location], handleStateChange);

  _location = null;
}

var _currentPath;

/**
 * Returns the value of the current URL path.
 */
exports.getCurrentPath = getCurrentPath;
function getCurrentPath() {
  return _currentPath;
}

/**
 * Updates the value of the current URL path and notifies subscribers of the change.
 */
exports.updateCurrentPath = updateCurrentPath;
function updateCurrentPath(path) {
  _currentPath = path;
  notifyChange();
}

function getCurrentPathUsingLocation(location) {
  var pathname = location === 'history' ? window.location.pathname : window.location.hash.substr(1);
  return pathname.replace(/^\//, '');
}

function handleStateChange() {
  updateCurrentPath(getCurrentPathUsingLocation(_location));
}

/**
 * Pushes the given path onto the browser navigation stack.
 */
exports.push = push;
function push(path) {
  return getLocation() === 'history' ? pushHistory(path) : pushHash(path);
}

function pushHash(path) {
  window.location.hash = path;
}

function pushHistory(path) {
  window.history.pushState({path: path}, '', path);
  handleStateChange();
}

/**
 * Replaces the current URL path with the given path without adding an entry
 * to the browser's history.
 */
exports.replace = replace;
function replace(path) {
  return getLocation() === 'history' ? replaceHistory(path) : replaceHash(path);
}

function replaceHash(path) {
  window.location.replace(path);
}

function replaceHistory(path) {
  window.history.replaceState({path: path}, '', path);
  handleStateChange();
}

// TODO: pubsub could probably be its own module.

var _subscriptions = [];

exports.subscribe = subscribe;
function subscribe(fn) {
  _subscriptions.push(fn);
}

exports. unsubscribe = unsubscribe;
function unsubscribe(fn) {
  for (var i = 0, l = _subscriptions.length; i < l; i++) {
    if (_subscriptions[i] === fn) {
      _subscriptions.splice(i, 1);
      break;
    }
  }
}

function notifyChange() {
  for (var i = 0, l = _subscriptions.length; i < l; i++) {
    if (_subscriptions[i] == null) {
      // some views may get wiped out during mid loop and so the subscription
      // is gone, so just bail
      return;
    }
    _subscriptions[i]();
  }
}
<commit_msg>Safer use of private _location var<commit_after>var CHANGE_EVENTS = {
  hash: 'hashchange',
  history: 'popstate'
};

var _location;

/**
 * Returns the type of navigation that is currently being used.
 */
exports.getLocation = getLocation;
function getLocation() {
  return _location || 'hash';
}

/**
 * Setup the URL store to get the value of the current path from window.location
 * as it changes. The location argument may be either "hash" (the default) or
 * "history" and specifies the type of change that should be listened for, hashchange
 * or popstate respectively.
 */
exports.setup = setup;
function setup(location) {
  if (_location)
    throw new Error('Cannot setup URL store twice');

  var changeEvent = CHANGE_EVENTS[location];

  if (!changeEvent)
    throw new Error('Invalid URL location: ' + location);

  _location = location;

  if (location === 'hash' && getCurrentPathUsingLocation(location) === '')
    pushHash('/');

  window.addEventListener(changeEvent, handleStateChange, false);

  handleStateChange();
}

/**
 * Stops listening for changes to window.location.
 */
exports.teardown = teardown;
function teardown() {
  if (!_location)
    return;

  window.removeEventListener(CHANGE_EVENTS[_location], handleStateChange);

  _location = null;
}

var _currentPath;

/**
 * Returns the value of the current URL path.
 */
exports.getCurrentPath = getCurrentPath;
function getCurrentPath() {
  return _currentPath;
}

/**
 * Updates the value of the current URL path and notifies subscribers of the change.
 */
exports.updateCurrentPath = updateCurrentPath;
function updateCurrentPath(path) {
  _currentPath = path;
  notifyChange();
}

function getCurrentPathUsingLocation(location) {
  var pathname = location === 'history' ? window.location.pathname : window.location.hash.substr(1);
  return pathname.replace(/^\//, '');
}

function handleStateChange() {
  updateCurrentPath(getCurrentPathUsingLocation(getLocation()));
}

/**
 * Pushes the given path onto the browser navigation stack.
 */
exports.push = push;
function push(path) {
  return getLocation() === 'history' ? pushHistory(path) : pushHash(path);
}

function pushHash(path) {
  window.location.hash = path;
}

function pushHistory(path) {
  window.history.pushState({path: path}, '', path);
  handleStateChange();
}

/**
 * Replaces the current URL path with the given path without adding an entry
 * to the browser's history.
 */
exports.replace = replace;
function replace(path) {
  return getLocation() === 'history' ? replaceHistory(path) : replaceHash(path);
}

function replaceHash(path) {
  window.location.replace(path);
}

function replaceHistory(path) {
  window.history.replaceState({path: path}, '', path);
  handleStateChange();
}

// TODO: pubsub could probably be its own module.

var _subscriptions = [];

exports.subscribe = subscribe;
function subscribe(fn) {
  _subscriptions.push(fn);
}

exports. unsubscribe = unsubscribe;
function unsubscribe(fn) {
  for (var i = 0, l = _subscriptions.length; i < l; i++) {
    if (_subscriptions[i] === fn) {
      _subscriptions.splice(i, 1);
      break;
    }
  }
}

function notifyChange() {
  for (var i = 0, l = _subscriptions.length; i < l; i++) {
    if (_subscriptions[i] == null) {
      // some views may get wiped out during mid loop and so the subscription
      // is gone, so just bail
      return;
    }
    _subscriptions[i]();
  }
}
<|endoftext|>


<commit_before>"use strict";

require("../vendor/FontLoader.js");

function buildFontFaceRule(family, urls) {
	var eot = urls["embedded-opentype"];
	var woff = urls.woff;
	var ttf = urls.truetype;
	var svg = urls.svg;

	var css = "\n";
	css += "@font-face {\n";
	css += "  font-family: '" + family + "';\n";
	css += "  src: url('" + eot + "');\n";
	css += "  src: url('" + eot + "?iefix') format('embedded-opentype'),\n";
	css += "       url('" + woff + "') format('woff'),\n";
	css += "       url('" + ttf + "') format('ttf'),\n";
	css += "       url('" + svg + "') format('svg');\n";
	css += "}\n";
	return css;
}

function createCssFontFaces(fontFamilies) {
	var style = document.createElement("style");
	style.setAttribute("type", "text/css");
	var css = "";
	for (var family in fontFamilies) {
		if (fontFamilies.hasOwnProperty(family)) {
			css += buildFontFaceRule(family, fontFamilies[family]);
		}
	}
	style.appendChild(document.createTextNode(css));
	document.head.appendChild(style);
}

function FontLoader() {
	this.totalFonts = 0;
	this.loadedFonts = 0;
}
FontLoader.prototype.load = function(fontFamilies) {
	createCssFontFaces(fontFamilies);

	var families = [];
	for (var family in fontFamilies) {
		if (families.hasOwnProperty(family)) {
			families.push(family);
		}
	}
	this.totalFonts += families.length;

	var that = this;
	var loader = new window.FontLoader(families, {
		"fontLoaded": function() {
			that.loadedFonts++;
		}
	});
	loader.loadFonts();
};
FontLoader.prototype.allLoaded = function() {
	return this.totalFonts === this.loadedFonts;
};

function EjectaFontLoader() {
}
EjectaFontLoader.prototype.load = function(fontFamilies) {
	for (var family in fontFamilies) {
		if (fontFamilies.hasOwnProperty(family)) {
			var fontPath = fontFamilies[family].truetype;
			if (fontPath) {
				window.ejecta.loadFont(fontPath);
			}
		}
	}
};
EjectaFontLoader.prototype.allLoaded = function() {
	return true;
};

if (window.ejecta) {
	module.exports = EjectaFontLoader;
} else {
	module.exports = FontLoader;
}
<commit_msg>Add variables for loading bar in Eject version.<commit_after>"use strict";

require("../vendor/FontLoader.js");

function buildFontFaceRule(family, urls) {
	var eot = urls["embedded-opentype"];
	var woff = urls.woff;
	var ttf = urls.truetype;
	var svg = urls.svg;

	var css = "\n";
	css += "@font-face {\n";
	css += "  font-family: '" + family + "';\n";
	css += "  src: url('" + eot + "');\n";
	css += "  src: url('" + eot + "?iefix') format('embedded-opentype'),\n";
	css += "       url('" + woff + "') format('woff'),\n";
	css += "       url('" + ttf + "') format('ttf'),\n";
	css += "       url('" + svg + "') format('svg');\n";
	css += "}\n";
	return css;
}

function createCssFontFaces(fontFamilies) {
	var style = document.createElement("style");
	style.setAttribute("type", "text/css");
	var css = "";
	for (var family in fontFamilies) {
		if (fontFamilies.hasOwnProperty(family)) {
			css += buildFontFaceRule(family, fontFamilies[family]);
		}
	}
	style.appendChild(document.createTextNode(css));
	document.head.appendChild(style);
}

function FontLoader() {
	this.totalFonts = 0;
	this.loadedFonts = 0;
}
FontLoader.prototype.load = function(fontFamilies) {
	createCssFontFaces(fontFamilies);

	var families = [];
	for (var family in fontFamilies) {
		if (families.hasOwnProperty(family)) {
			families.push(family);
		}
	}
	this.totalFonts += families.length;

	var that = this;
	var loader = new window.FontLoader(families, {
		"fontLoaded": function() {
			that.loadedFonts++;
		}
	});
	loader.loadFonts();
};
FontLoader.prototype.allLoaded = function() {
	return this.totalFonts === this.loadedFonts;
};

function EjectaFontLoader() {
	this.totalFonts = 0;
	this.loadedFonts = 0;
}
EjectaFontLoader.prototype.load = function(fontFamilies) {
	for (var family in fontFamilies) {
		if (fontFamilies.hasOwnProperty(family)) {
			var fontPath = fontFamilies[family].truetype;
			if (fontPath) {
				window.ejecta.loadFont(fontPath);
			}
		}
	}
};
EjectaFontLoader.prototype.allLoaded = function() {
	return true;
};

if (window.ejecta) {
	module.exports = EjectaFontLoader;
} else {
	module.exports = FontLoader;
}
<|endoftext|>


<commit_before>(function(name, factory) {

    if (typeof define === 'function') { // RequireJS
        define(function() { return factory(); });
    } else if (typeof module !== 'undefined' && module.exports) { // CommonJS
        module.exports = factory();
    } else { // Browser
        this[name] = factory();
    }

})('pledge', function(undefined) {

    var _invert = function(obj) {
            var result = {},
                key;
            for (key in obj) {
                result[obj[key]] = key;
            }
            return result;
        },

        _idCounter = 0,
        _uniqueId = function() {
            return ++_idCounter;
        },

        _protoToString = Object.prototype.toString,
        _protoSlice = Array.prototype.slice,

        _slice = function(arr) {
            return _protoSlice.call(arr);
        },

        _isArray = nativeIsArray || function(obj) {
            return _protoToString.call(obj) === '[object Array]';
        },

        _isNumber = function(obj) {
            return _protoToString.call(obj) === '[object Number]';
        };

        /**
         * Status values, determines
         * what the promise's status is
         * @readonly
         * @enum {Number}
         * @alias Promise.STATUS
         */
    var _PROMISE_STATUS = {
            idle:       0,
            progressed: 1,
            failed:     2,
            done:       3
        },
        /**
         * Call values, used to determine
         * what kind of functions to call
         * @readonly
         * @enum {Number}
         * @alias Promise.CALL
         */
        _PROMISE_CALL = {
            done:     0,
            fail:     1,
            always:   2,
            progress: 3,
            pipe:     4
        },
        _PROMISE_CALL_NAME = _invert(_PROMISE_CALL);

    /**
     * A lightweight implementation of promises.
     * API based on {@link https://api.jquery.com/promise/ jQuery.promises}
     * @class Promise
     */
    var Promise = function() {
        /**
         * @type {Id}
         * @private
         */
        this._id = _uniqId();

        /**
         * Registered functions organized by _PROMISE_CALL
         * @type {Object}
         * @private
         */
        this._calls = {};

        /**
         * Current status
         * @type {Number}
         * @private
         */
        this._status = _PROMISE_STATUS.idle;
    };

    Promise.STATUS = _PROMISE_STATUS;
    Promise.CALL = _PROMISE_CALL;

    Promise.prototype = /** @lends Promise# */ {
        constructor: Promise,

        /**
         * Register a done call that is fired after a Promise is resolved
         * @param  {Function} func
         * @return {Promise}
         */
        done: function(func) { return this._pushCall(_PROMISE_CALL.done, func); },
        then: function(func) { return this._pushCall(_PROMISE_CALL.done, func); },
        /**
         * Register a fail call that is fired after a Promise is rejected
         * @param  {Function} func
         * @return {Promise}
         */
        fail: function(func) { return this._pushCall(_PROMISE_CALL.fail, func); },
        /**
         * Register a call that fires after done or fail
         * @param  {Function} func
         * @return {Promise}
         */
        always: function(func) { return this._pushCall(_PROMISE_CALL.always, func); },
        finally: function(func) { return this._pushCall(_PROMISE_CALL.always, func); },
        /**
         * Register a progress call that is fired after a Promise is notified
         * @param  {Function} func
         * @return {Promise}
         */
        progress: function(func) { return this._pushCall(_PROMISE_CALL.progress, func); },
        /**
         * Register a pipe call that is fired before done or fail and whose return value
         * is passed to the next pipe/done/fail call
         * @param  {Function} func
         * @return {Promise}
         */
        pipe: function(func) { return this._pushCall(_PROMISE_CALL.pipe, func); },

        /**
         * Pushes a function into a call array by type
         * @param  {Promise.CALL} callType
         * @param  {Function} func
         * @return {Promise}
         * @private
         */
        _pushCall: function(callType, func) {
            this._getCalls(callType).push(func);
            return this;
        },

        /**
         * Notify the promise - calls any functions in
         * Promise.progress
         * @return {Promise}
         */
        notify: function() {
            this._status = _PROMISE_STATUS.progressed;

            var args = this._runPipe(arguments);
            this._fire(_PROMISE_CALL.progress, args)._fire(_PROMISE_CALL.always, args);

            return this;
        },

        /**
         * Reject the promise - calls any functions in
         * Promise.fail, then calls any functions in
         * Promise.always
         * @return {Promise}
         */
        reject: function() {
            // If we've already called failed or done, go no further
            if (this._status === _PROMISE_STATUS.failed || this._status === _PROMISE_STATUS.done) { return this; }

            this._status = _PROMISE_STATUS.failed;

            // Never run the pipe on fail. Simply fail.
            // Running the pipe after an unexpected failure may lead to
            // more failures
            this._fire(_PROMISE_CALL.fail, arguments)
                ._fire(_PROMISE_CALL.always, arguments);

            this._cleanup();

            return this;
        },

        /**
         * Resolve the promise - calls any functions in
         * Promise.done, then calls any functions in
         * Promise.always
         * @return {Promise}
         */
        resolve: function() {
            // If we've already called failed or done, go no further
            if (this._status === _PROMISE_STATUS.failed || this._status === _PROMISE_STATUS.done) { return this; }

            this._status = _PROMISE_STATUS.done;

            var args = this._runPipe(arguments);
            this._fire(_PROMISE_CALL.done, args)
                ._fire(_PROMISE_CALL.always, args);

            this._cleanup();

            return this;
        },

        /**
         * Determine if the promise is in the status provided
         * @param  {String|Promise.STATUS}  status key or STATUS value
         * @return {Boolean}
         */
        is: function(status) {
            if (_isNumber(status)) { return (this._status === status); }
            return (this._status === Promise.STATUS[status]);
        },

        /**
         * Returns the status of the Promise
         * @return {Number} STATUS
         */
        status: function() {
            return this._status;
        },

        /**
         * Fires a _PROMISE_CALL type with the provided arguments
         * @param  {Promise.CALL} callType
         * @param  {Array} args
         * @return {Promise}
         * @private
         */
        _fire: function(callType, args) {
            var calls = this._getCalls(callType),
                idx = 0, length = calls.length;
            for (; idx < length; idx++) {
                calls[idx].apply(null, args);
            }
            return this;
        },

        /**
         * Runs the pipe, catching the return value
         * to pass to the next pipe. Returns the
         * arguments to used by the calling method
         * to proceed to call other methods (e.g. done/fail/always)
         * @param  {Array} args
         * @return {Array} args
         * @private
         */
        _runPipe: function(args) {
            var pipes = this._getCalls(_PROMISE_CALL.pipe),
                idx = 0, length = pipes.length, val;
            for (; idx < length; idx++) {
                val = pipes[idx].apply(null, args);
                if (val !== undefined) { args = [val]; }
            }

            return args;
        },

        /**
         * Lazy generate arrays based on type to
         * avoid creating disposable arrays for
         * methods that aren't going to be used/called
         * @param  {Promise.CALL} type
         * @return {Array}
         * @private
         */
        _getCalls: function(type) {
            return this._calls[_PROMISE_CALL_NAME[type]] || (this._calls[_PROMISE_CALL_NAME[type]] = []);
        },

        /**
         * Allows a promise to be called like a
         * Function.call() or Function.apply()
         *
         * Very useful for passing a promise as
         * a callback function to 3rd party code
         * (as long as the third party code doesn't
         * try to invoke the Promise directly)
         */
        call: function() {
            var args = _slice(arguments);
            args.splice(0, 1); // Throw away the context
            this.notify.apply(this, args);
        },
        apply: function(ctx, args) {
            this.notify.apply(this, args);
        },

        /**
         * Cleanup references to functions stored in
         * arrays that are no longer able to be called
         * @private
         */
        _cleanup: function() {
            this._getCalls(_PROMISE_CALL.done).length = 0;
            this._getCalls(_PROMISE_CALL.fail).length = 0;
            this._getCalls(_PROMISE_CALL.always).length = 0;
        },

        /**
         * Create a protected object that does not
         * allow promise resolution.
         * @return {Object}
         */
        promise: function() {
            return {
                done:     this.done.bind(this),
                then:     this.then.bind(this),
                fail:     this.fail.bind(this),
                always:   this.always.bind(this),
                finally:  this.finally.bind(this),
                progress: this.progress.bind(this),
                pipe:     this.pipe.bind(this)
            };
        },

        /**
         * Debug string
         * @return {String}
         */
        toString: function() {
            return 'promise-js - ' + [
                'id: '       + this._id,
                'status: '   + _invert(_PROMISE_STATUS)[this._status],
                'done: '     + this._getCalls(_PROMISE_CALL.done).length,
                'fail: '     + this._getCalls(_PROMISE_CALL.fail).length,
                'always: '   + this._getCalls(_PROMISE_CALL.always).length,
                'progress: ' + this._getCalls(_PROMISE_CALL.progress).length,
                'pipe: '     + this._getCalls(_PROMISE_CALL.pipe).length
            ].join(', ');
        }
    };

    /**
     * When to go with Promise. Used by calling `when()` and passing
     * promises to listen to. when can be chained with multiple calls
     * e.g. `when(p1, p2, p3).then(func).then(func).done(func).always(func);`
     * @function when
     * @param {...Promise} promises
     * @return {Promise} A new promise that resolves when all of the given promises resolve.
     */

    /**
     * The when object. It's not exposed to the user,
     * they only see a promise (with a .then() method),
     * but all the magic happens here
     */
    var When = function() {
        /**
         * Store our promise
         * @type {Promise}
         */
        this._p = null;

        /**
         * Store the promises being listened to
         * @type {Array.<Promise>}
         */
        this._events = [];
    };

    When.prototype = {
        constructor: When,

        /**
         * Called by the public when function to initialize
         * the when object
         * @return {Promise}
         */
        init: function() {
            this._events = _isArray(arguments[0]) ? arguments[0] : _slice(arguments);
            this._subscribe();

            var promise = new Promise();
            promise.then = function() { this.done.apply(this, arguments); };
            this._p = promise;
            return promise; // Return the promise so that it can be subscribed to
        },

        /**
         * Subscribe to the promises passed and react
         * when they fire events
         * @private
         */
        _subscribe: function() {
            var check = this._checkStatus.bind(this),
                fireProgress = this._fireProgress.bind(this),
                events = this._events,
                idx = events.length;
            while (idx--) {
                events[idx].done(check).fail(check).progress(fireProgress);
            }
        },

        /**
         * Check the status of all promises when
         * any one promise fires an event
         * @private
         */
        _checkStatus: function() {
            var events = this._events, evt,
                total = events.length,
                done = 0, failed = 0,
                idx = total;
            while (idx--) {
                evt = events[idx];
                // We're waiting for everything to complete
                // so if there's an item with no status, stop
                if (evt.status() === Promise.STATUS.idle) { return; }
                if (evt.status() === Promise.STATUS.done) { done += 1; continue; }
                if (evt.status() === Promise.STATUS.failed) { failed += 1; continue; }
            }
            this._fire(total, done, failed, arguments);
        },

        /**
         * Based on the statuses of our promises, fire the
         * appropriate events
         * @param  {Number}    total  total number of promises
         * @param  {Number}    done   promises in a done state
         * @param  {Number}    failed promises in a failed state
         * @param  {Arguments} args   arguments to pass
         * @private
         */
        _fire: function(total, done, failed, args) {
            var promise = this._p; // Our promise

            // If everything completed, call done (this will call always)
            if (done === total) { return promise.resolve.apply(promise, args); }

            // If everything failed, call fail (this will call always)
            if (failed === total) { return promise.reject.apply(promise, args); }

            // If everything fired, but they're not all one thing, then just call always.
            // The only way to do that without exposing a public function in Promise is
            // to use the private _fire event
            if ((done + failed) === total) { return promise._fire(Promise.CALL.always, args); }
        },

        /**
         * Handled separately from fire because we want to trigger
         * anytime any of the promises progress regardless of sate
         * @private
         */
        _fireProgress: function() {
            var promise = this._p;
            promise.notify.apply(promise, arguments);
        }
    };

    var api = function() {
        return new Promise();
    };
    api.when = function() {
        var w = new When();
        return w.init.apply(w, arguments);
    };

    return api;

});<commit_msg>bug fixes<commit_after>(function(name, factory) {

    if (typeof define === 'function') { // RequireJS
        define(function() { return factory(); });
    } else if (typeof module !== 'undefined' && module.exports) { // CommonJS
        module.exports = factory();
    } else { // Browser
        this[name] = factory();
    }

})('pledge', function(undefined) {

    var _invert = function(obj) {
            var result = {},
                key;
            for (key in obj) {
                result[obj[key]] = key;
            }
            return result;
        },

        _idCounter = 0,
        _uniqueId = function() {
            return ++_idCounter;
        },

        _protoToString = Object.prototype.toString,
        _protoSlice = Array.prototype.slice,

        _slice = function(arr) {
            return _protoSlice.call(arr);
        },

        _isArray = Array.isArray || function(obj) {
            return _protoToString.call(obj) === '[object Array]';
        },

        _isNumber = function(obj) {
            return _protoToString.call(obj) === '[object Number]';
        };

        /**
         * Status values, determines
         * what the promise's status is
         * @readonly
         * @enum {Number}
         * @alias Promise.STATUS
         */
    var _PROMISE_STATUS = {
            idle:       0,
            progressed: 1,
            failed:     2,
            done:       3
        },
        /**
         * Call values, used to determine
         * what kind of functions to call
         * @readonly
         * @enum {Number}
         * @alias Promise.CALL
         */
        _PROMISE_CALL = {
            done:     0,
            fail:     1,
            always:   2,
            progress: 3,
            pipe:     4
        },
        _PROMISE_CALL_NAME = _invert(_PROMISE_CALL);

    /**
     * A lightweight implementation of promises.
     * API based on {@link https://api.jquery.com/promise/ jQuery.promises}
     * @class Promise
     */
    var Promise = function() {
        /**
         * @type {Id}
         * @private
         */
        this._id = _uniqueId();

        /**
         * Registered functions organized by _PROMISE_CALL
         * @type {Object}
         * @private
         */
        this._calls = {};

        /**
         * Current status
         * @type {Number}
         * @private
         */
        this._status = _PROMISE_STATUS.idle;
    };

    Promise.STATUS = _PROMISE_STATUS;
    Promise.CALL = _PROMISE_CALL;

    Promise.prototype = /** @lends Promise# */ {
        constructor: Promise,

        /**
         * Register a done call that is fired after a Promise is resolved
         * @param  {Function} func
         * @return {Promise}
         */
        done: function(func) { return this._pushCall(_PROMISE_CALL.done, func); },
        then: function(func) { return this._pushCall(_PROMISE_CALL.done, func); },
        /**
         * Register a fail call that is fired after a Promise is rejected
         * @param  {Function} func
         * @return {Promise}
         */
        fail: function(func) { return this._pushCall(_PROMISE_CALL.fail, func); },
        /**
         * Register a call that fires after done or fail
         * @param  {Function} func
         * @return {Promise}
         */
        always: function(func) { return this._pushCall(_PROMISE_CALL.always, func); },
        finally: function(func) { return this._pushCall(_PROMISE_CALL.always, func); },
        /**
         * Register a progress call that is fired after a Promise is notified
         * @param  {Function} func
         * @return {Promise}
         */
        progress: function(func) { return this._pushCall(_PROMISE_CALL.progress, func); },
        /**
         * Register a pipe call that is fired before done or fail and whose return value
         * is passed to the next pipe/done/fail call
         * @param  {Function} func
         * @return {Promise}
         */
        pipe: function(func) { return this._pushCall(_PROMISE_CALL.pipe, func); },

        /**
         * Pushes a function into a call array by type
         * @param  {Promise.CALL} callType
         * @param  {Function} func
         * @return {Promise}
         * @private
         */
        _pushCall: function(callType, func) {
            this._getCalls(callType).push(func);
            return this;
        },

        /**
         * Notify the promise - calls any functions in
         * Promise.progress
         * @return {Promise}
         */
        notify: function() {
            this._status = _PROMISE_STATUS.progressed;

            var args = this._runPipe(arguments);
            this._fire(_PROMISE_CALL.progress, args)._fire(_PROMISE_CALL.always, args);

            return this;
        },

        /**
         * Reject the promise - calls any functions in
         * Promise.fail, then calls any functions in
         * Promise.always
         * @return {Promise}
         */
        reject: function() {
            // If we've already called failed or done, go no further
            if (this._status === _PROMISE_STATUS.failed || this._status === _PROMISE_STATUS.done) { return this; }

            this._status = _PROMISE_STATUS.failed;

            // Never run the pipe on fail. Simply fail.
            // Running the pipe after an unexpected failure may lead to
            // more failures
            this._fire(_PROMISE_CALL.fail, arguments)
                ._fire(_PROMISE_CALL.always, arguments);

            this._cleanup();

            return this;
        },

        /**
         * Resolve the promise - calls any functions in
         * Promise.done, then calls any functions in
         * Promise.always
         * @return {Promise}
         */
        resolve: function() {
            // If we've already called failed or done, go no further
            if (this._status === _PROMISE_STATUS.failed || this._status === _PROMISE_STATUS.done) { return this; }

            this._status = _PROMISE_STATUS.done;

            var args = this._runPipe(arguments);
            this._fire(_PROMISE_CALL.done, args)
                ._fire(_PROMISE_CALL.always, args);

            this._cleanup();

            return this;
        },

        /**
         * Determine if the promise is in the status provided
         * @param  {String|Promise.STATUS}  status key or STATUS value
         * @return {Boolean}
         */
        is: function(status) {
            if (_isNumber(status)) { return (this._status === status); }
            return (this._status === Promise.STATUS[status]);
        },

        /**
         * Returns the status of the Promise
         * @return {Number} STATUS
         */
        status: function() {
            return this._status;
        },

        /**
         * Fires a _PROMISE_CALL type with the provided arguments
         * @param  {Promise.CALL} callType
         * @param  {Array} args
         * @return {Promise}
         * @private
         */
        _fire: function(callType, args) {
            var calls = this._getCalls(callType),
                idx = 0, length = calls.length;
            for (; idx < length; idx++) {
                calls[idx].apply(null, args);
            }
            return this;
        },

        /**
         * Runs the pipe, catching the return value
         * to pass to the next pipe. Returns the
         * arguments to used by the calling method
         * to proceed to call other methods (e.g. done/fail/always)
         * @param  {Array} args
         * @return {Array} args
         * @private
         */
        _runPipe: function(args) {
            var pipes = this._getCalls(_PROMISE_CALL.pipe),
                idx = 0, length = pipes.length, val;
            for (; idx < length; idx++) {
                val = pipes[idx].apply(null, args);
                if (val !== undefined) { args = [val]; }
            }

            return args;
        },

        /**
         * Lazy generate arrays based on type to
         * avoid creating disposable arrays for
         * methods that aren't going to be used/called
         * @param  {Promise.CALL} type
         * @return {Array}
         * @private
         */
        _getCalls: function(type) {
            return this._calls[_PROMISE_CALL_NAME[type]] || (this._calls[_PROMISE_CALL_NAME[type]] = []);
        },

        /**
         * Allows a promise to be called like a
         * Function.call() or Function.apply()
         *
         * Very useful for passing a promise as
         * a callback function to 3rd party code
         * (as long as the third party code doesn't
         * try to invoke the Promise directly)
         */
        call: function() {
            var args = _slice(arguments);
            args.splice(0, 1); // Throw away the context
            this.notify.apply(this, args);
        },
        apply: function(ctx, args) {
            this.notify.apply(this, args);
        },

        /**
         * Cleanup references to functions stored in
         * arrays that are no longer able to be called
         * @private
         */
        _cleanup: function() {
            this._getCalls(_PROMISE_CALL.done).length = 0;
            this._getCalls(_PROMISE_CALL.fail).length = 0;
            this._getCalls(_PROMISE_CALL.always).length = 0;
        },

        /**
         * Create a protected object that does not
         * allow promise resolution.
         * @return {Object}
         */
        promise: function() {
            return {
                done:     this.done.bind(this),
                then:     this.then.bind(this),
                fail:     this.fail.bind(this),
                always:   this.always.bind(this),
                finally:  this.finally.bind(this),
                progress: this.progress.bind(this),
                pipe:     this.pipe.bind(this)
            };
        },

        /**
         * Debug string
         * @return {String}
         */
        toString: function() {
            return 'promise-js - ' + [
                'id: '       + this._id,
                'status: '   + _invert(_PROMISE_STATUS)[this._status],
                'done: '     + this._getCalls(_PROMISE_CALL.done).length,
                'fail: '     + this._getCalls(_PROMISE_CALL.fail).length,
                'always: '   + this._getCalls(_PROMISE_CALL.always).length,
                'progress: ' + this._getCalls(_PROMISE_CALL.progress).length,
                'pipe: '     + this._getCalls(_PROMISE_CALL.pipe).length
            ].join(', ');
        }
    };

    /**
     * When to go with Promise. Used by calling `when()` and passing
     * promises to listen to. when can be chained with multiple calls
     * e.g. `when(p1, p2, p3).then(func).then(func).done(func).always(func);`
     * @function when
     * @param {...Promise} promises
     * @return {Promise} A new promise that resolves when all of the given promises resolve.
     */

    /**
     * The when object. It's not exposed to the user,
     * they only see a promise (with a .then() method),
     * but all the magic happens here
     */
    var When = function() {
        /**
         * Store our promise
         * @type {Promise}
         */
        this._p = null;

        /**
         * Store the promises being listened to
         * @type {Array.<Promise>}
         */
        this._events = [];
    };

    When.prototype = {
        constructor: When,

        /**
         * Called by the public when function to initialize
         * the when object
         * @return {Promise}
         */
        init: function() {
            this._events = _isArray(arguments[0]) ? arguments[0] : _slice(arguments);
            this._subscribe();

            var promise = new Promise();
            promise.then = function() { this.done.apply(this, arguments); };
            this._p = promise;
            return promise; // Return the promise so that it can be subscribed to
        },

        /**
         * Subscribe to the promises passed and react
         * when they fire events
         * @private
         */
        _subscribe: function() {
            var check = this._checkStatus.bind(this),
                fireProgress = this._fireProgress.bind(this),
                events = this._events,
                idx = events.length;
            while (idx--) {
                events[idx].done(check).fail(check).progress(fireProgress);
            }
        },

        /**
         * Check the status of all promises when
         * any one promise fires an event
         * @private
         */
        _checkStatus: function() {
            var events = this._events, evt,
                total = events.length,
                done = 0, failed = 0,
                idx = total;
            while (idx--) {
                evt = events[idx];
                // We're waiting for everything to complete
                // so if there's an item with no status, stop
                if (evt.status() === Promise.STATUS.idle) { return; }
                if (evt.status() === Promise.STATUS.done) { done += 1; continue; }
                if (evt.status() === Promise.STATUS.failed) { failed += 1; continue; }
            }
            this._fire(total, done, failed, arguments);
        },

        /**
         * Based on the statuses of our promises, fire the
         * appropriate events
         * @param  {Number}    total  total number of promises
         * @param  {Number}    done   promises in a done state
         * @param  {Number}    failed promises in a failed state
         * @param  {Arguments} args   arguments to pass
         * @private
         */
        _fire: function(total, done, failed, args) {
            var promise = this._p; // Our promise

            // If everything completed, call done (this will call always)
            if (done === total) { return promise.resolve.apply(promise, args); }

            // If everything failed, call fail (this will call always)
            if (failed === total) { return promise.reject.apply(promise, args); }

            // If everything fired, but they're not all one thing, then just call always.
            // The only way to do that without exposing a public function in Promise is
            // to use the private _fire event
            if ((done + failed) === total) { return promise._fire(Promise.CALL.always, args); }
        },

        /**
         * Handled separately from fire because we want to trigger
         * anytime any of the promises progress regardless of sate
         * @private
         */
        _fireProgress: function() {
            var promise = this._p;
            promise.notify.apply(promise, arguments);
        }
    };

    var api = function() {
        return new Promise();
    };
    api.when = function() {
        var w = new When();
        return w.init.apply(w, arguments);
    };

    return api;

});<|endoftext|>


<commit_before>/*jshint esversion: 6, node: true*/
const { spawn } = require("child_process");
const fs = require("fs-extra");
const co = require("co");

//load template latex file
const templateLatex = fs.readFile("./lib/template.latex", "utf8").then(data => {
  //split data on $body$ when done loading
  return data.split("$body$");
}, () => console.error("Could not read template latex file"));

//does one render
function doRenderPass(renderDirPath) {
  //return a promise linked to the errors or success of the render process
  //start the render process by spawning the xelatex process
  const process = spawn("xelatex", [
    //makes xelatex exit on errors
    "-halt-on-error",

    //tell it what the input file is called
    "document.latex"
  ], {
    //pass the working directory, the subdirectory in ./render
    cwd: renderDirPath,

    //make sure to not use shell mode
    shell: false
  });

  //output acculumator
  let data = "";

  //collect all the data sent over stdout
  process.stdout.on("data", output => {
    //convert the passed buffer to a string
    data += output.toString();
  });

  //return a promise that is handled according to the render outcome
  return new Promise((resolve, reject) => {
    //when the process ends, with an error or successful
    process.on("exit", () => {
      //the presence of a particular string in the output determines wether or not an error occured
      //TODO: proper logging of latex error output
      if (data.includes("No pages of output")) {
        console.error(data);
        reject("render error");
      } else {
        //rendering was probably ok, resolve with data
        resolve(data);
      }
    });
  });
}

//generates a pdf file for the given resolution document
module.exports = function generatePdf(resDoc) { return co(function* () {
  //the temporary directory to do the rendering in
  const renderDirPath = "./render/" + resDoc.token;

  //the path for the latex input file
  const inputFilePath = renderDirPath + "/document.latex";

  //create an empty dir to render in
  yield fs.emptyDir(renderDirPath);

  //wait for the template to have been loaded
  const splitData = yield templateLatex;

  //use the returned template processor function and write the result to a file
  //by giving it the generated latex wrapped in the template
  yield fs.writeFile(inputFilePath, splitData[0] + generateLatex(resDoc) + splitData[1]);

  //render twice to resolve page numbers
  yield doRenderPass(renderDirPath);
  const renderLog = yield doRenderPass(renderDirPath);

  //parse number of pages
  let pageAmount;
  try {
    //match where the total page number is and try to parse it
    pageAmount = parseInt(
      renderLog.match(/AED: lastpage setting LastPage\n\[([0-9]+)\]/)[1], 10);
  } catch (err) {
    //do error message, but don't reject, the pdf may still be fine
    console.error("could not parse page amount from pandoc output");
  }

  //move the pdf to the public static directory
  yield fs.rename(renderDirPath + "/document.pdf", "./public/rendered/" + resDoc.token + ".pdf");

  //finally delete the temporary rendering directory
  yield fs.remove(renderDirPath);

  //return found page amount (to be put in database)
  return pageAmount;
}); };

//returns the correct punctuation for a given op clause context
function getOpPunctuation(subPresent, lastInClause, isLastClause) {
  //essentially a precendence waterfall
  //it's the last in the doc if it's the last in the clause that is the last clause in the doc
  return subPresent ? ":" : (lastInClause && isLastClause) ? "." : lastInClause ? ";" : ",";
}

//capitalizes the first character of a string
function capFirst(str) {
  //depending on whether there are subsub clauses in ocs or not, one gets an
  //object and has to use the content field,
  //or gets a string and can use it right away
  if (str.content) {
    str = str.content;
  }

  //get first char and capitalize
  return str[0].toUpperCase() + str.slice(1);
}

//uncapitalizes the first character of a string
/*function uncapFirst(str) {
  //get first char and uncapitalize
  return str[0].toLowerCase() + str.slice(1);
}*/

//replacer functions for _ and * and ^
const modeScriptReplacers = {
  sub: (m, innerMatch) => "\\textsubscript{" + innerMatch + "}",
  super: (m, innerMatch) => "\\textsuperscript{" + innerMatch + "}",
  emph: (m, innerMatch) => "\\emph{" + innerMatch + "}"
};

//prepares the string by selectively removing punctuation
function prepareClause(str) {
  //remove unwanted chararcters and perform other transforms
  str = str
    //limit a single content node to 2500 characters
    .substring(0, 2500)

    //remove too long words, limit to 45 characters
    .replace(/\b[^\s ]{46,}/g, "")

    //convert detached ticks and normal apostrophes to nicer apostrophe character
    //remove space before such characters with " *"
    .replace(/ *[`´']+/g, "’")

    //convert all types of double quotes to normal quotes
    //for csquotes to do its thing we need them all to be the same
    .replace(/[“”‹›«»]/g, "\"")

    //remove all consecutive whitespace that includes a non-space whitespace character
    //(replace by single space to preserve words)
    .replace(/\s*[^\S ]+\s*/g, " ")

    //replace newlines with spaces
    .replace(/\n+/g, " ")

    //remove all spaces at tips
    .trim()

    //remove trailing _ and ^ (produce latex error)
    .replace(/[_^]$/g, "")

    //remove disallowed unicode characters (these are all the allowed characters)
    .replace(/[^a-zA-Z0-9*_^|&’"\-.,()/+\u00c0-\u024F ]+/g, "")

    //the & sign may be used in names and will be escaped
    .replace(/&/g, "\\&");

  //append final " if there is an odd amount of "s
  if ((str.match(/"/g) || []).length % 2) {
    //append at end of string to saitfy renderer (throws error otherwise)
    str += "\"";
  }

  //return with final modifications
  return str
    //parse special char markers: see issue douira/resolution-editor#104
    //asteriks enclose italics, add the italics markers
    .replace(/\*(.*?)\*/g, modeScriptReplacers.emph)

    //enclosed sub- and superscripts
    .replace(/_([^_^]*?)\|/g, modeScriptReplacers.sub)
    .replace(/\^([^_^]*?)\|/g, modeScriptReplacers.super)

    //single sub- and superscripts
    .replace(/_([^_^])/g, modeScriptReplacers.sub)
    .replace(/\^([^_^])/g, modeScriptReplacers.super);
}

//removes any non alphanumeric (or not space) characters from the string (for header info stings)
function prepareHeader(str) {
  return str.trim().replace(/[^0-9a-zA-Z ]/g, "").substring(0, 45);
}

/*generates latex code to be compiled and used by a installed latex binary
  and then turned into a pdf that is displayed to the user for printing
  Tested the performance of this function: about 0.029ms per call (for a mediumsmall resolution)
*/
function generateLatex(resDoc) {
  //unpack resolution
  const obj = resDoc.content;

  //add setup and some data
  let str =
`\\newcommand{\\forum}{${prepareHeader(capFirst(obj.resolution.address.forum))}}
\\newcommand{\\issue}{${prepareHeader(capFirst(obj.resolution.address.questionOf))}}
\\newcommand{\\sponsor}{${prepareHeader(capFirst(obj.resolution.address.sponsor.main))}}`;

  //check if a resolution ID has been generated
  if ("resolutionId" in resDoc) {
    //add dentifier to corner header
    str += `\\rhead{MUNOL / ${resDoc.idYear} / ${resDoc.resolutionId}}\n`;
  }

  //add co-sponsors if there are any
  const coSponsors = obj.resolution.address.sponsor.co;
  const coSponsorsPresent = coSponsors && coSponsors.length;
  if (coSponsorsPresent) {
    //add another command
    str += `\\newcommand{\\cosponsors}{${
      coSponsors.map(str => prepareHeader(capFirst(str))).join(", ")}}`;
  }

  //add the rest of the address
  str += "\\begin{tabbing}" +
    "FORUM: \\quad \\quad  \\quad \\quad\\=\\forum \\\\\\\\" +
    "QUESTION OF: \\>\\issue \\\\\\\\" +
    "SPONSOR: \\>\\sponsor \\\\";

  //add cosponsor part if present
  if (coSponsorsPresent) {
    str += "\\\\CO-SPONSORS: \\>\\cosponsors \\\\";
  }

  //finish the address
  str += "\\end{tabbing}\\MakeUppercase{THE \\forum},\\newline\\newline";

  //preambs
  str += obj.resolution.clauses.preambulatory.map(
    clause => {
      //check if subclauses are going to be added
      const subsPresent = clause.sub && clause.sub.length;

      //add code for clause, add correct punctuation at end of content
      let str = `\\begin{preamb}{${prepareHeader(capFirst(clause.phrase))}}{${
        prepareClause(clause.content) + (subsPresent ? ":" : ",")}}`;

      //handle subclauses
      if (subsPresent) {
        //start subclauses
        str += "\\begin{preambsubclause}";

        //fill in all subclause contents
        str += clause.sub.map(sub => `\\item ${prepareClause(sub.content)},`).join("\n");

        //end subclauses
        str += "\\end{preambsubclause}";
      }

      //add extended content if available
      if (clause.contentExt && clause.contentExt.length) {
        str += prepareClause(clause.contentExt) + ",";
      }

      //end of preamb
      str += "\\end{preamb}";

      //return created clause
      return str;
    }
  ).join("\n");

  //ops
  if (obj.resolution.clauses.operative.length) {

    //close gap between preambulatory and operative clauses
    str += "\\vspace{-12pt}";

    //begin operative clauses
    str += "\\begin{enumerate}[1., align = left, leftmargin =* , " +
      "widest* = 8, labelindent=0cm, labelsep=1cm, topsep=0pt] \\itemsep12pt " +

    //for every oc clause
    obj.resolution.clauses.operative.map((clause, index, arr) => {
      //check if there are going to be subclauses
      const subsPresent = clause.sub && clause.sub.length;

      //check if we are in the last clause
      const lastClause = index === arr.length - 1;

      //clause beginning with correct punctuation
      let cStr = `\\begin{oc}{${prepareHeader(capFirst(clause.phrase))}}{${
        prepareClause(clause.content) +
        getOpPunctuation(subsPresent, ! subsPresent, lastClause)}}`;

      //add subclauses, if detected
      if (subsPresent) {
        //begin of subclauses
        cStr += "\\begin{ocsubclause}";

        //check if super clause has extended content
        const hasExtContent = clause.contentExt && clause.contentExt.length;

        //for all subclauses add to string
        cStr += clause.sub.map((subclause, subIndex, subArr) => {
          //start with item
          let subContent = "\\item ";

          //add content, punctuation comes later
          subContent += prepareClause(capFirst(subclause));

          //check if subsubs are present
          const subsubsPresent = subclause.sub && subclause.sub.length;

          //check if this is the last subclause
          const lastSubClause = subIndex === subArr.length - 1;

          //add punctuation
          subContent += getOpPunctuation(
            subsubsPresent, ! subsubsPresent && lastSubClause && ! hasExtContent, lastClause);

          //add susubs if there are any
          if (subsubsPresent) {
            //begin subsubclause
            subContent += "\\begin{ocsubsubclause}";

            //check if super subclause has extended content
            const subHasExtContent = subclause.contentExt && subclause.contentExt.length;

            //add all subclauses of this subclause
            subContent += subclause.sub.map((subSub, subSubIndex, subSubArr) => {
              //check if this is the last subsub clause
              const lastSubSubClause = subSubIndex === subSubArr.length - 1;

              //generate items with correct punctuation
              return "\\item " +
                prepareClause(subSub.content) +
                getOpPunctuation(
                  false,
                  lastSubSubClause && lastSubClause && ! subHasExtContent && ! hasExtContent,
                  lastClause
                );
            }).join("\n");

            //end sub segment
            subContent += "\\end{ocsubsubclause}";
          }

          //extension of subclauses if available
          if(subclause.contentExt && subclause.contentExt.length){
            subContent += "~" + prepareClause(subclause.contentExt) + getOpPunctuation(
              false,
              lastSubClause && ! hasExtContent,
              lastClause
            );
          }

          //return content of subclause
          return subContent;
        }).join("\n");

        //end subclauses
        cStr += "\\end{ocsubclause}";
      }

      //extension of operative clause if available
      if(clause.contentExt && clause.contentExt.length){
          cStr += prepareClause(clause.contentExt) + getOpPunctuation(false, true, lastClause);
      }

      //end clause and return
      return cStr + "\\end{oc}";
    }).join("\n") + "\\end{enumerate}";
  }

  //return generated body latex string
  return str;
}
<commit_msg>Header Information not overlapping anymore #fixes 133<commit_after>/*jshint esversion: 6, node: true*/
const { spawn } = require("child_process");
const fs = require("fs-extra");
const co = require("co");

//load template latex file
const templateLatex = fs.readFile("./lib/template.latex", "utf8").then(data => {
  //split data on $body$ when done loading
  return data.split("$body$");
}, () => console.error("Could not read template latex file"));

//does one render
function doRenderPass(renderDirPath) {
  //return a promise linked to the errors or success of the render process
  //start the render process by spawning the xelatex process
  const process = spawn("xelatex", [
    //makes xelatex exit on errors
    "-halt-on-error",

    //tell it what the input file is called
    "document.latex"
  ], {
    //pass the working directory, the subdirectory in ./render
    cwd: renderDirPath,

    //make sure to not use shell mode
    shell: false
  });

  //output acculumator
  let data = "";

  //collect all the data sent over stdout
  process.stdout.on("data", output => {
    //convert the passed buffer to a string
    data += output.toString();
  });

  //return a promise that is handled according to the render outcome
  return new Promise((resolve, reject) => {
    //when the process ends, with an error or successful
    process.on("exit", () => {
      //the presence of a particular string in the output determines wether or not an error occured
      //TODO: proper logging of latex error output
      if (data.includes("No pages of output")) {
        console.error(data);
        reject("render error");
      } else {
        //rendering was probably ok, resolve with data
        resolve(data);
      }
    });
  });
}

//generates a pdf file for the given resolution document
module.exports = function generatePdf(resDoc) { return co(function* () {
  //the temporary directory to do the rendering in
  const renderDirPath = "./render/" + resDoc.token;

  //the path for the latex input file
  const inputFilePath = renderDirPath + "/document.latex";

  //create an empty dir to render in
  yield fs.emptyDir(renderDirPath);

  //wait for the template to have been loaded
  const splitData = yield templateLatex;

  //use the returned template processor function and write the result to a file
  //by giving it the generated latex wrapped in the template
  yield fs.writeFile(inputFilePath, splitData[0] + generateLatex(resDoc) + splitData[1]);

  //render twice to resolve page numbers
  yield doRenderPass(renderDirPath);
  const renderLog = yield doRenderPass(renderDirPath);

  //parse number of pages
  let pageAmount;
  try {
    //match where the total page number is and try to parse it
    pageAmount = parseInt(
      renderLog.match(/AED: lastpage setting LastPage\n\[([0-9]+)\]/)[1], 10);
  } catch (err) {
    //do error message, but don't reject, the pdf may still be fine
    console.error("could not parse page amount from pandoc output");
  }

  //move the pdf to the public static directory
  yield fs.rename(renderDirPath + "/document.pdf", "./public/rendered/" + resDoc.token + ".pdf");

  //finally delete the temporary rendering directory
  yield fs.remove(renderDirPath);

  //return found page amount (to be put in database)
  return pageAmount;
}); };

//returns the correct punctuation for a given op clause context
function getOpPunctuation(subPresent, lastInClause, isLastClause) {
  //essentially a precendence waterfall
  //it's the last in the doc if it's the last in the clause that is the last clause in the doc
  return subPresent ? ":" : (lastInClause && isLastClause) ? "." : lastInClause ? ";" : ",";
}

//capitalizes the first character of a string
function capFirst(str) {
  //depending on whether there are subsub clauses in ocs or not, one gets an
  //object and has to use the content field,
  //or gets a string and can use it right away
  if (str.content) {
    str = str.content;
  }

  //get first char and capitalize
  return str[0].toUpperCase() + str.slice(1);
}

//uncapitalizes the first character of a string
/*function uncapFirst(str) {
  //get first char and uncapitalize
  return str[0].toLowerCase() + str.slice(1);
}*/

//replacer functions for _ and * and ^
const modeScriptReplacers = {
  sub: (m, innerMatch) => "\\textsubscript{" + innerMatch + "}",
  super: (m, innerMatch) => "\\textsuperscript{" + innerMatch + "}",
  emph: (m, innerMatch) => "\\emph{" + innerMatch + "}"
};

//prepares the string by selectively removing punctuation
function prepareClause(str) {
  //remove unwanted chararcters and perform other transforms
  str = str
    //limit a single content node to 2500 characters
    .substring(0, 2500)

    //remove too long words, limit to 45 characters
    .replace(/\b[^\s ]{46,}/g, "")

    //convert detached ticks and normal apostrophes to nicer apostrophe character
    //remove space before such characters with " *"
    .replace(/ *[`´']+/g, "’")

    //convert all types of double quotes to normal quotes
    //for csquotes to do its thing we need them all to be the same
    .replace(/[“”‹›«»]/g, "\"")

    //remove all consecutive whitespace that includes a non-space whitespace character
    //(replace by single space to preserve words)
    .replace(/\s*[^\S ]+\s*/g, " ")

    //replace newlines with spaces
    .replace(/\n+/g, " ")

    //remove all spaces at tips
    .trim()

    //remove trailing _ and ^ (produce latex error)
    .replace(/[_^]$/g, "")

    //remove disallowed unicode characters (these are all the allowed characters)
    .replace(/[^a-zA-Z0-9*_^|&’"\-.,()/+\u00c0-\u024F ]+/g, "")

    //the & sign may be used in names and will be escaped
    .replace(/&/g, "\\&");

  //append final " if there is an odd amount of "s
  if ((str.match(/"/g) || []).length % 2) {
    //append at end of string to saitfy renderer (throws error otherwise)
    str += "\"";
  }

  //return with final modifications
  return str
    //parse special char markers: see issue douira/resolution-editor#104
    //asteriks enclose italics, add the italics markers
    .replace(/\*(.*?)\*/g, modeScriptReplacers.emph)

    //enclosed sub- and superscripts
    .replace(/_([^_^]*?)\|/g, modeScriptReplacers.sub)
    .replace(/\^([^_^]*?)\|/g, modeScriptReplacers.super)

    //single sub- and superscripts
    .replace(/_([^_^])/g, modeScriptReplacers.sub)
    .replace(/\^([^_^])/g, modeScriptReplacers.super);
}

//removes any non alphanumeric (or not space) characters from the string (for header info stings)
function prepareHeader(str) {
  return str.trim().replace(/[^0-9a-zA-Z ]/g, "").substring(0, 45);
}

/*generates latex code to be compiled and used by a installed latex binary
  and then turned into a pdf that is displayed to the user for printing
  Tested the performance of this function: about 0.029ms per call (for a mediumsmall resolution)
*/
function generateLatex(resDoc) {
  //unpack resolution
  const obj = resDoc.content;

  //add setup and some data
  let str =
`\\newcommand{\\forum}{${prepareHeader(capFirst(obj.resolution.address.forum))}}
\\newcommand{\\issue}{${prepareHeader(capFirst(obj.resolution.address.questionOf))}}
\\newcommand{\\sponsor}{${prepareHeader(capFirst(obj.resolution.address.sponsor.main))}}`;

  //check if a resolution ID has been generated
  if ("resolutionId" in resDoc) {
    //add dentifier to corner header
    str += `\\rhead{MUNOL / ${resDoc.idYear} / ${resDoc.resolutionId}}\n`;
  }

  //add co-sponsors if there are any
  const coSponsors = obj.resolution.address.sponsor.co;
  const coSponsorsPresent = coSponsors && coSponsors.length;
  if (coSponsorsPresent) {
    //add another command
    str += `\\newcommand{\\cosponsors}{${
      coSponsors.map(str => prepareHeader(capFirst(str))).join(", ")}}`;
  }

  //add the rest of the address
  str += "\\begin{tabular}{@{}p{.25\\linewidth}p{.72\\linewidth}}" +
    "FORUM: \\quad \\quad  \\quad \\quad &\\forum \\\\\\\\" +
    "QUESTION OF: &\\issue \\\\\\\\" +
    "SPONSOR: &\\sponsor \\\\";

  //add cosponsor part if present
  if (coSponsorsPresent) {
    str += "\\\\CO-SPONSORS: &\\cosponsors \\\\\\\\";
  }

  //finish the address
  str += "\\end{tabular}\\\\~\\newline~\\newline\\MakeUppercase{THE \\forum},\\newline\\newline";

  //preambs
  str += obj.resolution.clauses.preambulatory.map(
    clause => {
      //check if subclauses are going to be added
      const subsPresent = clause.sub && clause.sub.length;

      //add code for clause, add correct punctuation at end of content
      let str = `\\begin{preamb}{${prepareHeader(capFirst(clause.phrase))}}{${
        prepareClause(clause.content) + (subsPresent ? ":" : ",")}}`;

      //handle subclauses
      if (subsPresent) {
        //start subclauses
        str += "\\begin{preambsubclause}";

        //fill in all subclause contents
        str += clause.sub.map(sub => `\\item ${prepareClause(sub.content)},`).join("\n");

        //end subclauses
        str += "\\end{preambsubclause}";
      }

      //add extended content if available
      if (clause.contentExt && clause.contentExt.length) {
        str += prepareClause(clause.contentExt) + ",";
      }

      //end of preamb
      str += "\\end{preamb}";

      //return created clause
      return str;
    }
  ).join("\n");

  //ops
  if (obj.resolution.clauses.operative.length) {

    //close gap between preambulatory and operative clauses
    str += "\\vspace{-12pt}";

    //begin operative clauses
    str += "\\begin{enumerate}[1., align = left, leftmargin =* , " +
      "widest* = 8, labelindent=0cm, labelsep=1cm, topsep=0pt] \\itemsep12pt " +

    //for every oc clause
    obj.resolution.clauses.operative.map((clause, index, arr) => {
      //check if there are going to be subclauses
      const subsPresent = clause.sub && clause.sub.length;

      //check if we are in the last clause
      const lastClause = index === arr.length - 1;

      //clause beginning with correct punctuation
      let cStr = `\\begin{oc}{${prepareHeader(capFirst(clause.phrase))}}{${
        prepareClause(clause.content) +
        getOpPunctuation(subsPresent, ! subsPresent, lastClause)}}`;

      //add subclauses, if detected
      if (subsPresent) {
        //begin of subclauses
        cStr += "\\begin{ocsubclause}";

        //check if super clause has extended content
        const hasExtContent = clause.contentExt && clause.contentExt.length;

        //for all subclauses add to string
        cStr += clause.sub.map((subclause, subIndex, subArr) => {
          //start with item
          let subContent = "\\item ";

          //add content, punctuation comes later
          subContent += prepareClause(capFirst(subclause));

          //check if subsubs are present
          const subsubsPresent = subclause.sub && subclause.sub.length;

          //check if this is the last subclause
          const lastSubClause = subIndex === subArr.length - 1;

          //add punctuation
          subContent += getOpPunctuation(
            subsubsPresent, ! subsubsPresent && lastSubClause && ! hasExtContent, lastClause);

          //add susubs if there are any
          if (subsubsPresent) {
            //begin subsubclause
            subContent += "\\begin{ocsubsubclause}";

            //check if super subclause has extended content
            const subHasExtContent = subclause.contentExt && subclause.contentExt.length;

            //add all subclauses of this subclause
            subContent += subclause.sub.map((subSub, subSubIndex, subSubArr) => {
              //check if this is the last subsub clause
              const lastSubSubClause = subSubIndex === subSubArr.length - 1;

              //generate items with correct punctuation
              return "\\item " +
                prepareClause(subSub.content) +
                getOpPunctuation(
                  false,
                  lastSubSubClause && lastSubClause && ! subHasExtContent && ! hasExtContent,
                  lastClause
                );
            }).join("\n");

            //end sub segment
            subContent += "\\end{ocsubsubclause}";
          }

          //extension of subclauses if available
          if(subclause.contentExt && subclause.contentExt.length){
            subContent += "~" + prepareClause(subclause.contentExt) + getOpPunctuation(
              false,
              lastSubClause && ! hasExtContent,
              lastClause
            );
          }

          //return content of subclause
          return subContent;
        }).join("\n");

        //end subclauses
        cStr += "\\end{ocsubclause}";
      }

      //extension of operative clause if available
      if(clause.contentExt && clause.contentExt.length){
          cStr += prepareClause(clause.contentExt) + getOpPunctuation(false, true, lastClause);
      }

      //end clause and return
      return cStr + "\\end{oc}";
    }).join("\n") + "\\end{enumerate}";
  }

  //return generated body latex string
  return str;
}
<|endoftext|>