<commit_before>'use strict';

var Tree2Json = require('broccoli-tree-to-json'),
  RSVP = require('rsvp'),
  path = require('path'),
  fs = require('fs'),
  nano = require('nano'),
  lodash = require('lodash'),
  mktemp = require('mktemp'),
  mkdirp = require('mkdirp');

/**
 *
 * Synchronize CouchDB design documents.
 * Options include
 *  * `url`: the couchdb url (http://host:123/db)
 *  * `username`: optional. used to authenticate with couchdb
 *  * `password`: required if username is present.
 *  * `initDesign`:optional. Set to true if the directory structure should be created from the existing database design documents
 *
 * @param inputTree {string|tree} the input tree to monitor
 * @param options {Object} couchdb connection options
 * @return {CouchDBVersioning}
 * @constructor
 * @alias module:index
 */
function CouchDBVersioning(inputTree, options) {
  if (!(this instanceof CouchDBVersioning)) return new CouchDBVersioning(inputTree, options);

  this.srcDir = inputTree;
  this.revDir = inputTree + '/.rev';
  this.options = options;
  this.inputTree = new Tree2Json(inputTree + '/_design');

  this.initPromise = this.init(options)
    .catch(function (err) {
      console.trace('CouchDB Init ERROR:', new Date(), err);
    });
}

CouchDBVersioning.prototype.init = function (options) {
  return RSVP.all([this.initDesign(options),
    this.initTmpDir(),
    this.initRevCache()])
    .catch(function (err) {
      console.log('CouchDB Versioning initialization error:', err);
    });
};

CouchDBVersioning.prototype.initDesign = function (options) {
  var self = this;
  return new RSVP.Promise(function (resolve, reject) {
    if (options.initDesign) {
      mkdirp(path.join(self.srcDir, '_design'), function (err, made) {
        if (err) reject(err);
        else resolve(self.initExistingDesigns());
      });
    } else resolve();
  });
};

CouchDBVersioning.prototype.initConnection = function () {
  var self = this;
  return new RSVP.Promise(function (resolve, reject) {
    var connection = nano(self.options.url);

    if (self.options.username) {
      connection.auth(self.options.username, self.options.password, function (err, body, headers) {
        if (err) reject(err);
        else {
          self.connection = nano({url: self.options.url, cookie: headers['set-cookie'][0]});
          resolve();
        }
      });
    } else {
      self.connection = connection;
      resolve(connection);
    }
  });
};

CouchDBVersioning.prototype.initTmpDir = function () {
  var self = this;
  return new RSVP.Promise(function (resolve, reject) {
    mktemp.createDir(path.join((process.env.TMPDIR || 'tmp'), 'XXXXXXXX.tmp'), function (err, path) {
      if (err) {
        console.warn('CouchDBVersioning WARN:', new Date(), 'Could not create temp dir');
        resolve();
      }
      else {
        self.tempDir = path;
        resolve(path)
      }
    });
  });
};

CouchDBVersioning.prototype.initRevCache = function () {
  var self = this;
  return new RSVP.Promise(function (resolve, reject) {
    mkdirp(self.revDir, function (err, made) {
      if (err) reject(err);
      else resolve();
    });
  });
};

function getFiles(destDir) {
  return new RSVP.Promise(function (resolve, reject) {
    fs.readdir(destDir, function (err, files) {
      if (err) reject(err);
      else resolve(files);
    });
  })
}

function readFile(dir, file) {
  return new RSVP.Promise(function (resolve, reject) {
    fs.readFile(path.join(dir, file), function (err, data) {
      if (err) reject(err);
      else resolve(JSON.parse(data.toString()));
    });
  });
}

function reportError(docName, date, localRev, serverRev, nag) {
  console.log(nag ? 'NAG:' : 'ERROR:', date, docName + '._rev Conflict: local is', localRev, 'server is', serverRev);
}

/**
 * REDTAG:TJH
 * This code needs to be cleaned up
 * @param existingDesigns
 * @param design
 * @return {*}
 */
CouchDBVersioning.prototype.updateDesign = function (existingDesigns, design) {
  var self = this;
  return RSVP.all(Object.keys(design).map(function (key) {
    var designDoc = design[key];
    var designName = '_design/' + key;
    var existing = existingDesigns[key] || {_id: designName};
    designDoc._id = existing._id;

    return self.getRev(key)
      .then(function (rev) {
        var docName;
        var serverRevNum = 0, localRevNum;
        var result = null;
        if (existing._rev) {
          serverRevNum = parseInt(existing._rev.split('-')[0]);
        }
        localRevNum = parseInt(rev.split('-')[0]);

        /*
         NOTE - Messing with _revs is a really bad idea.
         But as a development tool this is a special case.
         Generally development revs will outpace production revs, so we're allowing
         updates when local revs are equal or greater
         */
        if (localRevNum >= serverRevNum) {
          //fake out the rev for the equality test and update
          designDoc._rev = existing._rev;
          if (!lodash.isEqual(designDoc, existing)) {
            result = new RSVP.Promise(function (resolve, reject) {
              self.connection.insert(designDoc, designName, function (err, body) {
                if (err)reject(err);
                else {
                  resolve(body.rev);
                }
              });
            }).then(function (newRev) {
                return self.updateRev(key, newRev);
              });
          }
        } else {
          docName = '_design/' + key;
          reportError(docName, new Date(), rev, existing._rev);
        }
        return result;
      });
  }));
};

CouchDBVersioning.prototype.getRev = function (key) {
  var self = this;
  return new RSVP.Promise(function (resolve, reject) {
    fs.readFile(path.join(self.revDir, key + '.txt'), function (err, data) {
      if (err) reject(err);
      else resolve(data.toString());
    });
  });
};

CouchDBVersioning.prototype.updateRev = function (key, rev) {
  var self = this;
  return new RSVP.Promise(function (resolve, reject) {
    var filePath = path.join(self.revDir, key + '.txt');
    fs.writeFile(filePath, rev, function (err, data) {
      if (err) {
        //HEROKU deploys won't update the rev (read only dirs), so this shouldn't be fatal
        console.warn('CouchDBVersioning WARN:', new Date(), 'could not update', filePath, '. :', err);
      }
      else resolve();
    });
  });
};

CouchDBVersioning.prototype.getExistingDesigns = function () {
  var self = this;
  return new RSVP.Promise(function (resolve, reject) {
    self.connection.get('_all_docs', {startkey: '_design', endkey: '_design0', include_docs: true},
      function (err, body) {
        var existing = {};
        if (err) reject(err);
        else {
          body.rows.forEach(function (row) {
            existing[row.id.split('/')[1]] = row.doc
          });
          resolve(existing);
        }
      });
  });
};

CouchDBVersioning.prototype.updateDocument = function (destDir, fileName) {
  var self = this;
  return readFile(destDir, fileName)
    .then(function (json) {
      if ('_design.json' === fileName) {
        return self.getExistingDesigns()
          .then(function (existing) {
            return self.updateDesign(existing, json);
          });
      }
    });
};

CouchDBVersioning.prototype.updateCouch = function (destDir) {
  var self = this;
  return getFiles(destDir)
    .then(function (files) {
      return RSVP.all(files.map(function (fileName) {
        return self.updateDocument(destDir, fileName);
      }));
    });
};

CouchDBVersioning.prototype.read = function (readTree) {
  var self = this;
  return this.initPromise
    .then(function () {
      return RSVP.all([self.initConnection(), readTree(self.inputTree)]);
    }).then(function (resolutions) {
      return self.updateCouch(resolutions[1]);
    }).then(function () {
      return self.tempDir;
    }).catch(function (err) {
      console.trace('CouchDBVersioning: ERROR:', new Date(), err);
      return self.tempDir;
    });
};

CouchDBVersioning.prototype.cleanup = function () {
  this.inputTree.cleanup();
  if (this.tempDir) fs.rmdirSync(this.tempDir);
};

CouchDBVersioning.prototype.writeDir = function (filePath, json) {
  var self = this;
  return new RSVP.Promise(function (resolve, reject) {
    mkdirp(filePath, function (err, made) {
      var promises;
      if (err) reject(err);
      else {
        promises = Object.keys(json).map(function (key) {
          var val = json[key];
          var subPath = path.join(filePath, key);
          var result;
          if (val instanceof Array || typeof val !== 'object') {
            result = self.writeFile(subPath, val);
          } else {
            result = self.writeDir(subPath, val);
          }
          return result;
        });
        resolve(RSVP.all(promises));
      }
    });
  });
};

var JS_PATH = new RegExp(path.sep + '((map)|(reduce))$');
var SPECIAL_VALUES = new RegExp(path.sep + '_rev$');
var IGNORED_VALUES = new RegExp(path.sep + '_id$');
CouchDBVersioning.prototype.writeFile = function (filePath, data) {
  var elems;
  if (IGNORED_VALUES.test(filePath)) return false;
  if (SPECIAL_VALUES.test(filePath)) {
    elems = filePath.split(path.sep);
    return this.updateRev(elems[elems.length - 2], data)
  }
  return new RSVP.Promise(function (resolve, reject) {
    var extension = JS_PATH.test(filePath) ? '.js' : '.txt';
    fs.writeFile(filePath + extension, data, function (err, data) {
      if (err) reject(err);
      else resolve();
    });
  });
};

CouchDBVersioning.prototype.initExistingDesigns = function () {
  var self = this;
  return this.initConnection()
    .then(function () {
      return self.getExistingDesigns();
    }).then(function (existing) {
      return RSVP.all(Object.keys(existing).map(function (designName) {
        return self.writeDir(path.join(self.srcDir, '_design', designName), existing[designName]);
      }));
    }).catch(function (err) {
      console.trace('CouchDBVersionin: ERROR:', new Date(), err);
    });
};

module.exports = CouchDBVersioning;<commit_msg>* using hack to make design doc pushes work<commit_after>'use strict';

var Tree2Json = require('broccoli-tree-to-json'),
  RSVP = require('rsvp'),
  path = require('path'),
  fs = require('fs'),
  nano = require('nano'),
  lodash = require('lodash'),
  mktemp = require('mktemp'),
  mkdirp = require('mkdirp');

/**
 *
 * Synchronize CouchDB design documents.
 * Options include
 *  * `url`: the couchdb url (http://host:123/db)
 *  * `username`: optional. used to authenticate with couchdb
 *  * `password`: required if username is present.
 *  * `initDesign`:optional. Set to true if the directory structure should be created from the existing database design documents
 *
 * @param inputTree {string|tree} the input tree to monitor
 * @param options {Object} couchdb connection options
 * @return {CouchDBVersioning}
 * @constructor
 * @alias module:index
 */
function CouchDBVersioning(inputTree, options) {
  if (!(this instanceof CouchDBVersioning)) return new CouchDBVersioning(inputTree, options);

  this.srcDir = inputTree;
  this.revDir = inputTree + '/.rev';
  this.options = options;
  this.inputTree = new Tree2Json(inputTree + '/_design');

  this.initPromise = this.init(options)
    .catch(function (err) {
      console.trace('CouchDB Init ERROR:', new Date(), err);
    });
}

