<commit_before>'use strict';

/**!
 * [square]
 * @copyright (c) 2012 observe.it (observe.it) <opensource@observe.it>
 * MIT Licensed
 */

/**
 * Native modules.
 */
var spawn = require('child_process').spawn
  , cluster = require('cluster')
  , zlib = require('zlib')
  , path = require('path')
  , os = require('os');

/**
 * Third party modules.
 */
var canihaz = require('canihaz')('square')
  , request = require('request')
  , async = require('async');

/**
 * A fork received a new task to process.
 *
 * Task:
 *
 * task.engines: Comma seperated list of compressors that need to be used
 * task.extension: The file extension of content
 * task.content: The actual content that needs to be processed
 * task.gzip: Calculate the size of content after gzipping it
 * task.id: The id of this task (private)
 *
 * @param {Object} task
 */
if (!cluster.isMaster) process.on('message', function message(task) {
  var engines = exports[task.extension]
    , started = Date.now()
    , durations = {};

  async.reduce(
      task.engines.split(/\,\s+?/)
    , task
    , function reduce(memo, engine, done) {
        var backup = memo.content;

        // Compile the shizzle.
        if (engine in engines) {
          durations[engine] = Date.now();

          return engines[engine](memo, function crush(err, content) {
            durations[engine] = Date.now() - durations[engine];

            if (err) {
              memo.content = backup;
              return done(err, memo);
            }

            // Update the content and process all the things again, and again and
            // again.
            memo.content = content;
            done(err, memo);
          });
        }

        // The engine does not exist, send an error response
        process.nextTick(function () {
          done(new Error('The engine '+ engine +' does not exist'), memo);
        });
      }
    , function done(err, result) {
        result = result || task || {};

        // Add some metrics about this task like:
        // - The total time it took to process this task
        // - The time each individual compiler took to compress the content
        result.duration = Date.now() - started;
        result.individual = durations;

        if (!result.gzip || err) return process.send(err, result);

        // We want to calculate the size of the generated code once it has been
        // gzipped as that might be more important to users than the actual file
        // size after minification.
        result.gzip = 0;
        zlib.gzip(result.content, function gzip(err, buff) {
          if (err) return process.send(err, result);

          result.gzip = buff.length;
          process.send(err, result);
        });
      }
  );
});

/**
 * Send a message to the workers that they need to start processing something.
 *
 *
 * @param {Object} task work for the workers
 * @param {Function} cb callback
 * @api public
 */
exports.send = function send(task, cb) {
  if (!exports.initialized) exports.initialize();

  var worker = exports.workers.pop();

  task.id = task.id || Date.now();  // use an id to tie a task to a callback
  worker.queue[task.id] = cb || function noop(){};
  worker.send.apply(worker.send, arguments);

  // Add it back at the end of the array, so we implement a round robin load
  // balancing technique for our workers.
  exports.workers.push(worker);
};

/**
 * Kill all the workers, as we are closing down.
 *
 * @api public
 */
exports.kill = function kill(workers) {
  if (!workers) workers = exports.workers;
  if (!Array.isArray(workers)) workers = [workers];

  workers.forEach(function shutdown(worker) {
    // Remove the worker from the array, so it will not be used again in the
    // `exports#send` method
    var index = exports.workers.indexOf(worker);
    if (~index) exports.workers.splice(index, 1);

    // @TODO Do we need to trigger any queued callbacks? If so with an error?
    worker.queue.length = 0;
    worker.destroy();
  });
};

/**
 * Is our cluster already initialized
 *
 * @type {Boolean}
 * @api private
 */
exports.initialized = false;

/**
 * Detect if the `java` binary is installed on this system. Supporing java
 * allows us to spawn a new childprocess for the closure compiler instead of
 * having to do HTTP requests to the closure api service.
 *
 * @type {String|Boolean}
 * @api private
 */
exports.java = false;
require('which')('java', function which(err, path) {
  if (err) return; // java is not supported on this system

  // we have found the path to the java executable, set it's path for the child
  // process
  exports.java = path;
});

/**
 * Configures a new child process spawn that is used to minify files. We use
 * new child processes for this as these kind of operations are CPU heavy and
 * would block the Node.js event loop resulting in slower conversion rates. This
 * setup also allows us to parallel convert code.
 *
 * @param {Array} args required configuration flags
 * @param {Object} config default configuration
 * @param {String} content content
 * @param {Function} fn callback
 * @api public
 */
exports.jar = function jar(args, config, content, fn) {
  var buffer = ''
    , errors = ''
    , compressor;

  // Generate the --key value options, both the key and the value should added
  // seperately to the `args` array or the child_process will chocke.
  Object.keys(config).filter(function filter(option) {
    return config[option];
  }).forEach(function format(option) {
    var bool = _.isBoolean(config[option]);

    if (!bool || config[option]) {
      args.push('--' + option);
      if (!bool) args.push(config[option]);
    }
  });

  // Spawn the shit and set the correct encoding.
  compressor = spawn(exports.java, args);
  compressor.stdout.setEncoding('utf8');
  compressor.stderr.setEncoding('utf8');

  /**
   * Buffer up the results so we can concat them once the compression is
   * finished.
   *
   * @param {Buffer} chunk
   * @api private
   */
  compressor.stdout.on('data', function data(chunk) {
    buffer += chunk;
  });

  compressor.stderr.on('data', function data(err) {
    errors += err;
  });

  /**
   * The compressor has finished can we now process the data and see if it was
   * a success.
   *
   * @param {Number} code
   * @api private
   */
  compressor.on('close', function close(code) {
    if (errors.length) return fn(new Error(errors));
    if (code !== 0) return fn(new Error('Process exited with code ' + code));
    if (!buffer.length) return fn(new Error('No data returned ' + exports.java + args));

    fn(undefined, buffer);
  });

  // Write out the content that needs to be minified
  compressor.stdin.end(content);
};

/**
 * Maintain a list of our workers. They should be ordered on usage, so we can
 * implement a round robin system by poping and pushing workers after usage.
 *
 * @type {Array}
 * @api private
 */
exports.workers = [];

/**
 * Initialize our cluster.
 *
 * @param {Number} workers
 * @api private
 */
exports.initialize = function initialize(workers) {
  var i = workers || os.cpus().length
    , fork;

  /**
   * Message handler for the workers.
   *
   * @param {Worker} worker
   * @param {Error} err
   * @param {Object} task the updated task
   * @api private
   */
  function message(worker, err, task) {
    var callback = worker.queue[task.id];

    // Kill the whole fucking system, we are in a fucked up state and should die
    // badly, so just throw something and have the process.uncaughtException
    // handle it.
    if (!callback) {
      if (err) console.error(err);
      console.error(task);
      throw new Error('Unable to process message from worker, can\'t locate the callback!');
    }

    callback(err, task);
    delete worker.queue[task.id];
  }

  while (i--) {
    // Configure the forked things
    fork = cluster.fork();
    fork.queue = [];
    fork.on('message', message.bind(message, fork));

    exports.workers.push(fork);
  }

  exports.initialized = true;
};

/**
 * The actual crushers that do the hard work inside this cluster. There are
 * couple of different crushers supported in this cluster:
 *
 * - closure: An interface to the Google Closure Compiler library, it requires
 *   the `java` binary to be installed in system, but gracefully degrades to
 *   their closure service when this is not available.
 * - jsmin: This is one of the earliest minifiers known, it's build by douglas
 *   crockford and does save transformations of the source code.
 * - uglify2: An rewrite of uglify 1, a powerful compiler for JavaScript it's
 *   almost as good as the Google Closure Compiler and in some cases even
 *   better.
 * - yui: The interface to the YUI compressor that was build upon Java. It
 *   requires Java to be installed on the users system or it will savely exit
 *   without compressing the content.
 * - yuglyif: An Yahoo fork of uglify it adds some addition features and fixes
 *   on top of the original uglify compiler.
 * - sqwish: A node.js based CSS compressor, it has the ability to combine
 *   duplicate CSS selectors as well as all the regular compilations.
 *
 * The API for each crusher is the same:
 *
 * @param {String} type the file extension they need to crush
 * @param {Object} collection the details and the data
 * @param {Function} cb error first styled callback
 * @api private
 */
