<commit_before>var fs = require("fs"),
    chalk = require('chalk'),
    yaml = require('js-yaml');

module.exports = {

  defaultOptions : {
    'min_wait' : 1000,
    'max_wait' : 3000
  },

  // Validates input file is a swagger spec
  validFile : function(filename) {
    try {
      // Exists?
      stats = fs.lstatSync(filename);
      // Extension is .yaml or .json ?
      return (['yaml','json'].indexOf(filename.split('.').pop().toLowerCase()) != -1);
    } catch (e) {
      return false;
    }
  },

  // Convert a file
  convertFile : function(filename, options) {
    var contents = fs.readFileSync(filename);
    ext = filename.split('.').pop().toLowerCase();
    if (ext == "yaml") {
      return this.convertYAML(contents, options);
    } else {
      return this.convertJSON(contents, options);
    }
  },

  // Convert a JSON string
  convertJSON : function(contents, options) {
    return this.convertSpec(JSON.parse(contents), options);

  },

  // Convert a YAML string
  convertYAML : function(contents, options) {
    return this.convertSpec(yaml.safeLoad(contents), options);
  },

  // Convert a swagger JSON Object
  convertSpec : function(swgObj, options) {
    var locustfile = ""

    var locustOptions = JSON.parse(JSON.stringify(options));
    var pythonOptions = {};

    if (!locustOptions['host'] && swgObj['host']) {
      locustOptions['host'] = 'https://' + swgObj['host'];
    }

    if (swgObj['x-locust-import']) {
      pythonOptions['x-locust-import'] = swgObj['x-locust-import'];
    }

    locustfile += this.locustfileHeader(pythonOptions);

    for(path in swgObj.paths) {
      if (swgObj.paths[path]['get']) {
        locustfile += this.HTTPLocustGetFromPath(path, swgObj.paths[path]['get'], swgObj['basePath'], {'parameters':swgObj['parameters']});
      }
    }

    locustfile += this.locustfileFooter(locustOptions);

    return locustfile;
  },

  // Outputs a locustfile header (including class def)
  locustfileHeader : function(options) {
    var header = ""
    header += "from locust import HttpLocust, TaskSet, task\n"
    if (options["x-locust-import"]) {
      for (imp in options["x-locust-import"]) {
        header += "import "+options["x-locust-import"][imp]+"\n";
      }
    }
    header += "\n"
    header += "class MyTaskSet(TaskSet):\n"

    return header;
  },

  // Outputs the locust class and the settings (host, min, max, etc).
  locustfileFooter : function(options) {
    var footer = ""
    footer += "class MyLocust(HttpLocust):\n"
    footer += "    task_set = MyTaskSet\n"
    for(currOpt in options){
      if (currOpt == "host") {
        footer += "    "+currOpt+' = "'+options[currOpt]+'"'+"\n"
      } else {
        footer += "    "+currOpt+" = "+options[currOpt]+"\n"
      }
    }
    return footer;
  },

  // Creates an HTTP Get task from swagger path object
  HTTPLocustGetFromPath : function(pathName, pathObj, basePath, sharedDefinitions) {
    var task = "";
    task += "    @task\n";
    task += "    def "+this.taskNameFromPath(pathName, 'get')+"(self):\n";
    var resolvedPath = this.resolvedPathWithDefaults(pathName, pathObj, sharedDefinitions);
    task += '        self.client.get("'+basePath+resolvedPath.path+'"'+(resolvedPath.paramsValues.length > 0 ? ('.format('+resolvedPath.paramsValues.join(',')+')') : '')+')'+"\n\n";

    return task;
  },

  // Normalizes paths to py method name (GET /path/to/{id} will become get_path_to_id)
  taskNameFromPath : function (path, method) {
    return method+path.toString().replace(/\//g,"_").replace(/[\{}]/g, "")
  },

  // Replaces Path-Parameters' holders in path with their default values.
  // Example: /path/to/{id} -> /path/to/1
  resolvedPathWithDefaults : function(pathName, pathObj, sharedDefinitions) {
    var resolvedPath = pathName;
    var requiredParamValues = [];
    var currPlaceholder = 0;

    // If path has path params, replace them with their default values
    if (pathName.indexOf("{") != -1) {
      var pathParamsNames = pathName.split("{");
      pathParamsNames = pathParamsNames.slice(1, pathParamsNames.length).map(function(pt){return pt.split("}").shift()});
      for (currParam in pathParamsNames) {
        resolvedPath = resolvedPath.replace('{'+pathParamsNames[currParam]+'}', '{'+currPlaceholder+'}');
        currPlaceholder += 1;
        requiredParamValues.push(this.getParamDefault(pathParamsNames[currParam], pathObj['parameters'], sharedDefinitions['parameters']));
      }
    }

    // Collect required Query String Parameters (and their defaults)
    var requiredQSParams = [];
    for (currPathParam in pathObj['parameters']){
      if (pathObj['parameters'][currPathParam]['in'] == 'query' && pathObj['parameters'][currPathParam]['required']) {
        requiredQSParams.push(pathObj['parameters'][currPathParam]['name']+'={'+currPlaceholder+'}');
        currPlaceholder += 1;
        if (pathObj['parameters'][currPathParam]['x-locust-value']) {
          requiredParamValues.push(pathObj['parameters'][currPathParam]['x-locust-value']);
        } else {
          requiredParamValues.push('"'+pathObj['parameters'][currPathParam]['default']+'"');
        }
         // +pathObj['parameters'][currPathParam]['default']);
      }
    }

    // Add Query String to path
    if (requiredQSParams.length > 0) {
      resolvedPath += "?"+requiredQSParams.join("&")
    }

    return {path: resolvedPath, paramsValues: requiredParamValues};
  },

  // Finds the default value of parameter by its name.
  getParamDefault : function(paramName, pathParameters, sharedParameters)  {
    for (currPathParam in pathParameters) {

      var pathParam = pathParameters[currPathParam];

      if (pathParam['$ref']) {
        var paramId = pathParam['$ref'].split('/').pop();
        pathParam = sharedParameters[paramId];
      }

      if (pathParam['name'] == paramName) {
        if (pathParam['x-locust-value']) {
          return pathParam['x-locust-value'];
        } else {
          return '"'+pathParam['default']+'"';
        }
      }
    }
    throw new Error("Couldn't find default value of path param "+paramName);
  }
}
<commit_msg>follow $ref for query strin parameters as well<commit_after>var fs = require("fs"),
    chalk = require('chalk'),
    yaml = require('js-yaml');

module.exports = {

  defaultOptions : {
    'min_wait' : 1000,
    'max_wait' : 3000
  },

  // Validates input file is a swagger spec
  validFile : function(filename) {
    try {
      // Exists?
      stats = fs.lstatSync(filename);
      // Extension is .yaml or .json ?
      return (['yaml','json'].indexOf(filename.split('.').pop().toLowerCase()) != -1);
    } catch (e) {
      return false;
    }
  },

  // Convert a file
  convertFile : function(filename, options) {
    var contents = fs.readFileSync(filename);
    ext = filename.split('.').pop().toLowerCase();
    if (ext == "yaml") {
      return this.convertYAML(contents, options);
    } else {
      return this.convertJSON(contents, options);
    }
  },

  // Convert a JSON string
  convertJSON : function(contents, options) {
    return this.convertSpec(JSON.parse(contents), options);

  },

  // Convert a YAML string
  convertYAML : function(contents, options) {
    return this.convertSpec(yaml.safeLoad(contents), options);
  },

  // Convert a swagger JSON Object
  convertSpec : function(swgObj, options) {
    var locustfile = ""

    var locustOptions = JSON.parse(JSON.stringify(options));
    var pythonOptions = {};

    if (!locustOptions['host'] && swgObj['host']) {
      locustOptions['host'] = 'https://' + swgObj['host'];
    }

    if (swgObj['x-locust-import']) {
      pythonOptions['x-locust-import'] = swgObj['x-locust-import'];
    }

    locustfile += this.locustfileHeader(pythonOptions);

    for(path in swgObj.paths) {
      if (swgObj.paths[path]['get']) {
        locustfile += this.HTTPLocustGetFromPath(path, swgObj.paths[path]['get'], swgObj['basePath'], {'parameters':swgObj['parameters']});
      }
    }

    locustfile += this.locustfileFooter(locustOptions);

    return locustfile;
  },

  // Outputs a locustfile header (including class def)
  locustfileHeader : function(options) {
    var header = ""
    header += "from locust import HttpLocust, TaskSet, task\n"
    if (options["x-locust-import"]) {
      for (imp in options["x-locust-import"]) {
        header += "import "+options["x-locust-import"][imp]+"\n";
      }
    }
    header += "\n"
    header += "class MyTaskSet(TaskSet):\n"

    return header;
  },

  // Outputs the locust class and the settings (host, min, max, etc).
  locustfileFooter : function(options) {
    var footer = ""
    footer += "class MyLocust(HttpLocust):\n"
    footer += "    task_set = MyTaskSet\n"
    for(currOpt in options){
      if (currOpt == "host") {
        footer += "    "+currOpt+' = "'+options[currOpt]+'"'+"\n"
      } else {
        footer += "    "+currOpt+" = "+options[currOpt]+"\n"
      }
    }
    return footer;
  },

  // Creates an HTTP Get task from swagger path object
  HTTPLocustGetFromPath : function(pathName, pathObj, basePath, sharedDefinitions) {
    var task = "";
    task += "    @task\n";
    task += "    def "+this.taskNameFromPath(pathName, 'get')+"(self):\n";
    var resolvedPath = this.resolvedPathWithDefaults(pathName, pathObj, sharedDefinitions);
    task += '        self.client.get("'+basePath+resolvedPath.path+'"'+(resolvedPath.paramsValues.length > 0 ? ('.format('+resolvedPath.paramsValues.join(',')+')') : '')+')'+"\n\n";

    return task;
  },

  // Normalizes paths to py method name (GET /path/to/{id} will become get_path_to_id)
  taskNameFromPath : function (path, method) {
    return method+path.toString().replace(/\//g,"_").replace(/[\{}]/g, "")
  },

  // Replaces Path-Parameters' holders in path with their default values.
  // Example: /path/to/{id} -> /path/to/1
  resolvedPathWithDefaults : function(pathName, pathObj, sharedDefinitions) {
    var resolvedPath = pathName;
    var requiredParamValues = [];
    var currPlaceholder = 0;

    // If path has path params, replace them with their default values
    if (pathName.indexOf("{") != -1) {
      var pathParamsNames = pathName.split("{");
      pathParamsNames = pathParamsNames.slice(1, pathParamsNames.length).map(function(pt){return pt.split("}").shift()});
      for (currParam in pathParamsNames) {
        resolvedPath = resolvedPath.replace('{'+pathParamsNames[currParam]+'}', '{'+currPlaceholder+'}');
        currPlaceholder += 1;
        requiredParamValues.push(this.getParamDefault(pathParamsNames[currParam], pathObj['parameters'], sharedDefinitions['parameters']));
      }
    }

    // Collect required Query String Parameters (and their defaults)
    var requiredQSParams = [];
    for (currPathParam in pathObj['parameters']){
      if (pathObj['parameters'][currPathParam]['in'] == 'query' && pathObj['parameters'][currPathParam]['required']) {
        requiredQSParams.push(pathObj['parameters'][currPathParam]['name']+'={'+currPlaceholder+'}');
        currPlaceholder += 1;
        requiredParamValues.push(this.getParamDefault(pathObj['parameters'][currPathParam]['name'], pathObj['parameters'], sharedDefinitions['parameters']));
      }
    }

    // Add Query String to path
    if (requiredQSParams.length > 0) {
      resolvedPath += "?"+requiredQSParams.join("&")
    }

    return {path: resolvedPath, paramsValues: requiredParamValues};
  },

  // Finds the default value of parameter by its name.
  getParamDefault : function(paramName, pathParameters, sharedParameters)  {
    for (currPathParam in pathParameters) {

      var pathParam = pathParameters[currPathParam];

      if (pathParam['$ref']) {
        var paramId = pathParam['$ref'].split('/').pop();
        pathParam = sharedParameters[paramId];
      }

      if (pathParam['name'] == paramName) {
        if (pathParam['x-locust-value']) {
          return pathParam['x-locust-value'];
        } else {
          return '"'+pathParam['default']+'"';
        }
      }
    }
    throw new Error("Couldn't find default value of path param "+paramName);
  }
}
<|endoftext|>


<commit_before>'use strict';

var _interopRequireWildcard = function (obj) { return obj && obj.__esModule ? obj : { 'default': obj }; };

var _defineProperty = function (obj, key, value) { return Object.defineProperty(obj, key, { value: value, enumerable: key == null || typeof Symbol == 'undefined' || key.constructor !== Symbol, configurable: true, writable: true }); };

var _inherits = function (subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };

var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } };

var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();

Object.defineProperty(exports, '__esModule', {
  value: true
});

var _Type = require('./type');

var _Type2 = _interopRequireWildcard(_Type);

var Model = (function () {
  function Model() {
    var attrs = arguments[0] === undefined ? {} : arguments[0];

    _classCallCheck(this, Model);

    this.attributes = {};
    this.observers = {};
    for (var attr in attrs) {
      if (!this.constructor.attributes[attr]) continue;
      this[attr] = attrs[attr];
    }
  }

  _createClass(Model, [{
    key: 'update',
    value: function update(attrs) {
      for (var attr in attrs) {
        this[attr] = attrs[attr];
      }
      this.notify('changed', attrs);
      return this;
    }
  }, {
    key: 'notify',
    value: function notify(event, data) {
      var observers = this.observers[event];
      if (!observers) {
        return;
      }for (var id in observers) {
        observers[id].call(event, data, this);
      }
      return this;
    }
  }, {
    key: 'on',
    value: function on(events, observer) {
      for (var i = 0; i < events.length; i++) {
        var _event = events[i];
        if (!this.observers[_event]) this.observers[_event] = {};
        this.observers[_event][observer.id] = observer;
      }
      return this;
    }
  }, {
    key: 'off',
    value: function off(events, observer) {
      for (var i = 0; i < events.length; i++) {
        var _event2 = events[i];
        if (!this.observers[_event2]) continue;
        delete this.observers[_event2][observer.id];
      }
      return this;
    }
  }, {
    key: 'getObservers',
    value: function getObservers() {
      return this.observers;
    }
  }, {
    key: 'removeAllObservers',
    value: function removeAllObservers() {
      this.observers = {};
      return this;
    }
  }, {
    key: 'toObject',
    value: function toObject() {
      return this.attributes;
    }
  }, {
    key: 'toString',
    value: function toString() {
      var repr = '';
      for (var attr in this.attributes) {
        repr += '' + attr + ': ' + this[attr] + ' ';
      }
      return '' + this.constructor.name + ' { ' + repr + ' }';
    }
  }], [{
    key: 'build',
    value: function build() {
      var attrs = arguments[0] === undefined ? {} : arguments[0];

      var Through = (function (_Model) {
        function Through() {
          _classCallCheck(this, Through);

          if (_Model != null) {
            _Model.apply(this, arguments);
          }
        }

        _inherits(Through, _Model);

        return Through;
      })(Model);

      ;
      Through.attributes = attrs;

      var _loop = function (attr) {
        Object.defineProperty(Through.prototype, attr, {
          get: function get() {
            return this.attributes[attr];
          },
          set: function set(value) {
            var previous = this.attributes[attr];
            var current = attrs[attr](value);
            if (current === previous) {
              return;
            }this.notify('changed', _defineProperty({}, attr, current));
            this.notify('changed:' + attr, { previous: previous, current: current });
            this.attributes[attr] = current;
          }
        });
      };

      for (var attr in attrs) {
        _loop(attr);
      }
      return Through;
    }
  }]);

  return Model;
})();

Model.type = _Type2['default'];

exports['default'] = Model;
module.exports = exports['default'];<commit_msg>Built version 1.1.1<commit_after>'use strict';

var _interopRequireWildcard = function (obj) { return obj && obj.__esModule ? obj : { 'default': obj }; };

var _defineProperty = function (obj, key, value) { return Object.defineProperty(obj, key, { value: value, enumerable: key == null || typeof Symbol == 'undefined' || key.constructor !== Symbol, configurable: true, writable: true }); };

var _inherits = function (subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };

var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } };

var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();

Object.defineProperty(exports, '__esModule', {
  value: true
});

var _Type = require('./type');

var _Type2 = _interopRequireWildcard(_Type);

var Blueprint = (function () {
  function Blueprint() {
    var attrs = arguments[0] === undefined ? {} : arguments[0];

    _classCallCheck(this, Blueprint);

    this.attributes = {};
    this.observers = {};
    for (var attr in attrs) {
      if (!this.constructor.attributes[attr]) continue;
      this[attr] = attrs[attr];
    }
  }

  _createClass(Blueprint, [{
    key: 'set',
    value: function set(attr, value) {
      var broadcastChange = arguments[2] === undefined ? true : arguments[2];

      var previous = this.attributes[attr];
      var current = this.constructor.attributes[attr](value);
      if (previous === current) {
        return;
      }this.attributes[attr] = current;
      this.notify('changed:' + attr, { previous: previous, current: current });
      if (broadcastChange) this.notify('changed', _defineProperty({}, attr, value));
    }
  }, {
    key: 'get',
    value: function get(attr) {
      return this.attributes[attr];
    }
  }, {
    key: 'update',
    value: function update(attrs) {
      for (var attr in attrs) {
        this.set(attr, attrs[attr], false);
      }
      this.notify('changed', attrs);
      return this;
    }
  }, {
    key: 'notify',
    value: function notify(event, data) {
      var observers = this.observers[event];
      if (!observers) {
        return;
      }for (var id in observers) {
        observers[id].call(event, data, this);
      }
      return this;
    }
  }, {
    key: 'on',
    value: function on(events, observer) {
      for (var i = 0; i < events.length; i++) {
        var _event = events[i];
        if (!this.observers[_event]) this.observers[_event] = {};
        this.observers[_event][observer.id] = observer;
      }
      return this;
    }
  }, {
    key: 'observe',
    value: function observe(events, observer) {
      return this.on(events, observer);
    }
  }, {
    key: 'off',
    value: function off(events, observer) {
      for (var i = 0; i < events.length; i++) {
        var _event2 = events[i];
        if (!this.observers[_event2]) continue;
        delete this.observers[_event2][observer.id];
      }
      return this;
    }
  }, {
    key: 'unobserve',
    value: function unobserve(events, observe) {
      return this.off(events, observer);
    }
  }, {
    key: 'getObservers',
    value: function getObservers() {
      return this.observers;
    }
  }, {
    key: 'removeObservers',
    value: function removeObservers() {
      this.observers = {};
      return this;
    }
  }, {
    key: 'toObject',
    value: function toObject() {
      return this.attributes;
    }
  }, {
    key: 'toString',
    value: function toString() {
      var repr = '';
      for (var attr in this.attributes) {
        repr += '' + attr + ': ' + this[attr] + ' ';
      }
      return '' + this.constructor.name + ' { ' + repr + ' }';
    }
  }], [{
    key: 'build',
    value: function build() {
      var attrs = arguments[0] === undefined ? {} : arguments[0];

      var Model = (function (_Blueprint) {
        function Model() {
          _classCallCheck(this, Model);

          if (_Blueprint != null) {
            _Blueprint.apply(this, arguments);
          }
        }

        _inherits(Model, _Blueprint);

        return Model;
      })(Blueprint);

      ;

      var _loop = function (attr) {
        Object.defineProperty(Model.prototype, attr, {
          get: function get() {
            return this.get(attr);
          },
          set: function set(value) {
            return this.set(attr, value);
          }
        });
      };

      for (var attr in attrs) {
        _loop(attr);
      }
      Model.attributes = attrs;
      return Model;
    }
  }]);

  return Blueprint;
})();

Blueprint.type = _Type2['default'];

exports['default'] = Blueprint;
module.exports = exports['default'];<|endoftext|>


<commit_before>// TODO:
// Remove 6 magic number
// Remove animationSpeed magic numbers