CouchDBVersioning.prototype.init = function (options) {
  return RSVP.all([this.initDesign(options),
    this.initTmpDir(),
    this.initRevCache()])
    .catch(function (err) {
      console.log('CouchDB Versioning initialization error:', err);
    });
};

CouchDBVersioning.prototype.initDesign = function (options) {
  var self = this;
  return new RSVP.Promise(function (resolve, reject) {
    if (options.initDesign) {
      mkdirp(path.join(self.srcDir, '_design'), function (err, made) {
        if (err) reject(err);
        else resolve(self.initExistingDesigns());
      });
    } else resolve();
  });
};

CouchDBVersioning.prototype.initConnection = function () {
  var self = this;
  return new RSVP.Promise(function (resolve, reject) {
    var connection = nano(self.options.url);

    if (self.options.username) {
      connection.auth(self.options.username, self.options.password, function (err, body, headers) {
        if (err) reject(err);
        else {
          self.connection = nano({url: self.options.url, cookie: headers['set-cookie'][0]});
          resolve();
        }
      });
    } else {
      self.connection = connection;
      resolve(connection);
    }
  });
};

var HEROKU_TEMP = path.join(path.sep, 'app', 'tmp');
CouchDBVersioning.prototype.initTmpDir = function () {
  var self = this;
  return new RSVP.Promise(function (resolve, reject) {
    //REDTAG:TJH - HEROKU_TEMP is a hack. Needs to e fixed
    mktemp.createDir(path.join((process.env.TMPDIR || HEROKU_TEMP), 'XXXXXXXX.tmp'), function (err, path) {
      if (err) {
        console.warn('CouchDBVersioning WARN:', new Date(), 'Could not create temp dir');
        resolve();
      }
      else {
        self.tempDir = path;
        resolve(path)
      }
    });
  });
};

CouchDBVersioning.prototype.initRevCache = function () {
  var self = this;
  return new RSVP.Promise(function (resolve, reject) {
    mkdirp(self.revDir, function (err, made) {
      if (err) reject(err);
      else resolve();
    });
  });
};

function getFiles(destDir) {
  return new RSVP.Promise(function (resolve, reject) {
    fs.readdir(destDir, function (err, files) {
      if (err) reject(err);
      else resolve(files);
    });
  })
}

function readFile(dir, file) {
  return new RSVP.Promise(function (resolve, reject) {
    fs.readFile(path.join(dir, file), function (err, data) {
      if (err) reject(err);
      else resolve(JSON.parse(data.toString()));
    });
  });
}

function reportError(docName, date, localRev, serverRev, nag) {
  console.log(nag ? 'NAG:' : 'ERROR:', date, docName + '._rev Conflict: local is', localRev, 'server is', serverRev);
}

/**
 * REDTAG:TJH
 * This code needs to be cleaned up
 * @param existingDesigns
 * @param design
 * @return {*}
 */
CouchDBVersioning.prototype.updateDesign = function (existingDesigns, design) {
  var self = this;
  return RSVP.all(Object.keys(design).map(function (key) {
    var designDoc = design[key];
    var designName = '_design/' + key;
    var existing = existingDesigns[key] || {_id: designName};
    designDoc._id = existing._id;

    return self.getRev(key)
      .then(function (rev) {
        var docName;
        var serverRevNum = 0, localRevNum;
        var result = null;
        if (existing._rev) {
          serverRevNum = parseInt(existing._rev.split('-')[0]);
        }
        localRevNum = parseInt(rev.split('-')[0]);

        /*
         NOTE - Messing with _revs is a really bad idea.
         But as a development tool this is a special case.
         Generally development revs will outpace production revs, so we're allowing
         updates when local revs are equal or greater
         */
        if (localRevNum >= serverRevNum) {
          //fake out the rev for the equality test and update
          designDoc._rev = existing._rev;
          if (!lodash.isEqual(designDoc, existing)) {
            result = new RSVP.Promise(function (resolve, reject) {
              self.connection.insert(designDoc, designName, function (err, body) {
                if (err)reject(err);
                else {
                  resolve(body.rev);
                }
              });
            }).then(function (newRev) {
                return self.updateRev(key, newRev);
              });
          }
        } else {
          docName = '_design/' + key;
          reportError(docName, new Date(), rev, existing._rev);
        }
        return result;
      });
  }));
};

CouchDBVersioning.prototype.getRev = function (key) {
  var self = this;
  return new RSVP.Promise(function (resolve, reject) {
    fs.readFile(path.join(self.revDir, key + '.txt'), function (err, data) {
      if (err) reject(err);
      else resolve(data.toString());
    });
  });
};

CouchDBVersioning.prototype.updateRev = function (key, rev) {
  var self = this;
  return new RSVP.Promise(function (resolve, reject) {
    var filePath = path.join(self.revDir, key + '.txt');
    fs.writeFile(filePath, rev, function (err, data) {
      if (err) {
        //HEROKU deploys won't update the rev (read only dirs), so this shouldn't be fatal
        console.warn('CouchDBVersioning WARN:', new Date(), 'could not update', filePath, '. :', err);
      }
      else resolve();
    });
  });
};

CouchDBVersioning.prototype.getExistingDesigns = function () {
  var self = this;
  return new RSVP.Promise(function (resolve, reject) {
    self.connection.get('_all_docs', {startkey: '_design', endkey: '_design0', include_docs: true},
      function (err, body) {
        var existing = {};
        if (err) reject(err);
        else {
          body.rows.forEach(function (row) {
            existing[row.id.split('/')[1]] = row.doc
          });
          resolve(existing);
        }
      });
  });
};

CouchDBVersioning.prototype.updateDocument = function (destDir, fileName) {
  var self = this;
  return readFile(destDir, fileName)
    .then(function (json) {
      if ('_design.json' === fileName) {
        return self.getExistingDesigns()
          .then(function (existing) {
            return self.updateDesign(existing, json);
          });
      }
    });
};

CouchDBVersioning.prototype.updateCouch = function (destDir) {
  var self = this;
  return getFiles(destDir)
    .then(function (files) {
      return RSVP.all(files.map(function (fileName) {
        return self.updateDocument(destDir, fileName);
      }));
    });
};

CouchDBVersioning.prototype.read = function (readTree) {
  var self = this;
  return this.initPromise
    .then(function () {
      return RSVP.all([self.initConnection(), readTree(self.inputTree)]);
    }).then(function (resolutions) {
      return self.updateCouch(resolutions[1]);
    }).then(function () {
      return self.tempDir;
    }).catch(function (err) {
      console.trace('CouchDBVersioning: ERROR:', new Date(), err);
      return self.tempDir;
    });
};

CouchDBVersioning.prototype.cleanup = function () {
  this.inputTree.cleanup();
  if (this.tempDir) fs.rmdirSync(this.tempDir);
};

CouchDBVersioning.prototype.writeDir = function (filePath, json) {
  var self = this;
  return new RSVP.Promise(function (resolve, reject) {
    mkdirp(filePath, function (err, made) {
      var promises;
      if (err) reject(err);
      else {
        promises = Object.keys(json).map(function (key) {
          var val = json[key];
          var subPath = path.join(filePath, key);
          var result;
          if (val instanceof Array || typeof val !== 'object') {
            result = self.writeFile(subPath, val);
          } else {
            result = self.writeDir(subPath, val);
          }
          return result;
        });
        resolve(RSVP.all(promises));
      }
    });
  });
};

var JS_PATH = new RegExp(path.sep + '((map)|(reduce))$');
var SPECIAL_VALUES = new RegExp(path.sep + '_rev$');
var IGNORED_VALUES = new RegExp(path.sep + '_id$');
CouchDBVersioning.prototype.writeFile = function (filePath, data) {
  var elems;
  if (IGNORED_VALUES.test(filePath)) return false;
  if (SPECIAL_VALUES.test(filePath)) {
    elems = filePath.split(path.sep);
    return this.updateRev(elems[elems.length - 2], data)
  }
  return new RSVP.Promise(function (resolve, reject) {
    var extension = JS_PATH.test(filePath) ? '.js' : '.txt';
    fs.writeFile(filePath + extension, data, function (err, data) {
      if (err) reject(err);
      else resolve();
    });
  });
};

CouchDBVersioning.prototype.initExistingDesigns = function () {
  var self = this;
  return this.initConnection()
    .then(function () {
      return self.getExistingDesigns();
    }).then(function (existing) {
      return RSVP.all(Object.keys(existing).map(function (designName) {
        return self.writeDir(path.join(self.srcDir, '_design', designName), existing[designName]);
      }));
    }).catch(function (err) {
      console.trace('CouchDBVersionin: ERROR:', new Date(), err);
    });
};

module.exports = CouchDBVersioning;<|endoftext|>


<commit_before>#! /usr/bin/env node

'use strict';

var net = require('net');
var eos = require('end-of-stream');
var through = require('through2');
var allContainers = require('docker-allcontainers');
var logFactory = require('docker-loghose');
var statsFactory = require('docker-stats');
var eventsFactory = require('docker-event-log');
var minimist = require('minimist');

function parseOptions(){
  var apiKey = process.argv[2];

  if(apiKey==null){
    console.log('You must define your Logmatic.io\'s api key as a first argument.\n' +
      '> logmatic-docker [apiKey] \n' +
                '   [-a ATTR (eg myattribute="my attribute")]\n' +
                '   [-h HOSTNAME (default "api.logmatic.io")] [-p PORT (default "10514")]\n' +
                '   [--matchByImage REGEXP] [--matchByName REGEXP]\n' +
                '   [--skipByImage REGEXP] [--skipByName REGEXP]\n' +
                '   [--no-dockerEvents]\n' +
                '   [--no-stats] [-i statsInterval]\n')

    process.exit(1);
  }

  var opts = minimist(process.argv.slice(3),{
              boolean: ["debug", "stats", "dockerEvents"],
              alias: {
                attr: "a",
                host: "h",
                port: "p",
                statsinterval: 'i'
              },
              default:{
                newline: true,
                stats: true,
                dockerEvents: true,
                host: 'api.logmatic.io',
                port: '10514',
                apiKey: apiKey,
                statsinterval: 30
              }
            });

  //Parse extra attributes
  if(opts.attr){
    if(!Array.isArray(opts.attr)){
      opts.attr = [opts.attr];
    }
    var extra_attributes = {};
    for (var i in opts.attr) {
      var keyvalue = opts.attr[i].split('=');
      extra_attributes[keyvalue[0]] = keyvalue[1];
    }
    opts.attr = extra_attributes;
  }

  if(opts.debug){
    console.log("> Options:\n",opts);
  }
  return opts;
}

function merge(object, into) {
    if (!object) { return; }

    var key;
    for (key in object) {
      if (object.hasOwnProperty(key)) {
        into[key] = object[key];
    }
  }
}