exports.crushers = {
    /**
     * @see https://github.com/mishoo/UglifyJS2
     */
    uglify2: function uglify2(type, collection, cb) {
      if (type !== 'js') return cb(new Error('Type is not supported'));
    }

    /**
     * @see https://github.com/yui/yuicompressor
     */
  , yui: function yui(type, collection, cb) {
      if (!exports.java) return cb(undefined, collection.content);

      // Don't set the 'charset': 'ascii' option for the YUI compressor, it will
      // break utf-8 chars. Other compilers do require this flag, or they will
      // transform escaped utf-8 chars to real utf-8 chars.
      exports.jar(['-jar', path.join(__dirname, '../../vendor/yui.jar')], {
          'type': type
        , 'line-break': 256
        , 'verbose': false
      }, collection.content, cb);
    }

    /**
     * @see https://developers.google.com/closure/compiler
     */
  , closure: function closure(type, collection, cb) {
      if (type !== 'js') return cb(new Error('Type is not supported'));

      // Check if java is supported on this system, if not we have to use the
      // external closure compiler service to handle all the compilation tasks
      // for us.
      if (!exports.java) return request.post({
          url: 'https://closure-compiler.appspot.com/compile'
        , body: {
              output_format: 'text'                     // we only want the compiled shizzle
            , js_code: collection.content               // the code that needs to be crushed
            , compilation_level: 'SIMPLE_OPTIMIZATIONS' // compression level
            , charset: 'ascii'                          // correct the charset
            , language_in: 'ECMASCRIPT5'                // language
            , warning_level: 'QUIET'                    // stfu warnings
          }
      }, function servicecall(err, req, body) {
        if (err) return cb(err);

        // @TODO check the returned body and / or response code for possible
        // service code failures
        cb(undefined, body);
      });

      // Java is supported on this system, use that instead as it will be
      // cheaper and faster then calling the service.
      exports.jar(['-jar', path.join(__dirname, '../../vendor/closure.jar')], {
          'charset': 'ascii'
        , 'compilation_level': 'SIMPLE_OPTIMIZATIONS'
        , 'language_in': 'ECMASCRIPT5'
        , 'warning_level': 'QUIET'
        , 'jscomp_off': 'uselessCode'
        , 'summary_detail_level': 0
      }, collection.content, cb);
    }

    /**
     * @see https://github.com/yui/yuglify
     */
  , yuglify: function yuglify(type, collection, cb) {
      canihaz.yuglify(function fetch(err, yuglify) {
        if (err) return cb(err);

        yuglify[type === 'js' ? 'jsmin' : 'cssmin'](collection.content, cb);
      });
    }

    /**
     * @see https://github.com/twolfson/node-jsmin-sourcemap
     */
  , jsmin: function jsmin(type, collection, cb) {
      if (type !== 'js') return cb(new Error('Type is not supported'));

      canihaz['jsmin-sourcemap'](function fetch(err, jsmin) {
        if (err) return cb(err);

        try { cb(undefined, jsmin(collection.content).code); }
        catch (e) { return cb(e); }
      });
    }

    /**
     * @see https://github.com/Constellation/esmangle
     */
  , esmangle: function esmangle(type, collection, cb) {
      if (type !== 'js') return cb(new Error('Type is not supported'));

      canihaz.all('esprima', 'escodegen', 'esmangle', function all(err, esprima, escodegen, esmangle) {
        if (err) return cb(err);

        var tree;
        try {
          tree = esprima.parse(collection.content, { loc: true });
          tree = esmangle.optimize(tree, null, {
              destructive: true
            , directive: true
          });
          tree = esmangle.mangle(tree, {
              destructive: true
          });

          cb(undefined, escodegen.generate(tree, {
              format: {
                  renumber: true
                , hexadecimal: true
                , escapeless: true
                , compact: true
                , semicolons: false
                , parentheses: false
              }
            , directive: true
          }));
        } catch(fail) {
          cb(fail);
        }
      });
    }

    /**
     * @see https://github.com/ded/sqwish
     */
  , sqwish: function sqwish(type, collection, cb) {
      if (type !== 'css') return cb(new Error('Type is not supported'));

      canihaz.sqwish(function fetch(err, sqwish) {
        if (err) return cb(err);

        try { cb(undefined, sqwish(collection.content, true)); }
        catch (fail) { cb(fail); }
      });
    }
};

/**
 * The compressors that are able to compile JavaScript.
 *
 * @type {Object}
 * @api private
 */
exports.js = {
    uglify2: exports.crushers.uglify2.bind(exports.crushers, 'js')
  , closure: exports.crushers.closure.bind(exports.crushers, 'js')
  , yuglyif: exports.crushers.yuglyif.bind(exports.crushers, 'js')
  , jsmin: exports.crushers.jsmin.bind(exports.crushers, 'js')
  , esmangle: exports.crushers.esmangle.bind(exports.crushers, 'js')
  , yui: exports.crushers.yui.bind(exports.crushers, 'js')
};

/**
 * The compressors that are able to compile Cascading Style Sheets.
 *
 * @type {Object}
 * @api private
 */
exports.css = {
    yuglyif: exports.crushers.yuglyif.bind(exports.crushers, 'css')
  , sqwish: exports.crushers.sqwish.bind(exports.crushers, 'css')
  , yui: exports.crushers.yui.bind(exports.crushers, 'css')
};
<commit_msg>[api] Added jscrush as compiler, not yet enabled..<commit_after>'use strict';

/**!
 * [square]
 * @copyright (c) 2012 observe.it (observe.it) <opensource@observe.it>
 * MIT Licensed
 */

/**
 * Native modules.
 */
var spawn = require('child_process').spawn
  , cluster = require('cluster')
  , zlib = require('zlib')
  , path = require('path')
  , os = require('os');

/**
 * Third party modules.
 */
var canihaz = require('canihaz')('square')
  , request = require('request')
  , async = require('async');

/**
 * A fork received a new task to process.
 *
 * Task:
 *
 * task.engines: Comma seperated list of compressors that need to be used
 * task.extension: The file extension of content
 * task.content: The actual content that needs to be processed
 * task.gzip: Calculate the size of content after gzipping it
 * task.id: The id of this task (private)
 *
 * @param {Object} task
 */
if (!cluster.isMaster) process.on('message', function message(task) {
  var engines = exports[task.extension]
    , started = Date.now()
    , durations = {};

  async.reduce(
      task.engines.split(/\,\s+?/)
    , task
    , function reduce(memo, engine, done) {
        var backup = memo.content;

        // Compile the shizzle.
        if (engine in engines) {
          durations[engine] = Date.now();

          return engines[engine](memo, function crush(err, content) {
            durations[engine] = Date.now() - durations[engine];

            if (err) {
              memo.content = backup;
              return done(err, memo);
            }

            // Update the content and process all the things again, and again and
            // again.
            memo.content = content;
            done(err, memo);
          });
        }

        // The engine does not exist, send an error response
        process.nextTick(function () {
          done(new Error('The engine '+ engine +' does not exist'), memo);
        });
      }
    , function done(err, result) {
        result = result || task || {};

        // Add some metrics about this task like:
        // - The total time it took to process this task
        // - The time each individual compiler took to compress the content
        result.duration = Date.now() - started;
        result.individual = durations;

        if (!result.gzip || err) return process.send(err, result);

        // We want to calculate the size of the generated code once it has been
        // gzipped as that might be more important to users than the actual file
        // size after minification.
        result.gzip = 0;
        zlib.gzip(result.content, function gzip(err, buff) {
          if (err) return process.send(err, result);

          result.gzip = buff.length;
          process.send(err, result);
        });
      }
  );
});

/**
 * Send a message to the workers that they need to start processing something.
 *
 *
 * @param {Object} task work for the workers
 * @param {Function} cb callback
 * @api public
 */
exports.send = function send(task, cb) {
  if (!exports.initialized) exports.initialize();

  var worker = exports.workers.pop();

  task.id = task.id || Date.now();  // use an id to tie a task to a callback
  worker.queue[task.id] = cb || function noop(){};
  worker.send.apply(worker.send, arguments);

  // Add it back at the end of the array, so we implement a round robin load
  // balancing technique for our workers.
  exports.workers.push(worker);
};

/**
 * Kill all the workers, as we are closing down.
 *
 * @api public
 */
exports.kill = function kill(workers) {
  if (!workers) workers = exports.workers;
  if (!Array.isArray(workers)) workers = [workers];

  workers.forEach(function shutdown(worker) {
    // Remove the worker from the array, so it will not be used again in the
    // `exports#send` method
    var index = exports.workers.indexOf(worker);
    if (~index) exports.workers.splice(index, 1);

    // @TODO Do we need to trigger any queued callbacks? If so with an error?
    worker.queue.length = 0;
    worker.destroy();
  });
};

/**
 * Is our cluster already initialized
 *
 * @type {Boolean}
 * @api private
 */