var SwipeCards = (function () {
	'use strict';

	var h = {};
	var direction = false;
	var cardEls = {};

	/**
	 * Easy shortener for handling adding and removing body classes.
	 */
	var bodyClass = document.body.classList;

	/**
	 * Initialize pull to refresh, hammer, and bind touch events.
	 */
	var init = function() {
		_setCardEls();
		_bindTouchEvents();
	};

	var _bindTouchEvents = function() {
		h = new Hammer( cardEls.active, { preventDefault: true } );

		h.on( 'dragup', _dragUp );
		h.on( 'dragdown', _dragDown );
		h.on( 'dragend', _dragEnd );

		cardEls.before.removeEventListener( 'touchstart', _disableTouch );
		cardEls.active.removeEventListener( 'touchstart', _disableTouch );
		cardEls.after.removeEventListener( 'touchstart', _disableTouch );
	};

	var _unbindTouchEvents = function() {
		h.off( 'dragup', _dragUp );
		h.off( 'dragdown', _dragDown );
		h.off( 'dragend', _dragEnd );

		cardEls.before.addEventListener( 'touchstart', _disableTouch, false );
		cardEls.active.addEventListener( 'touchstart', _disableTouch, false );
		cardEls.after.addEventListener( 'touchstart', _disableTouch, false );
	};

	var _setCardEls = function() {
		cardEls.before = document.getElementsByClassName( 'before' )[0];
		cardEls.active = document.getElementsByClassName( 'active' )[0];
		cardEls.after = document.getElementsByClassName( 'after' )[0];
	};

	var _dragUp = function( e ) {
		var dragDistance = e.gesture.distance,
			distancePercent = Math.round( ( e.gesture.distance / window.innerHeight ) * 100 ),
			opacity = ( distancePercent < 30 ) ? 30 : distancePercent;

		cardEls.active.style.transform = cardEls.active.style.webkitTransform = 'translate3d( 0, -' + dragDistance + 'px, 0 )';

		cardEls.after.style.transform = cardEls.after.style.webkitTransform = 'translate3d( 0, ' + ( window.innerHeight - dragDistance ) + 'px, 0 )';
		cardEls.after.style.opacity = '.' + opacity
	};

	var _dragDown = function( e ) {
		var dragDistance = e.gesture.distance,
			distancePercent = Math.round( ( e.gesture.distance / window.innerHeight ) * 100 ),
			opacity = ( distancePercent < 30 ) ? 30 : distancePercent;

		cardEls.active.style.transform = cardEls.active.style.webkitTransform = 'translate3d( 0, ' + dragDistance + 'px, 0 )';

		cardEls.before.style.transform = cardEls.before.style.webkitTransform = 'translate3d( 0, ' + ( -window.innerHeight + dragDistance ) +  'px, 0 )';
		cardEls.before.style.opacity = '.' + opacity
	};

	var _dragEnd = function( e ) {
		direction = e.gesture.direction;

		// Disable hammer and any touch events until animation is complete.
		_unbindTouchEvents();

		var animationSpeed = ( e.gesture.velocityY > 2.5 ) ? 100 : 250;

		cardEls.active.classList.add( 'animate' );
		cardEls.active.style.transition = 'all ' + animationSpeed + 'ms ease';
		cardEls.before.style.transition = 'all ' + animationSpeed + 'ms ease';
		cardEls.after.style.transition = 'all ' + animationSpeed + 'ms ease';

		// Finish the transition after swipe.
		if ( 'up' == direction ) {
			_completeUpTransition( e );
		} else {
			_completeDownTransition( e );
		}

		// When the transition is done shift around the active post.
		cardEls.active.addEventListener( 'transitionend', _shiftActivePost, false );
	};

	var _shiftActivePost = function( e ) {
		cardEls.active.classList.remove( 'animate' );

		[ 'animate', 'dragup-reset', 'drag-complete', 'dragup-complete', 'dragdown-reset', 'dragdown-complete' ].forEach( function( c ) {
			bodyClass.remove( c );
		} );

		cardEls.before.style.transition = '';
		cardEls.before.style.transform = cardEls.before.style.webkitTransform = '';
		cardEls.before.style.opacity = '';

		cardEls.active.style.transition = '';
		cardEls.active.style.transform = cardEls.active.style.webkitTransform = '';
		cardEls.active.style.opacity = '';

		cardEls.after.style.transition = '';
		cardEls.after.style.transform = cardEls.after.style.webkitTransform = '';
		cardEls.after.style.opacity = '';

		cardEls.active.removeEventListener( 'transitionend', _shiftActivePost, false );

		// If the transition to a new post was successful then shift posts
		if ( cardEls.active.classList.contains( 'drag-complete' ) ) {
			if ( 'up' == direction ) {
				cardEls.before.classList.remove( 'before' );
				cardEls.before.classList.add( 'after' );

				cardEls.active.classList.remove( 'active' );
				cardEls.active.classList.remove( 'drag-complete' );
				cardEls.active.classList.add( 'before' );

				cardEls.after.classList.remove( 'after' );
				cardEls.after.classList.add( 'active' );
			} else {
				cardEls.before.classList.remove( 'before' );
				cardEls.before.classList.add( 'active' );

				cardEls.active.classList.remove( 'active' );
				cardEls.active.classList.remove( 'drag-complete' );
				cardEls.active.classList.add( 'after' );

				cardEls.after.classList.remove( 'after' );
				cardEls.after.classList.add( 'before' );
			}
		}

		// Reselect post nodes
		_setCardEls();

		// re-enable drag events.
		_bindTouchEvents();
	};

	var _completeUpTransition = function( e ) {
		if ( e.gesture.distance < window.innerHeight / 6 ) {
			bodyClass.add( 'dragup-reset' );
		} else {
			bodyClass.add( 'dragup-complete' )
			cardEls.active.classList.add( 'drag-complete' );
		}
	};

	var _completeDownTransition = function( e ) {
		if ( e.gesture.distance < window.innerHeight / 6 ) {
			bodyClass.add( 'dragdown-reset' );
		} else {
			bodyClass.add( 'dragdown-complete' )
			cardEls.active.classList.add( 'drag-complete' );
		}
	};

	var _disableTouch = function( e ) {
		e.preventDefault();
	};

	return {
		init: init
	}

})();<commit_msg>Further cleanup<commit_after>// TODO:
// Remove 6 magic number
// Remove animationSpeed magic numbers

var SwipeCards = (function () {
	'use strict';

	var h = {};
	var direction = false;
	var cardEls = {};

	/**
	 * Easy shortener for handling adding and removing body classes.
	 */
	var bodyClass = document.body.classList;

	/**
	 * Initialize pull to refresh, hammer, and bind touch events.
	 */
	var init = function() {
		_setCardEls();
		_bindTouchEvents();
	};

	var _bindTouchEvents = function() {
		h = new Hammer( cardEls.active, { preventDefault: true } );

		h.on( 'dragup', _dragUp );
		h.on( 'dragdown', _dragDown );
		h.on( 'dragend', _dragEnd );

		for( var card in cardEls ) {
			cardEls[card].removeEventListener( 'touchstart', _disableTouch );
		}
	};

	var _unbindTouchEvents = function() {
		h.off( 'dragup', _dragUp );
		h.off( 'dragdown', _dragDown );
		h.off( 'dragend', _dragEnd );

		for( var card in cardEls ) {
			cardEls[card].addEventListener( 'touchstart', _disableTouch, false );
		}
	};

	var _setCardEls = function() {
		cardEls.before = document.getElementsByClassName( 'before' )[0];
		cardEls.active = document.getElementsByClassName( 'active' )[0];
		cardEls.after = document.getElementsByClassName( 'after' )[0];
	};

	var _dragUp = function( e ) {
		var distancePercent = Math.round( ( e.gesture.distance / window.innerHeight ) * 100 );
		var opacity = ( distancePercent < 30 ) ? 30 : distancePercent;

		cardEls.active.style.transform = cardEls.active.style.webkitTransform = 'translate3d( 0, -' + e.gesture.distance + 'px, 0 )';
		cardEls.after.style.transform  = cardEls.after.style.webkitTransform = 'translate3d( 0, ' + ( window.innerHeight - e.gesture.distance ) + 'px, 0 )';
		cardEls.after.style.opacity = '.' + opacity;
	};

	var _dragDown = function( e ) {
		var distancePercent = Math.round( ( e.gesture.distance / window.innerHeight ) * 100 );
		var opacity = ( distancePercent < 30 ) ? 30 : distancePercent;

		cardEls.active.style.transform = cardEls.active.style.webkitTransform = 'translate3d( 0, ' + e.gesture.distance + 'px, 0 )';
		cardEls.before.style.transform = cardEls.before.style.webkitTransform = 'translate3d( 0, ' + ( -window.innerHeight + e.gesture.distance ) +  'px, 0 )';
		cardEls.before.style.opacity = '.' + opacity;
	};

	var _dragEnd = function( e ) {
		direction = e.gesture.direction;

		// Disable hammer and any touch events until animation is complete.
		_unbindTouchEvents();

		console.log( e.gesture.velocityY );

		var animationSpeed = ( e.gesture.velocityY > 2 ) ? 100 : 200;

		cardEls.active.classList.add( 'animate' );

		for( var card in cardEls ) {
			cardEls[card].style.transition = 'all ' + animationSpeed + 'ms ease';
		}

		// Finish the transition after swipe.
		if ( 'up' == direction ) {
			_completeUpTransition( e );
		} else {
			_completeDownTransition( e );
		}

		// When the transition is done shift around the active post.
		cardEls.active.addEventListener( 'transitionend', _shiftActivePost, false );
	};

	var _shiftActivePost = function( e ) {
		cardEls.active.classList.remove( 'animate' );

		[ 'animate', 'dragup-reset', 'drag-complete', 'dragup-complete', 'dragdown-reset', 'dragdown-complete' ].forEach( function( c ) {
			bodyClass.remove( c );
		} );

		for( var card in cardEls ) {
			cardEls[card].style.transition = '';
			cardEls[card].style.transform = cardEls[card].style.webkitTransform = '';
			cardEls[card].style.opacity = '';
		}

		cardEls.active.removeEventListener( 'transitionend', _shiftActivePost, false );

		// If the transition to a new post was successful then shift posts
		if ( cardEls.active.classList.contains( 'drag-complete' ) ) {
			_setCardClasses();
		}

		// Reselect card elements
		_setCardEls();

		// re-enable drag events.
		_bindTouchEvents();
	};

	var _setCardClasses = function() {
		var beforeCard = cardEls.before.classList;
		var activeCard = cardEls.active.classList;
		var afterCard = cardEls.after.classList;

		if ( 'up' == direction ) {
			beforeCard.remove( 'before' );
			beforeCard.add( 'after' );

			activeCard.remove( 'active' );
			activeCard.remove( 'drag-complete' );
			activeCard.add( 'before' );

			afterCard.remove( 'after' );
			afterCard.add( 'active' );
		} else {
			beforeCard.remove( 'before' );
			beforeCard.add( 'active' );

			activeCard.remove( 'active' );
			activeCard.remove( 'drag-complete' );
			activeCard.add( 'after' );

			afterCard.remove( 'after' );
			afterCard.add( 'before' );
		}
	};

	var _completeUpTransition = function( e ) {
		if ( e.gesture.distance < window.innerHeight / 6 ) {
			bodyClass.add( 'dragup-reset' );
		} else {
			bodyClass.add( 'dragup-complete' );
			cardEls.active.classList.add( 'drag-complete' );
		}
	};

	var _completeDownTransition = function( e ) {
		if ( e.gesture.distance < window.innerHeight / 6 ) {
			bodyClass.add( 'dragdown-reset' );
		} else {
			bodyClass.add( 'dragdown-complete' );
			cardEls.active.classList.add( 'drag-complete' );
		}
	};

	var _disableTouch = function( e ) {
		e.preventDefault();
	};

	return {
		init: init
	}

})();<|endoftext|>


<commit_before>var createCharm = require('charm');
var inherits = require('inherits');
var EventEmitter = require('events').EventEmitter;
var through = require('through2');
var duplexer = require('duplexer2');
var visualwidth = require('visualwidth');
var Duplex = require('readable-stream/duplex');

