<commit_before>
"use strict";

var WWW_Applet = null;

(function () {

  // ================================================================
  // Scope
  // ================================================================
  var Scope = function (parent) {

    var o     = {};
    var vars  = {};
    var stack = [];

    if (parent) {
      o.parent = parent;
    }

    var run_func = function (name, args) {
      var func = funcs[name];

      if (!func) {
        throw new Error("Function not found: " + name);
      }

      return func.apply(null, [o]);
    }; // run_func ===================================

    var val = function (name, val) {
      var args       = _.toArray(arguments);
      var return_val = null;
      switch (args.length) {

        case 1:
          if (!vars.hasOwnProperty(name) && !parent) {
          throw new Error('No var found with name: ' + name);
        }

        return_val = (vars.hasOwnProperty(name)) ?
          vars[name] :
          parent.read_var(name);
        break;

        case 2:
          vars[name] = val;
        return_val = val;
        break;

        default:
          throw new Error("Unknown value: " + args.length);

      } // === switch args.length

      return return_val;
    }; // val =========================================

    var push = function () {
      var args = _.toArray(arguments);
      stack    = stack.concat(args);
      return args;
    }; // push ========================================

    var right_concat = function (arr) {
      var o = this;
      o.stack = o.stack.concat(arr);
      return arr;
    }; // right_concat ================================

    var right = function () {
      return last_types(o.stack, _.toArray(arguments), 'stack');
    };

    var left = function () {
      return last_types(o.run_args(), _.toArray(arguments), 'args');
    };

    var replace_left = function (arr) {
      o._left_args = arr;
      return o._left_args;
    };

    var run_args = function (args) {
      if (o.args && args) {
        throw new Error('Args already given.');
      }

      if (args) {
        o.args = args;
      }

      if (!o.args && !args) {
        throw new Error("No args given.");
      }

      if (o._left_args) {
        return o._left_args;
      }

      o._left_args = o.child().run(args).stack;

      return o._left_args;
    }; // run_args

    var raw_args = function () {
      return o.args;
    }; // raw_args

    var child = function () {
      return( new Scope(o) );
    };

    var run = function (code_array) {
      each(code_array, function (item, l) {

        if (_.isArray(item)) {
          throw new Error("Syntax error: args w/o function name.");

        } else if (_.isString(item), _.isArray(l.next_or_null())) {
          run_func(item, l.grab_next());

        } else {
          stack.push(item);
        } // === if

      }); // === .each

      return o;
    }; // run ========================================

    o.vars  = vars;
    o.stack = stack;
    o.run   = run;

    return o;

  // ================================================================
  }; // ===== Scope =================================================
  // ================================================================

  // ================================================================
  // Helper Functions
  // ================================================================

  // From:
  // http://stackoverflow.com/questions/1026069/capitalize-the-first-letter-of-string-in-javascript
  var capitalize = function (s) {
    return s.charAt(0).toUpperCase() + s.substring(1);
  };

  var inspect = function (val) {
    var name = func_name(val.constructor || 'Unknown');
    return name + ': ' + val;
  };

  var is_numeric = function (val) {
    return _.isNumber(val) && !_.isNaN(val);
  };

  var log = function () {
    if (!window.console) { return; }
    return console.log.apply(console, arguments);
  };

  var func_name = function (raw) {
    var s    = null;
    var name = null;

    if (_.isString(raw)) {
      s = raw;
    } else {
      s = ('' + raw);
    }

    name = _.last( _.first(s.split('(')).split('function ') );
    return name;
  };

  var each = function (arr, func) {
    var l      = arr.length;
    var i      = 0;
    var each_o = {

      next: function () {
        if (each_o.is_last()) {
          throw new Error('No more items: i: ' + i + ' total: ' + l);
        }
        return arr[i + 1];
      }, // === next

      prev : function () {
        if (each_o.is_first()) {
          throw new Error('No previous items: i: ' + i + ' total: ' + l);
        }
        return arr[i - 1];
      },

      next_or_null: function () {
        if (each_o.is_last()) { return null; }
        return arr[i + 1];
      },

      prev_or_null : function () {
        if (each_o.is_first()) { return null; }
        return arr[i - 1];
      },

      is_first: function () {
        return i < 1;
      },

      is_last: function () {
        return i >= (l - 1);
      },

      grab_next: function () {
        if (each_o.is_last()) {
          throw new Error('Can\'t grab next because already at last position.');
        }
        i = i + 1;
        return arr[i];
      }

    }; // === each_o

    while ( i < l ) {
      each_o.i = i;
      func(arr[i], each_o);
      i = i + 1;
    } // while

  }; // === each

  var to_number_name = function (num) {
    var name = null;
    switch (num) {
      case 0:
        name = 'first';
      break;

      case 1:
        name = 'second';
      break;

      case 2:
        name = 'third';
      break;

      case 3:
        name = 'fourth';
      break;

      case 4:
        name = 'fifth';
      break;

      case 5:
        name = 'sixth';
      break;

      default:
      throw new Error("Unknown value: " + num);
    } // === switch num
    return name;
  }; // function

  var last_types = function (stack, types, stack_name) {
    var o = {};

    if (types.length === 0) {
      throw new Error("No arguments.");
    }

    var vals = _.last(stack, types.length);

    if (vals.length !== types.length) {
      throw new Error('Not enough values in ' + stack_name + '.');
    }

    _.each(types, function (t, i) {
      var val = vals[i];
      var ignore_type = t === 'any' || t === 'last';

      if (!ignore_type) {
        if (_.isString(t)) {
          throw new Error('Unknown position or type: ' + t);
        }

        if (val.constructor !== t) {
          throw new Error('Value in ' + stack_name + " is not a " + func_name(t) + ': ' + inspect(val));
        }
      }

      o[to_number_name(i)] = val;
    });

    if (types.length === 1) {
      return o.first;
    }

    return o;
  }; // last_types

  // ===========================================================================
  // === End Helper Functions ==================================================
  // ===========================================================================

  var funcs =  {
    "focus on" : function (o) {
      o.val('the focus', $(o.left(String)));
      return o.val('the focus');
    },

    "focus on ancestor" : function (o) {
      o.val('the focus', o.val('the focus').parents(o.left(String)));
      return o.val('the focus');
    },

    "submit" : function (o) {
      var form = $(o.val('the focus'));
      var action = form.attr('action');
      if (/[^a-zA-Z0-9\_\-\/]+/.test(action)) {
        throw new Error("Invalid chars in #" + form.attr('id') + " action: " + action);
      }
    },

    "array" : function (o) {
      o.push(o.run_args());
      return o.right('last');
    },

    "add class" : function (o) {
      o.val('the focus').addClass(o.left(String));
      return o.left('last');
    },

    "add to stack": function (o) {
      o.right_concat( o.run_args() );
      return o.run_args();
    },

    "compare numbers" : function (o) {
      var scope = o.scope;
      var first = o.right(Number);

      var args    = o.left(String, Number);
      var compare = args.first;
      var second  = args.second;

      var result = null;

      switch (compare) {

        case '===':
          result = first === second;
        break;

        case '<=':
          result = first <= second;
        break;

        case '<':
          result = first < second;
        break;

        case '>=':
          result = first >= second;
        break;

        case '>':
          result = first > second;
        break;

        default:
          throw new Error("Unknown value : " + compare);
      } // === switch

      o.push(result);
      return result;
    },

    "equal" : function (o) {
      var first  = o.right('any');
      var second = o.left('any');
      o.push(first === second);
      return o.right('last');
    },

    "less or equal" : function (o) {
      o.replace_left(['<=', o.left(Number)]);
      return funcs['compare numbers'](o);
    },

    "less" : function (o) {
      o.replace_left(['<', o.left(Number)]);
      return funcs['compare numbers'](o);
    },

    "bigger" : function (o) {
      o.replace_left(['>', o.left(Number)]);
      return funcs['compare numbers'](o);
    },

    "bigger or equal" : function (o) {
      o.replace_left(['>=', o.left(Number)]);
      return funcs['compare numbers'](o);
    },

    "and": function (o) {
      var first     = o.right(Boolean);
      var second    = null;
      var left_args = null;

      if (first === true) {
        second = o.left(Boolean);

        if (first === true && second === true) {
          o.push(true);
          return true;
        }
      }

      o.push(false);
      return false;
    }, // === and

    "or": function (o) {
      var first     = o.right(Boolean);
      var second    = null;
      var left_args = null;

      if (first === true) {
        o.push(true);
        return true;
      }

      second = o.left(Boolean);

      if (second === true) {
        o.push(true);
        return true;
      }

      o.push(false);
      return false;

    }, // === or

    "if true": function (o) {
      var first = o.right(Boolean);

      if (first) {
        o.run_args();
      }

      return null;
    },

    "if false": function (o) {
      var first = o.right(Boolean);

      if (!first) {
        o.run_args();
      }

      return null;
    },

    "on click": function (o) {
      var scope  = o.child();
      var focus  = scope.var('the focus');
      scope.var('the focus', focus);

      $(focus).click(function () {
        scope.run(o.raw_args());
      });

      return null;
    },

    "on": function (o) {
      return null;
    }

  }; // === funcs ===================================================

  WWW_Applet = {
    funcs : funcs,
    Scope : Scope,
    run   : function (code_array) {
      return (new Scope()).run(code_array);
    } // run
  };

})(); // === WWW_Applet scope