function start() {
  var opts = parseOptions();

  var filter = through.obj(function(obj, enc, cb) {
    merge(opts.attr,obj);

    if (obj.line) {
      obj.message = obj.line;
      delete obj.line;
    }
    else if (obj.type) {
      obj.message = "[Docker event] host=\""+obj.host+"\" name=\""+obj.name+"\" event=\""+obj.type+"\"";
    }

    if(opts.debug){
      console.log("> Send entry:\n",obj);
    }

    this.push(opts.apiKey);
    this.push(' ');
    this.push(JSON.stringify(obj));
    this.push('\n');

    cb()
  });

  var events = allContainers(opts);
  var streamsOpened = 0;

  var loghose = logFactory(opts);
  loghose.pipe(filter);
  streamsOpened++;

  if (opts.stats) {
      var stats = statsFactory(opts);
      stats.pipe(filter);
      streamsOpened++;
  }

  if (opts.dockerEvents) {
      var dockerEvents = eventsFactory(opts);
      dockerEvents.pipe(filter);
      streamsOpened++;
  }

  pipe();

  // destroy out if all streams are destroyed
  loghose && eos(loghose, function() {
    streamsOpened--;
    streamClosed(streamsOpened);
  });
  dockerEvents && eos(dockerEvents, function() {
    streamsOpened--;
    streamClosed(streamsOpened);
  });

  return loghose;

  var out;
  var noRestart = function() {};

  function pipe() {
    if (out) {
      filter.unpipe(out);
    }

    out = net.createConnection(opts.port, opts.host);
    filter.pipe(out, { end: false });
    noRestart = eos(out, pipe);
  }

  function streamClosed(streamsOpened) {
    if (streamsOpened <= 0) {
      noRestart()
      out.destroy();
    }
  }
}

module.exports = start;

if (require.main === module) {
  start();
}
<commit_msg>add no-logs options<commit_after>#! /usr/bin/env node

'use strict';

var net = require('net');
var eos = require('end-of-stream');
var through = require('through2');
var allContainers = require('docker-allcontainers');
var logFactory = require('docker-loghose');
var statsFactory = require('docker-stats');
var eventsFactory = require('docker-event-log');
var minimist = require('minimist');

function parseOptions(){
  var apiKey = process.argv[2];

  if(apiKey==null){
    console.log('You must define your Logmatic.io\'s api key as a first argument.\n' +
      '> logmatic-docker [apiKey] \n' +
                '   [-a ATTR (eg myattribute="my attribute")]\n' +
                '   [-h HOSTNAME (default "api.logmatic.io")] [-p PORT (default "10514")]\n' +
                '   [--matchByImage REGEXP] [--matchByName REGEXP]\n' +
                '   [--skipByImage REGEXP] [--skipByName REGEXP]\n' +
                '   [--no-dockerEvents]\n' +
                '   [--no-logs]\n' +
                '   [--no-stats] [-i statsInterval]\n')

    process.exit(1);
  }

  var opts = minimist(process.argv.slice(3),{
              boolean: ["debug", "stats", "dockerEvents"],
              alias: {
                attr: "a",
                host: "h",
                port: "p",
                statsinterval: 'i'
              },
              default:{
                newline: true,
                stats: true,
                logs: true,
                dockerEvents: true,
                host: 'api.logmatic.io',
                port: '10514',
                apiKey: apiKey,
                statsinterval: 30
              }
            });

  //Parse extra attributes
  if(opts.attr){
    if(!Array.isArray(opts.attr)){
      opts.attr = [opts.attr];
    }
    var extra_attributes = {};
    for (var i in opts.attr) {
      var keyvalue = opts.attr[i].split('=');
      extra_attributes[keyvalue[0]] = keyvalue[1];
    }
    opts.attr = extra_attributes;
  }

  if(opts.debug){
    console.log("> Options:\n",opts);
  }
  return opts;
}

function merge(object, into) {
    if (!object) { return; }

    var key;
    for (key in object) {
      if (object.hasOwnProperty(key)) {
        into[key] = object[key];
    }
  }
}

function start() {
  var opts = parseOptions();

  var filter = through.obj(function(obj, enc, cb) {
    merge(opts.attr,obj);

    if (obj.line) {
      obj.message = obj.line;
      delete obj.line;
    }
    else if (obj.type) {
      obj.message = "[Docker event] host=\""+obj.host+"\" name=\""+obj.name+"\" event=\""+obj.type+"\"";
    }

    if(opts.debug){
      console.log("> Send entry:\n",obj);
    }

    this.push(opts.apiKey);
    this.push(' ');
    this.push(JSON.stringify(obj));
    this.push('\n');

    cb()
  });

  var events = allContainers(opts);
  var streamsOpened = 0;

  if (opts.logs) {
      var loghose = logFactory(opts);
      loghose.pipe(filter);
      streamsOpened++;
  }

  if (opts.stats) {
      var stats = statsFactory(opts);
      stats.pipe(filter);
      streamsOpened++;
  }

  if (opts.dockerEvents) {
      var dockerEvents = eventsFactory(opts);
      dockerEvents.pipe(filter);
      streamsOpened++;
  }

  pipe();

  // destroy out if all streams are destroyed
  loghose && eos(loghose, function() {
    streamsOpened--;
    streamClosed(streamsOpened);
  });
  dockerEvents && eos(dockerEvents, function() {
    streamsOpened--;
    streamClosed(streamsOpened);
  });

  return loghose;

  var out;
  var noRestart = function() {};

  function pipe() {
    if (out) {
      filter.unpipe(out);
    }

    out = net.createConnection(opts.port, opts.host);
    filter.pipe(out, { end: false });
    noRestart = eos(out, pipe);
  }

  function streamClosed(streamsOpened) {
    if (streamsOpened <= 0) {
      noRestart()
      out.destroy();
    }
  }
}

module.exports = start;

if (require.main === module) {
  start();
}
<|endoftext|>


<commit_before>// GET /db/{db}/mail/{id}/to/{to}
// Mail / View or preview e-mail
//
// Alpha @ 0.1.52
//
// `200 OK`,
// [Read-only](/docs/#/concept/read-only.md).
//
// Person [`{as}`](/docs/#/concept/as.md) views e-mail draft `{id}` as it will
// be sent, or has been sent, to person `{to}`.
//
// This is intended both as a **preview** tool for e-mail authors and as to
// let the recipient **view** an online version of the e-mail they received.
//
// ### Response format
//     { "status" : "preview" | "scheduled" | "sent",
//       "sent"   : <time> | null,
//       "view"   : {
//         "from"    : <string>,
//         "to"      : <string>,
//         "subject" : <string>,
//         "text"    : <string> | null,
//         "html"    : <string> | null
//       } }
// - `status` describes whether the e-mail was `"sent"`, the sending
//   was `"scheduled"` but RunOrg was too busy to send it, or the sending
//   was not requested yet and this is just a `"preview"`.
// - `sent` is the time when RunOrg sent the e-mail, only set when `status`
//   is `"sent"`.
// - `view` is the actual e-mail data, it includes:
// - `view.from` is the contents of the `From:` header.
// - `view.to` is the contents of the `To:` header.
// - `view.subject` is the contents of the `Subject:` header.
// - `view.text` is the text body of the e-mail, if provided.
// - `view.html` is the HTML body of the e-mail, if provided.
//
// When previewing an unsent e-mail, RunOrg will show the data that will be
// actually sent to the recipient if [sending](/docs/#/mail/send.js) is
// requested, with a few exceptions:
//
// - If data (the sender's or recipient's name or e-mail, or the e-mail draft
//   itself) are changed after the preview was generated.
// - The URLs in the preview version are used as they were provided in the
//   draft. URLs in the sent version contain a tracking token that is only
//   created once the e-mail is actually sent.
//

function Example(cid) {
    return {
	"from": cid,
	"subject": "Hello, world",
	"text": {
	    "script": "$0;to.label;$1;self",
	    "inline": ["Hello, ", ".\n\nPlease click on this link:\n"]
	},
	"html": {
	    "script": "$0;to.label;$1;self;$2",
	    "inline": ["Hello, ", ". Please click here"]
	},
	"audience": {},
	"urls": [],
	"self": "https://example.com/view/{id}",
	"custom": { "a": "b" }
    };
}

TEST("The response has valid return code and content type.", function(Query) {

    var db = Query.mkdb();
    var auth = Query.auth(db);

    var id = Query.post(["db/",db,"/mail"],Example(auth.id),auth).id();

    return Query.get(["db/",db,"/mail/",id,"/to/",auth.id],auth)
	.assertStatus(200).assertIsJson();

});

TEST("Template previewing shows correct data.", function(Query) {

    var db = Query.mkdb();
    var auth = Query.auth(db);

    var id = Query.post(["db/",db,"/mail"],Example(auth.id),auth).id();
    return id.then(function(id) {

	return Query.get(["db/",db,"/mail/",id,"/to/",auth.id],auth).then(function(d,s,r) {

	    var self = "https://example.com/view/" + id;
	    var expected = {
		"status": "preview",
		"sent"  : null,
		"view"  : {
		    "from"   : "test@runorg.com",
		    "to"     : "test@runorg.com",
		    "subject": "Hello, world",
		    "text"   : "Hello, test@runorg.com.\n\nPlease click on this link:\n" + self,
		    "html"   : "Hello, test@runorg.com. Please click here"
		}
	    };

	    return Assert.areEqual(expected, d);

	});

    });

});

TEST("Correct content number after sending ends.", function(Query) {

    var db = Query.mkdb();
    var auth = Query.auth(db,true,"test@runorg.com");

    var id = Query.post(["db/",db,"/mail"],Example(auth.id),auth).id();

    var count = 0;

    function loop() {

	if (count++ > 5) return Assert.fail("Mail is not being sent.");

	return Query.get(["db/",db,"/mail/",id,"/to/",auth.id],auth).then(function(d,s,r) {

	    if (d.status != "sent") return Async.sleep(1000).then(loop);

	    return db.then(function(db) {
		var expected = "https://" + document.location.host + "/db/" + db + "/link/";
		var link = d.view.text.split("\n")[3].substring(0,expected.length);
		return Assert.areEqual(expected,link);
	    });
	});

    }

    return Query.post(["db/",db,"/mail/",id,"/send"],{"group":"admin"},auth).then(loop);

});

//
//
// ### Example request
//     GET /db/0vJlA00726W/mail/0vJlA00A26W/to/0vJlA00826W?as=0vJlA00826W
//
// ### Example response
//     200 OK
//     Content-Type: application/json
//
//     { "status": "sent",
//       "sent"  : "2014-04-21T19:31:44Z",
//       "view"  : {
//         "from"   : "Victor Nicollet <vnicollet@runorg.com>",
//         "to"     : "postmaster@example.com",
//         "subject": "Greetings",
//         "text"   : null,
//         "html"   : "Hello, postmaster@example.com !"
//       } }
//
// # Errors
//
// ## Returns `404 Not Found`
// - ... if database `{db}` does not exist.

TEST("Returns 404 when database does not exist.", function(Query) {

    return Query.get("db/00000000000/mail/00000000001/to/00000000002").assertStatus(404);

});

// - ... if draft `{id}` does not exist in database `{db}`.

TEST("Returns 404 when draft does not exist.", function(Query) {

    var db = Query.mkdb();
    var auth = Query.auth(db);

    return Query.get(["db/",db,",/mail/00000000001/to/000000000002"],auth).assertStatus(404);

});