exports.initialized = false;

/**
 * Detect if the `java` binary is installed on this system. Supporing java
 * allows us to spawn a new childprocess for the closure compiler instead of
 * having to do HTTP requests to the closure api service.
 *
 * @type {String|Boolean}
 * @api private
 */
exports.java = false;
require('which')('java', function which(err, path) {
  if (err) return; // java is not supported on this system

  // we have found the path to the java executable, set it's path for the child
  // process
  exports.java = path;
});

/**
 * Configures a new child process spawn that is used to minify files. We use
 * new child processes for this as these kind of operations are CPU heavy and
 * would block the Node.js event loop resulting in slower conversion rates. This
 * setup also allows us to parallel convert code.
 *
 * @param {Array} args required configuration flags
 * @param {Object} config default configuration
 * @param {String} content content
 * @param {Function} fn callback
 * @api public
 */
exports.jar = function jar(args, config, content, fn) {
  var buffer = ''
    , errors = ''
    , compressor;

  // Generate the --key value options, both the key and the value should added
  // seperately to the `args` array or the child_process will chocke.
  Object.keys(config).filter(function filter(option) {
    return config[option];
  }).forEach(function format(option) {
    var bool = _.isBoolean(config[option]);

    if (!bool || config[option]) {
      args.push('--' + option);
      if (!bool) args.push(config[option]);
    }
  });

  // Spawn the shit and set the correct encoding.
  compressor = spawn(exports.java, args);
  compressor.stdout.setEncoding('utf8');
  compressor.stderr.setEncoding('utf8');

  /**
   * Buffer up the results so we can concat them once the compression is
   * finished.
   *
   * @param {Buffer} chunk
   * @api private
   */
  compressor.stdout.on('data', function data(chunk) {
    buffer += chunk;
  });

  compressor.stderr.on('data', function data(err) {
    errors += err;
  });

  /**
   * The compressor has finished can we now process the data and see if it was
   * a success.
   *
   * @param {Number} code
   * @api private
   */
  compressor.on('close', function close(code) {
    if (errors.length) return fn(new Error(errors));
    if (code !== 0) return fn(new Error('Process exited with code ' + code));
    if (!buffer.length) return fn(new Error('No data returned ' + exports.java + args));

    fn(undefined, buffer);
  });

  // Write out the content that needs to be minified
  compressor.stdin.end(content);
};

/**
 * Maintain a list of our workers. They should be ordered on usage, so we can
 * implement a round robin system by poping and pushing workers after usage.
 *
 * @type {Array}
 * @api private
 */
exports.workers = [];

/**
 * Initialize our cluster.
 *
 * @param {Number} workers
 * @api private
 */
exports.initialize = function initialize(workers) {
  var i = workers || os.cpus().length
    , fork;

  /**
   * Message handler for the workers.
   *
   * @param {Worker} worker
   * @param {Error} err
   * @param {Object} task the updated task
   * @api private
   */
  function message(worker, err, task) {
    var callback = worker.queue[task.id];

    // Kill the whole fucking system, we are in a fucked up state and should die
    // badly, so just throw something and have the process.uncaughtException
    // handle it.
    if (!callback) {
      if (err) console.error(err);
      console.error(task);
      throw new Error('Unable to process message from worker, can\'t locate the callback!');
    }

    callback(err, task);
    delete worker.queue[task.id];
  }

  while (i--) {
    // Configure the forked things
    fork = cluster.fork();
    fork.queue = [];
    fork.on('message', message.bind(message, fork));

    exports.workers.push(fork);
  }

  exports.initialized = true;
};

/**
 * The actual crushers that do the hard work inside this cluster. There are
 * couple of different crushers supported in this cluster:
 *
 * - closure: An interface to the Google Closure Compiler library, it requires
 *   the `java` binary to be installed in system, but gracefully degrades to
 *   their closure service when this is not available.
 * - jsmin: This is one of the earliest minifiers known, it's build by douglas
 *   crockford and does save transformations of the source code.
 * - uglify2: An rewrite of uglify 1, a powerful compiler for JavaScript it's
 *   almost as good as the Google Closure Compiler and in some cases even
 *   better.
 * - yui: The interface to the YUI compressor that was build upon Java. It
 *   requires Java to be installed on the users system or it will savely exit
 *   without compressing the content.
 * - yuglyif: An Yahoo fork of uglify it adds some addition features and fixes
 *   on top of the original uglify compiler.
 * - sqwish: A node.js based CSS compressor, it has the ability to combine
 *   duplicate CSS selectors as well as all the regular compilations.
 *
 * The API for each crusher is the same:
 *
 * @param {String} type the file extension they need to crush
 * @param {Object} collection the details and the data
 * @param {Function} cb error first styled callback
 * @api private
 */
exports.crushers = {
    /**
     * @see https://github.com/mishoo/UglifyJS2
     */
    uglify2: function uglify2(type, collection, cb) {
      if (type !== 'js') return cb(new Error('Type is not supported'));
    }

    /**
     * @see http://www.iteral.com/jscrush
     */
  , jscrush: function jscrush(type, collection, cb) {
      if (type !== 'js') return cb(new Error('Type is not supported'));
      var compiler = jscrush.crush || (jscrush.crush = require('./jscrush'));

      try { cb(undefined, compiler(collection.content)); }
      catch (e) { cb(e); }
    }

    /**
     * @see https://github.com/yui/yuicompressor
     */
  , yui: function yui(type, collection, cb) {
      if (!exports.java) return cb(undefined, collection.content);

      // Don't set the 'charset': 'ascii' option for the YUI compressor, it will
      // break utf-8 chars. Other compilers do require this flag, or they will
      // transform escaped utf-8 chars to real utf-8 chars.
      exports.jar(['-jar', path.join(__dirname, '../../vendor/yui.jar')], {
          'type': type
        , 'line-break': 256
        , 'verbose': false
      }, collection.content, cb);
    }

    /**
     * @see https://developers.google.com/closure/compiler
     */
  , closure: function closure(type, collection, cb) {
      if (type !== 'js') return cb(new Error('Type is not supported'));

      // Check if java is supported on this system, if not we have to use the
      // external closure compiler service to handle all the compilation tasks
      // for us.
      if (!exports.java) return request.post({
          url: 'https://closure-compiler.appspot.com/compile'
        , body: {
              output_format: 'text'                     // we only want the compiled shizzle
            , js_code: collection.content               // the code that needs to be crushed
            , compilation_level: 'SIMPLE_OPTIMIZATIONS' // compression level
            , charset: 'ascii'                          // correct the charset
            , language_in: 'ECMASCRIPT5'                // language
            , warning_level: 'QUIET'                    // stfu warnings
          }
      }, function servicecall(err, req, body) {
        if (err) return cb(err);

        // @TODO check the returned body and / or response code for possible
        // service code failures
        cb(undefined, body);
      });

      // Java is supported on this system, use that instead as it will be
      // cheaper and faster then calling the service.
      exports.jar(['-jar', path.join(__dirname, '../../vendor/closure.jar')], {
          'charset': 'ascii'
        , 'compilation_level': 'SIMPLE_OPTIMIZATIONS'
        , 'language_in': 'ECMASCRIPT5'
        , 'warning_level': 'QUIET'
        , 'jscomp_off': 'uselessCode'
        , 'summary_detail_level': 0
      }, collection.content, cb);
    }

    /**
     * @see https://github.com/yui/yuglify
     */
  , yuglify: function yuglify(type, collection, cb) {
      canihaz.yuglify(function fetch(err, yuglify) {
        if (err) return cb(err);

        yuglify[type === 'js' ? 'jsmin' : 'cssmin'](collection.content, cb);
      });
    }

    /**
     * @see https://github.com/twolfson/node-jsmin-sourcemap
     */
  , jsmin: function jsmin(type, collection, cb) {
      if (type !== 'js') return cb(new Error('Type is not supported'));

      canihaz['jsmin-sourcemap'](function fetch(err, jsmin) {
        if (err) return cb(err);

        try { cb(undefined, jsmin(collection.content).code); }
        catch (e) { return cb(e); }
      });
    }

    /**
     * @see https://github.com/Constellation/esmangle
     */
  , esmangle: function esmangle(type, collection, cb) {
      if (type !== 'js') return cb(new Error('Type is not supported'));

      canihaz.all('esprima', 'escodegen', 'esmangle', function all(err, esprima, escodegen, esmangle) {
        if (err) return cb(err);

        var tree;
        try {
          tree = esprima.parse(collection.content, { loc: true });
          tree = esmangle.optimize(tree, null, {
              destructive: true
            , directive: true
          });
          tree = esmangle.mangle(tree, {
              destructive: true
          });

          cb(undefined, escodegen.generate(tree, {
              format: {
                  renumber: true
                , hexadecimal: true
                , escapeless: true
                , compact: true
                , semicolons: false
                , parentheses: false
              }
            , directive: true
          }));
        } catch(fail) {
          cb(fail);
        }
      });
    }

    /**
     * @see https://github.com/ded/sqwish
     */
  , sqwish: function sqwish(type, collection, cb) {
      if (type !== 'css') return cb(new Error('Type is not supported'));

      canihaz.sqwish(function fetch(err, sqwish) {
        if (err) return cb(err);

        try { cb(undefined, sqwish(collection.content, true)); }
        catch (fail) { cb(fail); }
      });
    }
};