<commit_msg>Fixed: Scope implementation<commit_after>
"use strict";

var WWW_Applet = null;

(function () {

  // ================================================================
  // Helper Functions
  // ================================================================

  // From:
  // http://stackoverflow.com/questions/1026069/capitalize-the-first-letter-of-string-in-javascript
  var capitalize = function (s) {
    return s.charAt(0).toUpperCase() + s.substring(1);
  };

  var inspect = function (val) {
    var name = func_name(val.constructor || 'Unknown');
    return name + ': ' + val;
  };

  var is_numeric = function (val) {
    return _.isNumber(val) && !_.isNaN(val);
  };

  var log = function () {
    if (!window.console) { return; }
    return console.log.apply(console, arguments);
  };

  var func_name = function (raw) {
    var s    = null;
    var name = null;

    if (_.isString(raw)) {
      s = raw;
    } else {
      s = ('' + raw);
    }

    name = _.last( _.first(s.split('(')).split('function ') );
    return name;
  };

  var each = function (arr, func) {
    var l      = arr.length;
    var i      = 0;
    var each_o = {

      next: function () {
        if (each_o.is_last()) {
          throw new Error('No more items: i: ' + i + ' total: ' + l);
        }
        return arr[i + 1];
      }, // === next

      prev : function () {
        if (each_o.is_first()) {
          throw new Error('No previous items: i: ' + i + ' total: ' + l);
        }
        return arr[i - 1];
      },

      next_or_null: function () {
        if (each_o.is_last()) { return null; }
        return arr[i + 1];
      },

      prev_or_null : function () {
        if (each_o.is_first()) { return null; }
        return arr[i - 1];
      },

      is_first: function () {
        return i < 1;
      },

      is_last: function () {
        return i >= (l - 1);
      },

      grab_next: function () {
        if (each_o.is_last()) {
          throw new Error('Can\'t grab next because already at last position.');
        }
        i = i + 1;
        return arr[i];
      }

    }; // === each_o

    while ( i < l ) {
      each_o.i = i;
      func(arr[i], each_o);
      i = i + 1;
    } // while

  }; // === each

  var to_number_name = function (num) {
    var name = null;
    switch (num) {
      case 0:
        name = 'first';
      break;

      case 1:
        name = 'second';
      break;

      case 2:
        name = 'third';
      break;

      case 3:
        name = 'fourth';
      break;

      case 4:
        name = 'fifth';
      break;

      case 5:
        name = 'sixth';
      break;

      default:
      throw new Error("Unknown value: " + num);
    } // === switch num
    return name;
  }; // function

  var last_types = function (stack, types, stack_name) {
    var o = {};

    if (types.length === 0) {
      throw new Error("No arguments.");
    }

    var vals = _.last(stack, types.length);

    if (vals.length !== types.length) {
      throw new Error('Not enough values in ' + stack_name + '.');
    }

    _.each(types, function (t, i) {
      var val = vals[i];
      var ignore_type = t === 'any' || t === 'last';

      if (!ignore_type) {
        if (_.isString(t)) {
          throw new Error('Unknown position or type: ' + t);
        }

        if (val.constructor !== t) {
          throw new Error('Value in ' + stack_name + " is not a " + func_name(t) + ': ' + inspect(val));
        }
      }

      o[to_number_name(i)] = val;
    });

    if (types.length === 1) {
      return o.first;
    }

    return o;
  }; // last_types

  // ================================================================
  // === End Helper Functions =======================================
  // ================================================================

  // ================================================================
  // Scope
  // ================================================================
  var Scope = function (parent) {

    var o         = {};
    var vars      = {};
    var stack     = [];
    var first     = null;
    var second    = null;
    var left_args = null;

    if (parent) {
      o.parent = parent;
    }

    var right = function () {
      return last_types(stack, _.toArray(arguments), 'stack');
    };

    var run_func = function (name, raw_args) {

      var compiled_args = null;

      var run_args = function () {
        if (!compiled_args) {
          compiled_args = child().run(raw_args).stack;
        }

        return compiled_args;
      }; // run_args

      var left = function () {
        return last_types(run_args(), _.toArray(arguments), 'args');
      };

      var right_concat = function (arr) {
        stack = stack.concat(arr);
        return arr;
      }; // right_concat ================================

      var replace_left = function (arr) {
        compiled_args = arr;
        return compiled_args;
      };

      switch (name) {

        case 'focus on':
          val('the focus', $(left(String)));
          return val('the focus');

        case 'focus on ancestor':
          val('the focus', val('the focus').parents(left(String)));
          return val('the focus');

        case 'submit':
            var form   = $(val('the focus'));
            var action = form.attr('action');
            if (/[^a-zA-Z0-9\_\-\/]+/.test(action)) {
              throw new Error("Invalid chars in #" + form.attr('id') + " action: " + action);
            }
            return null;

        case 'array':
          push(run_args());
          return right('last');

        case 'add class':
          val('the focus').addClass(left(String));
          return left('last');

        case 'add to stack':
          right_concat( run_args() );
          return run_args();

        case "and":
          first     = right(Boolean);
          second    = null;
          left_args = null;

          if (first === true) {
            second = left(Boolean);

            if (first === true && second === true) {
              push(true);
              return true;
            }
          }

          push(false);
          return false;

        case "or":
          first     = right(Boolean);
          second    = null;
          left_args = null;

          if (first === true) {
            push(true);
            return true;
          }

          second = left(Boolean);

          if (second === true) {
            push(true);
            return true;
          }

          push(false);
          return false;

        case "if true":
          first = right(Boolean);

          if (first) {
            run_args();
          }

          return null;

        case "if false":
          first = right(Boolean);

          if (!first) {
            run_args();
          }

          return null;

        case "on click":
          var focus  = val('the focus');
          var scope  = child();
          scope.vars['the focus'] = focus;

          $(focus).click(function () {
            scope.run(raw_args);
          });

          return null;

        case "on":
          throw new Error('Not ready yet: on');

        case 'bigger or equal':
          return run_func('compare numbers', ['>=', left(Number)]);

        case 'bigger':
          return run_func('compare numbers', ['>', left(Number)]);

        case 'less':
          return run_func('compare numbers', ['<', left(Number)]);

        case 'less or equal':
          return run_func('compare numbers', ['<=', left(Number)]);

        case 'equal':
          first  = right('any');
          second = left('any');
          push(first === second);
          return right('last');

        case 'compare numbers':
          first = right(Number);

          var args    = left(String, Number);
          var compare = args.first;
          second  = args.second;

          var result = null;

          switch (compare) {

            case '===':
              result = first === second;
            break;

            case '<=':
              result = first <= second;
            break;

            case '<':
              result = first < second;
            break;

            case '>=':
              result = first >= second;
            break;

            case '>':
              result = first > second;
            break;

            default:
              throw new Error("Unknown value : " + compare);
          } // === switch

          o.push(result);
          return result;

        default:
          throw new Error("Function not found: " + name);
      } // === switch name

    }; // run_func ===================================

    var val = function (name, val) {
      var args       = _.toArray(arguments);
      var return_val = null;
      switch (args.length) {

        case 1:
          if (!vars.hasOwnProperty(name) && !parent) {
          throw new Error('No var found with name: ' + name);
        }

        return_val = (vars.hasOwnProperty(name)) ?
          vars[name] :
          parent.read_var(name);
        break;

        case 2:
          vars[name] = val;
        return_val = val;
        break;

        default:
          throw new Error("Unknown value: " + args.length);

      } // === switch args.length

      return return_val;
    }; // val =========================================

    var push = function () {
      var args = _.toArray(arguments);
      stack    = stack.concat(args);
      return args;
    }; // push ========================================

    var child = function () {
      return( new Scope(o) );
    };

    var run = function (code_array) {
      each(code_array, function (item, l) {

        if (_.isArray(item)) {
          throw new Error("Syntax error: args w/o function name.");

        } else if (_.isString(item), _.isArray(l.next_or_null())) {
          run_func(item, l.grab_next());

        } else {
          stack.push(item);
        } // === if

      }); // === .each

      return o;
    }; // run ========================================

    o.vars  = vars;
    o.stack = stack;
    o.run   = run;

    return o;

  // ================================================================
  }; // ===== Scope =================================================
  // ================================================================

  WWW_Applet = {
    Scope : Scope,
    run   : function (code_array) {
      return (new Scope()).run(code_array);
    } // run
  };

})(); // === WWW_Applet scope