// - ... if person `{as}` does not have **view** access to draft `{id}`,
//   to ensure[absence
//   equivalence](/docs/#/concept/absence-equivalence.md). Important exception:
//   a recipient is always able to view any e-mail they received, even if they
//   cannot view the original draft.

TEST("Returns 404 when draft cannot be viewed.", function(Query) {

    var db = Query.mkdb();
    var auth = Query.auth(db);

    var id = Query.post(["db/",db,"/mail"],Example(auth.id),auth).id();
    var peon = Query.auth(db, false, "peon@runorg.com");

    return Query.get(["db/",db,"/mail/",id,"/to",peon.id],peon)
	.assertStatus(404);

});

// ## Returns `403 Forbidden`
// - ... if `{as}` and `{to}` are not the same person, and `{as}` does not have
//   **admin** access to the draft. In other words, you cannot view other people's
//   e-mail.

TEST("Returns 403 when viewing someone else's mail.", function(Query) {

    var db = Query.mkdb();
    var auth = Query.auth(db);

    var example = Example(auth.id);
    example.audience = { "view": "anyone" };

    var id = Query.post(["db/",db,"/mail"],example,auth).id();
    var peon = Query.auth(db, false, "peon@runorg.com");

    return Query.get(["db/",db,"/mail/",id,"/to/",auth.id],peon)
	.assertStatus(403);

});

// ## Returns `401 Unauthorized`
// - ... if the provided token does not match person `{as}`.

TEST("Returns 401 when token is not valid.", function(Query) {

    var db = Query.mkdb();
    return Query.get(["db/",db,"/mail/00000000001/to/00000000002"],{token:"0123456789a",id:"0123456789a"})
	.assertStatus(401);

});

// # Access restrictions
//
// Person `{as}` is the same as person `{to}`, they must have
// received draft `{id}` (viewing received mail) or have at
// least **view** access to draft `{id}` (previewing their own mail).
//
// If person `{as}` view a different person `{to}`, they must have
// **admin** access to draft `{id}`.
<commit_msg>Fix mail test<commit_after>// GET /db/{db}/mail/{id}/to/{to}
// Mail / View or preview e-mail
//
// Alpha @ 0.1.52
//
// `200 OK`,
// [Read-only](/docs/#/concept/read-only.md).
//
// Person [`{as}`](/docs/#/concept/as.md) views e-mail draft `{id}` as it will
// be sent, or has been sent, to person `{to}`.
//
// This is intended both as a **preview** tool for e-mail authors and as to
// let the recipient **view** an online version of the e-mail they received.
//
// ### Response format
//     { "status" : "preview" | "scheduled" | "sent",
//       "sent"   : <time> | null,
//       "view"   : {
//         "from"    : {
//           "email" : <string>,
//           "name"  : <string> | null
//         },
//         "to"      : {
//           "email" : <string>,
//           "name"  : <string> | null
//         },
//         "subject" : <string>,
//         "text"    : <string> | null,
//         "html"    : <string> | null
//       } }
// - `status` describes whether the e-mail was `"sent"`, the sending
//   was `"scheduled"` but RunOrg was too busy to send it, or the sending
//   was not requested yet and this is just a `"preview"`.
// - `sent` is the time when RunOrg sent the e-mail, only set when `status`
//   is `"sent"`.
// - `view` is the actual e-mail data, it includes:
// - `view.from` is the contents of the `From:` header, split into the actual
//   `email` and an optional `name`.
// - `view.to` is the contents of the `To:` header, split into the actual
//   `email` and an optional `name`.
// - `view.subject` is the contents of the `Subject:` header.
// - `view.text` is the text body of the e-mail, if provided.
// - `view.html` is the HTML body of the e-mail, if provided.
//
// When previewing an unsent e-mail, RunOrg will show the data that will be
// actually sent to the recipient if [sending](/docs/#/mail/send.js) is
// requested, with a few exceptions:
//
// - If data (the sender's or recipient's name or e-mail, or the e-mail draft
//   itself) are changed after the preview was generated.
// - The URLs in the preview version are used as they were provided in the
//   draft. URLs in the sent version contain a tracking token that is only
//   created once the e-mail is actually sent.
//

function Example(cid) {
    return {
	"from": cid,
	"subject": "Hello, world",
	"text": {
	    "script": "$0;to.label;$1;self",
	    "inline": ["Hello, ", ".\n\nPlease click on this link:\n"]
	},
	"html": {
	    "script": "$0;to.label;$1;self;$2",
	    "inline": ["Hello, ", ". Please click here"]
	},
	"audience": {},
	"urls": [],
	"self": "https://example.com/view/{id}",
	"custom": { "a": "b" }
    };
}

TEST("The response has valid return code and content type.", function(Query) {

    var db = Query.mkdb();
    var auth = Query.auth(db);

    var id = Query.post(["db/",db,"/mail"],Example(auth.id),auth).id();

    return Query.get(["db/",db,"/mail/",id,"/to/",auth.id],auth)
	.assertStatus(200).assertIsJson();

});

TEST("Template previewing shows correct data.", function(Query) {

    var db = Query.mkdb();
    var auth = Query.auth(db);

    var id = Query.post(["db/",db,"/mail"],Example(auth.id),auth).id();
    return id.then(function(id) {

	return Query.get(["db/",db,"/mail/",id,"/to/",auth.id],auth).then(function(d,s,r) {

	    var self = "https://example.com/view/" + id;
	    var expected = {
		"status": "preview",
		"sent"  : null,
		"view"  : {
		    "from"   : { "email": "test@runorg.com", "name": null },
		    "to"     : { "email": "test@runorg.com", "name": null },
		    "subject": "Hello, world",
		    "text"   : "Hello, test@runorg.com.\n\nPlease click on this link:\n" + self,
		    "html"   : "Hello, test@runorg.com. Please click here"
		}
	    };

	    return Assert.areEqual(expected, d);

	});

    });

});

TEST("Correct content number after sending ends.", function(Query) {

    var db = Query.mkdb();
    var auth = Query.auth(db,true,"test@runorg.com");

    var id = Query.post(["db/",db,"/mail"],Example(auth.id),auth).id();

    var count = 0;

    function loop() {

	if (count++ > 5) return Assert.fail("Mail is not being sent.");

	return Query.get(["db/",db,"/mail/",id,"/to/",auth.id],auth).then(function(d,s,r) {

	    if (d.status != "sent") return Async.sleep(1000).then(loop);

	    return db.then(function(db) {
		var expected = "https://" + document.location.host + "/db/" + db + "/link/";
		var link = d.view.text.split("\n")[3].substring(0,expected.length);
		return Assert.areEqual(expected,link);
	    });
	});

    }

    return Query.post(["db/",db,"/mail/",id,"/send"],{"group":"admin"},auth).then(loop);

});

//
//
// ### Example request
//     GET /db/0vJlA00726W/mail/0vJlA00A26W/to/0vJlA00826W?as=0vJlA00826W
//
// ### Example response
//     200 OK
//     Content-Type: application/json
//
//     { "status": "sent",
//       "sent"  : "2014-04-21T19:31:44Z",
//       "view"  : {
//         "from"   : "Victor Nicollet <vnicollet@runorg.com>",
//         "to"     : "postmaster@example.com",
//         "subject": "Greetings",
//         "text"   : null,
//         "html"   : "Hello, postmaster@example.com !"
//       } }
//
// # Errors
//
// ## Returns `404 Not Found`
// - ... if database `{db}` does not exist.

TEST("Returns 404 when database does not exist.", function(Query) {

    return Query.get("db/00000000000/mail/00000000001/to/00000000002").assertStatus(404);

});

// - ... if draft `{id}` does not exist in database `{db}`.

TEST("Returns 404 when draft does not exist.", function(Query) {

    var db = Query.mkdb();
    var auth = Query.auth(db);

    return Query.get(["db/",db,",/mail/00000000001/to/000000000002"],auth).assertStatus(404);

});

// - ... if person `{as}` does not have **view** access to draft `{id}`,
//   to ensure [absence
//   equivalence](/docs/#/concept/absence-equivalence.md). Important exception:
//   a recipient is always able to view any e-mail they received, even if they
//   cannot view the original draft.

TEST("Returns 404 when draft cannot be viewed.", function(Query) {

    var db = Query.mkdb();
    var auth = Query.auth(db);

    var id = Query.post(["db/",db,"/mail"],Example(auth.id),auth).id();
    var peon = Query.auth(db, false, "peon@runorg.com");

    return Query.get(["db/",db,"/mail/",id,"/to",peon.id],peon)
	.assertStatus(404);

});

// ## Returns `403 Forbidden`
// - ... if `{as}` and `{to}` are not the same person, and `{as}` does not have
//   **admin** access to the draft. In other words, you cannot view other people's
//   e-mail.

TEST("Returns 403 when viewing someone else's mail.", function(Query) {

    var db = Query.mkdb();
    var auth = Query.auth(db);

    var example = Example(auth.id);
    example.audience = { "view": "anyone" };

    var id = Query.post(["db/",db,"/mail"],example,auth).id();
    var peon = Query.auth(db, false, "peon@runorg.com");

    return Query.get(["db/",db,"/mail/",id,"/to/",auth.id],peon)
	.assertStatus(403);

});

// ## Returns `401 Unauthorized`
// - ... if the provided token does not match person `{as}`.

TEST("Returns 401 when token is not valid.", function(Query) {

    var db = Query.mkdb();
    return Query.get(["db/",db,"/mail/00000000001/to/00000000002"],{token:"0123456789a",id:"0123456789a"})
	.assertStatus(401);

});

// # Access restrictions
//
// Person `{as}` is the same as person `{to}`, they must have
// received draft `{id}` (viewing received mail) or have at
// least **view** access to draft `{id}` (previewing their own mail).
//
// If person `{as}` view a different person `{to}`, they must have
// **admin** access to draft `{id}`.
<|endoftext|>


<commit_before>'use strict';