module.exports = function (opts) {
    return new Menu(opts || {});
}

inherits(Menu, EventEmitter);

function Menu (opts) {
    var self = this;
    self.width = opts.width || 50;
    self.x = opts.x || 1;
    self.y = opts.y || 1;
    self.init = { x: self.x, y: self.y };
    self.items = [];
    self.lines = {};
    self.selected = opts.selected || 0;
    self.colors = {
        fg: opts.fg || 'white',
        bg: opts.bg || 'blue'
    };

    self.padding = opts.padding || {
        left: 2,
        right: 2,
        top: 1,
        bottom: 1
    };
    if (typeof self.padding === 'number') {
        self.padding = {
            left: self.padding,
            right: self.padding,
            top: self.padding,
            bottom: self.padding
        };
    }
    self.x += self.padding.left;
    self.y += self.padding.top;
    self.size = {
        x: self.width + self.padding.left + self.padding.right
    };

    self._input = through(
        function (buf, enc, next) {
            self._ondata(buf);
            next();
        },
        function () { self.emit('close') }
    );
    self._output = through();
    self.charm = opts.charm || createCharm({
        input: self._input
    });
    self.charm.on('error', function () {});
    self.charm.pipe(self._output);

    self.stream = self.charm.pipe(through());
    try {
        self.charm.display('reset');
        self.charm.display('bright');
    }
    catch (e) {}

    process.nextTick(function () {
        self._ticked = true;
        self.charm.cursor(false);
        self._draw();
    });
};

Menu.prototype.createStream = function () {
    return duplexer(this._input, this._output);
};

Menu.prototype.add = function (label, cb) {
    var index = this.items.length;
    if (cb) {
        this.on('select', function (x, ix) {
            if (ix === index) cb(x, ix);
        });
    }

    this.items.push({
        x: this.x,
        y: this.y,
        label: label
    });
    this._fillLine(this.y);
    this.y ++;
};

Menu.prototype._fillLine = function (y) {
    if (!this.lines[y]) {
        this.charm.position(this.init.x, y);
        this.charm.write(Array(1 + this.size.x).join(' '));
        this.lines[y] = true;
    }
};

Menu.prototype.jump = function (name) {
    var index = typeof name === 'number'
        ? name
        : this.items
            .map(function (item) { return item.label })
            .indexOf(name)
    ;
    if (index < 0) return;
    var prev = this.selected;
    this.selected = index;
    if (this._ticked) {
        this._drawRow(prev);
        this._drawRow(index);
    }
};

Menu.prototype.close = function () {
    this._input.end();
    this.charm.cursor(true);
    this.charm.display('reset');
    this.charm.position(1, this.y + 1);
    this.charm.end();
};

Menu.prototype.reset = function () {
    this.charm.reset();
};

Menu.prototype.write = function (msg) {
    this.charm.background(this.colors.bg);
    this.charm.foreground(this.colors.fg);
    this._fillLine(this.y);

    var parts = msg.split('\n');

    for (var i = 0; i < parts.length; i++) {
        if (parts[i].length) {
            this.charm.position(this.x, this.y);
            this.charm.write(parts[i]);
        }
        if (i !== parts.length - 1) {
            this.x = this.init.x + this.padding.left;
            this._fillLine(this.y);
            this.y ++;
        }
    }
};

Menu.prototype._draw = function () {
    for (var i = 0; i < this.padding.top; i++) {
        this._fillLine(this.init.y + i);
    }
    for (var i = 0; i < this.items.length; i++) this._drawRow(i);

    // reset foreground and background colors
    this.charm.background(this.colors.bg);
    this.charm.foreground(this.colors.fg);

    for (var i = 0; i < this.padding.bottom; i++) {
        this._fillLine(this.y + i);
    }
};

Menu.prototype._drawRow = function (index) {
    index = (index + this.items.length) % this.items.length;
    var item = this.items[index];
    this.charm.position(item.x, item.y);

    if (this.selected === index) {
        this.charm.background(this.colors.fg);
        this.charm.foreground(this.colors.bg);
    }
    else {
        this.charm.background(this.colors.bg);
        this.charm.foreground(this.colors.fg);
    }

    var len = this.width - visualwidth.width(item.label, true) + 1;
    this.charm.write(item.label + Array(Math.max(0, len)).join(' '));
};

Menu.prototype._ondata = function ondata (buf) {
    var bytes = [].slice.call(buf);
    while (bytes.length) {
        var codes = [].join.call(bytes, '.');
        if (/^(27.91.65|27,79.65|107)\b/.test(codes)) { // up or k
            this.selected = (this.selected - 1 + this.items.length)
                % this.items.length
            ;
            this._drawRow(this.selected + 1);
            this._drawRow(this.selected);
            if (/^107\b/.test(codes)) bytes.shift()
            else bytes.splice(0, 3);
        }
        if (/^(27.91.66|27.79.66|106)\b/.test(codes)) { // down or j
            this.selected = (this.selected + 1) % this.items.length;
            this._drawRow(this.selected - 1);
            this._drawRow(this.selected);
            if (/^106\b/.test(codes)) bytes.shift()
            else bytes.splice(0, 3);
        }
        else if (/^(3|113)/.test(codes)) { // ^C or q
            this.charm.reset();
            this._input.end();
            this._output.end();
            bytes.shift();
        }
        else if (/^(13|10)\b/.test(codes)) { // enter
            this.charm.position(1, this.items[this.items.length-1].y + 2);
            this.charm.display('reset');
            this.emit('select', this.items[this.selected].label, this.selected);
            bytes.shift();
        }
        else bytes.shift();
    }
};
<commit_msg>fix merge conflict with emacs versions<commit_after>var createCharm = require('charm');
var inherits = require('inherits');
var EventEmitter = require('events').EventEmitter;
var through = require('through2');
var duplexer = require('duplexer2');
var visualwidth = require('visualwidth');
var Duplex = require('readable-stream/duplex');

module.exports = function (opts) {
    return new Menu(opts || {});
}

inherits(Menu, EventEmitter);

function Menu (opts) {
    var self = this;
    self.width = opts.width || 50;
    self.x = opts.x || 1;
    self.y = opts.y || 1;
    self.init = { x: self.x, y: self.y };
    self.items = [];
    self.lines = {};
    self.selected = opts.selected || 0;
    self.colors = {
        fg: opts.fg || 'white',
        bg: opts.bg || 'blue'
    };

    self.padding = opts.padding || {
        left: 2,
        right: 2,
        top: 1,
        bottom: 1
    };
    if (typeof self.padding === 'number') {
        self.padding = {
            left: self.padding,
            right: self.padding,
            top: self.padding,
            bottom: self.padding
        };
    }
    self.x += self.padding.left;
    self.y += self.padding.top;
    self.size = {
        x: self.width + self.padding.left + self.padding.right
    };

    self._input = through(
        function (buf, enc, next) {
            self._ondata(buf);
            next();
        },
        function () { self.emit('close') }
    );
    self._output = through();
    self.charm = opts.charm || createCharm({
        input: self._input
    });
    self.charm.on('error', function () {});
    self.charm.pipe(self._output);

    self.stream = self.charm.pipe(through());
    try {
        self.charm.display('reset');
        self.charm.display('bright');
    }
    catch (e) {}

    process.nextTick(function () {
        self._ticked = true;
        self.charm.cursor(false);
        self._draw();
    });
};

Menu.prototype.createStream = function () {
    return duplexer(this._input, this._output);
};

Menu.prototype.add = function (label, cb) {
    var index = this.items.length;
    if (cb) {
        this.on('select', function (x, ix) {
            if (ix === index) cb(x, ix);
        });
    }

    this.items.push({
        x: this.x,
        y: this.y,
        label: label
    });
    this._fillLine(this.y);
    this.y ++;
};

Menu.prototype._fillLine = function (y) {
    if (!this.lines[y]) {
        this.charm.position(this.init.x, y);
        this.charm.write(Array(1 + this.size.x).join(' '));
        this.lines[y] = true;
    }
};

Menu.prototype.jump = function (name) {
    var index = typeof name === 'number'
        ? name
        : this.items
            .map(function (item) { return item.label })
            .indexOf(name)
    ;
    if (index < 0) return;
    var prev = this.selected;
    this.selected = index;
    if (this._ticked) {
        this._drawRow(prev);
        this._drawRow(index);
    }
};

Menu.prototype.close = function () {
    this._input.end();
    this.charm.cursor(true);
    this.charm.display('reset');
    this.charm.position(1, this.y + 1);
    this.charm.end();
};

Menu.prototype.reset = function () {
    this.charm.reset();
};

Menu.prototype.write = function (msg) {
    this.charm.background(this.colors.bg);
    this.charm.foreground(this.colors.fg);
    this._fillLine(this.y);

    var parts = msg.split('\n');

    for (var i = 0; i < parts.length; i++) {
        if (parts[i].length) {
            this.charm.position(this.x, this.y);
            this.charm.write(parts[i]);
        }
        if (i !== parts.length - 1) {
            this.x = this.init.x + this.padding.left;
            this._fillLine(this.y);
            this.y ++;
        }
    }
};

Menu.prototype._draw = function () {
    for (var i = 0; i < this.padding.top; i++) {
        this._fillLine(this.init.y + i);
    }
    for (var i = 0; i < this.items.length; i++) this._drawRow(i);

    // reset foreground and background colors
    this.charm.background(this.colors.bg);
    this.charm.foreground(this.colors.fg);

    for (var i = 0; i < this.padding.bottom; i++) {
        this._fillLine(this.y + i);
    }
};

Menu.prototype._drawRow = function (index) {
    index = (index + this.items.length) % this.items.length;
    var item = this.items[index];
    this.charm.position(item.x, item.y);

    if (this.selected === index) {
        this.charm.background(this.colors.fg);
        this.charm.foreground(this.colors.bg);
    }
    else {
        this.charm.background(this.colors.bg);
        this.charm.foreground(this.colors.fg);
    }

    var len = this.width - visualwidth.width(item.label, true) + 1;
    this.charm.write(item.label + Array(Math.max(0, len)).join(' '));
};

Menu.prototype._ondata = function ondata (buf) {
    var bytes = [].slice.call(buf);
    while (bytes.length) {
        var codes = [].join.call(bytes, '.');
        if (/^(27.91.65|27,79.65|107|16)\b/.test(codes)) { // up or k
            this.selected = (this.selected - 1 + this.items.length)
                % this.items.length
            ;
            this._drawRow(this.selected + 1);
            this._drawRow(this.selected);
            if (/^107\b/.test(codes)) bytes.shift()
            else bytes.splice(0, 3);
        }
        if (/^(27.91.66|27.79.66|106|14)\b/.test(codes)) { // down or j
            this.selected = (this.selected + 1) % this.items.length;
            this._drawRow(this.selected - 1);
            this._drawRow(this.selected);
            if (/^106\b/.test(codes)) bytes.shift()
            else bytes.splice(0, 3);
        }
        else if (/^(3|113)/.test(codes)) { // ^C or q
            this.charm.reset();
            this._input.end();
            this._output.end();
            bytes.shift();
        }
        else if (/^(13|10)\b/.test(codes)) { // enter
            this.charm.position(1, this.items[this.items.length-1].y + 2);
            this.charm.display('reset');
            this.emit('select', this.items[this.selected].label, this.selected);
            bytes.shift();
        }
        else bytes.shift();
    }
};
<|endoftext|>