<|endoftext|>


<commit_before>
import NumberSettingView from '../View/Setting/Number'
import Setting from '../Setting'

/**
 * Number setting
 */
export default class NumberSetting extends Setting {
  /**
   * Setting constructor
   * @param {string} name
   * @param {Object} spec
   * @param {mixed} [spec.options] Setting options
   * @param {boolean} [spec.options.integer=false] Wether to use integer values
   * @param {?number} [spec.options.step=1] Step size
   * @param {?number} [spec.options.min=null] Minimum value (inclusive)
   * @param {?number} [spec.options.max=null] Maximum value (exclusive)
   */
  constructor (name, spec) {
    super(name, spec)
    this._viewPrototype = NumberSettingView

    const options = spec.options || {}
    this._integer = options.integer || false
    this._step = options.step || 1
    this._min = options.min || null
    this._max = options.max || null
  }

  /**
   * Returns wether to use integer values.
   * @return {boolean}
   */
  isInteger () {
    return this._integer
  }

  /**
   * Sets wether to use integer values.
   * @param {boolean} integer
   * @return {NumberSetting} Fluent interface
   */
  setInteger (integer) {
    this._integer = integer
    return this.revalidateValue()
  }

  /**
   * Returns how much to add or remove when stepping value up or down.
   * @return {?number}
   */
  getStep () {
    return this._step
  }

  /**
   * Sets step size.
   * @param {?number} step
   * @return {NumberSetting} Fluent interface
   */
  setStep (step) {
    this._step = step
    return this
  }

  /**
   * Step up or down value until finding the next valid one.
   * @param {number} step Relative step size
   * @param {number} [maxTries=100] Max number of tries to find a valid value
   * @return {?number} Resulting value or null if unable to find
   */
  stepValue (step, maxTries = 100) {
    let value = this.getValue()
    let tries = 0
    let valueFound = false

    // step until a valid value is found
    while (
      !valueFound &&
      tries++ < maxTries &&
      // eslint-disable-next-line no-unmodified-loop-condition
      (step > 0 || value !== this._min) &&
      // eslint-disable-next-line no-unmodified-loop-condition
      (step < 0 || value !== this._max)
    ) {
      // add step to value
      value += step

      // check bounds
      value = this._max !== null ? Math.min(value, this._max) : value
      value = this._min !== null ? Math.max(value, this._min) : value

      // validate value
      valueFound = this.validateValue(value) === true
    }

    return valueFound ? value : null
  }

  /**
   * Step up value.
   * @return {NumberSetting} Fluent interface
   */
  stepUp () {
    const value = this.stepValue(this._step)
    return value !== null ? this.setValue(value) : this
  }

  /**
   * Step down value.
   * @return {NumberSetting} Fluent interface
   */
  stepDown () {
    const value = this.stepValue(-this._step)
    return value !== null ? this.setValue(value) : this
  }