module.exports = function (rootPath) {
    var express = require('express'),
        path = require('path'),
        fs = require('fs'),
        clc = require('cli-color'),
        notice = clc.cyan,
        config = require('./config')(rootPath, notice),
        log4js = require('log4js'),
        logging = require('./logging')(
            config.path.logs, config.nodeEnv, config.logging.maxLogSize, config.logging.backups),
        syslog = logging.syslog,
        audit = logging.audit,
        expressLog = logging.express,
        redis = require('redis'),
        redisClient = redis.createClient(config.redis.server1.port, config.redis.server1.host),
        RedisSessionStore = require('connect-redis')(express),
        sessionStore = new RedisSessionStore({
            client: redisClient,
            ttl: config.sessionMaxLife
        }),
        middleware = {},
        app = express(),
        rights = require('./rights')(config, logging),
        api = require(config.path.api)({config: config, logging: logging, rights: rights, server: {app: app}}),
        RedisSocketStore = require('socket.io/lib/stores/redis'),
        pub = redis.createClient(config.redis.server1.port, config.redis.server1.host),
        sub = redis.createClient(config.redis.server1.port, config.redis.server1.host);

    ///////////////////////////////////////////
    // config server
    ///////////////////////////////////////////

    var server;

    // setup server to https or http from config protocol
    if (config.protocol === 'https') {
        var https = require('https'),
            serverOptions = {
                key: fs.readFileSync(path.join(rootPath, 'server', 'ssl', 'ssl.key')),
                cert: fs.readFileSync(path.join(rootPath, 'server', 'ssl', 'ssl.crt'))
            };

        server = https.createServer(serverOptions, app);
    } else if (config.protocol === 'http') {
        var http = require('http');

        server = http.createServer(app);
    } else {
        var msg = 'error server config - missing or wrong protocol' + config.protocol;

        syslog.error(msg);
        throw new Error(msg);
    }

    var io = require('socket.io').listen(server);

    ///////////////////////////////////////////
    // check redis Db
    ///////////////////////////////////////////

    redisClient.on('error', function (err) {
        var msg = 'error event - ' + redisClient.host + ':' + redisClient.port + ' - ' + err;

        syslog.error(msg);
        throw new Error(msg);
    });

    ///////////////////////////////////////////
    // Middleware
    ///////////////////////////////////////////

    middleware.errorHandler = require('./middleware/errorHandler')(syslog);
    middleware.session = require('./middleware/session')(rights, config);
    middleware.context = require('./middleware/context')(rights, config);

    if (config.useRightsSystem === true) {
        middleware.auth = require('./middleware/auth')(rights, config);
    }

    ///////////////////////////////////////////
    // start server
    ///////////////////////////////////////////

    var startServer = function () {
        var startOn = 'application server has been started on: ' + config.protocol + '://' + config.host + ':' + config.port,
            startMode = 'application server has been started in ' + config.nodeEnv + ' mode';

        server.listen(app.get('port'), function () {
            syslog.info(startOn);

            if (config.nodeEnv !== 'production') {
                syslog.warn(startMode);
            } else {
                syslog.info(startMode);
                console.log(
                    notice('   info  - ') + startOn + '\n' +
                        notice('   info  - ') + startMode + '\n' +
                        notice('   info  - ') + 'write all entries to logfile...');
            }
        });
    };

    ///////////////////////////////////////////
    // View Engine EJS
    ///////////////////////////////////////////

    // view engine for application
    app.engine('.html', require('ejs').__express);

    ///////////////////////////////////////////
    // Init all app controllers
    ///////////////////////////////////////////

    var controllers = api.initAppControllers();

    ///////////////////////////////////////////
    // Express
    ///////////////////////////////////////////

    // express default config
    var expressConfig = function () {
        app.set('port', config.port);
        app.set('views', config.path.dist + '/app');
        app.set('view engine', 'html');

        // logging
        if (config.nodeEnv === 'production') {
            app.use(log4js.connectLogger(expressLog, {level: log4js.levels[config.logging.express]}));
        } else {
            app.use(log4js.connectLogger(expressLog, {level: log4js.levels[config.logging.express], format: ':method :url :status'}));
        }

        app.use(express.favicon(config.path.public + '/favicon.ico'));
        app.use(express.compress());
        app.use(express.static(config.path.public));
        //app.use(express.bodyParser());
        app.use(express.json());
        app.use(express.urlencoded());
        app.use(express.methodOverride());
        app.use(express.cookieParser());

        // Session has session cookie with expires:false.
        // Session is only destroyed when the user closes the browser.
        // Redis ttl is maximal lifetime of session, after this time session is destroyed
        // Middleware checks the inactive period of the session by user.
        // When this period is over, the session destroyed.
        app.use(express.session({
            key: config.sessionKey,
            store: sessionStore,
            secret: config.sessionSecret,
            cookie: {expires: false}
        }));

        // routing
        app.use(app.router);

        // Catch all rest api requests and forward to controller
        app.use('/api', function (req, res) {
            // remove trailing slash
            var route = req.url.substring(1);
            var parsedRoute = api.parseRoute(route);

            // check if user has access to route
            if (rights.userHasAccessTo(req.session.user, route)) {
                var data = req.body;

                // add user object from session to data object
                data.user = req.session.user;

                if (controllers[parsedRoute.controllerFullPath]) {
                    controllers[parsedRoute.controllerFullPath][parsedRoute.action](data, function (error, result) {
                        res.send({error: api.parseError(error, data), data: result});
                    });
                } else {
                    res.send(403, 'Wrong url');
                }
            } else {
                res.send(403, 'Access denied.');
            }
        });

        // catch all unresolved routes
        app.use(function (req, res) {
            middleware.session.checkSession(req, res, function () {
                middleware.context.index(req, res);
                res.render('index');
            });
        });
    };

    // express production config
    var expressProductionConfig = function () {
        // error handling middleware
        app.use(middleware.errorHandler.productionHandler);
    };

    // express development config
    var expressDevelopmentConfig = function () {
        // error handling middleware
        app.use(middleware.errorHandler.developmentHandler);
    };

    // express configure
    app.configure(expressConfig);
    app.configure('development', expressDevelopmentConfig);
    app.configure('production', expressProductionConfig);

    ///////////////////////////////////////////
    // default routes
    ///////////////////////////////////////////

    if (config.useRightsSystem === true) {
        // login middleware
        app.post('/api/auth/login', middleware.auth.login);

        // logout middleware
        app.get('/api/auth/logout', middleware.auth.logout);
    }

    // session api
    app.post('/api/session/setActivity', middleware.session.setActivity);
    app.post('/api/session/getLastActivity', middleware.session.getLastActivity);
    app.post('/api/session/getData', middleware.session.getData);
    app.post('/api/session/setData', middleware.session.setData);
    app.post('/api/session/deleteData', middleware.session.deleteData);

    app.get('/*/*.md', function (req, res) {

        var mdPath = path.join(rootPath, 'client', 'app', req.url);
        fs.readFile(mdPath, 'utf8', function (err, data) {
            if (err) {
                res.json(200, { markdown: err});
            }
            res.json(200, { markdown: data});
        });
    });

    app.get('/api/lxNav/getNavData', function (req, res) {
        res.json(200, { data: req.session.navigation});
    });

    ///////////////////////////////////////////
    // Socket.IO
    ///////////////////////////////////////////

    // socket.io log levels mapping
    var socketLogLevel = {
        FATAL: 0,
        ERROR: 0,
        WARN: 1,
        INFO: 2,
        DEBUG: 3,
        TRACE: 3
    };

    // default config
    io.configure(function () {
        // set socket.io redis store
        io.set('store', new RedisSocketStore({
            redisPub: pub,
            redisSub: sub,
            redisClient: redisClient
        }));

        // Transport
        io.set('transports',
            [
                'websocket'
                //'htmlfile',
                //'xhr-polling',
                //'jsonp-polling'
            ]
        );

        io.set('authorization', function (data, callback) {
            // check if cookie exists
            if (!data.headers.cookie) {
                callback(null, false);
            } else {
                // parse cookie in sessionId
                var signedCookies = require('express/node_modules/cookie').parse(data.headers.cookie),
                    sessionId = require('express/node_modules/connect/lib/utils')
                        .parseSignedCookies(signedCookies, config.sessionSecret)['baboon.sid'];

                // get session from redis session store
                sessionStore.get(sessionId, function (err, session) {
                    if (err || !session) {
                        callback(null, false);
                    } else {
                        data.session = session;
                        data.session.sessionID = sessionId;
                        callback(null, true);
                    }
                });
            }
        });
    });

    // production config
    io.configure('production', function () {
        io.enable('browser client minification');  // send minified client
        io.enable('browser client etag');          // apply etag caching logic based on version number
        io.enable('browser client gzip');          // gzip the file
        io.set('log level', socketLogLevel[config.logging.socket]);
    });

    // development config
    io.configure('development', function () {
        io.set('log level', socketLogLevel[config.logging.socket]);
    });

    var socketApi = api.socket({config: config, logging: logging, rights: rights, server: {app: app}});

    // socket connection event
    io.sockets.on('connection', function (socket) {
        var session = socket.handshake.session;

        // save socketId in session
        session.socketID = socket.id;

        syslog.info('socket: client connected');
        syslog.info('socket: {socketId: %s, username: %s, sessionID: %s }', socket.id, ((session.user || {}).name || 'no user'), session.sessionID);

        socket.on('disconnect', function () {
            syslog.info('socket: %s disconnected', socket.id);
        });

        // register socket events based on user acl
        rights.getAclObj((session.user || {}).acl, function (error, result) {
            if (error) {
                syslog.error('rights: Error loading acl for user %s (%s)', (session.user || {}).name, (session.user || {})._id);
                return;
            }

            api.registerSocketEvents(socket, result, controllers, session.user);
        });
    });

    ///////////////////////////////////////////
    // Baboon API
    ///////////////////////////////////////////

    return {
        middleware: middleware,
        config: config,
        rights: rights,
        server: {
            start: startServer,
            sio: io,
            app: app
        },
        logging: {
            audit: audit,
            syslog: syslog
        },
        api: {
            socket: socketApi
        }
    };
};<commit_msg>fix(logging): set log level of syslog logger to the value defined in the app.conf.json<commit_after>'use strict';