<commit_before>var util = require('./lib/util')
	, mkdirp = require('mkdirp')
	, _ = require('underscore')
	, path = require('path');

/** Function: defaultPathBuilder
 * This function builds paths for a screenshot file. It is appended to the
 * constructors base directory and gets prependend with `.png` or `.json` when
 * storing a screenshot or JSON meta data file.
 *
 * Parameters:
 *     (Object) spec - The spec currently reported
 *     (Array) descriptions - The specs and their parent suites descriptions
 *     (Object) result - The result object of the current test spec.
 *     (Object) capabilities - WebDrivers capabilities object containing
 *                             in-depth information about the Selenium node
 *                             which executed the test case.
 *
 * Returns:
 *     (String) containing the built path
 */
function defaultPathBuilder(spec, descriptions, results, capabilities) {
	return util.generateGuid();
}

/** Function: defaultMetaDataBuilder
 * Uses passed information to generate a meta data object which can be saved
 * along with a screenshot.
 * You do not have to add the screenshots file path since this will be appended
 * automatially.
 *
 * Parameters:
 *     (Object) spec - The spec currently reported
 *     (Array) descriptions - The specs and their parent suites descriptions
 *     (Object) result - The result object of the current test spec.
 *     (Object) capabilities - WebDrivers capabilities object containing
 *                             in-depth information about the Selenium node
 *                             which executed the test case.
 *
 * Returns:
 *     (Object) containig meta data to store along with a screenshot
 */
function defaultMetaDataBuilder(spec, descriptions, results, capabilities) {
	var metaData = {
			description: descriptions.join(' ')
			, passed: results.passed()
			, os: capabilities.caps_.platform
			, browser: {
				name: capabilities.caps_.browserName
				, version: capabilities.caps_.version
			}
		};
	metaData.trace = "";
	metaData.message = "";
	if (results.startTime && results.endTime) {
		metaData.startTime = results.startTime;
		metaData.endTime = results.endTime;
		metaData.message += "
Start time: " + new Date(metaData.startTime).toISOString();
		metaData.message += "
End time: " + new Date(metaData.endTime).toISOString();
		metaData.message += "
Total time: " + ((metaData.endTime - metaData.startTime) / 100).toString() + "s";
	}

	var testcaseID = metaData.description;
	var top = -1;

	//Check and display top custom message
	if (detailReport.hasOwnProperty(testcaseID)) {
		if (detailReport[testcaseID].hasOwnProperty(top)) {
			for (var j = 0; j < detailReport[testcaseID][top].length; j++) {
				if (detailReport[testcaseID][top][j].type === 'link') {
					metaData.message += '
' + detailReport[testcaseID][top][j].text + '
';
				}
				else {
					metaData.message += '
' + detailReport[testcaseID][top][j].text + '
';
				}
			}
		}
	}

	for (var i = 0; i < results.items_.length; i++) {
		var result = results.items_[i];
		var priorItem = "", inlineItem = "", postItem = "", item = "";

		//Check and display custom message for each verification point (expect())
		if (detailReport.hasOwnProperty(testcaseID)) {
			if (detailReport[testcaseID].hasOwnProperty(i)) {
				for (var j = 0; j < detailReport[testcaseID][i].length; j++) {
					if (detailReport[testcaseID][i][j].type === 'link') {
						item = '' + detailReport[testcaseID][i][j].text + '
';
					}
					else {
						item = detailReport[testcaseID][i][j].text + '
';
					}

					position = detailReport[testcaseID][i][j].position;
					if (position == 0) inlineItem += item;
					else if (position == 1) postItem += item;
					else priorItem += item;
				}
			}
		}

		metaData.message += '
';
		if (priorItem.length > 0)
			metaData.message += priorItem;
		metaData.message += '' + (i+1) + '. ';
		if (inlineItem.length > 0) metaData.message += inlineItem;

		//Refine output message before displaying
		var msg = result.message;
		if (result.message) {
			//cut the session info
			var index = msg.indexOf("(Session info:");
			if (index >= 0) {
				msg = msg.substr(0, index);
			}
			//format message in case html result
			index = msg.indexOf("<");
			if (index >= 0) {
				msg = msg.substr(0, index) + "
" + msg.substr(index) + "
";
			}
		}

		//Check and display expected result(s) for this test case
		if(results.passed()){//Test case is passed, that means all verification points (expect()) are passed
			metaData.message += msg || 'Passed.';
			metaData.trace += '
' + result.trace.stack;
		}else{//Test case is failed, that means at least one VP is failed
			if (result.message != 'Passed.') {//a failed VP in this failed case
				metaData.message += '';
				metaData.message += result.message + '' || 'Failed.';
			}
			else {//a pass VP in this failed case
				metaData.message += '';
				metaData.message += result.message + '' || 'Passed.';
			}
			metaData.trace += '
' + result.trace? ('
' + result.trace.stack + '
' || 'No Stack trace information') : 'No Stack trace information';
		}
		if (postItem.length > 0) metaData.message += '
' + postItem;
		metaData.message += '
';
	}

	return metaData;
}

/** Class: ScreenshotReporter
 * Creates a new screenshot reporter using the given `options` object.
 *
 * For more information, please look at the README.md file.
 *
 * Parameters:
 *     (Object) options - Object with options as described below.
 *
 * Possible options:
 *     (String) baseDirectory - The path to the directory where screenshots are
 *                              stored. If not existing, it gets created.
 *                              Mandatory.
 *     (Function) pathBuilder - A function which returns a path for a screenshot
 *                              to be stored. Optional.
 *     (Function) metaDataBuilder - Function which returns an object literal
 *                                  containing meta data to store along with
 *                                  the screenshot. Optional.
 *     (Boolean) takeScreenShotsForSkippedSpecs - Do you want to capture a
 *                                                screenshot for a skipped spec?
 *                                                Optional (default: false).
 */
function ScreenshotReporter(options) {
	options = options || {};
	if(!options.baseDirectory || options.baseDirectory.length === 0) {
		throw new Error('Please pass a valid base directory to store the ' +
			'screenshots into.');
	} else {
		this.baseDirectory = options.baseDirectory;
	}

	if(typeof (options.cssOverrideFile) !== 'undefined' && _.isString(options.cssOverrideFile) ){
		this.cssOverrideFile = options.cssOverrideFile;
	} else {
		this.cssOverrideFile = null;
	}

	this.pathBuilder = options.pathBuilder || defaultPathBuilder;
	this.docTitle = options.docTitle || 'Generated test report';
	this.docName = options.docName || 'report.html';
	this.metaDataBuilder = options.metaDataBuilder || defaultMetaDataBuilder;
	this.preserveDirectory = options.preserveDirectory || false;
	this.takeScreenShotsForSkippedSpecs =
		options.takeScreenShotsForSkippedSpecs || false;
		this.takeScreenShotsOnlyForFailedSpecs =
 		options.takeScreenShotsOnlyForFailedSpecs || false;
 	this.finalOptions = {
 		takeScreenShotsOnlyForFailedSpecs: this.takeScreenShotsOnlyForFailedSpecs,
 		takeScreenShotsForSkippedSpecs: this.takeScreenShotsForSkippedSpecs,
 		metaDataBuilder: this.metaDataBuilder,
 		pathBuilder: this.pathBuilder,
 		baseDirectory: this.baseDirectory,
 		docTitle: this.docTitle,
 		docName: this.docName,
 		cssOverrideFile: this.cssOverrideFile
 	};

 	if(!this.preserveDirectory){
 		util.removeDirectory(this.finalOptions.baseDirectory);
 	}
}

/** Function: reportSpecResults
 * Called by Jasmine when reporting results for a test spec. It triggers the
 * whole screenshot capture process and stores any relevant information.
 *
 * Parameters:
 *     (Object) spec - The test spec to report.
 */
ScreenshotReporter.prototype.reportSpecResults =
function reportSpecResults(spec) {
	/* global browser */
	var self = this
		, results = spec.results();

	//ignore setup and teardown
	if (results.description == labelBeforeAll || results.description == labelAfterAll)
		return;

	if(!self.takeScreenShotsForSkippedSpecs && results.skipped) {
		return;
	}

	browser.takeScreenshot().then(function (png) {
		browser.getCapabilities().then(function (capabilities) {
			var descriptions = util.gatherDescriptions(
					spec.suite
					, [spec.description]
				)

				, baseName = self.pathBuilder(
					spec
					, descriptions
					, results
					, capabilities
				)
				, metaData = self.metaDataBuilder(
					spec
					, descriptions
					, results
					, capabilities
				)

				, screenShotFile = baseName + '.png'
				, metaFile = baseName + '.json'
				, screenShotPath = path.join(self.baseDirectory, screenShotFile)
				, metaDataPath = path.join(self.baseDirectory, metaFile)

				// pathBuilder can return a subfoldered path too. So extract the
				// directory path without the baseName
				, directory = path.dirname(screenShotPath);

			metaData.screenShotFile = screenShotFile;
			mkdirp(directory, function(err) {
				if(err) {
					throw new Error('Could not create directory ' + directory);
				} else {
					util.addMetaData(metaData, metaDataPath, descriptions, self.finalOptions);
					console.log(metaData.description + (metaData.passed ? " - Pass" : " - Fail"));
					if(!(self.takeScreenShotsOnlyForFailedSpecs && results.passed())) {
						util.storeScreenShot(png, screenShotPath);
					}
					util.storeMetaData(metaData, metaDataPath);
				}
			});
		});
	});

};

module.exports = ScreenshotReporter;<commit_msg>fix bug in total time report<commit_after>var util = require('./lib/util')
	, mkdirp = require('mkdirp')
	, _ = require('underscore')
	, path = require('path');

/** Function: defaultPathBuilder
 * This function builds paths for a screenshot file. It is appended to the
 * constructors base directory and gets prependend with `.png` or `.json` when
 * storing a screenshot or JSON meta data file.
 *
 * Parameters:
 *     (Object) spec - The spec currently reported
 *     (Array) descriptions - The specs and their parent suites descriptions
 *     (Object) result - The result object of the current test spec.
 *     (Object) capabilities - WebDrivers capabilities object containing
 *                             in-depth information about the Selenium node
 *                             which executed the test case.
 *
 * Returns:
 *     (String) containing the built path
 */