  /**
   * Returns minimum value (inclusive).
   * @return {?number} Minimum value
   */
  getMin () {
    return this._min
  }

  /**
   * Sets minimum value (inclusive).
   * @param {?number} min Minimum value
   * @return {NumberSetting} Fluent interface
   */
  setMin (min) {
    this._min = min
    return this
  }

  /**
   * Returns maximum value (exclusive).
   * @return {?number} Maximum value
   */
  getMax () {
    return this._max
  }

  /**
   * Sets maximum value (exclusive).
   * @param {?number} max Maximum value
   * @return {NumberSetting} Fluent interface
   */
  setMax (max) {
    this._max = max
    return this
  }

  /**
   * Validates given raw value.
   * @param {mixed} rawValue Value to be validated
   * @return {boolean|object} True if valid, message object or false if invalid
   */
  validateValue (rawValue) {
    const value = this.filterValue(rawValue)

    // is numeric
    if (isNaN(value) || !isFinite(value)) {
      return {
        key: 'numberNotNumeric',
        message: `The value is not numeric`
      }
    }

    // validate min value
    if (this._min !== null && value < this._min) {
      return {
        key: 'numberTooSmall',
        message: `The value must be greater than or equal to ${this._min}`
      }
    }

    // validate max value
    if (this._max !== null && value >= this._max) {
      return {
        key: 'numberTooLarge',
        message: `The value must be less than ${this._max}`
      }
    }

    return super.validateValue(value)
  }

  /**
   * Filters given raw value.
   * @param {mixed} rawValue Value to be filtered
   * @return {mixed} Filtered value
   */
  filterValue (rawValue) {
    if (this.isInteger()) {
      rawValue = parseInt(rawValue)
    } else {
      rawValue = parseFloat(rawValue)
    }
    return super.filterValue(rawValue)
  }

  /**
   * Returns a randomly chosen value or null if not applicable.
   * @param {Random} random Random number generator
   * @return {mixed} Randomly chosen value
   */
  randomizeValue (random) {
    const value = super.randomizeValue(random)
    if (value !== null) {
      return value
    }
    if (this.getMin() !== null && this.getMax() !== null) {
      return this.isInteger()
        ? random.nextInteger(this.getMin(), this.getMax())
        : random.nextFloat(this.getMin(), this.getMax())
    }
    return null
  }

  /**
   * Triggered when value has been changed inside the view.
   * @protected
   * @param {NumberSettingView} view
   * @param {number} value
   */
  viewValueDidChange (view, value) {
    this.setValue(value, view)
  }
}
<commit_msg>Adds value rotation to NumberSetting, refs #26<commit_after>
import NumberSettingView from '../View/Setting/Number'
import Setting from '../Setting'

/**
 * Number setting
 */
export default class NumberSetting extends Setting {
  /**
   * Constructor
   * @param {string} name
   * @param {object} spec
   * @param {mixed} [spec.options] Setting options
   * @param {boolean} [spec.options.integer=false] Wether to use integer values
   * @param {?number} [spec.options.step=1] Step size
   * @param {?number} [spec.options.min=null] Minimum value (inclusive)
   * @param {?number} [spec.options.max=null] Maximum value (exclusive)
   * @param {?boolean} [spec.options.rotate=true] Wether the value should rotate
   * when stepping over limits. Rotation can only be enabled when both min and
   * max values are defined.
   */
  constructor (name, spec) {
    super(name, spec)
    this._viewPrototype = NumberSettingView

    const options = spec.options || {}
    this._integer = options.integer || false
    this._step = options.step || 1
    this._min = options.min || null
    this._max = options.max || null
    this._rotate =
      (options.rotate || true) && this._min !== null && this._max !== null
  }

  /**
   * Returns wether to use integer values.
   * @return {boolean}
   */
  isInteger () {
    return this._integer
  }

  /**
   * Sets wether to use integer values.
   * @param {boolean} integer
   * @return {NumberSetting} Fluent interface
   */
  setInteger (integer) {
    this._integer = integer
    return this.revalidateValue()
  }

  /**
   * Returns how much to add or remove when stepping value up or down.
   * @return {?number}
   */
  getStep () {
    return this._step
  }

  /**
   * Sets step size.
   * @param {?number} step
   * @return {NumberSetting} Fluent interface
   */
  setStep (step) {
    this._step = step
    return this
  }

  /**
   * Step up or down value and repeat the process until finding a valid one.
   * @param {number} step Relative step size
   * @param {number} [maxTries=100] Max number of tries to find a valid value
   * @return {?number} Resulting value or null if unable to find
   */
  stepValue (step, maxTries = 100) {
    let value = this.getValue()
    let tries = 0
    let valueFound = false

    while (
      // step value until a valid one is found or until max tries is reached
      !valueFound &&
      tries++ < maxTries &&
      // stop when reaching limits with rotation disabled
      // eslint-disable-next-line no-unmodified-loop-condition
      (this._rotate || step > 0 || value !== this._min) &&
      // eslint-disable-next-line no-unmodified-loop-condition
      (this._rotate || step < 0 || value !== this._max)
    ) {
      // add step to value
      value += step

      // rotate to min or max value when reaching limits
      if (this._rotate && value > this._max && this._rotate) {
        value = this._min
      } else if (this._rotate && value < this._min && this._rotate) {
        // step is added during rotation due to max being defined exclusive
        value = this._max + step
      }

      // validate value
      valueFound = this.validateValue(value) === true
    }

    return valueFound ? value : null
  }

  /**
   * Step up value.
   * @return {NumberSetting} Fluent interface
   */
  stepUp () {
    const value = this.stepValue(this._step)
    return value !== null ? this.setValue(value) : this
  }

  /**
   * Step down value.
   * @return {NumberSetting} Fluent interface
   */
  stepDown () {
    const value = this.stepValue(-this._step)
    return value !== null ? this.setValue(value) : this
  }

  /**
   * Returns minimum value (inclusive).
   * @return {?number} Minimum value
   */
  getMin () {
    return this._min
  }

  /**
   * Sets minimum value (inclusive).
   * @param {?number} min Minimum value
   * @return {NumberSetting} Fluent interface
   */
  setMin (min) {
    this._min = min
    return this.revalidateValue()
  }

  /**
   * Returns maximum value (exclusive).
   * @return {?number} Maximum value
   */
  getMax () {
    return this._max
  }

  /**
   * Sets maximum value (exclusive).
   * @param {?number} max Maximum value
   * @return {NumberSetting} Fluent interface
   */
  setMax (max) {
    this._max = max
    return this.revalidateValue()
  }

  /**
   * Returns wether the value should rotate when stepping over limits.
   * @return {boolean}
   */
  isRotate () {
    return this._rotate
  }