module.exports = function (rootPath) {
    var express = require('express'),
        path = require('path'),
        fs = require('fs'),
        clc = require('cli-color'),
        notice = clc.cyan,
        config = require('./config')(rootPath, notice),
        log4js = require('log4js'),
        logging = require('./logging')(
            config.path.logs, config.nodeEnv, config.logging.maxLogSize, config.logging.backups),
        syslog = logging.syslog,
        audit = logging.audit,
        expressLog = logging.express,
        redis = require('redis'),
        redisClient = redis.createClient(config.redis.server1.port, config.redis.server1.host),
        RedisSessionStore = require('connect-redis')(express),
        sessionStore = new RedisSessionStore({
            client: redisClient,
            ttl: config.sessionMaxLife
        }),
        middleware = {},
        app = express(),
        rights = require('./rights')(config, logging),
        api = require(config.path.api)({config: config, logging: logging, rights: rights, server: {app: app}}),
        RedisSocketStore = require('socket.io/lib/stores/redis'),
        pub = redis.createClient(config.redis.server1.port, config.redis.server1.host),
        sub = redis.createClient(config.redis.server1.port, config.redis.server1.host);

    ///////////////////////////////////////////
    // config server
    ///////////////////////////////////////////

    // set log level for syslog
    syslog.setLevel(config.logging.syslog);

    var server;

    // setup server to https or http from config protocol
    if (config.protocol === 'https') {
        var https = require('https'),
            serverOptions = {
                key: fs.readFileSync(path.join(rootPath, 'server', 'ssl', 'ssl.key')),
                cert: fs.readFileSync(path.join(rootPath, 'server', 'ssl', 'ssl.crt'))
            };

        server = https.createServer(serverOptions, app);
    } else if (config.protocol === 'http') {
        var http = require('http');

        server = http.createServer(app);
    } else {
        var msg = 'error server config - missing or wrong protocol' + config.protocol;

        syslog.error(msg);
        throw new Error(msg);
    }

    var io = require('socket.io').listen(server);

    ///////////////////////////////////////////
    // check redis Db
    ///////////////////////////////////////////

    redisClient.on('error', function (err) {
        var msg = 'error event - ' + redisClient.host + ':' + redisClient.port + ' - ' + err;

        syslog.error(msg);
        throw new Error(msg);
    });

    ///////////////////////////////////////////
    // Middleware
    ///////////////////////////////////////////

    middleware.errorHandler = require('./middleware/errorHandler')(syslog);
    middleware.session = require('./middleware/session')(rights, config);
    middleware.context = require('./middleware/context')(rights, config);

    if (config.useRightsSystem === true) {
        middleware.auth = require('./middleware/auth')(rights, config);
    }

    ///////////////////////////////////////////
    // start server
    ///////////////////////////////////////////

    var startServer = function () {
        var startOn = 'application server has been started on: ' + config.protocol + '://' + config.host + ':' + config.port,
            startMode = 'application server has been started in ' + config.nodeEnv + ' mode';

        server.listen(app.get('port'), function () {
            syslog.info(startOn);

            if (config.nodeEnv !== 'production') {
                syslog.warn(startMode);
            } else {
                syslog.info(startMode);
                console.log(
                    notice('   info  - ') + startOn + '\n' +
                        notice('   info  - ') + startMode + '\n' +
                        notice('   info  - ') + 'write all entries to logfile...');
            }
        });
    };

    ///////////////////////////////////////////
    // View Engine EJS
    ///////////////////////////////////////////

    // view engine for application
    app.engine('.html', require('ejs').__express);

    ///////////////////////////////////////////
    // Init all app controllers
    ///////////////////////////////////////////

    var controllers = api.initAppControllers();

    ///////////////////////////////////////////
    // Express
    ///////////////////////////////////////////

    // express default config
    var expressConfig = function () {
        app.set('port', config.port);
        app.set('views', config.path.dist + '/app');
        app.set('view engine', 'html');

        // logging
        if (config.nodeEnv === 'production') {
            app.use(log4js.connectLogger(expressLog, {level: log4js.levels[config.logging.express]}));
        } else {
            app.use(log4js.connectLogger(expressLog, {level: log4js.levels[config.logging.express], format: ':method :url :status'}));
        }

        app.use(express.favicon(config.path.public + '/favicon.ico'));
        app.use(express.compress());
        app.use(express.static(config.path.public));
        //app.use(express.bodyParser());
        app.use(express.json());
        app.use(express.urlencoded());
        app.use(express.methodOverride());
        app.use(express.cookieParser());

        // Session has session cookie with expires:false.
        // Session is only destroyed when the user closes the browser.
        // Redis ttl is maximal lifetime of session, after this time session is destroyed
        // Middleware checks the inactive period of the session by user.
        // When this period is over, the session destroyed.
        app.use(express.session({
            key: config.sessionKey,
            store: sessionStore,
            secret: config.sessionSecret,
            cookie: {expires: false}
        }));

        // routing
        app.use(app.router);

        // Catch all rest api requests and forward to controller
        app.use('/api', function (req, res) {
            // remove trailing slash
            var route = req.url.substring(1);
            var parsedRoute = api.parseRoute(route);

            // check if user has access to route
            if (rights.userHasAccessTo(req.session.user, route)) {
                var data = req.body;

                // add user object from session to data object
                data.user = req.session.user;

                if (controllers[parsedRoute.controllerFullPath]) {
                    controllers[parsedRoute.controllerFullPath][parsedRoute.action](data, function (error, result) {
                        res.send({error: api.parseError(error, data), data: result});
                    });
                } else {
                    res.send(403, 'Wrong url');
                }
            } else {
                res.send(403, 'Access denied.');
            }
        });

        // catch all unresolved routes
        app.use(function (req, res) {
            middleware.session.checkSession(req, res, function () {
                middleware.context.index(req, res);
                res.render('index');
            });
        });
    };

    // express production config
    var expressProductionConfig = function () {
        // error handling middleware
        app.use(middleware.errorHandler.productionHandler);
    };

    // express development config
    var expressDevelopmentConfig = function () {
        // error handling middleware
        app.use(middleware.errorHandler.developmentHandler);
    };

    // express configure
    app.configure(expressConfig);
    app.configure('development', expressDevelopmentConfig);
    app.configure('production', expressProductionConfig);

    ///////////////////////////////////////////
    // default routes
    ///////////////////////////////////////////

    if (config.useRightsSystem === true) {
        // login middleware
        app.post('/api/auth/login', middleware.auth.login);

        // logout middleware
        app.get('/api/auth/logout', middleware.auth.logout);
    }

    // session api
    app.post('/api/session/setActivity', middleware.session.setActivity);
    app.post('/api/session/getLastActivity', middleware.session.getLastActivity);
    app.post('/api/session/getData', middleware.session.getData);
    app.post('/api/session/setData', middleware.session.setData);
    app.post('/api/session/deleteData', middleware.session.deleteData);

    app.get('/*/*.md', function (req, res) {

        var mdPath = path.join(rootPath, 'client', 'app', req.url);
        fs.readFile(mdPath, 'utf8', function (err, data) {
            if (err) {
                res.json(200, { markdown: err});
            }
            res.json(200, { markdown: data});
        });
    });

    app.get('/api/lxNav/getNavData', function (req, res) {
        res.json(200, { data: req.session.navigation});
    });

    ///////////////////////////////////////////
    // Socket.IO
    ///////////////////////////////////////////

    // socket.io log levels mapping
    var socketLogLevel = {
        FATAL: 0,
        ERROR: 0,
        WARN: 1,
        INFO: 2,
        DEBUG: 3,
        TRACE: 3
    };

    // default config
    io.configure(function () {
        // set socket.io redis store
        io.set('store', new RedisSocketStore({
            redisPub: pub,
            redisSub: sub,
            redisClient: redisClient
        }));

        // Transport
        io.set('transports',
            [
                'websocket'
                //'htmlfile',
                //'xhr-polling',
                //'jsonp-polling'
            ]
        );

        io.set('authorization', function (data, callback) {
            // check if cookie exists
            if (!data.headers.cookie) {
                callback(null, false);
            } else {
                // parse cookie in sessionId
                var signedCookies = require('express/node_modules/cookie').parse(data.headers.cookie),
                    sessionId = require('express/node_modules/connect/lib/utils')
                        .parseSignedCookies(signedCookies, config.sessionSecret)['baboon.sid'];

                // get session from redis session store
                sessionStore.get(sessionId, function (err, session) {
                    if (err || !session) {
                        callback(null, false);
                    } else {
                        data.session = session;
                        data.session.sessionID = sessionId;
                        callback(null, true);
                    }
                });
            }
        });
    });

    // production config
    io.configure('production', function () {
        io.enable('browser client minification');  // send minified client
        io.enable('browser client etag');          // apply etag caching logic based on version number
        io.enable('browser client gzip');          // gzip the file
        io.set('log level', socketLogLevel[config.logging.socket]);
    });

    // development config
    io.configure('development', function () {
        io.set('log level', socketLogLevel[config.logging.socket]);
    });

    var socketApi = api.socket({config: config, logging: logging, rights: rights, server: {app: app}});

    // socket connection event
    io.sockets.on('connection', function (socket) {
        var session = socket.handshake.session;

        // save socketId in session
        session.socketID = socket.id;

        syslog.info('socket: client connected');
        syslog.info('socket: {socketId: %s, username: %s, sessionID: %s }', socket.id, ((session.user || {}).name || 'no user'), session.sessionID);

        socket.on('disconnect', function () {
            syslog.info('socket: %s disconnected', socket.id);
        });

        // register socket events based on user acl
        rights.getAclObj((session.user || {}).acl, function (error, result) {
            if (error) {
                syslog.error('rights: Error loading acl for user %s (%s)', (session.user || {}).name, (session.user || {})._id);
                return;
            }

            api.registerSocketEvents(socket, result, controllers, session.user);
        });
    });

    ///////////////////////////////////////////
    // Baboon API
    ///////////////////////////////////////////

    return {
        middleware: middleware,
        config: config,
        rights: rights,
        server: {
            start: startServer,
            sio: io,
            app: app
        },
        logging: {
            audit: audit,
            syslog: syslog
        },
        api: {
            socket: socketApi
        }
    };
};<|endoftext|>