/**
 * The compressors that are able to compile JavaScript.
 *
 * @type {Object}
 * @api private
 */
exports.js = {
    uglify2: exports.crushers.uglify2.bind(exports.crushers, 'js')
  , closure: exports.crushers.closure.bind(exports.crushers, 'js')
  , yuglyif: exports.crushers.yuglyif.bind(exports.crushers, 'js')
  , jsmin: exports.crushers.jsmin.bind(exports.crushers, 'js')
  , esmangle: exports.crushers.esmangle.bind(exports.crushers, 'js')
  , yui: exports.crushers.yui.bind(exports.crushers, 'js')
};

/**
 * The compressors that are able to compile Cascading Style Sheets.
 *
 * @type {Object}
 * @api private
 */
exports.css = {
    yuglyif: exports.crushers.yuglyif.bind(exports.crushers, 'css')
  , sqwish: exports.crushers.sqwish.bind(exports.crushers, 'css')
  , yui: exports.crushers.yui.bind(exports.crushers, 'css')
};
<|endoftext|>


<commit_before>const fork = require('child_process').fork;
const path = require('path');
const reactDocs = require('react-docgen');
const isString = require('lodash/isString');
const isNaN = require('lodash/isNaN');
const defaults = require('lodash/defaults');
const styleguideResolver = require('./resolver.js').default;

function ReactPlugin(options) {
  // Make sure the plugin was instantiated as a constructor, i.e. new ReactPlugin()
  if (!(this instanceof ReactPlugin)) {
    throw new Error(
      'The ReactPlugin must be instantiated with the "new" keyword, i.e. "new ReactPlugin()"\n\n'
    );
  }

  this.options = options || {};

  // The hostname option must be a string
  if (this.options.hostname && !isString(this.options.hostname)) {
    throw new Error('The "hostname" option of the ReactPlugin must be a string!\n\n');
  }

  const parsedPort = parseFloat(this.options.port);
  // The port option must be something that can be made a number
  if (this.options.port && !isNaN(parsedPort)) {
    throw new Error('The "port" option of the ReactPlugin must be a number!\n\n');
  }

  // If the port can be interpreted as a number, it must be above 0 and below 65535
  if (parsedPort < 0 || parsedPort > 65535) {
    throw new Error('The "port" must be between 0 and 65535 (inc)!\n\n');
  }

  // The variationFolderName option must be a string
  if (this.options.variationFolderName && !isString(this.options.variationFolderName)) {
    throw new Error(
      'The "variationFolderName" option of the ReactPlugin must be a string!\n\n'
    );
  }
}

/**
 * Kill a Node.js process
 */
function killProcess(proc) {
  proc.kill('SIGINT');
  process.exit();
}

/**
 * Initializes the plugin, called after the main StyleguidePlugin function above
 */
ReactPlugin.prototype.apply = function apply(compiler) {
  // Default options
  const options = defaults({}, this.options, {
    hostname: 'localhost',
    // The default port is not really used by a popular service:
    // https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers
    port: 8082,
    variationFolderName: 'variations',
  });
  const projectBasePath = compiler.options.context;
  const variationBasePath = path.join(projectBasePath, options.variationFolderName);
  options.variationBasePath = variationBasePath;

  const server = fork(path.resolve(__dirname, './server/run.js'), [
    projectBasePath, // process.argv[2]
    JSON.stringify(options), // process.argv[3]
  ]);

  // Prevent the process from exiting immediately
  process.stdin.resume();

  // When the plugin exits for any reason, kill the forked Node.js process
  // with the server. (exit = process.exit(), SIGINT = CTRL+C, uncaughtException =
  // error in the code)
  process.on('exit', killProcess.bind(null, server));
  process.on('SIGINT', killProcess.bind(null, server));
  process.on('uncaughtException', killProcess.bind(null, server));

  compiler.plugin('compilation', (compilation) => {
    // Expose the react parse result to all other styleguide plugins
    compilation.plugin(
      'styleguide-plugin-before-processing',
      (data) => {
        // eslint-disable-next-line no-param-reassign
        data.reactDocs = reactDocs.parse(data.source, styleguideResolver);
      }
    );

    // The source styleguide plugin
    compilation.plugin(
      'styleguide-plugin-processing',
      (renderToClient) => {
        renderToClient({
          name: 'react',
          frontendData: { options },
          frontendPlugin: `${require.resolve('./frontend/index.js')}`,
        });
      }
    );
  });
};

export default ReactPlugin;
<commit_msg>Fix process exit<commit_after>const fork = require('child_process').fork;
const path = require('path');
const reactDocs = require('react-docgen');
const isString = require('lodash/isString');
const isNaN = require('lodash/isNaN');
const defaults = require('lodash/defaults');
const styleguideResolver = require('./resolver.js').default;

function ReactPlugin(options) {
  // Make sure the plugin was instantiated as a constructor, i.e. new ReactPlugin()
  if (!(this instanceof ReactPlugin)) {
    throw new Error(
      'The ReactPlugin must be instantiated with the "new" keyword, i.e. "new ReactPlugin()"\n\n'
    );
  }

  this.options = options || {};

  // The hostname option must be a string
  if (this.options.hostname && !isString(this.options.hostname)) {
    throw new Error('The "hostname" option of the ReactPlugin must be a string!\n\n');
  }

  const parsedPort = parseFloat(this.options.port);
  // The port option must be something that can be made a number
  if (this.options.port && !isNaN(parsedPort)) {
    throw new Error('The "port" option of the ReactPlugin must be a number!\n\n');
  }

  // If the port can be interpreted as a number, it must be above 0 and below 65535
  if (parsedPort < 0 || parsedPort > 65535) {
    throw new Error('The "port" must be between 0 and 65535 (inc)!\n\n');
  }

  // The variationFolderName option must be a string
  if (this.options.variationFolderName && !isString(this.options.variationFolderName)) {
    throw new Error(
      'The "variationFolderName" option of the ReactPlugin must be a string!\n\n'
    );
  }
}

/**
 * Kill a Node.js process
 */
function killProcess(proc, err) {
  proc.kill('SIGINT');
  if (err) {
    console.log('Uncaught Exception...');
    console.log(err.stack);
    process.exit(1);
  } else {
    process.exit();
  }
}

/**
 * Initializes the plugin, called after the main StyleguidePlugin function above
 */
ReactPlugin.prototype.apply = function apply(compiler) {
  // Default options
  const options = defaults({}, this.options, {
    hostname: 'localhost',
    // The default port is not really used by a popular service:
    // https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers
    port: 8082,
    variationFolderName: 'variations',
  });
  const projectBasePath = compiler.options.context;
  const variationBasePath = path.join(projectBasePath, options.variationFolderName);
  options.variationBasePath = variationBasePath;

  const server = fork(path.resolve(__dirname, './server/run.js'), [
    projectBasePath, // process.argv[2]
    JSON.stringify(options), // process.argv[3]
  ]);

  // Prevent the process from exiting immediately
  process.stdin.resume();

  // When the plugin exits for any reason, kill the forked Node.js process
  // with the server. (exit = process.exit(), SIGINT = CTRL+C, uncaughtException =
  // error in the code)
  process.on('exit', killProcess.bind(null, server));
  process.on('SIGINT', killProcess.bind(null, server));
  process.on('uncaughtException', killProcess.bind(null, server));

  compiler.plugin('compilation', (compilation) => {
    // Expose the react parse result to all other styleguide plugins
    compilation.plugin(
      'styleguide-plugin-before-processing',
      (data) => {
        // eslint-disable-next-line no-param-reassign
        data.reactDocs = reactDocs.parse(data.source, styleguideResolver);
      }
    );

    // The source styleguide plugin
    compilation.plugin(
      'styleguide-plugin-processing',
      (renderToClient) => {
        renderToClient({
          name: 'react',
          frontendData: { options },
          frontendPlugin: `${require.resolve('./frontend/index.js')}`,
        });
      }
    );
  });
};