  /**
   * Sets wether the value should rotate when stepping over limits. Rotation can
   * only be enabled when both min and max values are defined.
   * @param {boolean} rotate Wether rotation is enabled
   * @return {NumberSetting} Fluent interface
   */
  setRotate (rotate) {
    this._rotate = rotate && this._min !== null && this._max !== null
    return this
  }

  /**
   * Validates given raw value.
   * @param {mixed} rawValue Value to be validated
   * @return {boolean|object} True if valid, message object or false if invalid
   */
  validateValue (rawValue) {
    const value = this.filterValue(rawValue)

    // is numeric
    if (isNaN(value) || !isFinite(value)) {
      return {
        key: 'numberNotNumeric',
        message: `The value is not numeric`
      }
    }

    // validate min value
    if (this._min !== null && value < this._min) {
      return {
        key: 'numberTooSmall',
        message: `The value must be greater than or equal to ${this._min}`
      }
    }

    // validate max value
    if (this._max !== null && value >= this._max) {
      return {
        key: 'numberTooLarge',
        message: `The value must be less than ${this._max}`
      }
    }

    return super.validateValue(value)
  }

  /**
   * Filters given raw value.
   * @param {mixed} rawValue Value to be filtered
   * @return {mixed} Filtered value
   */
  filterValue (rawValue) {
    if (this.isInteger()) {
      rawValue = parseInt(rawValue)
    } else {
      rawValue = parseFloat(rawValue)
    }
    return super.filterValue(rawValue)
  }

  /**
   * Returns a randomly chosen value or null if not applicable.
   * @param {Random} random Random number generator
   * @return {mixed} Randomly chosen value
   */
  randomizeValue (random) {
    const value = super.randomizeValue(random)
    if (value !== null) {
      return value
    }
    if (this.getMin() !== null && this.getMax() !== null) {
      return this.isInteger()
        ? random.nextInteger(this.getMin(), this.getMax())
        : random.nextFloat(this.getMin(), this.getMax())
    }
    return null
  }

  /**
   * Triggered when value has been changed inside the view.
   * @protected
   * @param {NumberSettingView} view
   * @param {number} value
   */
  viewValueDidChange (view, value) {
    this.setValue(value, view)
  }
}
<|endoftext|>


<commit_before>/**
 * Controller for Components
 *
 * @module
 */

'use strict';

var _ = require('lodash'),
  responses = require('../responses'),
  composer = require('../composer'),
  files = require('../files'),
  queryStringOptions = ['ignore-data'],
  controller = require('../services/components'),
  acceptedExtensions = {
    html: 'text/html',
    json: 'application/json'
  };

/**
 * Validation of component routes goes here.
 *
 * They will all have the form (req, res, next).
 *
 * @namespace
 */
let validation = _.bindAll({
  /**
   * If component doesn't exist, then the resource cannot be found.
   *
   * @param req
   * @param res
   * @param next
   */
  componentMustExist: function (req, res, next) {
    var name = req.params.name;
    name = name.split('@')[0];
    name = name.split('.')[0];

    if (!!files.getComponentPath(name)) {
      next();
    } else {
      responses.notFound(res);
    }
  }
});

/**
 * All routes go here.
 *
 * They will all have the form (req, res), but never with next()
 *
 * @namespace
 */
let route = _.bindAll({

  /**
   * @param req
   * @param res
   */
  get: function (req, res) {
    responses.expectJSON(function () {
      return controller.get(req.uri);
    }, res);
  },

  list: function (req, res) {
    responses.expectJSON(function () {
      return controller.list();
    }, res);
  },

  /**
   * @param req
   * @param res
   */
  put: function (req, res) {
    responses.expectJSON(function () {
      return controller.put(req.uri, req.body);
    }, res);
  },

  /**
   * @param req
   * @param res
   */
  del: function (req, res) {
    responses.expectJSON(function () {
      return controller.del(req.uri);
    }, res);
  },

  /**
   * @param req
   * @param res
   */
  post: function (req, res) {
    responses.expectJSON(function () {
      return controller.post(req.uri, req.body);
    }, res);
  },

  /**
   * Change the acceptance type based on the extension they gave us
   *
   * Fail if they don't accept right protocol and not *
   *
   * @param req
   * @param res
   */
  extension: function (req, res) {
    switch (req.params.ext.toLowerCase()) {
      case 'html':
        return this.render(req, res);
      case 'json': // jshint ignore:line
      default:
        return this.get(req, res);
    }
  },

  /**
   * Return a schema for a component
   *
   * @param req
   * @param res
   */
  schema: function (req, res) {
    responses.expectJSON(function () {
      return controller.getSchema(req.uri);
    }, res);
  },

  render: function (req, res) {
    responses.expectHTML(function () {
      return composer.renderComponent(req.uri, res, _.pick(req.query, queryStringOptions));
    }, res);
  }
});

function routes(router) {
  router.use(responses.varyWithoutExtension({varyBy: ['Accept']}));
  router.use(responses.onlyCachePublished);

  router.all('/', responses.methodNotAllowed({allow: ['get']}));
  router.all('/', responses.notAcceptable({accept: ['application/json']}));
  router.get('/', route.list);

  router.all('/:name*', validation.componentMustExist);
  router.get('/:name.:ext', responses.onlyAcceptExtensions({extensions: acceptedExtensions}));
  router.get('/:name.:ext', route.extension);

  router.all('/:name@:version', responses.acceptJSONOnly);
  router.all('/:name@:version', responses.methodNotAllowed({allow: ['get', 'put']}));
  router.get('/:name@:version', route.get);
  router.put('/:name@:version', responses.denyReferenceAtRoot);
  router.put('/:name@:version', route.put);

  router.all('/:name', responses.acceptJSONOnly);
  router.all('/:name', responses.methodNotAllowed({allow: ['get', 'put', 'delete']}));
  router.get('/:name', route.get);
  router.put('/:name', responses.denyReferenceAtRoot);
  router.put('/:name', route.put);
  router.delete('/:name', route.del);

  router.all('/:name/instances', responses.acceptJSONOnly);
  router.all('/:name/instances', responses.methodNotAllowed({allow: ['get', 'post']}));
  router.get('/:name/instances', responses.listWithoutVersions());
  router.post('/:name/instances', responses.denyReferenceAtRoot);
  router.post('/:name/instances', route.post);
  router.get('/:name/instances/:id.:ext', responses.onlyAcceptExtensions({extensions: acceptedExtensions}));
  router.get('/:name/instances/:id.:ext', route.extension);

  router.all('/:name/instances/:id@:version', responses.acceptJSONOnly);
  router.all('/:name/instances/:id@:version', responses.methodNotAllowed({allow: ['get', 'put']}));
  router.get('/:name/instances/:id@:version', route.get);
  router.put('/:name/instances/:id@:version', responses.denyReferenceAtRoot);
  router.put('/:name/instances/:id@:version', route.put);

  router.all('/:name/instances/:id', responses.acceptJSONOnly);
  router.all('/:name/instances/:id', responses.methodNotAllowed({allow: ['get', 'put', 'delete']}));
  router.get('/:name/instances/:id', route.get);
  router.put('/:name/instances/:id', responses.denyReferenceAtRoot);
  router.put('/:name/instances/:id', route.put);
  router.delete('/:name/instances/:id', route.del);

  router.all('/:name/schema', responses.methodNotAllowed({allow: ['get']}));
  router.all('/:name/schema', responses.notAcceptable({accept: ['application/json']}));
  router.get('/:name/schema', route.schema);
}