<commit_before>var Color = (function () {
  /**
   * Constructs a 256-bit color that can be displayed in a pixel, given three primary color components.
   * @param {number=0} red a non-negative integer ≤ 255; the red   component of this color
   * @param {number=0} grn a non-negative integer ≤ 255; the green component of this color
   * @param {number=0} blu a non-negative integer ≤ 255; the blue  component of this color
   */
  function Color(red, grn, blu) {
    var self = this

    self.red   = +red || 0
    self.green = +grn || 0
    self.blue  = +blu || 0

    /**
     * The HSV-space hue of this color, or what "color" this color is.
     */
    self.hsv_hue = (function () {
      return 0 // FIXME
    })()
    /**
     * The vividness of this color. A lower saturation means the color is closer to white,
     * a higher saturation means the color is more true to its hue.
     */
    self.hsv_sat = (function () {
      return 0 // FIXME
    })()
    /**
     * The brightness of this color. A lower value means the color is closer to black, a higher
     * value means the color is more true to its hue.
     * The HSV-space value ("brightness") of this color is equivalent to the ratio of the
     * brightest RGB-component’s value to 255, as a percentage.
     */
    self.hsv_val = (function () {
      return Math.max(self.red, self.green, self.blue) / 255
    })()
    /**
     * The Hue of this color. Identical to `this.hsv_hue`.
     */
    self.hsl_hue = (function () {
      return self.hsv_hue
    })()
    /**
     * The amount of "color" in the color. A lower saturation means the color is more grayer,
     * a higher saturation means the color is more colorful.
     */
    self.hsl_sat = (function () {
      return 0 // FIXME
    })();
    /**
     * How "white" or "black" the color is. A lower luminosity means the color is closer to black,
     * a higher luminosity means the color is closer to white.
     */
    self.hsl_lum = (function () {
      return 0 // FIXME
    })()
  }

  /**
   * Returns a new color that is the complement of this color.
   * The complement of a color is the difference between that color and white (#fff).
   * @return a new Color object that corresponds to this color’s complement
   */
  Color.prototype.complement = function complement() {
    return new Color(255 - this.red, 255 - this.green, 255 - this.blue)
  }

  /**
   * Returns a new color that is the inverse of this color.
   * The inverse of a color is that color with a hue rotation of 180 degrees.
   * @return a new Color object that corresponds to this color’s inverse
   */
  Color.prototype.invert = function invert() {
    var newhue = (this.hsv_hue + 180) % 360
    return Color.newColorHSV(newhue, this.hsv_sat, this.hsv_val)
  }

  /**
   * Makes a new color that is a brighter version of this color by a percentage.
   * 1.0 corresponds to making it completely white (#fff), and 0% keeps this color the same.
   * A negative parameter will darken this color (see `this.darken(p)`).
   * @param {number} p must be between -1.0 and 1.0; the percentage by which to lighten this color
   * @return a new Color object that corresponds to this color brightened by a percentage `p`
   */
  Color.prototype.brighten = function brighten(p) {
    // return Color.newColorHSL(this.hsl_hue, this.hsl_sat, this.hsl_val + p)
  }
  /**
   * Makes a new color that is a darker version of this color by a percentage.
   * 1.0 corresponds to making it completely black (#000), abd 0% keeps this color the same.
   * A negative parameter will lighten this color (see `this.brighten(p)`).
   * @param {number} p must be between -1.0 and 1.0; the percentage by which to darken this color
   * @return a new Color object that corresponds to this color darkened by a percentage `p`
   */
  Color.prototype.darken = function darken(p) {
    // return Color.newColorHSL(this.hsl_hue, this.hsl_sat, this.hsl_val - p)
    // return this.brighten(-p)
  }

  /**
   * Mixes (averages) another color with this color, with a given weight favoring that color.
   * If `w == 0.0`, this method will return exactly this color.
   * `w == 1.0` will return exactly the other color.
   * `w == 0.5` (default if omitted) will result in a perfectly even mix.
   * Note that `color1.mix(color2, w)` will return the same result as `color2.mix(color1, 1-w)`.
   * @param {Color} $color the second color
   * @param {number=0.5} w between 0.0 and 1.0; the weight favoring the other color
   * @return a mix of the two given colors
   */
  Color.prototype.mix = function mix($color, w) {
    if (w !== 0) w = +w || 0.5
    function average(a, b, w) {
      return (a * (1-w)) + (b * w)
    }
    var r = Math.round(average(this.red,   $color.red,   w))
      , g = Math.round(average(this.green, $color.green, w))
      , b = Math.round(average(this.blue,  $color.blue,  w))
    return new Color(r, g, b)
  }

  /**
   * Returns the *contrast ratio* between two colors.
   * More info can be found at
   * https://www.w3.org/TR/WCAG/#contrast-ratiodef
   * @param {Color} $color the second color to check
   * @return the contrast ratio of this color with the argument
   */
  Color.prototype.contrastRatio = function contrastRatio($color) {
    function relLum(c) {
      function coef(p) {
        return (p <= 0.03928) ? p/12.92 : Math.pow((p+0.055)/1.055,2.4)
      }
      return 0.2126*coef(c.red  /255)
           + 0.7152*coef(c.green/255)
           + 0.0722*coef(c.blue /255)
    }
    var lum1 = relLum(this)
      , lum2 = relLum($color)
    return (Math.max(lum1, lum2) + 0.05) / (Math.min(lum1, lum2) + 0.05)
  }

  /**
   * Returns a string representation of this color.
   * If `space === 'hsv'`, returns `hsv(h, s, v)`
   * If `space === 'hsl'`, returns `hsl(h, s, l)`
   * If `space === 'hex'`, returns `#rrggbb`
   * If `space === 'rgb'`, or if no param is given, returns `rgb(r, g, b)`
   * @param {string='rgb'} space represents the space in which this color exists
   * @return a string representing this color.
   */
  Color.prototype.toString = function toString(space) {
    function toHex(n) {
      return '0123456789abcdef'.charAt((n - n % 16) / 16) + '0123456789abcdef'.charAt(n % 16)
    }
    if (space === 'hex') return '#' + toHex(this.red) + toHex(this.green) + toHex(this.blue)
    if (space === 'hsv') return 'hsv(' + this.hsv_hue + ', ' + this.hsv_sat + ', ' + this.hsv_val + ')'
    if (space === 'hsl') return 'hsl(' + this.hsl_hue + ', ' + this.hsl_sat + ', ' + this.hsl_lum + ')'
                         return 'rgb(' + this.red     + ', ' + this.green   + ', ' + this.blue    + ')'
  }

  /**
   * Returns a new Color object, given a string of the form `rgb(r,g,b)` or `rgb(r, g, b)`,
   * where `r`, `g`, and `b` are decimal RGB components (in base 10, out of 255).
   * @param {string} rgb_string a string of the form `rgb(r,g,b)` or `rgb(r, g, b)`
   * @return a new Color object constructed from the given rgb string
   */
  Color.newColorRGBString = function newColorRGBString(rgb_string) {
    var splitted = rgb_string.slice(4, -1).split(',')
    return new Color(+splitted[0], +splitted[1], +splitted[2])
  }

  /**
   * Returns a new Color object, given a string of the form `#rrggbb`,
   * where `rr`, `gg`, and `bb` are hexadecimal RGB components (in base 16, out of ff, lowercase).
   * The `#` must be included.
   * @param {string} hex_string a string of the form `#rrggbb` (lowercase)
   * @return a new Color object constructed from the given hex string
   */
  Color.newColorHexString = function newColorHexString(hex_string) {
    var r_hex = hex_string.slice(1,3)
      , g_hex = hex_string.slice(3,5)
      , b_hex = hex_string.slice(5,7)
    function toDec(x) {
      var tens = 0
        , ones = 0
      for (var i = 0; i < 16; i++) {
        if ('0123456789abcdef'.charAt(i) === x.slice(0,1)) tens = i*16
        if ('0123456789abcdef'.charAt(i) === x.slice(1,2)) ones = i
      }
      return tens + ones
    }
    return new Color(toDec(r_hex), toDec(g_hex), toDec(b_hex))
  }

  /**
   * Returns a new Color object, given hue, saturation, and value in HSV-space.
   *
   * Takes HSV-components as number arguments and returns a new Color object with
   * RGB-components, each a base-10 number from 0 to 255.
   *
   * Photoshop, etc. represents the saturation and "brightness" (value) values from 0 to 100,
   * however, the saturation and value are calculated from a range of 0 to 1. Make that conversion
   * before calling this method.
   *
   * Ported from the excellent java algorithm by Eugene Vishnevsky at:
   * http://www.cs.rit.edu/~ncs/color/t_convert.html
   *
   * @param {number} hue must be between 0 and 360; hue in HSV-space
   * @param {number} sat must be between 0.0 and 1.0; saturation in HSV-space
   * @param {number} val must be between 0.0 and 1.0; brightness in HSV-space
   * @return a new Color object with hsv(hue, sat, val)
   */
  Color.newColorHSV = function newColorHSV(hue, sat, val) {
    var red, grn, blu
    if (sat === 0) {
      // achromatic (gray)
      red = grn = blu = val
    } else {
      ;(function () {
        var h = hue / 60 // sector 0 to 5
          , i = Math.floor(h)
          , f = h - i // factorial part of h
          , p = val * (1 - sat)
          , q = val * (1 - sat * f)
          , t = val * (1 - sat * (1 - f))
        var cases = {
          0 : function () { red = val; grn = t;   blu = p;   }
        , 1 : function () { red = q;   grn = val; blu = p;   }
        , 2 : function () { red = p;   grn = val; blu = t;   }
        , 3 : function () { red = p;   grn = q;   blu = val; }
        , 4 : function () { red = t;   grn = p;   blu = val; }
        , 5 : function () { red = val; grn = p;   blu = q;   }
        }
        cases[i]()
      })()
    }

    red = Math.round(red * 255)
    grn = Math.round(grn * 255)
    blu = Math.round(blu * 255)

    var returned = new Color(red, grn, blu)
    returned.hsv_hue = hue
    returned.hsv_sat = sat
    returned.hsv_val = val
    return returned
  }

  /**
   * Returns a new Color object, given hue, saturation, and luminosity.
   * @param {number} hue must be between 0 and 360; same as the `hue` in HSV-space
   * @param {number} sat must be between 0.0 and 1.0; saturation in HSL-space
   * @param {number} lum must be between 0.0 and 1.0; luminosity in HSL-space
   * @return a new Color object with hsl(hue, sat, lum)
   */
  Color.newColorHSL = function newColorHSL(hue, sat, lum) {
    return new Color() // FIXME
  }

  /**
   *
   */
  /**
   * Checks the type of an argument, and converts it to a color.
   * @param  {unknown} arg any argument
   * @return {[type]}       [description]
   */
  Color.typeCheck = function typeCheck(arg) {
    if (arg.red || arg.green || arg.blue) return arg
    if (typeof arg === 'string') {
      if (arg.slice(0,1) === '#')    return Color.newColorHexString(arg)
      if (arg.slice(0,4) === 'rgb(') return Color.newColorRGBString(arg)
      return new Color()
    }
    if (typeof arg === 'number') {
      var graytone = Util.bound(arg, 0, 255)
      return new Color(+graytone, +graytone, +graytone)
    }
    return new Color()
  }

  /**
   * Mixes (averages) two colors, with a given weight favoring the first color.
   * If `w == 1.0`, this method will return `color1`. `w == 0.0`, will return `color2`.
   * `w == 0.5` (default if omitted) will result in a perfectly even mix.
   * CHANGED this function is deprecated. Use `Color.prototype.mix()` instead.
   * @param `color1` required Color object; the first color
   * @param `color2` required Color object; the second color
   * @param `w`      optional number between 0.0 and 1.0, defaults to 0.5; the weight favoring the first color
   * @return         a mix of the two given colors
   */
  Color.mix = function mix(color1, color2, w) {
    return color1.mix(color2, 1-w)
  }

  return Color
})()
<commit_msg>fix more jsdoc<commit_after>/**
 * A 256-bit color that can be displayed in a pixel, given three primary color components.
 * @type {Color}
 */