export default ReactPlugin;
<|endoftext|>


<commit_before>/******************************************************************************
Slack integration
*******************************************************************************/
'use strict';

var _ = require('underscore'),
    request = require('request'),
    logger = require('../../server/logger');

var fruum_username = 'Fruum',
    fruum_icon_emoji = ':racing_car:';

function Slack(options, instance) {

  // ---------------------------------- COMMANDS -------------------------------

  //respond to /fruum slack command
  instance.server.post('/slack/:app_id', function(req, res) {
    var app_id = req.params.app_id,
        text = req.body.text,
        token = req.body.token;
    if (!text) {
      res.send('*Fruum:* Could not understand your command');
    }
    else {
      //get app
      instance.storage.get_app(app_id, function(application) {
        if (!application) {
          res.send('*Fruum:* Invalid app_id, check your slack integration');
          return;
        }
        var app_token = application.getProperty('slack:command_token');
        if (app_token && app_token != token) {
          res.send('*Fruum:* Permission denied, check your slack integration');
          return;
        }
        //perform search
        instance.storage.search(app_id, text, function(results) {
          if (!results.length) {
            res.send('*Fruum:* No search results');
            return;
          }
          var response = 'Fruum search results for: *' + text + '*\n';
          _.each(results, function(document) {
            var link = '';
            if (document.get('type') == 'post') {
              link = application.getShareURL(document.get('parent'));
            }
            else {
              link = application.getShareURL(document.get('id'));
            }
            response += '<' + link + '|' + document.get('header') + '>\n';
          });
          res.send(response);
        });
      });
    }
  });

  // ---------------------------------- WEBHOOKS -------------------------------

  //Report to slack when a new document has been added
  this.add = function(payload, callback) {
    //do not block operation
    callback(null, payload);
    //check if we have an outgoing webhook registered in the app
    var document = payload.document,
        app_id = payload.app_id;
    //skip chat messages
    if (document.get('parent_type') == 'channel') return;
    //get application instance
    instance.storage.get_app(app_id, function(application) {
      if (!application) return;
      var webhook = application.getProperty('slack:incoming_webhook');
      if (!webhook) return;
      var pretext = document.get('user_displayname') || document.get('user_username'),
          link = '';
      if (document.get('type') == 'post') {
        link = application.getShareURL(document.get('parent'));
        pretext += ' replied to ' + document.get('parent_type');
      }
      else {
        link = application.getShareURL(document.get('id'));
        pretext += ' created new ' + document.get('type');
      }
      request({
        url: webhook,
        method: 'POST',
        json: {
          username: fruum_username,
          icon_emoji: fruum_icon_emoji,
          attachments: [{
            pretext: pretext,
            title: document.get('header'),
            title_link: link,
            text: document.get('body'),
            mrkdwn_in: ['text', 'pretext']
          }]
        }
      }, function(error, response, body) {
        if (error) logger.error(app_id, 'slack_incoming_webhook_failed', value);
      });
    });
  }
}

module.exports = Slack;
<commit_msg>Improve Slack integration<commit_after>/******************************************************************************
Slack integration
*******************************************************************************/
'use strict';

var _ = require('underscore'),
    request = require('request'),
    logger = require('../../server/logger');

var fruum_username = 'Fruum',
    fruum_icon_emoji = ':racing_car:',
    fruum_icon_url = 'http://fruum.github.io/static/slack.jpg';

function Slack(options, instance) {

  // ---------------------------------- COMMANDS -------------------------------

  //respond to /fruum slack command
  instance.server.post('/slack/:app_id', function(req, res) {
    var app_id = req.params.app_id,
        text = req.body.text,
        token = req.body.token;

    //get app
    instance.storage.get_app(app_id, function(application) {
      if (!application) {
        res.send('*Fruum:* Invalid app_id, check your slack integration');
        return;
      }
      var app_token = application.getProperty('slack:command_token');
      if (app_token && app_token != token) {
        res.send('*Fruum:* Permission denied, check your slack integration');
        return;
      }
      if (!text) {
        var fullpage_url = application.get('fullpage_url');
        if (!fullpage_url) {
          res.send('Setup a <https://fruum.github.io/#v/setting-up-full-page-forums|full page fruum> to enable all features');
        }
        else {
          res.send('Click <' + fullpage_url + '|here> to open Fruum and share your thoughts');
        }
      }
      else {
        //perform search
        instance.storage.search(app_id, text, function(results) {
          if (!results.length) {
            res.send('*Fruum:* No search results');
            return;
          }
          var response = 'Fruum search results for: *' + text + '*\n';
          _.each(results, function(document) {
            var link = '';
            if (document.get('type') == 'post') {
              link = application.getShareURL(document.get('parent'));
            }
            else {
              link = application.getShareURL(document.get('id'));
            }
            response += '<' + link + '|' + document.get('header') + '>\n';
          });
          res.send(response);
        });
      }
    });
  });

  // ---------------------------------- WEBHOOKS -------------------------------

  //Report to slack when a new document has been added
  this.add = function(payload, callback) {
    //do not block operation
    callback(null, payload);
    //check if we have an outgoing webhook registered in the app
    var document = payload.document,
        app_id = payload.app_id;
    //skip chat messages
    if (document.get('parent_type') == 'channel') return;
    //get application instance
    instance.storage.get_app(app_id, function(application) {
      if (!application) return;
      var webhook = application.getProperty('slack:incoming_webhook');
      if (!webhook) return;
      var pretext = document.get('user_displayname') || document.get('user_username'),
          link = '';
      if (document.get('type') == 'post') {
        link = application.getShareURL(document.get('parent'));
        pretext += ' replied to ' + document.get('parent_type');
      }
      else {
        link = application.getShareURL(document.get('id'));
        pretext += ' created new ' + document.get('type');
      }
      request({
        url: webhook,
        method: 'POST',
        json: {
          username: fruum_username,
          //icon_emoji: fruum_icon_emoji,
          icon_url: fruum_icon_url,
          attachments: [{
            pretext: pretext,
            title: document.get('header'),
            title_link: link,
            text: document.get('body'),
            mrkdwn_in: ['text', 'pretext']
          }]
        }
      }, function(error, response, body) {
        if (error) logger.error(app_id, 'slack_incoming_webhook_failed', value);
      });
    });
  }
}

module.exports = Slack;
<|endoftext|>


<commit_before>require('app-module-path').addPath(__dirname + '/../app');

var app                 = require('config/app');
var db                  = require('config/db');
var mongoose            = require('mongoose');
var request             = require('request');
var Promise             = require('promise');
var achievements        = require('./wow-achievements.json');
var WarcraftProvider    = require('modules/gaming/warcraft/warcraft-provider');
var warcraftProvider    = new WarcraftProvider();

console.log('connecting to db...');
mongoose.connect(db.url);

var profile = {
    "characters": []
};
var promises = [];
app.wow.characters.forEach(function(character) {
    var promise = new Promise(function(resolve) {
        var options = {
            uri: 'http://us.battle.net/api/wow/character/' + character.realm + '/' + character.name,
            method: 'GET',
            headers: {
                'Content-Type': 'application/json'
            }
        };
        if(character.showcase) {
            // just ensure we only pull these once, all characters return account-wide pets and mounts
            options.uri +=  '?fields=feed,items,stats,pvp,progression,pets,mounts,achievements';
        }

        console.log('requesting character data for %s...', character.name);
        request(options, function(err, res, body) {
            if(err) {
                console.err(err);
                mongoose.disconnect();
                return;
            }
            if(!body) {
                console.err('request returned empty');
                mongoose.disconnect();
                return;
            }

            var data = JSON.parse(body);
            if(data) {
                console.log('received info for character: ' + data.name);
                if(character.showcase) {
                    profile.achievementPoints = data.achievementPoints;
                    profile.pets = data.pets;
                    profile.mounts = data.mounts;
                    profile.achievements = parseAchievementObject(achievements.supercats, data);
                    data.showcase = true;
                    parseFeed(data.feed).then(function(feed) {
                        data.feed = feed;
                        profile.characters.push(data);
                        resolve(data);
                    });
                } else {
                    profile.characters.push(data);
                    resolve(data);
                }
            } else {
                console.log('unexpected results...');
                mongoose.disconnect();
            }
        }).on('error', function(e) {
            console.error(e.message);
            mongoose.disconnect();
        });
    });
    promises.push(promise);
});