module.exports = routes;
<commit_msg>add locals to routes<commit_after>/**
 * Controller for Components
 *
 * @module
 */

'use strict';

var _ = require('lodash'),
  responses = require('../responses'),
  composer = require('../composer'),
  files = require('../files'),
  queryStringOptions = ['ignore-data'],
  controller = require('../services/components'),
  acceptedExtensions = {
    html: 'text/html',
    json: 'application/json'
  };

/**
 * Validation of component routes goes here.
 *
 * They will all have the form (req, res, next).
 *
 * @namespace
 */
let validation = _.bindAll({
  /**
   * If component doesn't exist, then the resource cannot be found.
   *
   * @param req
   * @param res
   * @param next
   */
  componentMustExist: function (req, res, next) {
    var name = req.params.name;
    name = name.split('@')[0];
    name = name.split('.')[0];

    if (!!files.getComponentPath(name)) {
      next();
    } else {
      responses.notFound(res);
    }
  }
});

/**
 * All routes go here.
 *
 * They will all have the form (req, res), but never with next()
 *
 * @namespace
 */
let route = _.bindAll({

  /**
   * @param req
   * @param res
   */
  get: function (req, res) {
    responses.expectJSON(function () {
      return controller.get(req.uri, res.locals);
    }, res);
  },

  list: function (req, res) {
    responses.expectJSON(function () {
      return controller.list();
    }, res);
  },

  /**
   * @param req
   * @param res
   */
  put: function (req, res) {
    responses.expectJSON(function () {
      return controller.put(req.uri, req.body, res.locals);
    }, res);
  },

  /**
   * @param req
   * @param res
   */
  del: function (req, res) {
    responses.expectJSON(function () {
      return controller.del(req.uri, res.locals);
    }, res);
  },

  /**
   * @param req
   * @param res
   */
  post: function (req, res) {
    responses.expectJSON(function () {
      return controller.post(req.uri, req.body, res.locals);
    }, res);
  },

  /**
   * Change the acceptance type based on the extension they gave us
   *
   * Fail if they don't accept right protocol and not *
   *
   * @param req
   * @param res
   */
  extension: function (req, res) {
    switch (req.params.ext.toLowerCase()) {
      case 'html':
        return this.render(req, res);
      case 'json': // jshint ignore:line
      default:
        return this.get(req, res);
    }
  },

  /**
   * Return a schema for a component
   *
   * @param req
   * @param res
   */
  schema: function (req, res) {
    responses.expectJSON(function () {
      return controller.getSchema(req.uri);
    }, res);
  },

  render: function (req, res) {
    responses.expectHTML(function () {
      return composer.renderComponent(req.uri, res, _.pick(req.query, queryStringOptions));
    }, res);
  }
});

function routes(router) {
  router.use(responses.varyWithoutExtension({varyBy: ['Accept']}));
  router.use(responses.onlyCachePublished);

  router.all('/', responses.methodNotAllowed({allow: ['get']}));
  router.all('/', responses.notAcceptable({accept: ['application/json']}));
  router.get('/', route.list);

  router.all('/:name*', validation.componentMustExist);
  router.get('/:name.:ext', responses.onlyAcceptExtensions({extensions: acceptedExtensions}));
  router.get('/:name.:ext', route.extension);

  router.all('/:name@:version', responses.acceptJSONOnly);
  router.all('/:name@:version', responses.methodNotAllowed({allow: ['get', 'put']}));
  router.get('/:name@:version', route.get);
  router.put('/:name@:version', responses.denyReferenceAtRoot);
  router.put('/:name@:version', route.put);

  router.all('/:name', responses.acceptJSONOnly);
  router.all('/:name', responses.methodNotAllowed({allow: ['get', 'put', 'delete']}));
  router.get('/:name', route.get);
  router.put('/:name', responses.denyReferenceAtRoot);
  router.put('/:name', route.put);
  router.delete('/:name', route.del);

  router.all('/:name/instances', responses.acceptJSONOnly);
  router.all('/:name/instances', responses.methodNotAllowed({allow: ['get', 'post']}));
  router.get('/:name/instances', responses.listWithoutVersions());
  router.post('/:name/instances', responses.denyReferenceAtRoot);
  router.post('/:name/instances', route.post);
  router.get('/:name/instances/:id.:ext', responses.onlyAcceptExtensions({extensions: acceptedExtensions}));
  router.get('/:name/instances/:id.:ext', route.extension);

  router.all('/:name/instances/:id@:version', responses.acceptJSONOnly);
  router.all('/:name/instances/:id@:version', responses.methodNotAllowed({allow: ['get', 'put']}));
  router.get('/:name/instances/:id@:version', route.get);
  router.put('/:name/instances/:id@:version', responses.denyReferenceAtRoot);
  router.put('/:name/instances/:id@:version', route.put);

  router.all('/:name/instances/:id', responses.acceptJSONOnly);
  router.all('/:name/instances/:id', responses.methodNotAllowed({allow: ['get', 'put', 'delete']}));
  router.get('/:name/instances/:id', route.get);
  router.put('/:name/instances/:id', responses.denyReferenceAtRoot);
  router.put('/:name/instances/:id', route.put);
  router.delete('/:name/instances/:id', route.del);

  router.all('/:name/schema', responses.methodNotAllowed({allow: ['get']}));
  router.all('/:name/schema', responses.notAcceptable({accept: ['application/json']}));
  router.get('/:name/schema', route.schema);
}

module.exports = routes;
<|endoftext|>


<commit_before>/*!
 * qwebs
 * Copyright(c) 2015 Benoît Claveau
 * MIT Licensed
 */

"use strict";

const zlib = require("zlib");
const crypto = require('crypto');
const DataError = require("./../dataerror");
const JSONStream = require("JSONStream");

class ResponseService {
    constructor() {
    };

    setCharset(data) {
        if (!data.header["Content-Type"].match(/charset/)) {
            data.header["Content-Type"] += "; charset=utf-8";
        }
    };

    setVary(data) {
        data.header["Vary"] = "Accept-Encoding";
    };