function defaultPathBuilder(spec, descriptions, results, capabilities) {
	return util.generateGuid();
}

/** Function: defaultMetaDataBuilder
 * Uses passed information to generate a meta data object which can be saved
 * along with a screenshot.
 * You do not have to add the screenshots file path since this will be appended
 * automatially.
 *
 * Parameters:
 *     (Object) spec - The spec currently reported
 *     (Array) descriptions - The specs and their parent suites descriptions
 *     (Object) result - The result object of the current test spec.
 *     (Object) capabilities - WebDrivers capabilities object containing
 *                             in-depth information about the Selenium node
 *                             which executed the test case.
 *
 * Returns:
 *     (Object) containig meta data to store along with a screenshot
 */
function defaultMetaDataBuilder(spec, descriptions, results, capabilities) {
	var metaData = {
			description: descriptions.join(' ')
			, passed: results.passed()
			, os: capabilities.caps_.platform
			, browser: {
				name: capabilities.caps_.browserName
				, version: capabilities.caps_.version
			}
		};
	metaData.trace = "";
	metaData.message = "";
	if (results.startTime && results.endTime) {
		metaData.startTime = results.startTime;
		metaData.endTime = results.endTime;
		metaData.message += "
Start time: " + new Date(metaData.startTime).toISOString();
		metaData.message += "
End time: " + new Date(metaData.endTime).toISOString();
		metaData.message += "
Total time: " + (Math.round((metaData.endTime - metaData.startTime) / 100) / 10).toString() + "s";
	}

	var testcaseID = metaData.description;
	var top = -1;

	//Check and display top custom message
	if (detailReport.hasOwnProperty(testcaseID)) {
		if (detailReport[testcaseID].hasOwnProperty(top)) {
			for (var j = 0; j < detailReport[testcaseID][top].length; j++) {
				if (detailReport[testcaseID][top][j].type === 'link') {
					metaData.message += '
' + detailReport[testcaseID][top][j].text + '
';
				}
				else {
					metaData.message += '
' + detailReport[testcaseID][top][j].text + '
';
				}
			}
		}
	}

	for (var i = 0; i < results.items_.length; i++) {
		var result = results.items_[i];
		var priorItem = "", inlineItem = "", postItem = "", item = "";

		//Check and display custom message for each verification point (expect())
		if (detailReport.hasOwnProperty(testcaseID)) {
			if (detailReport[testcaseID].hasOwnProperty(i)) {
				for (var j = 0; j < detailReport[testcaseID][i].length; j++) {
					if (detailReport[testcaseID][i][j].type === 'link') {
						item = '' + detailReport[testcaseID][i][j].text + '
';
					}
					else {
						item = detailReport[testcaseID][i][j].text + '
';
					}

					position = detailReport[testcaseID][i][j].position;
					if (position == 0) inlineItem += item;
					else if (position == 1) postItem += item;
					else priorItem += item;
				}
			}
		}

		metaData.message += '
';
		if (priorItem.length > 0)
			metaData.message += priorItem;
		metaData.message += '' + (i+1) + '. ';
		if (inlineItem.length > 0) metaData.message += inlineItem;

		//Refine output message before displaying
		var msg = result.message;
		if (result.message) {
			//cut the session info
			var index = msg.indexOf("(Session info:");
			if (index >= 0) {
				msg = msg.substr(0, index);
			}
			//format message in case html result
			index = msg.indexOf("<");
			if (index >= 0) {
				msg = msg.substr(0, index) + "
" + msg.substr(index) + "
";
			}
		}

		//Check and display expected result(s) for this test case
		if(results.passed()){//Test case is passed, that means all verification points (expect()) are passed
			metaData.message += msg || 'Passed.';
			metaData.trace += '
' + result.trace.stack;
		}else{//Test case is failed, that means at least one VP is failed
			if (result.message != 'Passed.') {//a failed VP in this failed case
				metaData.message += '';
				metaData.message += result.message + '' || 'Failed.';
			}
			else {//a pass VP in this failed case
				metaData.message += '';
				metaData.message += result.message + '' || 'Passed.';
			}
			metaData.trace += '
' + result.trace? ('
' + result.trace.stack + '
' || 'No Stack trace information') : 'No Stack trace information';
		}
		if (postItem.length > 0) metaData.message += '
' + postItem;
		metaData.message += '
';
	}

	return metaData;
}

/** Class: ScreenshotReporter
 * Creates a new screenshot reporter using the given `options` object.
 *
 * For more information, please look at the README.md file.
 *
 * Parameters:
 *     (Object) options - Object with options as described below.
 *
 * Possible options:
 *     (String) baseDirectory - The path to the directory where screenshots are
 *                              stored. If not existing, it gets created.
 *                              Mandatory.
 *     (Function) pathBuilder - A function which returns a path for a screenshot
 *                              to be stored. Optional.
 *     (Function) metaDataBuilder - Function which returns an object literal
 *                                  containing meta data to store along with
 *                                  the screenshot. Optional.
 *     (Boolean) takeScreenShotsForSkippedSpecs - Do you want to capture a
 *                                                screenshot for a skipped spec?
 *                                                Optional (default: false).
 */
function ScreenshotReporter(options) {
	options = options || {};
	if(!options.baseDirectory || options.baseDirectory.length === 0) {
		throw new Error('Please pass a valid base directory to store the ' +
			'screenshots into.');
	} else {
		this.baseDirectory = options.baseDirectory;
	}

	if(typeof (options.cssOverrideFile) !== 'undefined' && _.isString(options.cssOverrideFile) ){
		this.cssOverrideFile = options.cssOverrideFile;
	} else {
		this.cssOverrideFile = null;
	}

	this.pathBuilder = options.pathBuilder || defaultPathBuilder;
	this.docTitle = options.docTitle || 'Generated test report';
	this.docName = options.docName || 'report.html';
	this.metaDataBuilder = options.metaDataBuilder || defaultMetaDataBuilder;
	this.preserveDirectory = options.preserveDirectory || false;
	this.takeScreenShotsForSkippedSpecs =
		options.takeScreenShotsForSkippedSpecs || false;
		this.takeScreenShotsOnlyForFailedSpecs =
 		options.takeScreenShotsOnlyForFailedSpecs || false;
 	this.finalOptions = {
 		takeScreenShotsOnlyForFailedSpecs: this.takeScreenShotsOnlyForFailedSpecs,
 		takeScreenShotsForSkippedSpecs: this.takeScreenShotsForSkippedSpecs,
 		metaDataBuilder: this.metaDataBuilder,
 		pathBuilder: this.pathBuilder,
 		baseDirectory: this.baseDirectory,
 		docTitle: this.docTitle,
 		docName: this.docName,
 		cssOverrideFile: this.cssOverrideFile
 	};

 	if(!this.preserveDirectory){
 		util.removeDirectory(this.finalOptions.baseDirectory);
 	}
}

/** Function: reportSpecResults
 * Called by Jasmine when reporting results for a test spec. It triggers the
 * whole screenshot capture process and stores any relevant information.
 *
 * Parameters:
 *     (Object) spec - The test spec to report.
 */
ScreenshotReporter.prototype.reportSpecResults =
function reportSpecResults(spec) {
	/* global browser */
	var self = this
		, results = spec.results();

	//ignore setup and teardown
	if (results.description == labelBeforeAll || results.description == labelAfterAll)
		return;

	if(!self.takeScreenShotsForSkippedSpecs && results.skipped) {
		return;
	}

	browser.takeScreenshot().then(function (png) {
		browser.getCapabilities().then(function (capabilities) {
			var descriptions = util.gatherDescriptions(
					spec.suite
					, [spec.description]
				)

				, baseName = self.pathBuilder(
					spec
					, descriptions
					, results
					, capabilities
				)
				, metaData = self.metaDataBuilder(
					spec
					, descriptions
					, results
					, capabilities
				)

				, screenShotFile = baseName + '.png'
				, metaFile = baseName + '.json'
				, screenShotPath = path.join(self.baseDirectory, screenShotFile)
				, metaDataPath = path.join(self.baseDirectory, metaFile)

				// pathBuilder can return a subfoldered path too. So extract the
				// directory path without the baseName
				, directory = path.dirname(screenShotPath);

			metaData.screenShotFile = screenShotFile;
			mkdirp(directory, function(err) {
				if(err) {
					throw new Error('Could not create directory ' + directory);
				} else {
					util.addMetaData(metaData, metaDataPath, descriptions, self.finalOptions);
					console.log(metaData.description + (metaData.passed ? " - Pass" : " - Fail"));
					if(!(self.takeScreenShotsOnlyForFailedSpecs && results.passed())) {
						util.storeScreenShot(png, screenShotPath);
					}
					util.storeMetaData(metaData, metaDataPath);
				}
			});
		});
	});

};

module.exports = ScreenshotReporter;<|endoftext|>


<commit_before>
var decorate = require('resultify')
var fs = require('fs')

module.exports = decorate(readSlice)
module.exports.plain = readSlice

/**
 * read a portion of `file`
 *
 * @param {String} file
 * @param {Number} from
 * @param {Number} to
 * @param {Function} cb(error, buffer)
 */

function readSlice(file, from, to, cb){
	var len = to - from
	var buf = new Buffer(len)
	fs.open(file, 'r', function(e, fd){
		if (e) cb(e)
		else fs.read(fd, buf, 0, len, from, function(e, bytes, buf){
			if (e) cb(e)
			else fs.close(fd, function(e){
				if (e) cb(e)
				else cb(null, buf)
			})
		})
	})
}
<commit_msg>ensure file is closed on error<commit_after>
var decorate = require('resultify')
var fs = require('fs')

module.exports = decorate(readSlice)
module.exports.plain = readSlice

/**
 * read a portion of `file`
 *
 * @param {String} file
 * @param {Number} from
 * @param {Number} to
 * @param {Function} cb(error, buffer)
 */

function readSlice(file, from, to, cb){
	var len = to - from
	var buf = new Buffer(len)
	fs.open(file, 'r', function(e, fd){
		if (e) cb(e)
		else fs.read(fd, buf, 0, len, from, function(e, bytes, buf){
			fs.close(fd, function(e2){
				if (e || e2) cb(e || e2)
				else cb(null, buf)
			})
		})
	})
}
<|endoftext|>


<commit_before>var _ = require('lodash');
var request = require('request');
var express = require('express');
var bodyParser = require('body-parser');
var Parse = require('parse/node');
var config = require('./config.json');
var configKeyWhitelist = ['minRating', 'maxRating'];