Promise.all(promises).then(function() {
    console.log('saving profile...');
    warcraftProvider.saveProfile(profile)
        .then(function(res) {
            console.log('profile saved');
            mongoose.disconnect();
        })
        .catch(function(err) {
            console.err(err);
            mongoose.disconnect();
        });
});

function parseFeed(feed) {
    return new Promise(function(resolve, reject){
        var promises = [];
        feed.forEach(function(feedItem) {
            if (feedItem.type === "LOOT") {
                var promise = new Promise(function(resolve, reject) {
                    getItemData(feedItem.itemId).then(function(itemData) {
                        feedItem.item = itemData;
                        resolve();
                    });
                });
                promises.push(promise);
            }
        });

        Promise.all(promises).then(function() {
            console.log('finished parsing activity feed...');
            resolve(feed);
        });
    });
}

function getItemData(itemId, context) {
    return new Promise(function(resolve, reject) {
        var options = {
            uri: 'http://us.battle.net/api/wow/item/' + itemId,
            method: 'GET',
            headers: {
                'Content-Type': 'application/json'
            }
        };
        if (context) {
            options.uri += '/' + context;
        }

        console.log('requesting item data for %s...', itemId);
        request(options, function(err, res, body) {
            if(err) {
                console.err(err);
                mongoose.disconnect();
                return;
            }
            if(!body) {
                console.err('request returned empty');
                mongoose.disconnect();
                return;
            }

            var data = JSON.parse(body);
            if(data) {
                if (!data.name) {
                    if (data.availableContexts) {
                        console.log('require a context for item %s...', data.id);
                        getItemData(itemId, data.availableContexts[0]).then(function(itemData) {
                            resolve(itemData);
                        });
                    } else {
                        console.warn('couldn\'t retrieve data for item %s...', itemId);
                        reject();
                    }
                } else {
                    console.log('received info for item %s...', data.name);
                    resolve(data);
                }
            } else {
                console.log('unexpected results...');
                mongoose.disconnect();
            }
        });
    });
}

function parseAchievementObject(supercats, character) {
    var FAKE_COMPLETION_TIME = new Date('2015-01-01').getTime();
    var IGNORE_ACHIEVEMENTS =
    {
        10050: true, // learn a primary prof
        10051: true  // learn two primary prof
    };

    var obj = {};
    var completed = {};
    var totalPossible = 0;
    var totalCompleted = 0;
    var totalFoS = 0;
    var totalLegacy = 0;
    var found = {};
    console.log('Parsing achievements.json...');

    // Build up lookup for achievements that character has completed
    character.achievements.achievementsCompleted.forEach(function(ach, index) {
        // hash the achievement and its timestamp
        completed[ach] = character.achievements.achievementsCompletedTimestamp[index];

        // FoS will no longer have timestamps, so we need to fake their timestamp
        if (!character.achievements.achievementsCompletedTimestamp[index])
        {
            completed[ach] = FAKE_COMPLETION_TIME;
        }
        found[ach] = false;
    });

    // Lets parse out all the super categories and build out our structure
    supercats.forEach(function(supercat) {
        var possibleCount = 0;
        var completedCount = 0;

        // remove the "." to fix parsing errors
        if (supercat.name === "Player vs. Player") {
            supercat.name = "Player vs Player";
        }

        // Add the supercategory to the object, so we can do quick lookups on category
        obj[supercat.name] = {};
        obj[supercat.name].categories = [];

        supercat.cats.forEach(function(cat) {
            var myCat = {'name': cat.name, 'zones': []};

            cat.zones.forEach(function(zone) {
                var myZone = {'name': zone.name, 'achievements': []};

                zone.achs.forEach(function(ach) {

                    // Mark this achievement in our found tracker
                    found[ach.id] = true;

                    var myAchievement = ach, added = false;
                    myAchievement.completed = completed[ach.id];
                    if (myAchievement.completed) {
                        myAchievement.rel = 'who=' + character.name + '&when=' + myAchievement.completed;
                    }

                    // Hack: until blizz fixes api, don't stamp with date
                    if (myAchievement.completed && myAchievement.completed !== FAKE_COMPLETION_TIME) {
                        myAchievement.rel = 'who=' + character.name + '&when=' + myAchievement.completed;
                    }

                    // Always add it if we've completed it, it should show up regardless if its avaiable
                    if (completed[ach.id]) {
                        added = true;
                        myZone.achievements.push(myAchievement);

                        // if this is feats of strength then I want to keep a seperate count for that
                        // since its not a percentage thing
                        if (supercat.name === 'Feats of Strength') {
                            totalFoS++;
                        } else if (supercat.name === 'Legacy') {
                            totalLegacy++;
                        }
                    }

                    // Update counts proper
                    if (supercat.name !== 'Feats of Strength' && supercat.name !== 'Legacy' && ach.obtainable &&
                        (ach.side === '' || ach.side === character.faction)){
                        possibleCount++;
                        totalPossible++;

                        if (completed[ach.id]) {
                            completedCount++;
                            totalCompleted++;
                        }

                        // if we haven't already added it, then this is one that should show up in the page of achievements
                        // so add it
                        if (!added) {
                            myZone.achievements.push(myAchievement);
                        }
                    }
                });

                if (myZone.achievements.length > 0) {
                    myCat.zones.push(myZone);
                }
            });

            // Add the category to the obj
            obj[supercat.name].categories.push(myCat);
        });

        obj[supercat.name].possible = possibleCount;
        obj[supercat.name].completed = completedCount;

        // Add the FoS count if this is the FoS
        if (supercat.name === 'Feats of Strength') {
            obj[supercat.name].fosTotal = totalFoS;
        } else if (supercat.name === 'Legacy') {
            obj[supercat.name].legacyTotal = totalLegacy;
        }
    });

    for (var achId in found) {
        if (found.hasOwnProperty(achId) && !found[achId] && !IGNORE_ACHIEVEMENTS[achId]) {
            console.log('WARN: Found achievement "' + achId + '" from character but not in db.');
        }
    }

    // Add totals
    obj.possible = totalPossible;
    obj.completed = totalCompleted;

    // Data object we expose externally
    return obj;
}<commit_msg>update wow script with mashery api<commit_after>require('app-module-path').addPath(__dirname + '/../app');

var app                 = require('config/app');
var db                  = require('config/db');
var mongoose            = require('mongoose');
var request             = require('request');
var Promise             = require('promise');
var achievements        = require('./wow-achievements.json');
var WarcraftProvider    = require('modules/gaming/warcraft/warcraft-provider');
var warcraftProvider    = new WarcraftProvider();

console.log('connecting to db...');
mongoose.connect(db.url);

var profile = {
    "characters": []
};
var promises = [];
app.wow.characters.forEach(function(character) {
    var promise = new Promise(function(resolve) {
        var options = {
            uri: app.battlenet.baseUrl + '/wow/character/' + character.realm + '/' + character.name + '?locale=en_US&apikey=' + app.keys.battlenet,
            method: 'GET',
            headers: {
                'Content-Type': 'application/json'
            }
        };
        if(character.showcase) {
            // just ensure we only pull these once, all characters return account-wide pets and mounts
            options.uri += '&fields=feed,items,stats,pvp,progression,pets,mounts,achievements';
        }

        console.log('requesting character data for %s...', character.name);
        request(options, function(err, res, body) {
            if(err) {
                console.err(err);
                mongoose.disconnect();
                return;
            }
            if(!body) {
                console.err('request returned empty');
                mongoose.disconnect();
                return;
            }

            var data = JSON.parse(body);
            if(data) {
                console.log('received info for character: ' + data.name);
                if(character.showcase) {
                    profile.achievementPoints = data.achievementPoints;
                    profile.pets = data.pets;
                    profile.mounts = data.mounts;
                    profile.achievements = parseAchievementObject(achievements.supercats, data);
                    data.showcase = true;
                    parseFeed(data.feed).then(function(feed) {
                        data.feed = feed;
                        profile.characters.push(data);
                        resolve(data);
                    });
                } else {
                    profile.characters.push(data);
                    resolve(data);
                }
            } else {
                console.log('unexpected results...');
                mongoose.disconnect();
            }
        }).on('error', function(e) {
            console.error(e.message);
            mongoose.disconnect();
        });
    });
    promises.push(promise);
});