    fresh(data) {

        let etagMatches = true;
        let notModified = true;

        // fields
        let modifiedSince = data.request['If-Modified-Since'];
        let noneMatch = data.request['If-None-Match'];
        let lastModified = data.request['Last-Modified'];
        let etag = data.header["Etag"];
        let cc = data.request['Cache-Control'];

        // unconditional request
        if (!modifiedSince && !noneMatch) return false;

        // check for no-cache cache request directive
        if (cc && cc.indexOf('no-cache') !== -1) return false;

        // parse if-none-match
        if (noneMatch) noneMatch = noneMatch.split(/ *, */);

        // if-none-match
        if (noneMatch) {
            etagMatches = noneMatch.some(match => {
                return match === '*' || match === etag || match === 'W/' + etag;
            });
        }

        // if-modified-since
        if (modifiedSince) {
            modifiedSince = new Date(modifiedSince);
            lastModified = new Date(lastModified);
            notModified = lastModified <= modifiedSince;
        }
        return !! (etagMatches && notModified);
    };

    envelope(data) {
        if (!data) throw new DataError({ message: "No data." });
        if (!data.request) throw new DataError({ message: "Request is null." });
        if (!data.header) data.header = {};
        if (!data.statusCode) data.statusCode = 200;

        let atime = new Date(Date.now());

        data.header["Date"] = data.header["Date"] || atime.toUTCString();
        data.header["Cache-Control"] = data.header["Cache-Control"] || "no-cache";
        data.header["Expires"] = data.header["Expires"] || atime.toUTCString();
        data.header["Content-Type"] = data.header["Content-Type"] || "application/json";

        switch (this.method) {
            case "GET":
            case "POST":
            case "PUT":
            case "DELETE":
                if (!data.content && !data.stream) throw new DataError({ message: "Content is null." });
                break;
            case "OPTIONS":
                data.header["Content-Type"] = data.content = data.stream = null; //Only header no content
                break;
        }

        if (data.header["Content-Type"]) {
            if (data.header["Content-Type"].match(/\bapplication\/json\b/)) {
                if (data.content && !(data.content instanceof String || typeof data.content === "string")) {
                    try {
                        data.content = JSON.stringify(data.content);
                    }
                    catch(error) {
                        throw new DataError({ message: "Failed to stringify content", data: {content: data.content }, stack: error.stack });
                    }
                }
                else if (data.stream) {
                    data.stream = data.stream.pipe(JSONStream.stringify());
                }
                this.setCharset(data);
                this.setVary(data);
            }
            else if (data.header["Content-Type"].match(/\btext\/html\b/)) {
                this.setCharset(data);
                this.setVary(data);
            }

            if (!data.header["Etag"]) {
                if (data.content) data.header["Etag"] = crypto.createHash('sha256').update(data.content).digest("hex");
                //default etag stream is not managed.
            }

            data.fresh = this.fresh(data);
            if (!data.fresh) data.header["Last-Modified"] = data.header["Last-Modified"] || atime.toUTCString();

            let acceptEncoding = data.request.headers["accept-encoding"] || "";
            if (acceptEncoding.match(/\bgzip\b/)) data.header["Content-Encoding"] = "gzip";
            else if (acceptEncoding.match(/\bdeflate\b/)) data.header["Content-Encoding"] = "deflate";
        }
    };

    /* Promise ---------- */

    redirect(response, data) {
        try {
            if (!data) throw new DataError({ message: "No data." });
            if (!data.url) throw new DataError({ message: "No url." });

            data.header = data.header || {};
            data.statusCode = data.statusCode || 307;
            data.header["Content-Type"] = data.header["Content-Type"] || "text/html";
            data.header["Location"] = data.header["Location"] || data.url;

            response.writeHead(data.statusCode, data.header);
            response.end();
            return Promise.resolve(this);
        }
        catch(error) {
            return Promise.reject(error);
        }
    };

    compress(data) {
        try {
            if (data.fresh) return Promise.resolve({ header: data.header, statusCode: 304 });
            else if (data.header["Content-Encoding"] == "gzip") return this.compressGzip(data);
            else if (data.header["Content-Encoding"] == "deflate") return this.compressDeflate(data);
            else return Promise.resolve(data);
        } catch(error) {
            return Promise.reject(error);
        }
    };

    compressGzip(data) {
        return new Promise((resolve, reject) => {
            if (data.contentGzip) { //assets already gzipped
                resolve({ header: data.header, content: data.contentGzip, statusCode: data.statusCode });
            }
            else if (data.stream) { //stream
                let stream = data.stream.pipe(zlib.createGzip());
                return { header: data.header, stream: stream, statusCode: data.statusCode };
            }
            else if (data.content) {
                zlib.gzip(data.content, (error, buffer) => {
                    if (error) reject(new DataError({ message: error.message, stack: error.stack }));
                    else resolve({ header: data.header, content: buffer, statusCode: data.statusCode });
                });
            }
            else resolve({ header: data.header, statusCode: data.statusCode });
        });
    };

    compressDeflate(data) {
        return new Promise((resolve, reject) => {
            if (data.contentDeflate) { //assets already deflated
                resolve({ header: data.header, content: data.contentDeflate, statusCode: data.statusCode });
            }
            else if (data.stream) { //stream
                let stream = data.stream.pipe(zlib.createDeflate());
                return { header: data.header, stream: stream, statusCode: data.statusCode };
            }
            else if (data.content) {
                zlib.deflate(data.content, (error, buffer) => {
                    if (error) reject(new DataError({ message: error.message, stack: error.stack }));
                    else resolve({ header: data.header, content: buffer, statusCode: data.statusCode });
                });
            }
            else resolve({ header: data.header, statusCode: data.statusCode });
        });
    };

    send(response, data) {
        try {
            this.envelope(data);
            return this.compress(data).then(resp => {
                return new Promise((resolve, reject) => {
                    try {
                        response.writeHead(resp.statusCode, resp.header);
                        if (resp.stream) {
                            resp.stream
                                .on("error", error => {
                                    reject(new DataError({ message: error.message, stack: error.stack }));
                                }).pipe(response)
                                .on("error", error => {
                                    reject(new DataError({ message: error.message, stack: error.stack }));
                                }).on("finish", () => {
                                    resolve(resp);
                                });
                        }
                        else {
                            if (resp.content) response.end(resp.content);
                            else response.end();
                            resolve(resp);
                        }
                    }
                    catch (error) {
                        reject(new DataError({ message: error.message, stack: error.stack }));
                    }
                });
            });
        }
        catch(error) {
            return Promise.reject(error);
        }
    };
};

exports = module.exports = ResponseService;
<commit_msg>Update response.js<commit_after>/*!
 * qwebs
 * Copyright(c) 2015 Benoît Claveau
 * MIT Licensed
 */

"use strict";

const zlib = require("zlib");
const crypto = require('crypto');
const DataError = require("./../dataerror");
const JSONStream = require("JSONStream");

class ResponseService {
    constructor() {
    };

    setCharset(data) {
        if (!data.header["Content-Type"].match(/charset/)) {
            data.header["Content-Type"] += "; charset=utf-8";
        }
    };

    setVary(data) {
        data.header["Vary"] = "Accept-Encoding";
    };