var loadReviewFromItunes = function(teamSettings, successCallback, errorCallback) {
  var appIdList = teamSettings.get('appIdList');
  var minRating = teamSettings.get('minRating') || 1;
  var maxRating = teamSettings.get('maxRating') || 5;
  var randomAppIndex = Math.floor(Math.random() * (appIdList.length));
  var randomAppId = appIdList[randomAppIndex];
  // Query iTunes
  request(config.itunesApi.baseUrl + randomAppId + '/' + config.itunesApi.responseFormat,
    function(error, response, body) {
      if (!error && response.statusCode == 200) {
        var appName;
        var entries = JSON.parse(body).feed.entry;
        var reviews = _.filter(entries, function(entry) {
          if (entry['im:rating']) {
            // All but the first entry have that property.
            if (entry['im:rating'].label >= minRating && entry['im:rating'].label <= maxRating) {
              return entry;
            } else {
              return null;
            }
          } else {
            // Voila, the first entry is the app info
            appName = entry['im:name'].label;
          }
        });

        // Alright, let's see if we've found some reviews
        if (reviews.length > 0) {
          // Woohoo, let's share some random review
          var randomIndex = Math.floor(Math.random() * (reviews.length - 0)) + 0;
          var randomReview = reviews[randomIndex];
          var randomReviewRating = randomReview['im:rating'].label;
          var randomReviewRatingStars = '';
          for (var i = 1; i <= 5; i++) {
            if (i <= randomReviewRating) {
              randomReviewRatingStars += '★';
            } else {
              randomReviewRatingStars += '☆';
            }
          }
          var randomReviewLabel = randomReview.content.label;
          var slackMessage = appName + '\n' + randomReviewRatingStars + ': ' + randomReviewLabel;
          successCallback(slackMessage);
        } else {
          var errorMessage = 'No reviews for app: ' + (appName ? appName : randomAppId);
          console.log(errorMessage);
          errorCallback(errorMessage);
        }
      }
    }
  );
};

var persistTeamSettings = function(teamDomain, appIdList, successCallback, errorCallback) {
  var TeamSettings = Parse.Object.extend('TeamSettings');
  var query = new Parse.Query(TeamSettings);
  query.equalTo('teamDomain', teamDomain);
  query.find({
    success: function(results) {
      // No settings yet, create a new entry
      if (results.length === 0) {
        var newTeamSettings = new TeamSettings();
        newTeamSettings.set('teamDomain', teamDomain);
        newTeamSettings.set('appIdList', appIdList);
        newTeamSettings.save(null, {
          success: function(persistedTeamSettings) {
            successCallback('Success! Your app IDs are persisted. From now on, use `/<your-slack-command>` to get a random review. For example: `/appreview`');
          },
          error: function(persistedTeamSettings, error) {
            // Execute any logic that should take place if the save fails.
            // error is a Parse.Error with an error code and message.
            console.log('Failed to create new object, with error code: ' + error.message);
            errorCallback('Failed to create new object, with error code: ' + error.message);
          }
        });
      // Settings available, update
      } else if (results.length === 1) {
        for (var index = 0; index < appIdList.length; index++) {
          var appId = appIdList[index];
          results[0].addUnique('appIdList', appId);
        }
        results[0].save();
        successCallback('Success! Your app IDs are persisted. From now on, use `/<your-slack-command>` to get a random review. For example: `/appreview`');
      // More than 1 settings entry for the given team... problem
      } else {
        console.log('More than 1 settings entry for team: ' + teamDomain);
        errorCallback('Something went wrong... Please try again');
      }
    },
    error: function(error) {
      console.log('Cannot load team settings for team: ' + teamDomain + '. Error: ' + error);
      errorCallback('Something went wrong... Please try again');
    }
  });
};

var listTeamSettings = function(teamDomain, successCallback, errorCallback) {
  // Load team settings
  var TeamSettings = Parse.Object.extend('TeamSettings');
  var query = new Parse.Query(TeamSettings);
  query.equalTo('teamDomain', teamDomain);
  query.find({
    success: function(results) {
      if (results.length === 0) {
        successCallback('We don\'t yet have any app IDs for your team. Please add some app IDs from iTunes Connect like so: `/<your-slack-command> add <comma separated app ID list>`. For example: `/appreview add 123,456`');
      } else if (results.length === 1) {
        var appIdArray = _.sortBy(results[0].get('appIdList'));
        var message = _.reduce(appIdArray, function(result, appId) {
          return result + '\n' + appId;
        });
        successCallback('Your team has the following app IDs configured:\n' + message);
      } else {
        console.log('0 or more than 1 settings entry for team: ' + teamDomain);
        errorCallback('Something went wrong... Please try again');
      }
    },
    error: function(error) {
      console.log('Cannot load team settings for team: ' + teamDomain + '. Error: ' + error);
      errorCallback('Something went wrong... Please try again');
    }
  });
};

var setTeamSettingsConfigValue = function(teamDomain, configKey, configValue, successCallback, errorCallback) {
  var TeamSettings = Parse.Object.extend('TeamSettings');
  var query = new Parse.Query(TeamSettings);
  query.equalTo('teamDomain', teamDomain);
  query.find({
    success: function(results) {
      // No settings yet, create a new entry
      if (results.length === 0) {
        var newTeamSettings = new TeamSettings();
        newTeamSettings.set('teamDomain', teamDomain);
        newTeamSettings.set('appIdList', []);
        if (_.contains(configKeyWhitelist, configKey)) {
          newTeamSettings.set(configKey, configValue);
        }
        newTeamSettings.save(null, {
          success: function(persistedTeamSettings) {
            successCallback('Success! Your settings are persisted.');
          },
          error: function(persistedTeamSettings, error) {
            // Execute any logic that should take place if the save fails.
            // error is a Parse.Error with an error code and message.
            console.log('Failed to create new object, with error code: ' + error.message);
            errorCallback('Failed to create new object, with error code: ' + error.message);
          }
        });
      // Settings available, update
      } else if (results.length === 1) {
        if (_.contains(configKeyWhitelist, configKey)) {
          results[0].set(configKey, configValue);
        }
        results[0].save();
        successCallback('Success! Your settings are persisted.');
      // More than 1 settings entry for the given team... problem
      } else {
        console.log('More than 1 settings entry for team: ' + teamDomain);
        errorCallback('Something went wrong... Please try again');
      }
    },
    error: function(error) {
      console.log('Cannot load team settings for team: ' + teamDomain + '. Error: ' + error);
      errorCallback('Something went wrong... Please try again');
    }
  });
};

var processCommand = function(slackCommand, teamDomain, parameters, successCallback, errorCallback) {
  var parametersArray = parameters.split(' ');
  var command = parametersArray[0];
  if (command === 'add') {
    var appIdList = parametersArray[1].split(',');
    persistTeamSettings(teamDomain, appIdList, successCallback, errorCallback);
  } else if (command === 'list') {
    listTeamSettings(teamDomain, successCallback, errorCallback);
  } else if (command === 'set') {
    var configKey = parametersArray[1].split('=')[0];
    var configValue = parametersArray[1].split('=')[1];
    setTeamSettingsConfigValue(teamDomain, configKey, configValue, successCallback, errorCallback);
  } else if (command === 'help') {
    var helpMessage = 'Welcome to `slack-app-reviews`!\n\n' +
      'Usage: `' + slackCommand + ' <command>`\n\n' +
      'where `<command>` is one of:\n' +
      '    `add`, `list`, `set`, `help`\n\n' +
      '`' + slackCommand + ' add <list of app IDs>    ` A comma separated list of app IDs from iTunes Connect\n' +
      '`' + slackCommand + ' list                     ` Lists all configured app IDs\n' +
      '`' + slackCommand + ' set <configKey>=<value>  ` where `<configKey>` is `minRating` or `maxRating`. A value between 1 and 5, inclusive\n' +
      '`' + slackCommand + ' help                     ` Help\n';
    successCallback(helpMessage);
  }
  // TODO: Add more commands: 'reset', 'remove'
};

var sendSlackResponse = function(res, message) {
  res.json({
    response_type: 'in_channel',
    text: message
  });
};

var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: false
}));

app.post('/app-review', function(req, res) {
  var teamDomain = req.body.team_domain;
  var user = req.body.user_name;
  var slackCommand = req.body.command;
  var parameters = req.body.text;
  console.log('Team: ' + teamDomain + '; user: ' + user + '; Slack command: ' + slackCommand + '; parameters: ' + parameters);

  // Load team settings
  var TeamSettings = Parse.Object.extend('TeamSettings');
  var query = new Parse.Query(TeamSettings);
  query.equalTo('teamDomain', teamDomain);
  query.find({
    success: function(results) {
      // No team settings persisted yet
      if (results.length === 0) {
        // If parameters were provided, process them
        if (parameters && parameters.length > 0) {
          processCommand(slackCommand, teamDomain, parameters, function(message) {
            sendSlackResponse(res, message);
          }, function(error) {
            sendSlackResponse(res, error);
          });
        } else {
          console.log('No command provided');
          sendSlackResponse(res, 'Please provide a command, such as `/appreview add 123,456,789`');
        }
      // Exactly one team settings row - perfect
      } else if (results.length === 1) {
        // If parameters were provided, process them
        if (parameters && parameters.length > 0) {
          processCommand(slackCommand, teamDomain, parameters, function(message) {
            sendSlackResponse(res, message);
          }, function(error) {
            sendSlackResponse(res, error);
          });
        // If no paramters were provided, load a review
        } else {
          var teamSettings = results[0];
          var appIdList = teamSettings.get('appIdList');
          if (appIdList && appIdList.length === 0) {
            // No settings yet for the given team domain
            sendSlackResponse(res, 'Hey there ' + user + '! We don\'t yet have any app IDs for your team. Please add some app IDs from iTunes Connect like so: `/<your-slack-command> add <comma separated app ID list>`. For example: `/appreview add 123,456`');
          } else {
            loadReviewFromItunes(teamSettings, function(message) {
              sendSlackResponse(res, message);
            }, function(error) {
              sendSlackResponse(res, error);
            });
          }
        }
      } else {
        console.log('More than 1 settings entry for team: ' + teamDomain);
        sendSlackResponse(res, 'Hey there ' + user + '! Something went wrong... Please try again');
      }
    },
    error: function(error) {
      console.log('Cannot load team settings for team: ' + teamDomain + '. Error: ' + error);
      sendSlackResponse(res, 'Hey there ' + user + '! Something went wrong... Please try again');
    }
  });
});

var port = process.env.PORT || 3000;
var server = app.listen(port, function() {
  Parse.initialize(process.env.PARSE_APP_ID, process.env.PARSE_APP_KEY);

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

  console.log('Example app listening at http://%s:%s', host, port);
});
<commit_msg>Updated help<commit_after>var _ = require('lodash');
var request = require('request');
var express = require('express');
var bodyParser = require('body-parser');
var Parse = require('parse/node');
var config = require('./config.json');
var configKeyWhitelist = ['minRating', 'maxRating'];