Promise.all(promises).then(function() {
    console.log('saving profile...');
    warcraftProvider.saveProfile(profile)
        .then(function(res) {
            console.log('profile saved');
            mongoose.disconnect();
        })
        .catch(function(err) {
            console.err(err);
            mongoose.disconnect();
        });
});

function parseFeed(feed) {
    return new Promise(function(resolve, reject){
        var promises = [];
        feed.forEach(function(feedItem) {
            if (feedItem.type === "LOOT") {
                var promise = new Promise(function(resolve, reject) {
                    getItemData(feedItem.itemId).then(function(itemData) {
                        feedItem.item = itemData;
                        resolve();
                    });
                });
                promises.push(promise);
            }
        });

        Promise.all(promises).then(function() {
            console.log('finished parsing activity feed...');
            resolve(feed);
        });
    });
}

function getItemData(itemId, context) {
    return new Promise(function(resolve, reject) {
        var options = {
            uri: app.battlenet.baseUrl + '/wow/item/' + itemId + '?locale=en_US&apikey=' + app.keys.battlenet,
            method: 'GET',
            headers: {
                'Content-Type': 'application/json'
            }
        };
        if (context) {
            options.uri += '/' + context;
        }

        console.log('requesting item data for %s...', itemId);
        request(options, function(err, res, body) {
            if(err) {
                console.err(err);
                mongoose.disconnect();
                return;
            }
            if(!body) {
                console.err('request returned empty');
                mongoose.disconnect();
                return;
            }

            var data = JSON.parse(body);
            if(data) {
                if (!data.name) {
                    if (data.availableContexts) {
                        console.log('require a context for item %s...', data.id);
                        getItemData(itemId, data.availableContexts[0]).then(function(itemData) {
                            resolve(itemData);
                        });
                    } else {
                        console.warn('couldn\'t retrieve data for item %s...', itemId);
                        resolve();
                    }
                } else {
                    console.log('received info for item %s...', data.name);
                    resolve(data);
                }
            } else {
                console.log('unexpected results...');
                mongoose.disconnect();
            }
        });
    });
}

function parseAchievementObject(supercats, character) {
    var FAKE_COMPLETION_TIME = new Date('2015-01-01').getTime();
    var IGNORE_ACHIEVEMENTS =
    {
        10050: true, // learn a primary prof
        10051: true  // learn two primary prof
    };

    var obj = {};
    var completed = {};
    var totalPossible = 0;
    var totalCompleted = 0;
    var totalFoS = 0;
    var totalLegacy = 0;
    var found = {};
    console.log('Parsing achievements.json...');

    // Build up lookup for achievements that character has completed
    character.achievements.achievementsCompleted.forEach(function(ach, index) {
        // hash the achievement and its timestamp
        completed[ach] = character.achievements.achievementsCompletedTimestamp[index];

        // FoS will no longer have timestamps, so we need to fake their timestamp
        if (!character.achievements.achievementsCompletedTimestamp[index])
        {
            completed[ach] = FAKE_COMPLETION_TIME;
        }
        found[ach] = false;
    });

    // Lets parse out all the super categories and build out our structure
    supercats.forEach(function(supercat) {
        var possibleCount = 0;
        var completedCount = 0;

        // remove the "." to fix parsing errors
        if (supercat.name === "Player vs. Player") {
            supercat.name = "Player vs Player";
        }

        // Add the supercategory to the object, so we can do quick lookups on category
        obj[supercat.name] = {};
        obj[supercat.name].categories = [];

        supercat.cats.forEach(function(cat) {
            var myCat = {'name': cat.name, 'zones': []};

            cat.zones.forEach(function(zone) {
                var myZone = {'name': zone.name, 'achievements': []};

                zone.achs.forEach(function(ach) {

                    // Mark this achievement in our found tracker
                    found[ach.id] = true;

                    var myAchievement = ach, added = false;
                    myAchievement.completed = completed[ach.id];
                    if (myAchievement.completed) {
                        myAchievement.rel = 'who=' + character.name + '&when=' + myAchievement.completed;
                    }

                    // Hack: until blizz fixes api, don't stamp with date
                    if (myAchievement.completed && myAchievement.completed !== FAKE_COMPLETION_TIME) {
                        myAchievement.rel = 'who=' + character.name + '&when=' + myAchievement.completed;
                    }

                    // Always add it if we've completed it, it should show up regardless if its avaiable
                    if (completed[ach.id]) {
                        added = true;
                        myZone.achievements.push(myAchievement);

                        // if this is feats of strength then I want to keep a seperate count for that
                        // since its not a percentage thing
                        if (supercat.name === 'Feats of Strength') {
                            totalFoS++;
                        } else if (supercat.name === 'Legacy') {
                            totalLegacy++;
                        }
                    }

                    // Update counts proper
                    if (supercat.name !== 'Feats of Strength' && supercat.name !== 'Legacy' && ach.obtainable &&
                        (ach.side === '' || ach.side === character.faction)){
                        possibleCount++;
                        totalPossible++;

                        if (completed[ach.id]) {
                            completedCount++;
                            totalCompleted++;
                        }

                        // if we haven't already added it, then this is one that should show up in the page of achievements
                        // so add it
                        if (!added) {
                            myZone.achievements.push(myAchievement);
                        }
                    }
                });

                if (myZone.achievements.length > 0) {
                    myCat.zones.push(myZone);
                }
            });

            // Add the category to the obj
            obj[supercat.name].categories.push(myCat);
        });

        obj[supercat.name].possible = possibleCount;
        obj[supercat.name].completed = completedCount;

        // Add the FoS count if this is the FoS
        if (supercat.name === 'Feats of Strength') {
            obj[supercat.name].fosTotal = totalFoS;
        } else if (supercat.name === 'Legacy') {
            obj[supercat.name].legacyTotal = totalLegacy;
        }
    });

    for (var achId in found) {
        if (found.hasOwnProperty(achId) && !found[achId] && !IGNORE_ACHIEVEMENTS[achId]) {
            console.log('WARN: Found achievement "' + achId + '" from character but not in db.');
        }
    }

    // Add totals
    obj.possible = totalPossible;
    obj.completed = totalCompleted;

    // Data object we expose externally
    return obj;
}<|endoftext|>


<commit_before>/*jslint devel: true*/
/*global perfis */

// variables
var project, beam, mk, concrete, steel;
var tsd, as, bw, fcd;
var betax23, betax34, epc, eps, epyd, fyd, Es, fck, fyk, fckForm, fykForm;
var d, h, cob, diamEstForm, diamLongForm, diamEst, diamLong;
var a, b, c, delta, deltaR, x1, x2;
var x, mk, md;
var x2lim, x3lim, dominio, dl;
var gamac, gamaf, gamas, s;
var result, situationD, situationLN;

var bitola = [
    {
        "diametro": 5.0,
        "area": 0.20
    },
    {
        "diametro": 6.3,
        "area": 0.315
    },
    {
        "diametro": 8.0,
        "area": 0.50
    },
    {
        "diametro": 10.0,
        "area": 0.80
    },
    {
        "diametro": 12.5,
        "area": 1.25
    },
    {
        "diametro": 16.0,
        "area": 2.0
    },
    {
        "diametro": 20.0,
        "area": 3.15
    },
    {
        "diametro": 25.0,
        "area": 5.0
    },
    {
        "diametro": 32.0,
        "area": 8.0
    },
    {
        "diametro": 40.0,
        "area": 12.50
    }

];

var steelProp = [
    {
        "steelType": "CA25",
        "fy": 250,
        "fu": 217.4,
        "E": 21000
    },
    {
        "steelType": "CA50",
        "fy": 500,
        "fu": 434.8,
        "E": 21000
    },
    {
        "steelType": "CA60",
        "fy": 600,
        "fu": 521.7,
        "E": 21000
    }
];

var concreteProp = [
    {
        "concType": "C20",
        "fckProp": 200
    },
    {
        "concType": "C25",
        "fckProp": 250
    },
    {
        "concType": "C30",
        "fckProp": 300
    },
    {
        "concType": "C35",
        "fckProp": 350
    },

    {
        "concType": "C40",
        "fckProp": 400
    },
    {
        "concType": "C45",
        "fckProp": 450
    },
    {
        "concType": "C50",
        "fckProp": 500
    }

];