    fresh(data) {

        let etagMatches = true;
        let notModified = true;

        // fields
        let modifiedSince = data.request['If-Modified-Since'];
        let noneMatch = data.request['If-None-Match'];
        let lastModified = data.request['Last-Modified'];
        let etag = data.header["Etag"];
        let cc = data.request['Cache-Control'];

        // unconditional request
        if (!modifiedSince && !noneMatch) return false;

        // check for no-cache cache request directive
        if (cc && cc.indexOf('no-cache') !== -1) return false;

        // parse if-none-match
        if (noneMatch) noneMatch = noneMatch.split(/ *, */);

        // if-none-match
        if (noneMatch) {
            etagMatches = noneMatch.some(match => {
                return match === '*' || match === etag || match === 'W/' + etag;
            });
        }

        // if-modified-since
        if (modifiedSince) {
            modifiedSince = new Date(modifiedSince);
            lastModified = new Date(lastModified);
            notModified = lastModified <= modifiedSince;
        }
        return !! (etagMatches && notModified);
    };

    envelope(data) {
        if (!data) throw new DataError({ message: "No data." });
        if (!data.request) throw new DataError({ message: "Request is null." });
        if (!data.header) data.header = {};
        if (!data.statusCode) data.statusCode = 200;

        let atime = new Date(Date.now());

        data.header["Date"] = data.header["Date"] || atime.toUTCString();
        data.header["Cache-Control"] = data.header["Cache-Control"] || "no-cache";
        data.header["Expires"] = data.header["Expires"] || atime.toUTCString();
        data.header["Content-Type"] = data.header["Content-Type"] || "application/json";

        switch (this.method) {
            case "GET":
            case "POST":
            case "PUT":
            case "DELETE":
                if (!data.content && !data.stream) throw new DataError({ message: "Content is null." });
                break;
            case "OPTIONS":
                data.header["Content-Type"] = data.content = data.stream = null; //Only header no content
                break;
        }

        if (data.header["Content-Type"]) {
            if (data.header["Content-Type"].match(/\bapplication\/json\b/)) {
                if (data.content && !(data.content instanceof String || typeof data.content === "string")) {
                    try {
                        data.content = JSON.stringify(data.content);
                    }
                    catch(error) {
                        throw new DataError({ message: "Failed to stringify content", data: {content: data.content }, stack: error.stack });
                    }
                }
                else if (data.stream) {
                   data.stream = data.stream.on("error", error => {
                     console.error("Stream", error);
                     //reject(new DataError({ message: error.message, stack: error.stack }));
                   }).pipe(JSONStream.stringify()).on("error", error => {
                      console.error("Stringify", error);
                     //reject(new DataError({ message: error.message, stack: error.stack }));
                   }).on("finish", () => {
                    console.log("JSONStream.stringify() completed");
                   });
                }
                this.setCharset(data);
                this.setVary(data);
            }
            else if (data.header["Content-Type"].match(/\btext\/html\b/)) {
                this.setCharset(data);
                this.setVary(data);
            }

            if (!data.header["Etag"]) {
                if (data.content) data.header["Etag"] = crypto.createHash('sha256').update(data.content).digest("hex");
                //default etag stream is not managed.
            }

            data.fresh = this.fresh(data);
            if (!data.fresh) data.header["Last-Modified"] = data.header["Last-Modified"] || atime.toUTCString();

            let acceptEncoding = data.request.headers["accept-encoding"] || "";
            if (acceptEncoding.match(/\bgzip\b/)) data.header["Content-Encoding"] = "gzip";
            else if (acceptEncoding.match(/\bdeflate\b/)) data.header["Content-Encoding"] = "deflate";
        }
    };

    /* Promise ---------- */

    redirect(response, data) {
        try {
            if (!data) throw new DataError({ message: "No data." });
            if (!data.url) throw new DataError({ message: "No url." });

            data.header = data.header || {};
            data.statusCode = data.statusCode || 307;
            data.header["Content-Type"] = data.header["Content-Type"] || "text/html";
            data.header["Location"] = data.header["Location"] || data.url;

            response.writeHead(data.statusCode, data.header);
            response.end();
            return Promise.resolve(this);
        }
        catch(error) {
            return Promise.reject(error);
        }
    };

    compress(data) {
        try {
            if (data.fresh) return Promise.resolve({ header: data.header, statusCode: 304 });
            else if (data.header["Content-Encoding"] == "gzip") return this.compressGzip(data);
            else if (data.header["Content-Encoding"] == "deflate") return this.compressDeflate(data);
            else return Promise.resolve(data);
        } catch(error) {
            return Promise.reject(error);
        }
    };

    compressGzip(data) {
        return new Promise((resolve, reject) => {
            if (data.contentGzip) { //assets already gzipped
                resolve({ header: data.header, content: data.contentGzip, statusCode: data.statusCode });
            }
            else if (data.stream) { //stream
                let stream = data.stream.pipe(zlib.createGzip());
                return { header: data.header, stream: stream, statusCode: data.statusCode };
            }
            else if (data.content) {
                zlib.gzip(data.content, (error, buffer) => {
                    if (error) reject(new DataError({ message: error.message, stack: error.stack }));
                    else resolve({ header: data.header, content: buffer, statusCode: data.statusCode });
                });
            }
            else resolve({ header: data.header, statusCode: data.statusCode });
        });
    };

    compressDeflate(data) {
        return new Promise((resolve, reject) => {
            if (data.contentDeflate) { //assets already deflated
                resolve({ header: data.header, content: data.contentDeflate, statusCode: data.statusCode });
            }
            else if (data.stream) { //stream
                let stream = data.stream.pipe(zlib.createDeflate());
                return { header: data.header, stream: stream, statusCode: data.statusCode };
            }
            else if (data.content) {
                zlib.deflate(data.content, (error, buffer) => {
                    if (error) reject(new DataError({ message: error.message, stack: error.stack }));
                    else resolve({ header: data.header, content: buffer, statusCode: data.statusCode });
                });
            }
            else resolve({ header: data.header, statusCode: data.statusCode });
        });
    };

    send(response, data) {
        try {
            this.envelope(data);
            return this.compress(data).then(resp => {
                return new Promise((resolve, reject) => {
                    try {
                        response.writeHead(resp.statusCode, resp.header);
                        if (resp.stream) {
                            resp.stream
                                .on("error", error => {
                                    reject(new DataError({ message: error.message, stack: error.stack }));
                                }).pipe(response)
                                .on("error", error => {
                                    reject(new DataError({ message: error.message, stack: error.stack }));
                                }).on("finish", () => {
                                    resolve(resp);
                                });
                        }
                        else {
                            if (resp.content) response.end(resp.content);
                            else response.end();
                            resolve(resp);
                        }
                    }
                    catch (error) {
                        reject(new DataError({ message: error.message, stack: error.stack }));
                    }
                });
            });
        }
        catch(error) {
            return Promise.reject(error);
        }
    };
};

exports = module.exports = ResponseService;
<|endoftext|>