var Color = (function () {
  // CONSTRUCTOR
  /**
   * Construct a Color object.
   * @constructor
   * @param {number=0} red a non-negative integer ≤ 255; the red   component of this color
   * @param {number=0} grn a non-negative integer ≤ 255; the green component of this color
   * @param {number=0} blu a non-negative integer ≤ 255; the blue  component of this color
   */
  function Color(red, grn, blu) {
    var self = this

    self.red   = +red || 0
    self.green = +grn || 0
    self.blue  = +blu || 0

    /**
     * The HSV-space hue of this color, or what "color" this color is.
     * @type {number}
     */
    self.hsv_hue = (function () {
      return 0 // FIXME
    })()

    /**
     * The vividness of this color. A lower saturation means the color is closer to white,
     * a higher saturation means the color is more true to its hue.
     * @type {number}
     */
    self.hsv_sat = (function () {
      return 0 // FIXME
    })()

    /**
     * The brightness of this color. A lower value means the color is closer to black, a higher
     * value means the color is more true to its hue.
     * The HSV-space value ("brightness") of this color is equivalent to the ratio of the
     * brightest RGB-component’s value to 255, as a percentage.
     * @type {number}
     */
    self.hsv_val = (function () {
      return Math.max(self.red, self.green, self.blue) / 255
    })()

    /**
     * The Hue of this color. Identical to `this.hsv_hue`.
     * @type {number}
     */
    self.hsl_hue = (function () {
      return self.hsv_hue
    })()

    /**
     * The amount of "color" in the color. A lower saturation means the color is more grayer,
     * a higher saturation means the color is more colorful.
     * @type {number}
     */
    self.hsl_sat = (function () {
      return 0 // FIXME
    })();

    /**
     * How "white" or "black" the color is. A lower luminosity means the color is closer to black,
     * a higher luminosity means the color is closer to white.
     * @type {number}
     */
    self.hsl_lum = (function () {
      return 0 // FIXME
    })()
  }

  // ACCESSOR FUNCTIONS

  // METHODS
  /**
   * Return a new color that is the complement of this color.
   * The complement of a color is the difference between that color and white (#fff).
   * @return {Color} a new Color object that corresponds to this color’s complement
   */
  Color.prototype.complement = function complement() {
    return new Color(255 - this.red, 255 - this.green, 255 - this.blue)
  }

  /**
   * Return a new color that is the inverse of this color.
   * The inverse of a color is that color with a hue rotation of 180 degrees.
   * @return {Color} a new Color object that corresponds to this color’s inverse
   */
  Color.prototype.invert = function invert() {
    var newhue = (this.hsv_hue + 180) % 360
    return Color.newColorHSV(newhue, this.hsv_sat, this.hsv_val)
  }

  /**
   * Make a new color that is a brighter version of this color by a percentage.
   * 1.0 corresponds to making it completely white (#fff), and 0% keeps this color the same.
   * A negative parameter will darken this color (see `this.darken(p)`).
   * @param {number} p must be between -1.0 and 1.0; the percentage by which to lighten this color
   * @return {Color} a new Color object that corresponds to this color brightened by a percentage `p`
   */
  Color.prototype.brighten = function brighten(p) {
    // return Color.newColorHSL(this.hsl_hue, this.hsl_sat, this.hsl_val + p)
  }
  /**
   * Make a new color that is a darker version of this color by a percentage.
   * 1.0 corresponds to making it completely black (#000), abd 0% keeps this color the same.
   * A negative parameter will lighten this color (see `this.brighten(p)`).
   * @param {number} p must be between -1.0 and 1.0; the percentage by which to darken this color
   * @return {Color} a new Color object that corresponds to this color darkened by a percentage `p`
   */
  Color.prototype.darken = function darken(p) {
    // return Color.newColorHSL(this.hsl_hue, this.hsl_sat, this.hsl_val - p)
    // return this.brighten(-p)
  }

  /**
   * Mix (average) another color with this color, with a given weight favoring that color.
   * If `w == 0.0`, return exactly this color.
   * `w == 1.0` return exactly the other color.
   * `w == 0.5` (default if omitted) return a perfectly even mix.
   * Note that `color1.mix(color2, w)` returns the same result as `color2.mix(color1, 1-w)`.
   * @param {Color} $color the second color
   * @param {number=0.5} w between 0.0 and 1.0; the weight favoring the other color
   * @return {Color} a mix of the two given colors
   */
  Color.prototype.mix = function mix($color, w) {
    if (w !== 0) w = +w || 0.5
    function average(a, b, w) {
      return (a * (1-w)) + (b * w)
    }
    var r = Math.round(average(this.red,   $color.red,   w))
      , g = Math.round(average(this.green, $color.green, w))
      , b = Math.round(average(this.blue,  $color.blue,  w))
    return new Color(r, g, b)
  }

  /**
   * Return the *contrast ratio* between two colors.
   * More info can be found at
   * {@link https://www.w3.org/TR/WCAG/#contrast-ratiodef}
   * @param {Color} $color the second color to check
   * @return {number} the contrast ratio of this color with the argument
   */
  Color.prototype.contrastRatio = function contrastRatio($color) {
    /**
     * Return the relative lumance of a color.
     * @param  {Color} c a Color object
     * @return {number} the relative lumance of the color
     */
    function relLum(c) {
      /**
       * A helper function.
       * @param  {number} p a decimal representation of an rgb component of a color
       * @return {number} the output of some mathematical function of `p`
       */
      function coef(p) {
        return (p <= 0.03928) ? p/12.92 : Math.pow((p+0.055)/1.055,2.4)
      }
      return 0.2126*coef(c.red  /255)
           + 0.7152*coef(c.green/255)
           + 0.0722*coef(c.blue /255)
    }
    var lum1 = relLum(this)
      , lum2 = relLum($color)
    return (Math.max(lum1, lum2) + 0.05) / (Math.min(lum1, lum2) + 0.05)
  }

  /**
   * Return a string representation of this color.
   * If `space === 'hsv'`, return `hsv(h, s, v)`
   * If `space === 'hsl'`, return `hsl(h, s, l)`
   * If `space === 'hex'`, return `#rrggbb`
   * If `space === 'rgb'` (default), return `rgb(r, g, b)`
   * @param {string='rgb'} space represents the space in which this color exists
   * @return {string} a string representing this color.
   */
  Color.prototype.toString = function toString(space) {
    /**
     * Converts a decimal number to a hexadecimal number, as a string.
     * @param  {number} n a number in base 10
     * @return {string} a number in base 16
     */
    function toHex(n) {
      return '0123456789abcdef'.charAt((n - n % 16) / 16) + '0123456789abcdef'.charAt(n % 16)
    }
    if (space === 'hex') return '#' + toHex(this.red) + toHex(this.green) + toHex(this.blue)
    if (space === 'hsv') return 'hsv(' + this.hsv_hue + ', ' + this.hsv_sat + ', ' + this.hsv_val + ')'
    if (space === 'hsl') return 'hsl(' + this.hsl_hue + ', ' + this.hsl_sat + ', ' + this.hsl_lum + ')'
                         return 'rgb(' + this.red     + ', ' + this.green   + ', ' + this.blue    + ')'
  }

  // STATIC MEMBERS
  /**
   * Return a new Color object, given a string of the form `rgb(r,g,b)` or `rgb(r, g, b)`,
   * where `r`, `g`, and `b` are decimal RGB components (in base 10, out of 255).
   * @param {string} rgb_string a string of the form `rgb(r,g,b)` or `rgb(r, g, b)`
   * @return {Color} a new Color object constructed from the given rgb string
   */
  Color.newColorRGBString = function newColorRGBString(rgb_string) {
    var splitted = rgb_string.slice(4, -1).split(',')
    return new Color(+splitted[0], +splitted[1], +splitted[2])
  }

  /**
   * Return a new Color object, given a string of the form `#rrggbb`,
   * where `rr`, `gg`, and `bb` are hexadecimal RGB components (in base 16, out of ff, lowercase).
   * The `#` must be included.
   * @param {string} hex_string a string of the form `#rrggbb` (lowercase)
   * @return {Color} a new Color object constructed from the given hex string
   */
  Color.newColorHexString = function newColorHexString(hex_string) {
    var r_hex = hex_string.slice(1,3)
      , g_hex = hex_string.slice(3,5)
      , b_hex = hex_string.slice(5,7)
    /**
     * Converts a hexadecimal number (as a string) to a decimal number.
     * @param  {string} n a number in base 16
     * @return {number} a number in base 10
     */
    function toDec(x) {
      var tens = 0
        , ones = 0
      for (var i = 0; i < 16; i++) {
        if ('0123456789abcdef'.charAt(i) === x.slice(0,1)) tens = i*16
        if ('0123456789abcdef'.charAt(i) === x.slice(1,2)) ones = i
      }
      return tens + ones
    }
    return new Color(toDec(r_hex), toDec(g_hex), toDec(b_hex))
  }

  /**
   * Return a new Color object, given hue, saturation, and value in HSV-space.
   *
   * Takes HSV-components as number arguments and returns a new Color object with
   * RGB-components, each a base-10 number from 0 to 255.
   *
   * Photoshop, etc. represents the saturation and "brightness" (value) values from 0 to 100,
   * however, the saturation and value are calculated from a range of 0 to 1. Make that conversion
   * before calling this method.
   *
   * Ported from the excellent java algorithm by Eugene Vishnevsky at:
   * http://www.cs.rit.edu/~ncs/color/t_convert.html
   *
   * @param {number} hue must be between 0 and 360; hue in HSV-space
   * @param {number} sat must be between 0.0 and 1.0; saturation in HSV-space
   * @param {number} val must be between 0.0 and 1.0; brightness in HSV-space
   * @return {Color} a new Color object with hsv(hue, sat, val)
   */
  Color.newColorHSV = function newColorHSV(hue, sat, val) {
    var red, grn, blu
    if (sat === 0) {
      // achromatic (gray)
      red = grn = blu = val
    } else {
      ;(function () {
        var h = hue / 60 // sector 0 to 5
          , i = Math.floor(h)
          , f = h - i // factorial part of h
          , p = val * (1 - sat)
          , q = val * (1 - sat * f)
          , t = val * (1 - sat * (1 - f))
        var cases = {
          0 : function () { red = val; grn = t;   blu = p;   }
        , 1 : function () { red = q;   grn = val; blu = p;   }
        , 2 : function () { red = p;   grn = val; blu = t;   }
        , 3 : function () { red = p;   grn = q;   blu = val; }
        , 4 : function () { red = t;   grn = p;   blu = val; }
        , 5 : function () { red = val; grn = p;   blu = q;   }
        }
        cases[i]()
      })()
    }

    red = Math.round(red * 255)
    grn = Math.round(grn * 255)
    blu = Math.round(blu * 255)

    var returned = new Color(red, grn, blu)
    returned.hsv_hue = hue
    returned.hsv_sat = sat
    returned.hsv_val = val
    return returned
  }

  /**
   * Return a new Color object, given hue, saturation, and luminosity.
   * @param {number} hue must be between 0 and 360; same as the `hue` in HSV-space
   * @param {number} sat must be between 0.0 and 1.0; saturation in HSL-space
   * @param {number} lum must be between 0.0 and 1.0; luminosity in HSL-space
   * @return {Color} a new Color object with hsl(hue, sat, lum)
   */
  Color.newColorHSL = function newColorHSL(hue, sat, lum) {
    return new Color() // FIXME
  }

  /**
   * Checks the type of an argument, and converts it to a color.
   * @param  {unknown} arg any argument
   * @return {Color} a new Color object constructed from the given argument
   */
  Color.typeCheck = function typeCheck(arg) {
    if (arg.red || arg.green || arg.blue) return arg
    if (typeof arg === 'string') {
      if (arg.slice(0,1) === '#')    return Color.newColorHexString(arg)
      if (arg.slice(0,4) === 'rgb(') return Color.newColorRGBString(arg)
      return new Color()
    }
    if (typeof arg === 'number') {
      var graytone = Util.bound(arg, 0, 255)
      return new Color(+graytone, +graytone, +graytone)
    }
    return new Color()
  }

  /**
   * Mixes (averages) two colors, with a given weight favoring the first color.
   * If `w == 1.0`, this method will return `color1`. `w == 0.0`, will return `color2`.
   * `w == 0.5` (default if omitted) will result in a perfectly even mix.
   * CHANGED this function is deprecated. Use `Color.prototype.mix()` instead.
   * @param `color1` required Color object; the first color
   * @param `color2` required Color object; the second color
   * @param `w`      optional number between 0.0 and 1.0, defaults to 0.5; the weight favoring the first color
   * @return         a mix of the two given colors
   */
  Color.mix = function mix(color1, color2, w) {
    return color1.mix(color2, 1-w)
  }

  return Color
})()
<|endoftext|>