var loadReviewFromItunes = function(teamSettings, successCallback, errorCallback) {
  var appIdList = teamSettings.get('appIdList');
  var minRating = teamSettings.get('minRating') || 1;
  var maxRating = teamSettings.get('maxRating') || 5;
  var randomAppIndex = Math.floor(Math.random() * (appIdList.length));
  var randomAppId = appIdList[randomAppIndex];
  // Query iTunes
  request(config.itunesApi.baseUrl + randomAppId + '/' + config.itunesApi.responseFormat,
    function(error, response, body) {
      if (!error && response.statusCode == 200) {
        var appName;
        var entries = JSON.parse(body).feed.entry;
        var reviews = _.filter(entries, function(entry) {
          if (entry['im:rating']) {
            // All but the first entry have that property.
            if (entry['im:rating'].label >= minRating && entry['im:rating'].label <= maxRating) {
              return entry;
            } else {
              return null;
            }
          } else {
            // Voila, the first entry is the app info
            appName = entry['im:name'].label;
          }
        });

        // Alright, let's see if we've found some reviews
        if (reviews.length > 0) {
          // Woohoo, let's share some random review
          var randomIndex = Math.floor(Math.random() * (reviews.length - 0)) + 0;
          var randomReview = reviews[randomIndex];
          var randomReviewRating = randomReview['im:rating'].label;
          var randomReviewRatingStars = '';
          for (var i = 1; i <= 5; i++) {
            if (i <= randomReviewRating) {
              randomReviewRatingStars += '★';
            } else {
              randomReviewRatingStars += '☆';
            }
          }
          var randomReviewLabel = randomReview.content.label;
          var slackMessage = appName + '\n' + randomReviewRatingStars + ': ' + randomReviewLabel;
          successCallback(slackMessage);
        } else {
          var errorMessage = 'No reviews for app: ' + (appName ? appName : randomAppId);
          console.log(errorMessage);
          errorCallback(errorMessage);
        }
      }
    }
  );
};

var persistTeamSettings = function(teamDomain, appIdList, successCallback, errorCallback) {
  var TeamSettings = Parse.Object.extend('TeamSettings');
  var query = new Parse.Query(TeamSettings);
  query.equalTo('teamDomain', teamDomain);
  query.find({
    success: function(results) {
      // No settings yet, create a new entry
      if (results.length === 0) {
        var newTeamSettings = new TeamSettings();
        newTeamSettings.set('teamDomain', teamDomain);
        newTeamSettings.set('appIdList', appIdList);
        newTeamSettings.save(null, {
          success: function(persistedTeamSettings) {
            successCallback('Success! Your app IDs are persisted. From now on, use `/<your-slack-command>` to get a random review. For example: `/appreview`');
          },
          error: function(persistedTeamSettings, error) {
            // Execute any logic that should take place if the save fails.
            // error is a Parse.Error with an error code and message.
            console.log('Failed to create new object, with error code: ' + error.message);
            errorCallback('Failed to create new object, with error code: ' + error.message);
          }
        });
      // Settings available, update
      } else if (results.length === 1) {
        for (var index = 0; index < appIdList.length; index++) {
          var appId = appIdList[index];
          results[0].addUnique('appIdList', appId);
        }
        results[0].save();
        successCallback('Success! Your app IDs are persisted. From now on, use `/<your-slack-command>` to get a random review. For example: `/appreview`');
      // More than 1 settings entry for the given team... problem
      } else {
        console.log('More than 1 settings entry for team: ' + teamDomain);
        errorCallback('Something went wrong... Please try again');
      }
    },
    error: function(error) {
      console.log('Cannot load team settings for team: ' + teamDomain + '. Error: ' + error);
      errorCallback('Something went wrong... Please try again');
    }
  });
};

var listTeamSettings = function(teamDomain, successCallback, errorCallback) {
  // Load team settings
  var TeamSettings = Parse.Object.extend('TeamSettings');
  var query = new Parse.Query(TeamSettings);
  query.equalTo('teamDomain', teamDomain);
  query.find({
    success: function(results) {
      if (results.length === 0) {
        successCallback('We don\'t yet have any app IDs for your team. Please add some app IDs from iTunes Connect like so: `/<your-slack-command> add <comma separated app ID list>`. For example: `/appreview add 123,456`');
      } else if (results.length === 1) {
        var appIdArray = _.sortBy(results[0].get('appIdList'));
        var message = _.reduce(appIdArray, function(result, appId) {
          return result + '\n' + appId;
        });
        successCallback('Your team has the following app IDs configured:\n' + message);
      } else {
        console.log('0 or more than 1 settings entry for team: ' + teamDomain);
        errorCallback('Something went wrong... Please try again');
      }
    },
    error: function(error) {
      console.log('Cannot load team settings for team: ' + teamDomain + '. Error: ' + error);
      errorCallback('Something went wrong... Please try again');
    }
  });
};

var setTeamSettingsConfigValue = function(teamDomain, configKey, configValue, successCallback, errorCallback) {
  var TeamSettings = Parse.Object.extend('TeamSettings');
  var query = new Parse.Query(TeamSettings);
  query.equalTo('teamDomain', teamDomain);
  query.find({
    success: function(results) {
      // No settings yet, create a new entry
      if (results.length === 0) {
        var newTeamSettings = new TeamSettings();
        newTeamSettings.set('teamDomain', teamDomain);
        newTeamSettings.set('appIdList', []);
        if (_.contains(configKeyWhitelist, configKey)) {
          newTeamSettings.set(configKey, configValue);
        }
        newTeamSettings.save(null, {
          success: function(persistedTeamSettings) {
            successCallback('Success! Your settings are persisted.');
          },
          error: function(persistedTeamSettings, error) {
            // Execute any logic that should take place if the save fails.
            // error is a Parse.Error with an error code and message.
            console.log('Failed to create new object, with error code: ' + error.message);
            errorCallback('Failed to create new object, with error code: ' + error.message);
          }
        });
      // Settings available, update
      } else if (results.length === 1) {
        if (_.contains(configKeyWhitelist, configKey)) {
          results[0].set(configKey, configValue);
        }
        results[0].save();
        successCallback('Success! Your settings are persisted.');
      // More than 1 settings entry for the given team... problem
      } else {
        console.log('More than 1 settings entry for team: ' + teamDomain);
        errorCallback('Something went wrong... Please try again');
      }
    },
    error: function(error) {
      console.log('Cannot load team settings for team: ' + teamDomain + '. Error: ' + error);
      errorCallback('Something went wrong... Please try again');
    }
  });
};

var processCommand = function(slackCommand, teamDomain, parameters, successCallback, errorCallback) {
  var parametersArray = parameters.split(' ');
  var command = parametersArray[0];
  if (command === 'add') {
    var appIdList = parametersArray[1].split(',');
    persistTeamSettings(teamDomain, appIdList, successCallback, errorCallback);
  } else if (command === 'list') {
    listTeamSettings(teamDomain, successCallback, errorCallback);
  } else if (command === 'set') {
    var configKey = parametersArray[1].split('=')[0];
    var configValue = parametersArray[1].split('=')[1];
    setTeamSettingsConfigValue(teamDomain, configKey, configValue, successCallback, errorCallback);
  } else if (command === 'help') {
    var helpMessage = 'Welcome to `slack-app-reviews`!\n\n' +
      'Usage: `' + slackCommand + ' [<command>]`\n\n' +
      'where `<command>` is one of:\n' +
      '    `add`, `list`, `set`, `help`\n\n' +
      '`' + slackCommand + '                          ` Displays a random review\n' +
      '`' + slackCommand + ' add <list of app IDs>    ` A comma separated list of app IDs from iTunes Connect\n' +
      '`' + slackCommand + ' list                     ` Lists all configured app IDs\n' +
      '`' + slackCommand + ' set <configKey>=<value>  ` where `<configKey>` is `minRating` or `maxRating`. A value between 1 and 5, inclusive\n' +
      '`' + slackCommand + ' help                     ` Help\n';
    successCallback(helpMessage);
  }
  // TODO: Add more commands: 'reset', 'remove'
};

var sendSlackResponse = function(res, message) {
  res.json({
    response_type: 'in_channel',
    text: message
  });
};

var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: false
}));

app.post('/app-review', function(req, res) {
  var teamDomain = req.body.team_domain;
  var user = req.body.user_name;
  var slackCommand = req.body.command;
  var parameters = req.body.text;
  console.log('Team: ' + teamDomain + '; user: ' + user + '; Slack command: ' + slackCommand + '; parameters: ' + parameters);

  // Load team settings
  var TeamSettings = Parse.Object.extend('TeamSettings');
  var query = new Parse.Query(TeamSettings);
  query.equalTo('teamDomain', teamDomain);
  query.find({
    success: function(results) {
      // No team settings persisted yet
      if (results.length === 0) {
        // If parameters were provided, process them
        if (parameters && parameters.length > 0) {
          processCommand(slackCommand, teamDomain, parameters, function(message) {
            sendSlackResponse(res, message);
          }, function(error) {
            sendSlackResponse(res, error);
          });
        } else {
          console.log('No command provided');
          sendSlackResponse(res, 'Please provide a command, such as `/appreview add 123,456,789`');
        }
      // Exactly one team settings row - perfect
      } else if (results.length === 1) {
        // If parameters were provided, process them
        if (parameters && parameters.length > 0) {
          processCommand(slackCommand, teamDomain, parameters, function(message) {
            sendSlackResponse(res, message);
          }, function(error) {
            sendSlackResponse(res, error);
          });
        // If no paramters were provided, load a review
        } else {
          var teamSettings = results[0];
          var appIdList = teamSettings.get('appIdList');
          if (appIdList && appIdList.length === 0) {
            // No settings yet for the given team domain
            sendSlackResponse(res, 'Hey there ' + user + '! We don\'t yet have any app IDs for your team. Please add some app IDs from iTunes Connect like so: `/<your-slack-command> add <comma separated app ID list>`. For example: `/appreview add 123,456`');
          } else {
            loadReviewFromItunes(teamSettings, function(message) {
              sendSlackResponse(res, message);
            }, function(error) {
              sendSlackResponse(res, error);
            });
          }
        }
      } else {
        console.log('More than 1 settings entry for team: ' + teamDomain);
        sendSlackResponse(res, 'Hey there ' + user + '! Something went wrong... Please try again');
      }
    },
    error: function(error) {
      console.log('Cannot load team settings for team: ' + teamDomain + '. Error: ' + error);
      sendSlackResponse(res, 'Hey there ' + user + '! Something went wrong... Please try again');
    }
  });
});

var port = process.env.PORT || 3000;
var server = app.listen(port, function() {
  Parse.initialize(process.env.PARSE_APP_ID, process.env.PARSE_APP_KEY);

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

  console.log('Example app listening at http://%s:%s', host, port);
});
<|endoftext|>