function processFormDC() {
    "use strict";
    var i, j, k;

    // Getting values from HTML form inputs
    project = document.formDC.projectDC.value;
    /* if (project === "") {
        alert("Por favor preencha o campo Projeto.");
        console.log("Por favor preencha o campo Projeto.");
        return false;
    } */
    if (project === "") {
        project = "Exemplo 1";
    }

    beam = document.formDC.beamDC.value;
    /* if (beam === "") {
        alert("Por favor preencha o campo Viga.");
        console.log("Por favor preencha o campo Viga.");
        return false;
    } */
    if (beam === "") {
        beam = "V1";
    }

    h = Number(document.formDC.hDC.value);
    if (document.formDC.hDC.value === "" || isNaN(h)) {
        alert("Por favor preencha o campo Altura da Viga com números.");
        console.log("Por favor preencha o campo Altura da Viga.");
        return false;
    }

    bw = Number(document.formDC.bwDC.value);
    if (document.formDC.bwDC.value === "" || isNaN(bw)) {
        alert("Por favor preencha o campo Largura da Viga com números.");
        console.log("Por favor preencha o campo Largura da Viga.");
        return false;
    }

    mk = Number(document.formDC.mDC.value);
    if (document.formDC.mDC.value === "" || isNaN(mk)) {
        alert("Por favor preencha o campo Momento com números.");
        console.log("Por favor preencha o campo Momento.");
        return false;
    }

    concrete = concreteProp[(document.formDC.concreteDC.value)].concType;
    steel = steelProp[(document.formDC.steelDC.value)].steelType;
    diamLongForm = bitola[Number(document.formDC.longDC.value)].diametro;
    diamEstForm = bitola[Number(document.formDC.estDC.value)].diametro;
    gamac = Number(document.formDC.gamacDC.value);
    gamaf = Number(document.formDC.gamafDC.value);
    gamas = Number(document.formDC.gamasDC.value);
    s = Number(document.formDC.sDC.value);
    cob = Number(document.formDC.cobDC.value);

    // Getting bitolaLong properties
    i = Number(document.getElementById("bitolaLong").value);

        // Getting concrete properties
    j = Number(document.getElementById("concrete").value);

    fckForm = concreteProp[j].fckProp;

    // Getting steel properties
    k = Number(document.getElementById("steel").value);

    fykForm = steelProp[k].fy;
    Es = steelProp[k].E;

    // Converting Units
    fyk = fykForm / 10;
    fck = fckForm / 100;
    diamEst = diamEstForm / 10;
    diamLong = diamLongForm / 10;

    // Calculating parameters
    fcd = fck / gamac;
    fyd = fyk / gamas;
    tsd = fyd;
    epc = 3.5;
    eps = 10.0;

    //CÁLCULO DO MOMENTO FLETOR MAJORADO
    md = mk * gamaf;

    //CÁLCULO DA ALTURA ÚTIL (d)
    dl = 3.0; //(estimar valor entre 3 e 6 cm)/
    d = h-dl;

    //CÁLCULO DOS LIMITES DOS DOMÍNIOS 2 E 3
    //DOMÍNIO 2
    betax23 = epc/(epc+eps)

    //DOMÍNIO 3
    epyd = (fyd/Es)*1000;
    betax34 = epc/(epc+epyd);

    //CÁLCULO DA POSIÇÃO DA LINHA NEUTRA (x)
    //md = 0.68*bw*x*fcd*(d-0.4*x);
    //(0.4*x^2)+(d*x)+(md/(0.68*bw*fcd))=0

    a = 0.4;
    b = -1*d;
    c = md/(0.68*bw*fcd);
    delta = Math.pow(b , 2) - ((4 * a)*c);

    if( delta > 0){
	deltaR = Math.sqrt(delta);
	x1 = ((-b +  deltaR)  /(2 * a));
	x2 = ((-b -  deltaR)  /(2 * a));
		    	}
                else{
			x1 = ("Sem raiz");
			x2 = ("Sem raiz");
                    }

    if (x1 > h) {
        x = x2
                }
            else{
            x = x1
                }
    //VERIFICAÇÃO DO DOMÍNIO DA VIGA

    x2lim = betax23 * d;
    x3lim = betax34 * d;

    if (x > x2lim && x < x3lim) {
        dominio = "Domínio 3";
        situationD = "Aprovado";
    } else if (x < x2lim) {
        dominio = "Domínio 2";
        situationD = "Aprovado";
    } else if (x > x3lim) {
        dominio = "Domínio 4";
        situationD = "Reprovado";
    } else {
        dominio = "Error";
    }

    //VERIFICAÇÃO DA RELAÇÃO x/d

    if (x / d <= 0.45) {
        situationLN = "Aprovada";
    } else {
        situationLN = "Reprovada";
    }

    //CÁLCULO DA ÁREA DE AÇO

    as = md/(tsd*(d-0.4*x));
    result = as;
    alert(result);

}

  /*

    var resultado =
        {
            "project":project,
            "beam":beam,
            "vk":vk,
            "mk":mk,
            "lbForm":lbForm,
            "section":section,
            "steel":steel,
            "fyForm":fyForm,
            "fuForm":fuForm,
            "EForm":EForm,
            "ml":ml,
            "d":d,
            "bf":bf,
            "tw":tw,
            "tf":tf,
            "h":h,
            "dl":dl,
            "area":area,
            "ix":ix,
            "wx":wx,
            "rx":rx,
            "zx":zx,
            "iy":iy,
            "wy":wy,
            "ry":ry,
            "zy":zy,
            "rt":rt,
            "it":it,
            "mesa":mesa,
            "alma":alma,
            "cw":cw,
            "u":u,
            "fy":fy,
            "fu":fu,
            "E":E,
            "aw":aw,
            "gama":gama,
            "gama1":gama1,
            "cb":cb,
            "kv":kv,
            "lb":lb,
            "kc":kc,
            "tr":tr,
            "beta1":beta1,
            "a":a,
            "lambV":lambV,
            "lambpV":lambpV,
            "lambrV":lambrV,
            "vpl":vpl,
            "vrd":vrd,
            "mpl":mpl,
            "lambA":lambA,
            "lambpA":lambpA,
            "lambrA":lambrA,
            "mrA":mrA,
            "mcrA":mcrA,
            "mrdA":mrdA,
            "lambM":lambM,
            "lambpM":lambpM,
            "lambrM":lambrM,
            "mrM":mrM,
            "mcrM":mcrM,
            "mrdM":mrdM,
            "lambT":lambT,
            "lambpT":lambpT,
            "lambrT":lambrT,
            "mrT":mrT,
            "mcrT":mcrT,
            "mrdT":mrdT,
            "vsd":vsd,
            "msd":msd,
            "vrd":vrd,
            "mrd":mrd,
            "mrdOut":mrdOut,
            "situationV":situationV,
            "situationA":situationA,
            "situationM":situationM,
            "situationT":situationT,
            "result":result,
            "ratioCSV":ratioCSV,
            "ratioCSM":ratioCSM,
            "ratiopCSV":ratiopCSV,
            "ratiopCSM":ratiopCSM
        };

    document.getElementById("botaoRelatorio").href = "reportCS.html?test=" + JSON.stringify(resultado);
} */

/*
    Cria um objeto bitola
*/
function criarBitola(nomeBitola, linhaBitola) {
    "use strict";
    var opcao = document.createElement("OPTION"),
        texto = document.createTextNode(nomeBitola);
    opcao.value = linhaBitola;
    opcao.appendChild(texto);
    return opcao;
}

var bitolaLista1 = document.getElementById("bitolaLong"),
    bitolaLista2 = document.getElementById("bitolaEst"),
    acoLista = document.getElementById("steel"),
    concretoLista = document.getElementById("concrete"),
    i;

for (i = 0; i < bitola.length; i += 1) {
    bitolaLista1.appendChild(criarBitola(bitola[i].diametro, i));
}

for (i = 0; i < bitola.length; i += 1) {
    bitolaLista2.appendChild(criarBitola(bitola[i].diametro, i));
}

function criarAco(nomeAco, linhaAco) {
    "use strict";
    var opcao = document.createElement("OPTION"),
        texto = document.createTextNode(nomeAco);
    opcao.value = linhaAco;
    opcao.appendChild(texto);
    return opcao;
}

for (i = 0; i < steelProp.length; i += 1) {
    acoLista.appendChild(criarAco(steelProp[i].steelType, i));
}

function criarConcreto(nomeConcreto, linhaConcreto) {
    "use strict";
    var opcao = document.createElement("OPTION"),
        texto = document.createTextNode(nomeConcreto);
    opcao.value = linhaConcreto;
    opcao.appendChild(texto);
    return opcao;
}

for (i = 0; i < concreteProp.length; i += 1) {
    concretoLista.appendChild(criarConcreto(concreteProp[i].concType, i));
}
<commit_msg>Delete scriptDC.js<commit_after><|endoftext|>