<commit_before>var _      = require('underscore');

// Glue is an object listener library for javascript.
//
// For example, the value of 'v1' of hash:
// {v1: 'value'}
//
// Can be observe with key:
// "v1"
//
// Objects can be arbitrarily complex. For example, one can observe 'value'
// within this structure:
//
// {v1: arr1: [{v2: v3: arr2: ['value']}]}
//
// with the key:
// 'v1.arr1[0].v2.v3.arr2[0]'
//
// Similarly one can observe an arbitrary element of arr2 with the key:
// 'v1.arr1[0].v2.v3.arr2[]'
//
// And of course one can observe the array itself with the key:
// 'v1.arr1[0].v2.v3.arr'
//
// Possible values for key:
// ''                 all key
// '*':               any key
// 'v1':              target: {v1: 'foo'}
// 'v1.v2':           target: {v1: {v2: {...}}
// '[]':              target: []
// 'arr':             target: {arr: []}                 * the array itself
// 'arr[]':           target: {arr: []}                 * targets the elements of the array
// 'v1.arr:           target: {v1: {arr: []}}
// 'arr[i].v1.arr     target: {arr: [{v1: arr: []}]}
// 'arr[i].v1.arr[]   target: {arr: [{v1: arr: []}]}
//
// And any other permutations of the combinations describe
// above nested n layers deep (ex. 'v1.v2.v3.arr[i].v4 ...)

var Glue = function(target) {
  this.target = target;

  this.updateCache();
  this.resetListeners();
};

Glue.version = '0.5.1'

Glue.prototype.resetListeners = function() {
  this.listeners = {specific: {}, generic: {}};
};

Glue.deepClone = function(obj) {
  var clone;

  if (_.isArray(obj)) {
    clone = _.map(obj, function(elem) {
      return Glue.deepClone(elem);
    });
  } else if (typeof obj === 'object') {
    clone = {};

    _.each(obj, function(val, key) {
      clone[key] = Glue.deepClone(val);
    });
  } else {
    clone = obj;
  }

  return clone;
};

Glue.prototype.updateCache = function() {
  this.cache = Glue.deepClone(this.target);
};

Glue.normalizeKey = function(key) {
  return key.replace(/\s/g, '');
};

Glue.permutateKey = function (key) {
  var keys = [],
      segments = key.split('.');

  _.each(segments, function(segment, i) {
    var k        = _.first(segments, i+1).join('.'),
        regIndex = /\d*(?=\]$)/;

    if (k.match(regIndex)) {
      keys.push([k, k.replace(regIndex, ''), parseInt(k.match(regIndex)[0])]);
    }
  });

  return keys.reverse();
};

Glue.keysAndOperations = function(ko) {
  if (arguments.length === 0) return [[], []];

  var k = Glue.normalizeKey(ko).split(':'),
      keys = _.isEmpty(k[0]) ? [''] : k[0].split(','),
      operations = _.isEmpty(k[1]) ? [] : k[1].split(',');

  return [keys, operations];
};

// Usage:
// glue.addListener([key(s):operation(s)], [context], callback)

Glue.prototype.addListener = function() {
  var self = this,
      a    = arguments;

  if (a.length === 1) {
    add('*', a[0], this.target);
  } else if (a.length === 2) {
    _.isString(a[0]) ? add(a[0], a[1], this.target) : add('*', a[1], a[0]);
  } else {
    add(a[0], a[2], a[1]);
  }

  function add(k, callback, context) {
    var ko         = Glue.keysAndOperations(k),
        keys       = _.isEqual(ko[0], ['']) ? ['*'] : ko[0],
        operations = ko[1];

    _.each(keys, function(key) {
      var type = key.match(/\[\]$/) ? 'generic' : 'specific';

      self.listeners[type][key] = self.listeners[type][key] || [];

      self.listeners[type][key].push({
        callback: callback, operations: operations, context: context
      });
    });
  }
};

// Usage:
// glue.removeListener((key(s):operation(s)), [context]);
Glue.prototype.removeListener = function() {
  var self = this,
      a    = arguments;

  if(a.length === 0) {
    remove('');
  } else if (a.length === 1) {
    _.isString(a[0]) ? remove(Glue.normalizeKey(a[0])) : remove('', a[0]);
  } else if (a.length === 2) {
    remove(Glue.normalizeKey(a[0]), a[1]);
  }

  function remove(k, context) {
    var ko         = Glue.keysAndOperations(k),
        operations = ko[1],
        keys;

    if (_.isEqual(ko[0], [''])) {
      keys = _.union(_.keys(self.listeners.specific), _.keys(self.listeners.generic));
    } else {
      keys = ko[0];
    }

    _.each(keys, function(key) {
      var type = key.match(/\[\]$/) ? 'generic' : 'specific';

      if(!_.isEmpty(operations)) {
        _.each(self.listeners[type][key], function(listener) {
          listener.operations = _.difference(listener.operations, operations);
        });
      }

      self.listeners[type][key] = _.reject(self.listeners[type][key], function(listener) {
        if (!_.isEmpty(operations) && context) {
          return _.isEmpty(listener.operations) && listener.context === context;
        } else if (context) {
          return listener.context === context;
        } else if (!_.isEmpty(operations)) {
          return _.isEmpty(listener.operations);
        } else {
          return true;
        }
      });

      if (_.isEmpty(self.listeners[type][key])) delete self.listeners[type][key];
    });
  };
};

// Not in the public API.
//
// Usage:
// glue.notify(key, operation);

Glue.prototype.notify = function(operation, key, opts) {
  var self = this;

  _.each(this.listeners.specific, function(listeners, k) {
    var current = self.get(k),
        old     = self.get(k, self.cache);

      _.each(listeners, function(listener) {
        invoke(operation, listener, old, current);
     });
  });

  invokeGeneric(key, operation, self.cache, self.target);

  function invokeGeneric(k, operation, oldTarget, currentTarget) {
    var keysAndIndices = Glue.permutateKey(k);

    _.each(keysAndIndices, function(ki) {
      var specific = ki[0],
          generic  = ki[1],
          index    = ki[2];

      _.each(self.listeners.generic[generic], function(listener) {
        invoke(operation, listener, self.get(specific, oldTarget), self.get(specific, currentTarget), index);
      });
    });

    var affectedChildren = {};
    _.each(self.listeners.generic, function(listeners, k) {
      if (k.match(new RegExp("^" + key.replace('[', '\\\[').replace(']', '\\\]')))) {
        affectedChildren[k] = listeners;
      };
    });

    _.each(affectedChildren, function(listeners, k) {
      var base     = k.replace(/\[[^\[]*\]$/, ''),
          old      = self.get(base, oldTarget),
          current  = self.get(base, currentTarget),
          maxRange = old.length > current.length ? old.length : current.length,
          range = opts && opts.reverse ? _.range(0, maxRange).reverse() : _.range(0, maxRange);

      _.each(range, function(index) {
        var oldValue = old[index],
            currentValue = current[index];

        _.each(listeners, function(listener) {
          invoke(operation, listener, oldValue, currentValue, index);
        });
      });
    });
  };

  function invoke(operation, listener, oldValue, currentValue, index) {
    if (!_.isEqual(oldValue, Glue.deepClone(currentValue))) {
      var message = {
        operation: operation,
        oldValue: oldValue,
        currentValue: currentValue
      };

      if (!_.isUndefined(index)) message.index = index;

      if (!_.isEmpty(listener.operations)) {
        if (_.include(listener.operations, operation)) callListener(listener, message);
      } else {
        callListener(listener, message);
      }
    }
  };

  function callListener(listener, message) {
    listener.callback.call(listener.context, message);
  };
};

// Not in the public API. It is more performant to access
// values directly through glue.target. However, one should never
// modify the values of a target object obtain directly from
// glue.target.
//
// Usage:
// glue.get([key], [obj]);

Glue.prototype.get = function(key, obj) {
  if (key === '' || key === '*') return obj || this.target;

  var target = obj || this.target;

  key = Glue.normalizeKey(key);
  key = (_.isArray(target) ? "target" : "target.") + key;

  return eval(key); // I know eval is generally evil, but I think it's acceptable for this use case.
};

// Usage:
// glue.set(key, value);
Glue.prototype.set = function(key, value) {
  var lastDot     = key.lastIndexOf(".")
    , lastBracket = key.lastIndexOf("[")
    , index       = lastBracket > lastDot ? lastBracket : lastDot
    , keySuffix   = key.substring(index+1).replace(/\]/g, '');

  var base = this.get(key.substring(0, index));
  base[keySuffix] = value;

  this.notify('set', key);
  this.updateCache();

  return this;
};

// Usage
// glue.remove([key]);

Glue.prototype.remove = function(key){
  var removed,
      match = key.match(/\[(\d+)\]$/);

  if (match) {
    var index           = match[1]
      , suffixLastIndex = key.lastIndexOf(match[0]);

    if (suffixLastIndex === 0) {
      removed = this.target.splice(index, 1)[0];
    } else {
      removed = this.get(key.substr(0, suffixLastIndex)).splice(index, 1)[0];
    }
  } else {
    key = key.split('.');

    if (key.length > 1) {
      var top = key.pop();
      removed = this.get(key.join('.'))[top];
      delete this.get(key.join('.'))[top];
    } else {
      removed = this.target[key[0]];
      delete this.target[key[0]];
    }

    key = key.join('.');
  }

  this.notify("remove", key);
  this.updateCache();

  return removed;
};

// Usage
// topic.push([key], value);
Glue.prototype.push = function() {
  var self = this,
      a = arguments;

  if (a.length === 1) {
    push('', this.target, a[0]);
  } else {
    push(a[0], self.get(a[0]), a[1]);
  }

  function push(key, collection, item) {
    collection.push(item);
    self.notify("push", key);
    self.updateCache();
  };

  return this;
};

// Usage
// glue.pop([key]);
Glue.prototype.pop = function(key){
  var key = key || '',
      collection = this.get(key),
      value = collection.pop();

  this.notify("pop", key);
  this.updateCache();

  return value;
};

// Usage
// glue.insert([key], index, value);
Glue.prototype.insert = function() {
  var i = 0, a = arguments;

  var key   = a.length < 3 ?  '' : a[i++],
      index = a[i++],
      value = a[i++];

  this.get(key).splice(index, 0, value);
  this.notify('insert', key);
  this.updateCache();

  return this;
};

// Usage
// glue.filter([key], filterBy);

Glue.prototype.filter = function() {
  var self     = this,
      i        = 0,
      a        = arguments,
      removed  = [],
      indices,

      key      = a.length < 2 ?  '' : a[i++],
      filterBy = a[i++];

  var collection = key === '' ? this.target : self.get(key);

  indices = _.without(_.map(collection, function(value, index) {
    if (!filterBy(value)) return index;
  }), undefined).reverse();

  _.each(indices, function(index) {
    self.get(key).splice(index, 1)[0];
  });

  self.notify("filter", Glue.baseKey(key), { reverse: true });
  this.updateCache();

  return this.get(Glue.baseKey(key));
};

Glue.baseKey = function(key) {
  return key.replace(/\[[^\[]*\]$/, '');
};

Glue.prototype.baseKeyAndSuffix = function(key) {
  var lastDot     = key.lastIndexOf(".")
    , lastBracket = key.lastIndexOf("[")
    , index       = lastBracket > lastDot ? lastBracket : lastDot
    , keySuffix   = key.substring(index+1).replace(/\]/g, '');

  return [this.get(key.substring(0, index)), keySuffix];
};

// Usage
// glue.sort([key], sortBy);

Glue.prototype.sortBy = function() {
  var self = this,
      i = 0, a = arguments,
      key    = a.length < 2 ?  '' : a[i++],
      sortBy = a[i++];

  collectionWithIndex = _.map(this.get(key), function(val, index) {
    return [val, index];
  });

  var sortedWithIndex = _.sortBy(collectionWithIndex, function(elem) {
    return sortBy(elem[0]);
  });

  var sorted = _.map(sortedWithIndex, function(elem) { return elem[0] });

  if (key === '') {
    this.target = sorted;
  } else {
    bs = this.baseKeyAndSuffix(key);
    bs[0][bs[1]] = sorted;
  };

  var indices = _.map(sortedWithIndex, function(elem) {
    return elem[1];
  });

  this.notify("filter", Glue.baseKey(key), { reverse: true });
  this.updateCache();

  return sorted;
};

// Usage
// glue.swap(loc1, loc2);
Glue.prototype.swap = function(loc1, loc2) {
  var self = this,
      value1 = this.get(loc1),
      value2 = this.get(loc2),
      bs1 = this.baseKeyAndSuffix(loc1),
      bs2 = this.baseKeyAndSuffix(loc2);

  bs1[0][bs1[1]] = value2;
  bs2[0][bs2[1]] = value1;

  _.each(_.uniq(_.union(Glue.baseKey(loc1), Glue.baseKey(loc2))), function(key) {
    self.notify("swap", key);
  });

  return this;
};

module.exports = Glue;

<commit_msg>Faster Glue.deepClone<commit_after>var _      = require('underscore');

// Glue is an object listener library for javascript.
//
// For example, the value of 'v1' of hash:
// {v1: 'value'}
//
// Can be observe with key:
// "v1"
//
// Objects can be arbitrarily complex. For example, one can observe 'value'
// within this structure:
//
// {v1: arr1: [{v2: v3: arr2: ['value']}]}
//
// with the key:
// 'v1.arr1[0].v2.v3.arr2[0]'
//
// Similarly one can observe an arbitrary element of arr2 with the key:
// 'v1.arr1[0].v2.v3.arr2[]'
//
// And of course one can observe the array itself with the key:
// 'v1.arr1[0].v2.v3.arr'
//
// Possible values for key:
// ''                 all key
// '*':               any key
// 'v1':              target: {v1: 'foo'}
// 'v1.v2':           target: {v1: {v2: {...}}
// '[]':              target: []
// 'arr':             target: {arr: []}                 * the array itself
// 'arr[]':           target: {arr: []}                 * targets the elements of the array
// 'v1.arr:           target: {v1: {arr: []}}
// 'arr[i].v1.arr     target: {arr: [{v1: arr: []}]}
// 'arr[i].v1.arr[]   target: {arr: [{v1: arr: []}]}
//
// And any other permutations of the combinations describe
// above nested n layers deep (ex. 'v1.v2.v3.arr[i].v4 ...)

var Glue = function(target) {
  this.target = target;

  this.updateCache();
  this.resetListeners();
};

Glue.version = '0.5.1'

Glue.prototype.resetListeners = function() {
  this.listeners = {specific: {}, generic: {}};
};

Glue.deepClone = function(obj) {
  return JSON.parse(JSON.stringify(obj));
};

Glue.prototype.updateCache = function() {
  this.cache = Glue.deepClone(this.target);
};

Glue.normalizeKey = function(key) {
  return key.replace(/\s/g, '');
};

Glue.permutateKey = function (key) {
  var keys = [],
      segments = key.split('.');

  _.each(segments, function(segment, i) {
    var k        = _.first(segments, i+1).join('.'),
        regIndex = /\d*(?=\]$)/;

    if (k.match(regIndex)) {
      keys.push([k, k.replace(regIndex, ''), parseInt(k.match(regIndex)[0])]);
    }
  });

  return keys.reverse();
};

Glue.keysAndOperations = function(ko) {
  if (arguments.length === 0) return [[], []];

  var k = Glue.normalizeKey(ko).split(':'),
      keys = _.isEmpty(k[0]) ? [''] : k[0].split(','),
      operations = _.isEmpty(k[1]) ? [] : k[1].split(',');

  return [keys, operations];
};

// Usage:
// glue.addListener([key(s):operation(s)], [context], callback)

Glue.prototype.addListener = function() {
  var self = this,
      a    = arguments;

  if (a.length === 1) {
    add('*', a[0], this.target);
  } else if (a.length === 2) {
    _.isString(a[0]) ? add(a[0], a[1], this.target) : add('*', a[1], a[0]);
  } else {
    add(a[0], a[2], a[1]);
  }

  function add(k, callback, context) {
    var ko         = Glue.keysAndOperations(k),
        keys       = _.isEqual(ko[0], ['']) ? ['*'] : ko[0],
        operations = ko[1];

    _.each(keys, function(key) {
      var type = key.match(/\[\]$/) ? 'generic' : 'specific';

      self.listeners[type][key] = self.listeners[type][key] || [];

      self.listeners[type][key].push({
        callback: callback, operations: operations, context: context
      });
    });
  }
};

// Usage:
// glue.removeListener((key(s):operation(s)), [context]);
Glue.prototype.removeListener = function() {
  var self = this,
      a    = arguments;

  if(a.length === 0) {
    remove('');
  } else if (a.length === 1) {
    _.isString(a[0]) ? remove(Glue.normalizeKey(a[0])) : remove('', a[0]);
  } else if (a.length === 2) {
    remove(Glue.normalizeKey(a[0]), a[1]);
  }

  function remove(k, context) {
    var ko         = Glue.keysAndOperations(k),
        operations = ko[1],
        keys;

    if (_.isEqual(ko[0], [''])) {
      keys = _.union(_.keys(self.listeners.specific), _.keys(self.listeners.generic));
    } else {
      keys = ko[0];
    }

    _.each(keys, function(key) {
      var type = key.match(/\[\]$/) ? 'generic' : 'specific';

      if(!_.isEmpty(operations)) {
        _.each(self.listeners[type][key], function(listener) {
          listener.operations = _.difference(listener.operations, operations);
        });
      }

      self.listeners[type][key] = _.reject(self.listeners[type][key], function(listener) {
        if (!_.isEmpty(operations) && context) {
          return _.isEmpty(listener.operations) && listener.context === context;
        } else if (context) {
          return listener.context === context;
        } else if (!_.isEmpty(operations)) {
          return _.isEmpty(listener.operations);
        } else {
          return true;
        }
      });

      if (_.isEmpty(self.listeners[type][key])) delete self.listeners[type][key];
    });
  };
};

// Not in the public API.
//
// Usage:
// glue.notify(key, operation);

Glue.prototype.notify = function(operation, key, opts) {
  var self = this;

  _.each(this.listeners.specific, function(listeners, k) {
    var current = self.get(k),
        old     = self.get(k, self.cache);

      _.each(listeners, function(listener) {
        invoke(operation, listener, old, current);
     });
  });

  invokeGeneric(key, operation, self.cache, self.target);

  function invokeGeneric(k, operation, oldTarget, currentTarget) {
    var keysAndIndices = Glue.permutateKey(k);

    _.each(keysAndIndices, function(ki) {
      var specific = ki[0],
          generic  = ki[1],
          index    = ki[2];

      _.each(self.listeners.generic[generic], function(listener) {
        invoke(operation, listener, self.get(specific, oldTarget), self.get(specific, currentTarget), index);
      });
    });

    var affectedChildren = {};
    _.each(self.listeners.generic, function(listeners, k) {
      if (k.match(new RegExp("^" + key.replace('[', '\\\[').replace(']', '\\\]')))) {
        affectedChildren[k] = listeners;
      };
    });

    _.each(affectedChildren, function(listeners, k) {
      var base     = k.replace(/\[[^\[]*\]$/, ''),
          old      = self.get(base, oldTarget),
          current  = self.get(base, currentTarget),
          maxRange = old.length > current.length ? old.length : current.length,
          range = opts && opts.reverse ? _.range(0, maxRange).reverse() : _.range(0, maxRange);

      _.each(range, function(index) {
        var oldValue = old[index],
            currentValue = current[index];

        _.each(listeners, function(listener) {
          invoke(operation, listener, oldValue, currentValue, index);
        });
      });
    });
  };

  function invoke(operation, listener, oldValue, currentValue, index) {
    if (!_.isEqual(oldValue, Glue.deepClone(currentValue))) {
      var message = {
        operation: operation,
        oldValue: oldValue,
        currentValue: currentValue
      };

      if (!_.isUndefined(index)) message.index = index;

      if (!_.isEmpty(listener.operations)) {
        if (_.include(listener.operations, operation)) callListener(listener, message);
      } else {
        callListener(listener, message);
      }
    }
  };

  function callListener(listener, message) {
    listener.callback.call(listener.context, message);
  };
};

// Not in the public API. It is more performant to access
// values directly through glue.target. However, one should never
// modify the values of a target object obtain directly from
// glue.target.
//
// Usage:
// glue.get([key], [obj]);

Glue.prototype.get = function(key, obj) {
  if (key === '' || key === '*') return obj || this.target;

  var target = obj || this.target;

  key = Glue.normalizeKey(key);
  key = (_.isArray(target) ? "target" : "target.") + key;

  return eval(key); // I know eval is generally evil, but I think it's acceptable for this use case.
};

// Usage:
// glue.set(key, value);
Glue.prototype.set = function(key, value) {
  var lastDot     = key.lastIndexOf(".")
    , lastBracket = key.lastIndexOf("[")
    , index       = lastBracket > lastDot ? lastBracket : lastDot
    , keySuffix   = key.substring(index+1).replace(/\]/g, '');

  var base = this.get(key.substring(0, index));
  base[keySuffix] = value;

  this.notify('set', key);
  this.updateCache();

  return this;
};

// Usage
// glue.remove([key]);

Glue.prototype.remove = function(key){
  var removed,
      match = key.match(/\[(\d+)\]$/);

  if (match) {
    var index           = match[1]
      , suffixLastIndex = key.lastIndexOf(match[0]);

    if (suffixLastIndex === 0) {
      removed = this.target.splice(index, 1)[0];
    } else {
      removed = this.get(key.substr(0, suffixLastIndex)).splice(index, 1)[0];
    }
  } else {
    key = key.split('.');

    if (key.length > 1) {
      var top = key.pop();
      removed = this.get(key.join('.'))[top];
      delete this.get(key.join('.'))[top];
    } else {
      removed = this.target[key[0]];
      delete this.target[key[0]];
    }

    key = key.join('.');
  }

  this.notify("remove", key);
  this.updateCache();

  return removed;
};

// Usage
// topic.push([key], value);
Glue.prototype.push = function() {
  var self = this,
      a = arguments;

  if (a.length === 1) {
    push('', this.target, a[0]);
  } else {
    push(a[0], self.get(a[0]), a[1]);
  }

  function push(key, collection, item) {
    collection.push(item);
    self.notify("push", key);
    self.updateCache();
  };

  return this;
};

// Usage
// glue.pop([key]);
Glue.prototype.pop = function(key){
  var key = key || '',
      collection = this.get(key),
      value = collection.pop();

  this.notify("pop", key);
  this.updateCache();

  return value;
};

// Usage
// glue.insert([key], index, value);
Glue.prototype.insert = function() {
  var i = 0, a = arguments;

  var key   = a.length < 3 ?  '' : a[i++],
      index = a[i++],
      value = a[i++];

  this.get(key).splice(index, 0, value);
  this.notify('insert', key);
  this.updateCache();

  return this;
};

// Usage
// glue.filter([key], filterBy);

Glue.prototype.filter = function() {
  var self     = this,
      i        = 0,
      a        = arguments,
      removed  = [],
      indices,

      key      = a.length < 2 ?  '' : a[i++],
      filterBy = a[i++];

  var collection = key === '' ? this.target : self.get(key);

  indices = _.without(_.map(collection, function(value, index) {
    if (!filterBy(value)) return index;
  }), undefined).reverse();

  _.each(indices, function(index) {
    self.get(key).splice(index, 1)[0];
  });

  self.notify("filter", Glue.baseKey(key), { reverse: true });
  this.updateCache();

  return this.get(Glue.baseKey(key));
};

Glue.baseKey = function(key) {
  return key.replace(/\[[^\[]*\]$/, '');
};

Glue.prototype.baseKeyAndSuffix = function(key) {
  var lastDot     = key.lastIndexOf(".")
    , lastBracket = key.lastIndexOf("[")
    , index       = lastBracket > lastDot ? lastBracket : lastDot
    , keySuffix   = key.substring(index+1).replace(/\]/g, '');

  return [this.get(key.substring(0, index)), keySuffix];
};

// Usage
// glue.sort([key], sortBy);

Glue.prototype.sortBy = function() {
  var self = this,
      i = 0, a = arguments,
      key    = a.length < 2 ?  '' : a[i++],
      sortBy = a[i++];

  collectionWithIndex = _.map(this.get(key), function(val, index) {
    return [val, index];
  });

  var sortedWithIndex = _.sortBy(collectionWithIndex, function(elem) {
    return sortBy(elem[0]);
  });

  var sorted = _.map(sortedWithIndex, function(elem) { return elem[0] });

  if (key === '') {
    this.target = sorted;
  } else {
    bs = this.baseKeyAndSuffix(key);
    bs[0][bs[1]] = sorted;
  };

  var indices = _.map(sortedWithIndex, function(elem) {
    return elem[1];
  });

  this.notify("filter", Glue.baseKey(key), { reverse: true });
  this.updateCache();

  return sorted;
};

// Usage
// glue.swap(loc1, loc2);
Glue.prototype.swap = function(loc1, loc2) {
  var self = this,
      value1 = this.get(loc1),
      value2 = this.get(loc2),
      bs1 = this.baseKeyAndSuffix(loc1),
      bs2 = this.baseKeyAndSuffix(loc2);

  bs1[0][bs1[1]] = value2;
  bs2[0][bs2[1]] = value1;

  _.each(_.uniq(_.union(Glue.baseKey(loc1), Glue.baseKey(loc2))), function(key) {
    self.notify("swap", key);
  });

  return this;
};

module.exports = Glue;

<|endoftext|>


<commit_before>module.exports = function(crowi) {
  const mongoose = require('mongoose')
  const debug = require('debug')('crowi:models:config')
  const configSchema = new mongoose.Schema({
    ns: { type: String, required: true, index: true },
    key: { type: String, required: true, index: true },
    value: { type: String, required: true },
  })
  const SECURITY_REGISTRATION_MODE_OPEN = 'Open'
  const SECURITY_REGISTRATION_MODE_RESTRICTED = 'Resricted'
  const SECURITY_REGISTRATION_MODE_CLOSED = 'Closed'

  function getArrayForInstalling() {
    return {
      // 'app:installed'     : "0.0.0",
      'app:title': 'Crowi',
      'app:confidential': '',

      'app:fileUpload': false,
      'app:externalShare': false,

      'security:registrationMode': 'Open',
      'security:registrationWhiteList': [],

      'auth:requireThirdPartyAuth': false,
      'auth:disablePasswordAuth': false,

      'aws:bucket': 'crowi',
      'aws:region': 'ap-northeast-1',
      'aws:accessKeyId': '',
      'aws:secretAccessKey': '',

      'mail:from': '',
      'mail:smtpHost': '',
      'mail:smtpPort': '',
      'mail:smtpUser': '',
      'mail:smtpPassword': '',

      'google:clientId': '',
      'google:clientSecret': '',

      'github:clientId': '',
      'github:clientSecret': '',
      'github:organization': '',
    }
  }

  configSchema.statics.getRegistrationModeLabels = function() {
    return {
      SECURITY_REGISTRATION_MODE_OPEN: '公開 (だれでも登録可能)',
      SECURITY_REGISTRATION_MODE_RESTRICTED: '制限 (登録完了には管理者の承認が必要)',
      SECURITY_REGISTRATION_MODE_CLOSED: '非公開 (登録には管理者による招待が必要)',
    }
  }

  // Execute only once for installing application
  configSchema.statics.applicationInstall = function(callback) {
    const Config = this
    Config.count({ ns: 'crowi' }, function(err, count) {
      if (count > 0) {
        return callback(new Error('Application already installed'), null)
      }
      try {
        return callback(Config.updateConfigByNamespace('crowi', getArrayForInstalling()), null)
      } catch (err) {
        return callback(err, null)
      }
    })
  }

  configSchema.statics.updateCache = function(ns, key, value) {
    const config = crowi.getConfig()

    if (!config[ns]) {
      config[ns] = {}
    }

    config[ns][key] = value

    crowi.setConfig(config)
  }

  configSchema.statics.updateCacheByNamespace = function(ns, nsConfig) {
    const config = crowi.getConfig()

    if (!config[ns]) {
      config[ns] = {}
    }

    Object.entries(nsConfig).forEach(([key, value]) => (config[ns][key] = value))

    crowi.setConfig(config)
  }

  configSchema.statics.update = async function(ns, key, value) {
    const Config = this

    await Config.findOneAndUpdate({ ns, key }, { ns, key, value: JSON.stringify(value) }, { upsert: true }).exec()
  }

  configSchema.statics.updateConfig = async function(ns, key, value) {
    const Config = this

    try {
      await Config.update(ns, key, value)
    } catch (err) {
      debug('updateConfig', err)
    }

    Config.updateCache(ns, key, value)
  }

  configSchema.statics.updateConfigByNamespace = async function(ns, nsConfig) {
    const Config = this

    try {
      await Promise.all(Object.entries(nsConfig).map(([key, value]) => Config.update(ns, key, value)))
    } catch (err) {
      debug('updateConfigByNamespace', err)
    }

    Config.updateCacheByNamespace(ns, nsConfig)
  }

  configSchema.statics.loadAllConfig = async function() {
    const Config = this

    const config = { crowi: {} }

    const doc = await Config.find()
      .sort({ ns: 1, key: 1 })
      .exec()

    doc.forEach(({ ns, key, value }) => {
      if (!config[ns]) {
        config[ns] = {}
      }

      config[ns][key] = JSON.parse(value)
    })

    return config
  }

  configSchema.statics.isRequiredThirdPartyAuth = function(config) {
    return !!config.crowi['auth:requireThirdPartyAuth']
  }

  configSchema.statics.isDisabledPasswordAuth = function(config) {
    return !!config.crowi['auth:disablePasswordAuth']
  }

  configSchema.statics.isUploadable = function(config) {
    var method = crowi.env.FILE_UPLOAD || 'aws'

    if (
      method == 'aws' &&
      (!config.crowi['aws:accessKeyId'] || !config.crowi['aws:secretAccessKey'] || !config.crowi['aws:region'] || !config.crowi['aws:bucket'])
    ) {
      return false
    }

    return method != 'none'
  }

  configSchema.statics.fileUploadEnabled = function(config) {
    const Config = this

    if (!Config.isUploadable(config)) {
      return false
    }

    return config.crowi['app:fileUpload'] || false
  }

  configSchema.statics.googleLoginEnabled = function(config) {
    return config.crowi['google:clientId'] && config.crowi['google:clientSecret']
  }

  configSchema.statics.githubLoginEnabled = function(config) {
    return config.crowi['github:clientId'] && config.crowi['github:clientSecret']
  }

  configSchema.statics.hasSlackConfig = function(config) {
    if (!config.notification) {
      return false
    }
    if (!config.notification['slack:clientId'] || !config.notification['slack:clientSecret']) {
      return false
    }

    return true
  }

  configSchema.statics.hasSlackToken = function(config) {
    if (!this.hasSlackConfig(config)) {
      return false
    }

    if (!config.notification['slack:token']) {
      return false
    }

    return true
  }

  configSchema.statics.getLocalconfig = function(config) {
    const Config = this
    const env = crowi.getEnv()

    const localConfig = {
      crowi: {
        title: config.crowi['app:title'],
        url: config.crowi['app:url'] || '',
        auth: {
          requireThirdPartyAuth: Config.isRequiredThirdPartyAuth(config),
          disablePasswordAuth: Config.isDisabledPasswordAuth(config),
        },
      },
      upload: {
        image: Config.isUploadable(config),
        file: Config.fileUploadEnabled(config),
      },
      env: {
        PLANTUML_URI: env.PLANTUML_URI || null,
        MATHJAX: env.MATHJAX || null,
      },
    }

    return localConfig
  }

  configSchema.statics.SECURITY_REGISTRATION_MODE_OPEN = SECURITY_REGISTRATION_MODE_OPEN
  configSchema.statics.SECURITY_REGISTRATION_MODE_RESTRICTED = SECURITY_REGISTRATION_MODE_RESTRICTED
  configSchema.statics.SECURITY_REGISTRATION_MODE_CLOSED = SECURITY_REGISTRATION_MODE_CLOSED

  return mongoose.model('Config', configSchema)
}
<commit_msg>Fix keys<commit_after>module.exports = function(crowi) {
  const mongoose = require('mongoose')
  const debug = require('debug')('crowi:models:config')
  const configSchema = new mongoose.Schema({
    ns: { type: String, required: true, index: true },
    key: { type: String, required: true, index: true },
    value: { type: String, required: true },
  })
  const SECURITY_REGISTRATION_MODE_OPEN = 'Open'
  const SECURITY_REGISTRATION_MODE_RESTRICTED = 'Resricted'
  const SECURITY_REGISTRATION_MODE_CLOSED = 'Closed'

  function getArrayForInstalling() {
    return {
      // 'app:installed'     : "0.0.0",
      'app:title': 'Crowi',
      'app:confidential': '',

      'app:fileUpload': false,
      'app:externalShare': false,

      'security:registrationMode': 'Open',
      'security:registrationWhiteList': [],

      'auth:requireThirdPartyAuth': false,
      'auth:disablePasswordAuth': false,

      'aws:bucket': 'crowi',
      'aws:region': 'ap-northeast-1',
      'aws:accessKeyId': '',
      'aws:secretAccessKey': '',

      'mail:from': '',
      'mail:smtpHost': '',
      'mail:smtpPort': '',
      'mail:smtpUser': '',
      'mail:smtpPassword': '',

      'google:clientId': '',
      'google:clientSecret': '',

      'github:clientId': '',
      'github:clientSecret': '',
      'github:organization': '',
    }
  }

  configSchema.statics.getRegistrationModeLabels = function() {
    return {
      [SECURITY_REGISTRATION_MODE_OPEN]: '公開 (だれでも登録可能)',
      [SECURITY_REGISTRATION_MODE_RESTRICTED]: '制限 (登録完了には管理者の承認が必要)',
      [SECURITY_REGISTRATION_MODE_CLOSED]: '非公開 (登録には管理者による招待が必要)',
    }
  }

  // Execute only once for installing application
  configSchema.statics.applicationInstall = function(callback) {
    const Config = this
    Config.count({ ns: 'crowi' }, function(err, count) {
      if (count > 0) {
        return callback(new Error('Application already installed'), null)
      }
      try {
        return callback(Config.updateConfigByNamespace('crowi', getArrayForInstalling()), null)
      } catch (err) {
        return callback(err, null)
      }
    })
  }

  configSchema.statics.updateCache = function(ns, key, value) {
    const config = crowi.getConfig()

    if (!config[ns]) {
      config[ns] = {}
    }

    config[ns][key] = value

    crowi.setConfig(config)
  }

  configSchema.statics.updateCacheByNamespace = function(ns, nsConfig) {
    const config = crowi.getConfig()

    if (!config[ns]) {
      config[ns] = {}
    }

    Object.entries(nsConfig).forEach(([key, value]) => (config[ns][key] = value))

    crowi.setConfig(config)
  }

  configSchema.statics.update = async function(ns, key, value) {
    const Config = this

    await Config.findOneAndUpdate({ ns, key }, { ns, key, value: JSON.stringify(value) }, { upsert: true }).exec()
  }

  configSchema.statics.updateConfig = async function(ns, key, value) {
    const Config = this

    try {
      await Config.update(ns, key, value)
    } catch (err) {
      debug('updateConfig', err)
    }

    Config.updateCache(ns, key, value)
  }

  configSchema.statics.updateConfigByNamespace = async function(ns, nsConfig) {
    const Config = this

    try {
      await Promise.all(Object.entries(nsConfig).map(([key, value]) => Config.update(ns, key, value)))
    } catch (err) {
      debug('updateConfigByNamespace', err)
    }

    Config.updateCacheByNamespace(ns, nsConfig)
  }

  configSchema.statics.loadAllConfig = async function() {
    const Config = this

    const config = { crowi: {} }

    const doc = await Config.find()
      .sort({ ns: 1, key: 1 })
      .exec()

    doc.forEach(({ ns, key, value }) => {
      if (!config[ns]) {
        config[ns] = {}
      }

      config[ns][key] = JSON.parse(value)
    })

    return config
  }

  configSchema.statics.isRequiredThirdPartyAuth = function(config) {
    return !!config.crowi['auth:requireThirdPartyAuth']
  }

  configSchema.statics.isDisabledPasswordAuth = function(config) {
    return !!config.crowi['auth:disablePasswordAuth']
  }

  configSchema.statics.isUploadable = function(config) {
    var method = crowi.env.FILE_UPLOAD || 'aws'

    if (
      method == 'aws' &&
      (!config.crowi['aws:accessKeyId'] || !config.crowi['aws:secretAccessKey'] || !config.crowi['aws:region'] || !config.crowi['aws:bucket'])
    ) {
      return false
    }

    return method != 'none'
  }

  configSchema.statics.fileUploadEnabled = function(config) {
    const Config = this

    if (!Config.isUploadable(config)) {
      return false
    }

    return config.crowi['app:fileUpload'] || false
  }

  configSchema.statics.googleLoginEnabled = function(config) {
    return config.crowi['google:clientId'] && config.crowi['google:clientSecret']
  }

  configSchema.statics.githubLoginEnabled = function(config) {
    return config.crowi['github:clientId'] && config.crowi['github:clientSecret']
  }

  configSchema.statics.hasSlackConfig = function(config) {
    if (!config.notification) {
      return false
    }
    if (!config.notification['slack:clientId'] || !config.notification['slack:clientSecret']) {
      return false
    }

    return true
  }

  configSchema.statics.hasSlackToken = function(config) {
    if (!this.hasSlackConfig(config)) {
      return false
    }

    if (!config.notification['slack:token']) {
      return false
    }

    return true
  }

  configSchema.statics.getLocalconfig = function(config) {
    const Config = this
    const env = crowi.getEnv()

    const localConfig = {
      crowi: {
        title: config.crowi['app:title'],
        url: config.crowi['app:url'] || '',
        auth: {
          requireThirdPartyAuth: Config.isRequiredThirdPartyAuth(config),
          disablePasswordAuth: Config.isDisabledPasswordAuth(config),
        },
      },
      upload: {
        image: Config.isUploadable(config),
        file: Config.fileUploadEnabled(config),
      },
      env: {
        PLANTUML_URI: env.PLANTUML_URI || null,
        MATHJAX: env.MATHJAX || null,
      },
    }

    return localConfig
  }

  configSchema.statics.SECURITY_REGISTRATION_MODE_OPEN = SECURITY_REGISTRATION_MODE_OPEN
  configSchema.statics.SECURITY_REGISTRATION_MODE_RESTRICTED = SECURITY_REGISTRATION_MODE_RESTRICTED
  configSchema.statics.SECURITY_REGISTRATION_MODE_CLOSED = SECURITY_REGISTRATION_MODE_CLOSED

  return mongoose.model('Config', configSchema)
}
<|endoftext|>


<commit_before>var Cell = require('./cell');

function Grid (width,height) {
  if (!(this instanceof Grid)) return new Grid(width,height);

  this.width = width;
  this.height = height;
  this.cells = new Array(width * height);
}

Grid.prototype.index = function (x, y) {
  return (y * this.width) + x;
};

Grid.prototype.cell = function (x, y) {
  return this.cells[this.index(x,y)] ? true : false;
};

Grid.prototype.live = function (x, y) {
  this.cells[this.index(x,y)] = true;
};

Grid.prototype.die = function (x, y) {
  this.cells[this.index(x,y)] = false;
};

Grid.prototype.neighbors = function (x, y) {
  var x1 = this.width - 1;
  var y1 = this.height - 1;
  var count = 0;
  var cell = Cell(x, y);
  if (this._aliveneighbor(cell.top())) count++;
  if (this._aliveneighbor(cell.topRight())) count++;
  if (this._aliveneighbor(cell.right())) count++;
  if (this._aliveneighbor(cell.bottomRight())) count++;
  if (this._aliveneighbor(cell.bottom())) count++;
  if (this._aliveneighbor(cell.bottomLeft())) count++;
  if (this._aliveneighbor(cell.left())) count++;
  if (this._aliveneighbor(cell.topLeft())) count++;
  return count;
};

Grid.prototype._aliveneighbor = function (cell) {
  return cell.within(0,0,this.width-1,this.height-1) && this.cell(cell.x,cell.y);
};

Grid.prototype.generate = function () {
  var x, y, neighbor
  var grid = Grid(this.width, this.height);
  for (x=0; x<this.width; x++) {
    for (y=0; y<this.height; y++) {
      neighbors = this.neighbors(x, y);
      if (this.cell(x,y)) {
        if (neighbors < 2) {
          grid.die(x, y);
        }
        else if (neighbors == 2 || neighbors == 3) {
          grid.live(x, y);
        }
        else if (neighbors > 3) {
          grid.die(x, y);
        }
      }
    }
  }
  return grid;
};

module.exports = Grid;
<commit_msg>any dead cell with 3 or more live neighbors spawns a live cell<commit_after>var Cell = require('./cell');

function Grid (width,height) {
  if (!(this instanceof Grid)) return new Grid(width,height);

  this.width = width;
  this.height = height;
  this.cells = new Array(width * height);
}

Grid.prototype.index = function (x, y) {
  return (y * this.width) + x;
};

Grid.prototype.cell = function (x, y) {
  return this.cells[this.index(x,y)] ? true : false;
};

Grid.prototype.live = function (x, y) {
  this.cells[this.index(x,y)] = true;
};

Grid.prototype.die = function (x, y) {
  this.cells[this.index(x,y)] = false;
};

Grid.prototype.neighbors = function (x, y) {
  var x1 = this.width - 1;
  var y1 = this.height - 1;
  var count = 0;
  var cell = Cell(x, y);
  if (this._aliveneighbor(cell.top())) count++;
  if (this._aliveneighbor(cell.topRight())) count++;
  if (this._aliveneighbor(cell.right())) count++;
  if (this._aliveneighbor(cell.bottomRight())) count++;
  if (this._aliveneighbor(cell.bottom())) count++;
  if (this._aliveneighbor(cell.bottomLeft())) count++;
  if (this._aliveneighbor(cell.left())) count++;
  if (this._aliveneighbor(cell.topLeft())) count++;
  return count;
};

Grid.prototype._aliveneighbor = function (cell) {
  return cell.within(0,0,this.width-1,this.height-1) && this.cell(cell.x,cell.y);
};

Grid.prototype.generate = function () {
  var x, y, neighbor
  var grid = Grid(this.width, this.height);
  for (x=0; x<this.width; x++) {
    for (y=0; y<this.height; y++) {
      neighbors = this.neighbors(x, y);
      if (this.cell(x,y)) {
        if (neighbors < 2) {
          grid.die(x, y);
        }
        else if (neighbors === 2 || neighbors === 3) {
          grid.live(x, y);
        }
        else if (neighbors > 3) {
          grid.die(x, y);
        }
      }
      else {
        if (neighbors === 3) {
          grid.live(x, y);
        }
      }
    }
  }
  return grid;
};

module.exports = Grid;
<|endoftext|>


<commit_before>/*global require, exports, spawn: true */

var spawn = require('child_process').spawn,
    path = require('path'),
    sys = require('sys'),
    Log = require('./log').Log;

function logBuffer(log, prefix, buffer) {
    var message = buffer.toString();
    log.puts(message, prefix);
}

function listen(process, log, callback) {
    var codes = '';
    process.stdout.addListener('data', function (data) {
        logBuffer(log, 'stdout: ', data);
    });

    process.stderr.addListener('data', function (data) {
        logBuffer(log, 'stderr: ', data);
    });

    process.addListener('exit', function (code) {
        logBuffer(log, 'exit: ', code);
        if (code === 0 && callback) {
            callback(this);
        }
    });
}

function ssh(command, callback) {
    if (!command) {
        throw new Error(this.address + ': No command to run');
    }

    var log = this.logger,
        user = this.user,
        process = spawn('ssh',
            ['-l' + user, this.address, "''" + command + "''"]);

    log.puts(user + ':ssh ' + command);
    listen(process, log, callback);
}

function scp(local, remote, callback) {
    if (!local) {
        throw new Error(this.address + ': No local file path');
    }

    if (!remote) {
        throw new Error(this.address + ': No remote file path');
    }

    var log = this.logger,
        user = this.user,
        address = this.address;
    path.exists(local, function (exists) {
        if (exists) {
            var reference = user + '@' + address + ':' + remote,
                args = [local, reference],
                process = spawn('scp', args);

            log.puts(user + ':scp: ' + local + ' ' + reference);
            listen(process, log, callback);
        } else {
            throw new Error('Local: ' + local + ' does not exist');
        }
    });
}

function log(message) {
    this.logger.puts(' ' + message);
}

var defaultLogPath = 'hosts.log';

function hostConstructor(config) {
    // This function may get called with different config objects
    // during a single config task (see deployment example). Therefore
    // we cannot define the constructor as a function declaration at module
    // scope and modify its prototype because the last config would become
    // the config for all hosts.
    function Host(address) {
        var logPath = config.log || defaultLogPath;
        this.address = address;
        this.logger = new Log(this.address + ':', logPath, true);
        this.log = log;
        this.ssh = ssh;
        this.scp = scp;

        // Allows task execution output to identify the host a task
        // is being executed for.
        this.id = address;

    }
    Host.prototype = config;
    return Host;
}

function host(config, address) {
    if (!config) {
        throw new Error("No config");
    }

    if (!address) {
        throw new Error("No remote host address");
    }

    var Host = hostConstructor(config);
    return new Host(address);
}

function hosts(config, addresses) {
    if (!config) {
        throw new Error("No config");
    }

    if (!addresses || !(addresses instanceof Array)) {
        throw new Error("No array of addresses");
    }

    var list = [], a, address, host, c,
        Host = hostConstructor(config);
    for (a in addresses) {
        if (addresses.hasOwnProperty(a)) {
            address = addresses[a];
            host = new Host(address);
            list.push(host);
        }
    }
    return list;
}

exports.host = host;
exports.hosts = hosts;
<commit_msg>Removes unecessary sys require<commit_after>/*global require, exports, spawn: true */

var spawn = require('child_process').spawn,
    path = require('path'),
    Log = require('./log').Log;

function logBuffer(log, prefix, buffer) {
    var message = buffer.toString();
    log.puts(message, prefix);
}

function listen(process, log, callback) {
    var codes = '';
    process.stdout.addListener('data', function (data) {
        logBuffer(log, 'stdout: ', data);
    });

    process.stderr.addListener('data', function (data) {
        logBuffer(log, 'stderr: ', data);
    });

    process.addListener('exit', function (code) {
        logBuffer(log, 'exit: ', code);
        if (code === 0 && callback) {
            callback(this);
        }
    });
}

function ssh(command, callback) {
    if (!command) {
        throw new Error(this.address + ': No command to run');
    }

    var log = this.logger,
        user = this.user,
        process = spawn('ssh',
            ['-l' + user, this.address, "''" + command + "''"]);

    log.puts(user + ':ssh ' + command);
    listen(process, log, callback);
}

function scp(local, remote, callback) {
    if (!local) {
        throw new Error(this.address + ': No local file path');
    }

    if (!remote) {
        throw new Error(this.address + ': No remote file path');
    }

    var log = this.logger,
        user = this.user,
        address = this.address;
    path.exists(local, function (exists) {
        if (exists) {
            var reference = user + '@' + address + ':' + remote,
                args = [local, reference],
                process = spawn('scp', args);

            log.puts(user + ':scp: ' + local + ' ' + reference);
            listen(process, log, callback);
        } else {
            throw new Error('Local: ' + local + ' does not exist');
        }
    });
}

function log(message) {
    this.logger.puts(' ' + message);
}

var defaultLogPath = 'hosts.log';

function hostConstructor(config) {
    // This function may get called with different config objects
    // during a single config task (see deployment example). Therefore
    // we cannot define the constructor as a function declaration at module
    // scope and modify its prototype because the last config would become
    // the config for all hosts.
    function Host(address) {
        var logPath = config.log || defaultLogPath;
        this.address = address;
        this.logger = new Log(this.address + ':', logPath, true);
        this.log = log;
        this.ssh = ssh;
        this.scp = scp;

        // Allows task execution output to identify the host a task
        // is being executed for.
        this.id = address;

    }
    Host.prototype = config;
    return Host;
}

function host(config, address) {
    if (!config) {
        throw new Error("No config");
    }

    if (!address) {
        throw new Error("No remote host address");
    }

    var Host = hostConstructor(config);
    return new Host(address);
}

function hosts(config, addresses) {
    if (!config) {
        throw new Error("No config");
    }

    if (!addresses || !(addresses instanceof Array)) {
        throw new Error("No array of addresses");
    }

    var list = [], a, address, host, c,
        Host = hostConstructor(config);
    for (a in addresses) {
        if (addresses.hasOwnProperty(a)) {
            address = addresses[a];
            host = new Host(address);
            list.push(host);
        }
    }
    return list;
}

exports.host = host;
exports.hosts = hosts;
<|endoftext|>


<commit_before>// Copyright (c) CBC/Radio-Canada. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

define([
        'knockout',
        'jquery',
        'lodash',
        'url-utilities',
        'router',
        'object-utilities',
        'string-utilities',
        'mapping-utilities',
        'disposer',
        'list-base-viewmodel'
    ],
    function(ko, $, _, urlUtilities, router,
        objectUtilities, stringUtilities, mappingUtilities, Disposer, ListBaseViewModel) {
        'use strict';

        var ContentListPageBaseViewModel = function(api, apiResourceName, settings) {
            var self = this;

            self.skipUpdateUrlOneTime = false;
            self.isSorting = ko.observable(false);
            self.title = ko.observable('');
            self.returnUrl = ko.observable('');
            self.returnTitle = ko.observable('');

            ListBaseViewModel.call(self, api, apiResourceName, settings);

            self.returnToQueryString = ko.observable('');

            self.creationFormUrl = ko.pureComputed(function() {
                //Attention - ceci est une convention!
                return urlUtilities.url(apiResourceName + '/edit?' + self.returnToQueryString());
            });
            self.disposer.add(self.creationFormUrl);

            return self;
        };

        ContentListPageBaseViewModel.prototype = Object.create(ListBaseViewModel.prototype);
        ContentListPageBaseViewModel.prototype.contructor = ContentListPageBaseViewModel;

        ContentListPageBaseViewModel.prototype.getCurrentQueryString = function() {
            var self = this;
            var cleanedArguments = objectUtilities.pickNonFalsy(self.searchArguments);
            delete cleanedArguments.pageNumber;

            return $.param(cleanedArguments);
        };

        ContentListPageBaseViewModel.prototype.onSearchSuccess = function(searchResult) {
            var self = this;

            if (self.settings.pageable) {
                self.updatePagingInfo(searchResult);
            }

            if (self.skipUpdateUrlOneTime === false) {
                updateUrlWithSearchArguments(self);
            }

            self.skipUpdateUrlOneTime = false;

            self.updateReturnToQueryString();

            ListBaseViewModel.prototype.onSearchSuccess.call(self, searchResult);

        };

        ContentListPageBaseViewModel.prototype.updateReturnToQueryString = function() {
            var self = this;

            var queryParams = {
                returnTo: getUrlWithUpdatedQueryString(self)
            };

            self.returnToQueryString($.param(queryParams));
        };

        ContentListPageBaseViewModel.prototype.searchWithFilters = function() {
            var self = this;
            window.scrollTo(0, 0);

            return ListBaseViewModel.prototype.searchWithFilters.call(self);
        };

        ContentListPageBaseViewModel.prototype.searchByKeywords = function() {
            var self = this;

            self.searchWithFilters();
        };

        ContentListPageBaseViewModel.prototype.goToNextPage = function() {

            var self = this;

            var search = ListBaseViewModel.prototype.goToNextPage.call(self);
            search.always(function() {
                self.isPaging(false);
            });

            return search;
        };

        ContentListPageBaseViewModel.prototype.addPropertiesToSearchResultItem = function(item) {
            var self = this;

            self.addEditingFormUrlToSearchResultItem(item);
        };

        ContentListPageBaseViewModel.prototype.addEditingFormUrlToSearchResultItem = function(item) {
            var self = this;

            item.editingFormUrl = ko.pureComputed(function() {
                //Attention - on utilise les conventions
                return urlUtilities.url(self.apiResourceName + '/edit/' + item.id + '?' + self.returnToQueryString());
            });
        };

        ContentListPageBaseViewModel.prototype.loadLookups = function() {
            return new $.Deferred(function(dfd) {
                dfd.resolve();
            }).promise();
        };

        ContentListPageBaseViewModel.prototype.deserializeSearchArguments = function(serializedSearchArguments) {
            return new $.Deferred(function(dfd) {
                dfd.resolve(serializedSearchArguments);
            }).promise();
        };

        ContentListPageBaseViewModel.prototype.initSearchArgumentsAndPagingInfoOuter = function() {
            var self = this;

            return new $.Deferred(function(dfd) {
                try {
                    self.initSearchArgumentsAndPagingInfo(dfd);
                } catch (err) {
                    dfd.reject(err);
                }
            }).promise();
        };

        ContentListPageBaseViewModel.prototype.initSearchArgumentsAndPagingInfo = function(dfd) {
            var self = this;

            if (self.route.query && !_.isEmpty(self.route.query)) {

                if (self.settings.pageable) {
                    updatePagingInfoFromQueryParams(self, self.route.query);
                }

                self.deserializeSearchArguments(self.route.query).then(function(deserializedSearchArguments) {
                    var searchArguments = objectUtilities.pickInBoth(deserializedSearchArguments, self.settings.defaultSearchArguments);

                    ko.mapping.fromJS(searchArguments, self.searchArguments);

                    dfd.resolve();
                });
            } else {
                dfd.resolve();
            }
        };

        function updatePagingInfoFromQueryParams(self, queryParams) {
            var pagingArguments = self.pagingArguments();

            pagingArguments = $.extend({}, pagingArguments, objectUtilities.pickInBoth(queryParams, pagingArguments));
            self.pagingArguments(pagingArguments);
        }

        function updateUrlWithSearchArguments(self) {
            router.setUrlSilently({
                url: getUrlWithUpdatedQueryString(self),
                replace: true
            });
        }

        function getUrlWithUpdatedQueryString(self) {
            var route = !router.isActivating() && router.context() ? router.context().route : self.route;
            var routeUrl = route.url;
            var currentQueryString = route.query.toString();
            var newQueryString = self.getCurrentQueryString();

            var result;

            if (currentQueryString) {
                result = routeUrl.replace(currentQueryString, newQueryString);
            } else {
                result = routeUrl + '?' + newQueryString;
            }

            if (result.charAt(result.length - 1) === '?') {
                result = result.slice(0, -1);
            }

            return result;
        }

        return ContentListPageBaseViewModel;
    });
<commit_msg>fix compatibility with custom paging args introduced in koco-list-base-viewmodel v0.2.0<commit_after>// Copyright (c) CBC/Radio-Canada. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

define([
        'knockout',
        'jquery',
        'lodash',
        'url-utilities',
        'router',
        'object-utilities',
        'string-utilities',
        'mapping-utilities',
        'disposer',
        'list-base-viewmodel'
    ],
    function(ko, $, _, urlUtilities, router,
        objectUtilities, stringUtilities, mappingUtilities, Disposer, ListBaseViewModel) {
        'use strict';

        var ContentListPageBaseViewModel = function(api, apiResourceName, settings) {
            var self = this;

            self.skipUpdateUrlOneTime = false;
            self.isSorting = ko.observable(false);
            self.title = ko.observable('');
            self.returnUrl = ko.observable('');
            self.returnTitle = ko.observable('');

            ListBaseViewModel.call(self, api, apiResourceName, settings);

            self.returnToQueryString = ko.observable('');

            self.creationFormUrl = ko.pureComputed(function() {
                //Attention - ceci est une convention!
                return urlUtilities.url(apiResourceName + '/edit?' + self.returnToQueryString());
            });
            self.disposer.add(self.creationFormUrl);

            return self;
        };

        ContentListPageBaseViewModel.prototype = Object.create(ListBaseViewModel.prototype);
        ContentListPageBaseViewModel.prototype.contructor = ContentListPageBaseViewModel;

        ContentListPageBaseViewModel.prototype.getCurrentQueryString = function() {
            var self = this;
            var args = mappingUtilities.toJS(self.searchArguments);
            var cleanedArguments = objectUtilities.pickNonFalsy(args);
            delete cleanedArguments[this.settings.defaultPagingAttr.pageNumber];

            return $.param(cleanedArguments);
        };

        ContentListPageBaseViewModel.prototype.onSearchSuccess = function(searchResult) {
            var self = this;

            if (self.settings.pageable) {
                self.updatePagingInfo(searchResult);
            }

            if (self.skipUpdateUrlOneTime === false) {
                updateUrlWithSearchArguments(self);
            }

            self.skipUpdateUrlOneTime = false;

            self.updateReturnToQueryString();

            ListBaseViewModel.prototype.onSearchSuccess.call(self, searchResult);

        };

        ContentListPageBaseViewModel.prototype.updateReturnToQueryString = function() {
            var self = this;

            var queryParams = {
                returnTo: getUrlWithUpdatedQueryString(self)
            };

            self.returnToQueryString($.param(queryParams));
        };

        ContentListPageBaseViewModel.prototype.searchWithFilters = function() {
            var self = this;
            window.scrollTo(0, 0);

            return ListBaseViewModel.prototype.searchWithFilters.call(self);
        };

        ContentListPageBaseViewModel.prototype.searchByKeywords = function() {
            var self = this;

            self.searchWithFilters();
        };

        ContentListPageBaseViewModel.prototype.goToNextPage = function() {

            var self = this;

            var search = ListBaseViewModel.prototype.goToNextPage.call(self);
            search.always(function() {
                self.isPaging(false);
            });

            return search;
        };

        ContentListPageBaseViewModel.prototype.addPropertiesToSearchResultItem = function(item) {
            var self = this;

            self.addEditingFormUrlToSearchResultItem(item);
        };

        ContentListPageBaseViewModel.prototype.addEditingFormUrlToSearchResultItem = function(item) {
            var self = this;

            item.editingFormUrl = ko.pureComputed(function() {
                //Attention - on utilise les conventions
                return urlUtilities.url(self.apiResourceName + '/edit/' + item.id + '?' + self.returnToQueryString());
            });
        };

        ContentListPageBaseViewModel.prototype.loadLookups = function() {
            return new $.Deferred(function(dfd) {
                dfd.resolve();
            }).promise();
        };

        ContentListPageBaseViewModel.prototype.deserializeSearchArguments = function(serializedSearchArguments) {
            return new $.Deferred(function(dfd) {
                dfd.resolve(serializedSearchArguments);
            }).promise();
        };

        ContentListPageBaseViewModel.prototype.initSearchArgumentsAndPagingInfoOuter = function() {
            var self = this;

            return new $.Deferred(function(dfd) {
                try {
                    self.initSearchArgumentsAndPagingInfo(dfd);
                } catch (err) {
                    dfd.reject(err);
                }
            }).promise();
        };

        ContentListPageBaseViewModel.prototype.initSearchArgumentsAndPagingInfo = function(dfd) {
            var self = this;

            if (self.route.query && !_.isEmpty(self.route.query)) {

                if (self.settings.pageable) {
                    updatePagingInfoFromQueryParams(self, self.route.query);
                }

                self.deserializeSearchArguments(self.route.query).then(function(deserializedSearchArguments) {
                    var searchArguments = objectUtilities.pickInBoth(deserializedSearchArguments, self.settings.defaultSearchArguments);

                    ko.mapping.fromJS(searchArguments, self.searchArguments);

                    dfd.resolve();
                });
            } else {
                dfd.resolve();
            }
        };

        function updatePagingInfoFromQueryParams(self, queryParams) {
            var pagingArguments = self.pagingArguments();

            pagingArguments = $.extend({}, pagingArguments, objectUtilities.pickInBoth(queryParams, pagingArguments));
            self.pagingArguments(pagingArguments);
        }

        function updateUrlWithSearchArguments(self) {
            router.setUrlSilently({
                url: getUrlWithUpdatedQueryString(self),
                replace: true
            });
        }

        function getUrlWithUpdatedQueryString(self) {
            var route = !router.isActivating() && router.context() ? router.context().route : self.route;
            var routeUrl = route.url;
            var currentQueryString = route.query.toString();
            var newQueryString = self.getCurrentQueryString();

            var result;

            if (currentQueryString) {
                result = routeUrl.replace(currentQueryString, newQueryString);
            } else {
                result = routeUrl + '?' + newQueryString;
            }

            if (result.charAt(result.length - 1) === '?') {
                result = result.slice(0, -1);
            }

            return result;
        }

        return ContentListPageBaseViewModel;
    });
<|endoftext|>


<commit_before>var Q = require('q');
var async = require('async');
var url = require('url');
var cheerio = require('cheerio');
var fs = require('fs');

module.exports = {
    /*
     * Go through HTML to match CSS selectors
     * @param {Array} html Html to search through. This can be either an array
     *         of Strings (containing html code), an array of URIs to visit, or
     *         an array of paths to html files.
     * @param {String} cookie Cookie to use for login, on the form
     *         "sessionid=foo". Each uri in the html parameter will
     *         be visited both with and without the cookie.
     * @param {String} whitelist List of selectors to ignore.
     * @param {Array} selectors Array of CSS selectors
     * @param {Object} result Object on the form { used: { ".foo": 1 },
     *                                             duplicates: { ".bar": 0 } }
     */
    matchSelectors: function(pages, cookie, selectors, whitelist) {
        var result = { used: {}, ignored: {} };
        var deferred = Q.defer();
        var processed = [];
        var i, l;

        // Handle excludes by adding them to processed, so they won't be visited
        var excludedSubdomains = [];
        if (pages.exclude) {
            for (i=0, l=pages.exclude.length; i<l; i++) {
                var current = pages.exclude[i];
                if (current.indexOf("*") !== -1) {
                    var subdomain = current.substring(0, current.length - 1);
                    if (subdomain.indexOf("http") === 0) {
                        subdomain = url.parse(subdomain).pathname;
                    }
                    excludedSubdomains.push(subdomain);
                    continue;
                }

                processed.push(current.split("?")[0]);
            }
        }

        var self = this;
        var queue = async.queue(function(item, queueCallback) {
            // If in processed, skip (may have been in excluded list)
            if (-1 !== processed.indexOf(item.page.split("?")[0])) {
                queueCallback();
                return;
            }

            var page = item.page;
            var uri, host;
            if (0 === page.indexOf("http")) {
                console.log("Visiting URL: ", item.page.split("?")[0]);
                uri = page;
                host = url.parse(uri).host || "";
            }

            var visits = [];
            visits.push(self._getHtmlAsString(item, null)); // regular visit
            if (cookie) {
                visits.push(self._getHtmlAsString(item, cookie)); // logged in visit
            }
            processed.push(page.split("?")[0]);

            Q.all(visits)
            .spread(function(regularResult, loggedInResult) {
                var context = {
                    selectors: selectors,
                    whitelist: whitelist,
                    excludedSubdomains: excludedSubdomains,
                    result: result
                };

                var regularItem = {
                    page: regularResult,
                    followLinks: item.followLinks,
                    uri: uri
                };
                self._processHtmlString(regularItem, context, queue);

                if (loggedInResult) {
                    var loggedInItem = {
                        page: loggedInResult,
                        followLinks: item.followLinks,
                        uri: uri
                    };
                    self._processHtmlString(loggedInItem, context, queue);
                }
            }).fail(function(error) {
                console.log(error);
            }).done(queueCallback); // Passing queue callback, in case it needs to abort
        }, 8);

        queue.drain = function(err) { // TODO: Handle err
            if (err) {
                deferred.reject(new Error(err));
            } else {
                deferred.resolve(result);
            }
        };

        // Crawl to find all HTML links
        if (pages.crawl) {
            for (i=0, l=pages.crawl.length; i<l; i++) {
                queue.push({page: pages.crawl[i], followLinks: true});
            }
        }

        if (pages.include) {
            for (i=0, l=pages.include.length; i<l; i++) {
                queue.push({page: pages.include[i], followLinks: false});
            }
        }

        return deferred.promise;
    },

    // TODO: Rename. Add docstring. Clean up parameters.
    _processHtmlString: function(item, context, queue) {
        var html = item.page;
        var followLinks = item.followLinks;
        var uri = item.uri;

        var selectors = context.selectors;
        var whitelist = context.whitelist;
        var result = context.result;
        var excludedSubdomains = context.excludedSubdomains;

        var document = cheerio.load(html);

        if (followLinks) { // look for links in document, add to queue
            var links = document("a");
            for (var i=0, l=links.length; i<l; i++) {
                var handleThis = true;

                var href = links[i].attribs.href.split("#")[0];
                if (!href || href.indexOf("?") === 0) {
                    continue;
                }

                // If under excluded domain, skip
                for (var j=0; j<excludedSubdomains.length; j++) {
                    var excluded = excludedSubdomains[j];
                    if (href.indexOf(excluded) === 0 ||
                        url.parse(href).pathname.indexOf(excluded) === 0) {

                        handleThis = false;
                        break;
                    }
                }

                if (handleThis) {
                    this._handleLink(links[i].attribs.href, uri, followLinks, queue);
                }
            }
        }

        // Process current document
        return this._matchSelectorsInString(document, selectors, whitelist, result);
    },

    _handleLink: function(link, uri, followLinks, queue) {
        var host = url.parse(uri).host;
        if (0 === link.indexOf(uri)) { // current domain
            queue.push({page: link, followLinks: followLinks});
        } else if (0 === link.indexOf("http")) {
            if (url.parse(link).host === host) {
                queue.push({page: link, followLinks: followLinks});
            } else {
                // Skip, another domain
            }
        } else {
            if (uri) {
                link = url.resolve(uri, link);
                queue.push({page: link, followLinks: followLinks});
            } else {
                console.log("Could not resolve " + link);
            }
        }
    },

    // TODO: Rename. Add docstring. Clean up parameters.
    _matchSelectorsInString: function(htmlString, selectors, whitelist, result) {
        // Loop through selectors
        for (var k=0, l=selectors.length; k<l; k++) {
            var selector = selectors[k];

            // If current selector is whitelisted, skip.
            if (whitelist && -1 < whitelist.indexOf(selector)) {
                continue;
            }
            if (-1 < selector.indexOf("@")) {
                result.ignored[selector] = 1;
                continue;
            }

            if (selector) {
                var oSelector = selector;

                // Add selector to index, if not already added
                if (undefined === result.used[oSelector]) {
                    result.used[oSelector] = 0;
                }

                // Remove pseudo part of selector
                selector = selector.split(":")[0];

                // Check if selector is used
                try {
                    if (htmlString(selector).length > 0) {
                        result.used[oSelector] =
                            result.used[oSelector]
                            + htmlString(selector).length;
                    }
                } catch (e) {
                    console.log("Problem with selector: "
                                + oSelector);
                }
            }
        }
        return;
    },

    // TODO: Add docstring. Clean up parameters.
    _getHtmlAsString: function(item, cookie) {
        var deferred = Q.defer();
        var page = item.page;
        var data;

        // Get page as raw html
        // If URI is given, fetch HTML
        //
        // Note: _handleLink will add domain to relative URLs when crawling,
        // so they won't be mistaken for strings.
        if (0 === page.indexOf("http")) { // From URI
            var uri = page;

            var headers = {};
            if (cookie) {
                headers = {
                    "Cookie": cookie,
                    "Referer": uri
                };
            }

            var options = { uri: uri,
                            headers: headers };

            require('request').get(options, function(error, res, data) {
                                       // TODO: Error checking, response status code, etc.
                                       deferred.resolve(data);
                                   });
        } else if (-1 === page.indexOf("<html>")) { // From file
            try {
                data = fs.readFileSync(page).toString();
            } catch (e) {
                console.log(e.message);
            }
            deferred.resolve(data);
        } else { // From string
            deferred.resolve(page);
        }

        return deferred.promise;
    }
};
<commit_msg>Remove redundant test<commit_after>var Q = require('q');
var async = require('async');
var url = require('url');
var cheerio = require('cheerio');
var fs = require('fs');

module.exports = {
    /*
     * Go through HTML to match CSS selectors
     * @param {Array} html Html to search through. This can be either an array
     *         of Strings (containing html code), an array of URIs to visit, or
     *         an array of paths to html files.
     * @param {String} cookie Cookie to use for login, on the form
     *         "sessionid=foo". Each uri in the html parameter will
     *         be visited both with and without the cookie.
     * @param {String} whitelist List of selectors to ignore.
     * @param {Array} selectors Array of CSS selectors
     * @param {Object} result Object on the form { used: { ".foo": 1 },
     *                                             duplicates: { ".bar": 0 } }
     */
    matchSelectors: function(pages, cookie, selectors, whitelist) {
        var result = { used: {}, ignored: {} };
        var deferred = Q.defer();
        var processed = [];
        var i, l;

        // Handle excludes by adding them to processed, so they won't be visited
        var excludedSubdomains = [];
        if (pages.exclude) {
            for (i=0, l=pages.exclude.length; i<l; i++) {
                var current = pages.exclude[i];
                if (current.indexOf("*") !== -1) {
                    var subdomain = current.substring(0, current.length - 1);
                    if (subdomain.indexOf("http") === 0) {
                        subdomain = url.parse(subdomain).pathname;
                    }
                    excludedSubdomains.push(subdomain);
                    continue;
                }

                processed.push(current.split("?")[0]);
            }
        }

        var self = this;
        var queue = async.queue(function(item, queueCallback) {
            // If in processed, skip (may have been in excluded list)
            if (-1 !== processed.indexOf(item.page.split("?")[0])) {
                queueCallback();
                return;
            }

            var page = item.page;
            var uri, host;
            if (0 === page.indexOf("http")) {
                console.log("Visiting URL: ", item.page.split("?")[0]);
                uri = page;
                host = url.parse(uri).host || "";
            }

            var visits = [];
            visits.push(self._getHtmlAsString(item, null)); // regular visit
            if (cookie) {
                visits.push(self._getHtmlAsString(item, cookie)); // logged in visit
            }
            processed.push(page.split("?")[0]);

            Q.all(visits)
            .spread(function(regularResult, loggedInResult) {
                var context = {
                    selectors: selectors,
                    whitelist: whitelist,
                    excludedSubdomains: excludedSubdomains,
                    result: result
                };

                var regularItem = {
                    page: regularResult,
                    followLinks: item.followLinks,
                    uri: uri
                };
                self._processHtmlString(regularItem, context, queue);

                if (loggedInResult) {
                    var loggedInItem = {
                        page: loggedInResult,
                        followLinks: item.followLinks,
                        uri: uri
                    };
                    self._processHtmlString(loggedInItem, context, queue);
                }
            }).fail(function(error) {
                console.log(error);
            }).done(queueCallback); // Passing queue callback, in case it needs to abort
        }, 8);

        queue.drain = function(err) { // TODO: Handle err
            if (err) {
                deferred.reject(new Error(err));
            } else {
                deferred.resolve(result);
            }
        };

        // Crawl to find all HTML links
        if (pages.crawl) {
            for (i=0, l=pages.crawl.length; i<l; i++) {
                queue.push({page: pages.crawl[i], followLinks: true});
            }
        }

        if (pages.include) {
            for (i=0, l=pages.include.length; i<l; i++) {
                queue.push({page: pages.include[i], followLinks: false});
            }
        }

        return deferred.promise;
    },

    // TODO: Rename. Add docstring. Clean up parameters.
    _processHtmlString: function(item, context, queue) {
        var html = item.page;
        var followLinks = item.followLinks;
        var uri = item.uri;

        var selectors = context.selectors;
        var whitelist = context.whitelist;
        var result = context.result;
        var excludedSubdomains = context.excludedSubdomains;

        var document = cheerio.load(html);

        if (followLinks) { // look for links in document, add to queue
            var links = document("a");
            for (var i=0, l=links.length; i<l; i++) {
                var handleThis = true;

                var href = links[i].attribs.href.split("#")[0];
                if (!href || href.indexOf("?") === 0) {
                    continue;
                }

                // If under excluded domain, skip
                for (var j=0; j<excludedSubdomains.length; j++) {
                    var excluded = excludedSubdomains[j];
                    if (href.indexOf(excluded) === 0 ||
                        url.parse(href).pathname.indexOf(excluded) === 0) {

                        handleThis = false;
                        break;
                    }
                }

                if (handleThis) {
                    this._handleLink(links[i].attribs.href, uri, followLinks, queue);
                }
            }
        }

        // Process current document
        return this._matchSelectorsInString(document, selectors, whitelist, result);
    },

    _handleLink: function(link, uri, followLinks, queue) {

        var host = url.parse(uri).host;
        if (0 === link.indexOf("http")) {
            if (url.parse(link).host === host) {
                queue.push({page: link, followLinks: followLinks});
            } else {
                // Skip, another domain
            }
        } else {
            if (uri) {
                link = url.resolve(uri, link);
                queue.push({page: link, followLinks: followLinks});
            } else {
                console.log("Could not resolve " + link);
            }
        }
    },

    // TODO: Rename. Add docstring. Clean up parameters.
    _matchSelectorsInString: function(htmlString, selectors, whitelist, result) {
        // Loop through selectors
        for (var k=0, l=selectors.length; k<l; k++) {
            var selector = selectors[k];

            // If current selector is whitelisted, skip.
            if (whitelist && -1 < whitelist.indexOf(selector)) {
                continue;
            }
            if (-1 < selector.indexOf("@")) {
                result.ignored[selector] = 1;
                continue;
            }

            if (selector) {
                var oSelector = selector;

                // Add selector to index, if not already added
                if (undefined === result.used[oSelector]) {
                    result.used[oSelector] = 0;
                }

                // Remove pseudo part of selector
                selector = selector.split(":")[0];

                // Check if selector is used
                try {
                    if (htmlString(selector).length > 0) {
                        result.used[oSelector] =
                            result.used[oSelector]
                            + htmlString(selector).length;
                    }
                } catch (e) {
                    console.log("Problem with selector: "
                                + oSelector);
                }
            }
        }
        return;
    },

    // TODO: Add docstring. Clean up parameters.
    _getHtmlAsString: function(item, cookie) {
        var deferred = Q.defer();
        var page = item.page;
        var data;

        // Get page as raw html
        // If URI is given, fetch HTML
        //
        // Note: _handleLink will add domain to relative URLs when crawling,
        // so they won't be mistaken for strings.
        if (0 === page.indexOf("http")) { // From URI
            var uri = page;

            var headers = {};
            if (cookie) {
                headers = {
                    "Cookie": cookie,
                    "Referer": uri
                };
            }

            var options = { uri: uri,
                            headers: headers };

            require('request').get(options, function(error, res, data) {
                                       // TODO: Error checking, response status code, etc.
                                       deferred.resolve(data);
                                   });
        } else if (-1 === page.indexOf("<html>")) { // From file
            try {
                data = fs.readFileSync(page).toString();
            } catch (e) {
                console.log(e.message);
            }
            deferred.resolve(data);
        } else { // From string
            deferred.resolve(page);
        }

        return deferred.promise;
    }
};
<|endoftext|>


<commit_before>define({
    type_attributes: {
        'keyword': {
            'title': 'Keyword',
            'attributes': ['description', 'url'],
        },
        'person': {
            'title': 'Person',
            'attributes': ['affiliation',
                           'description',
                           'email',
                           'image-url',
                           'name',
                           'url'
                           ],
            },
        'project': {
            'title': 'Project',
            'attributes': ['description', 'url', 'rhizi-url'],
        },
        'project_component': {
            'title': 'Project Component',
            'attributes': ['description', 'url'],
        },
        'objective': {
            'title': 'Objective',
            'attributes': ['description', 'url'],
        },
        'outcome': {
            'title': 'Objective',
            'attributes': ['description', 'url'],
        }
    },
    attribute_titles: {
        'affiliation': 'Affiliation',
        'description': 'Description',
        'email': 'Email',
        'name': 'Name',
        'type': 'Type',
        'url': 'URL',
        'image-url': 'Image URL',
        'rhizi-url': 'Rhizi URL',
    }
});<commit_msg>commons: move domain_types to leap, add ui names (unused)<commit_after>define({
    type_attributes: {
        'keyword': {
            'title': 'Keyword',
            'attributes': ['description', 'url'],
        },
        'person': {
            'title': 'Person',
            'attributes': ['affiliation',
                           'description',
                           'email',
                           'image-url',
                           'name',
                           'url'
                           ],
            },
        'project': {
            'title': 'Project',
            'attributes': ['description', 'url', 'rhizi-url'],
        },
        'project_component': {
            'title': 'Project Component',
            'attributes': ['description', 'url'],
        },
        'objective': {
            'title': 'Objective',
            'attributes': ['description', 'url'],
        },
        'outcome': {
            'title': 'Objective',
            'attributes': ['description', 'url'],
        }
    },
    attribute_titles: {
        'affiliation': 'Affiliation',
        'description': 'Description',
        'email': 'Email',
        'image-url': 'Image URL',
        'name': 'Name',
        'street-address': 'Street Address',
        'type': 'Type',
        'url': 'URL',
    },
    attribute_ui: {
        'affiliation': 'textarea',
        'description': 'textarea',
        'email': 'input',
        'image-url': 'image',
        'name': 'input',
        'street-address': 'textarea',
        'type': 'type',
        'url': 'url',
    },
});
<|endoftext|>


<commit_before>/*
 * ACME HTTP client
 */

var crypto = require('crypto');
var fmt = require('util').format;
var async = require('async');
var merge = require('merge');
var request = require('request');
var debug = require('debug')('acme-client');
var openssl = require('./openssl');
var helper = require('./helper');

/**
 * ACME HTTP client
 *
 * @class
 * @param {string} directoryUri Directory URL to the ACME provider
 * @param {buffer} accountKey PEM encoded private account key
 */

function HttpClient(directoryUri, accountKey) {
    this.directoryUri = directoryUri;
    this.accountKey = accountKey;

    this.directory = null;
    this.jwk = null;
    this.nonces = [];
}

/**
 * HTTP request
 *
 * @param {string} uri HTTP URI
 * @param {string} method HTTP method
 * @param {object} [opts] Request options
 * @param {boolean} [saveNonce] Save nonce in queue if returned, default: `true`
 * @param {function} callback `{string}` err, `{object}` resp, `{object}` body
 */

HttpClient.prototype.request = function(uri, method, opts, saveNonce, callback) {
    if (typeof saveNonce === 'undefined') {
        saveNonce = opts;
        opts = {};
    }

    if (typeof callback === 'undefined') {
        callback = saveNonce;
        saveNonce = true;
    }

    var self = this;
    opts.uri = uri;
    opts.method = method;
    opts.json = true;

    debug('HTTP request: %s %s', method, uri);

    /* Request */
    request(opts, function(err, resp, body) {
        if (err) return callback(err);

        debug('RESP %d %s %s', resp.statusCode, method, uri);

        /* Save nonce for later */
        if (saveNonce && resp.headers['replay-nonce']) {
            self.nonces.push(resp.headers['replay-nonce']);
        }

        callback(err, resp, body);
    });
};

/**
 * Ensure provider directory exists
 *
 * https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#directory
 *
 * @param {function} callback `{string}` err
 */

HttpClient.prototype.getDirectory = function(callback) {
    var self = this;

    if (self.directory) {
        return callback(null);
    }

    self.request(self.directoryUri, 'GET', function(err, resp, body) {
        if (err) return callback(err);

        self.directory = body;

        callback(null);
    });
};

/**
 * Get JSON Web Key
 *
 * @param {function} callback `{string}` err, `{object}` {e, kty, n}
 */

HttpClient.prototype.getJwk = function(callback) {
    var self = this;

    if (self.jwk) {
        return callback(null, self.jwk);
    }

    /* Get public exponent and modulus */
    async.parallel({
        exponent: async.apply(openssl.getPublicExponent, self.accountKey),
        modulus: async.apply(openssl.getModulus, self.accountKey)
    }, function(err, data) {
        if (err) return callback(err);

        /* Create JWK */
        self.jwk = {
            e: helper.b64encode(data.exponent),
            kty: 'RSA',
            n: helper.b64encode(data.modulus)
        };

        callback(null, self.jwk);
    });
};

/**
 * Get nonce from directory API endpoint
 *
 * https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#getting-a-nonce
 *
 * @param {function} callback `{string}` err, `{string}` nonce
 */

HttpClient.prototype.getNonce = function(callback) {
    debug('Nonce count: %d', this.nonces.length);

    if (this.nonces.length) {
        return callback(null, this.nonces.shift());
    }

    /* Request a fresh nonce */
    this.request(this.directoryUri, 'HEAD', {}, false, function(err, resp) {
        if (err) return callback(err);

        if (!resp.headers['replay-nonce']) {
            return callback('Failed to get nonce from provider');
        }

        callback(null, resp.headers['replay-nonce']);
    });
};

/**
 * Get URI for a directory resource
 *
 * @param {string} resource API resource name
 * @param {function} callback `{string}` err, `{string}` uri
 */

HttpClient.prototype.getResourceUri = function(resource, callback) {
    var self = this;

    self.getDirectory(function(err) {
        if (err) return callback(err);

        if (!self.directory[resource]) {
            return callback(fmt('Could not resolve URI for API resource: "%s"', resource));
        }

        callback(null, self.directory[resource]);
    });
};

/**
 * Create signed HTTP request body
 *
 * @param {object} payload Request payload
 * @param {string} [nonce] Request nonce
 * @param {function} callback `{string}` err, `{object}` body
 */

HttpClient.prototype.createSignedBody = function(payload, nonce, callback) {
    if (typeof callback === 'undefined') {
        callback = nonce;
        nonce = null;
    }

    var self = this;

    /* Get JWK */
    self.getJwk(function(err, jwk) {
        if (err) return callback(err);

        /* Request header */
        var header = {
            alg: 'RS256',
            jwk: jwk
        };

        /* Request payload */
        var protectedOpts = {};

        if (nonce) {
            debug('Using nonce: %s', nonce);
            protectedOpts.nonce = nonce;
        }

        var payload64 = helper.b64encode(JSON.stringify(payload));
        var protected = merge(true, header, protectedOpts);
        var protected64 = helper.b64encode(JSON.stringify(protected));

        /* Signature */
        var signer = crypto.createSign('RSA-SHA256')
            .update(fmt('%s.%s', protected64, payload64), 'utf8');

        var signature64 = helper.b64escape(signer.sign(self.accountKey, 'base64'));

        /* Request data */
        callback(null, {
            header: header,
            protected: protected64,
            payload: payload64,
            signature: signature64
        });
    });
};

/**
 * Signed HTTP request
 *
 * https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#request-authentication
 *
 * @param {string} uri HTTP URI
 * @param {string} method HTTP method
 * @param {object} payload Request payload
 * @param {function} callback `{string}` err, `{object}` resp, `{object}` body
 */

HttpClient.prototype.signedRequest = function(uri, method, payload, callback) {
    var self = this;

    async.waterfall([
        /* Get nonce */
        self.getNonce.bind(self),

        /* Create signed body */
        async.apply(self.createSignedBody.bind(self), payload),

        /* Send request */
        function(data, done) {
            self.request(uri, method, { body: data }, done);
        }
    ], callback);
};

/* Expose client */
module.exports = HttpClient;
<commit_msg>Fixed bug causing invalid anti-replay nonce<commit_after>/*
 * ACME HTTP client
 */

var crypto = require('crypto');
var fmt = require('util').format;
var async = require('async');
var merge = require('merge');
var request = require('request');
var debug = require('debug')('acme-client');
var openssl = require('./openssl');
var helper = require('./helper');

/**
 * ACME HTTP client
 *
 * @class
 * @param {string} directoryUri Directory URL to the ACME provider
 * @param {buffer} accountKey PEM encoded private account key
 */

function HttpClient(directoryUri, accountKey) {
    this.directoryUri = directoryUri;
    this.accountKey = accountKey;

    this.directory = null;
    this.jwk = null;
}

/**
 * HTTP request
 *
 * @param {string} uri HTTP URI
 * @param {string} method HTTP method
 * @param {object} [opts] Request options
 * @param {function} callback `{string}` err, `{object}` resp, `{object}` body
 */

HttpClient.prototype.request = function(uri, method, opts, callback) {
    if (typeof callback === 'undefined') {
        callback = opts;
        opts = {};
    }

    opts.uri = uri;
    opts.method = method;
    opts.json = true;

    debug('HTTP request: %s %s', method, uri);

    /* Request */
    request(opts, function(err, resp, body) {
        if (err) return callback(err);

        debug('RESP %d %s %s', resp.statusCode, method, uri);
        callback(err, resp, body);
    });
};

/**
 * Ensure provider directory exists
 *
 * https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#directory
 *
 * @param {function} callback `{string}` err
 */

HttpClient.prototype.getDirectory = function(callback) {
    var self = this;

    if (self.directory) {
        return callback(null);
    }

    self.request(self.directoryUri, 'GET', function(err, resp, body) {
        if (err) return callback(err);

        self.directory = body;

        callback(null);
    });
};

/**
 * Get JSON Web Key
 *
 * @param {function} callback `{string}` err, `{object}` {e, kty, n}
 */

HttpClient.prototype.getJwk = function(callback) {
    var self = this;

    if (self.jwk) {
        return callback(null, self.jwk);
    }

    /* Get public exponent and modulus */
    async.parallel({
        exponent: async.apply(openssl.getPublicExponent, self.accountKey),
        modulus: async.apply(openssl.getModulus, self.accountKey)
    }, function(err, data) {
        if (err) return callback(err);

        /* Create JWK */
        self.jwk = {
            e: helper.b64encode(data.exponent),
            kty: 'RSA',
            n: helper.b64encode(data.modulus)
        };

        callback(null, self.jwk);
    });
};

/**
 * Get nonce from directory API endpoint
 *
 * https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#getting-a-nonce
 *
 * @param {function} callback `{string}` err, `{string}` nonce
 */

HttpClient.prototype.getNonce = function(callback) {
    /* Request a fresh nonce */
    this.request(this.directoryUri, 'HEAD', function(err, resp) {
        if (err) return callback(err);

        if (!resp.headers['replay-nonce']) {
            return callback('Failed to get nonce from provider');
        }

        callback(null, resp.headers['replay-nonce']);
    });
};

/**
 * Get URI for a directory resource
 *
 * @param {string} resource API resource name
 * @param {function} callback `{string}` err, `{string}` uri
 */

HttpClient.prototype.getResourceUri = function(resource, callback) {
    var self = this;

    self.getDirectory(function(err) {
        if (err) return callback(err);

        if (!self.directory[resource]) {
            return callback(fmt('Could not resolve URI for API resource: "%s"', resource));
        }

        callback(null, self.directory[resource]);
    });
};

/**
 * Create signed HTTP request body
 *
 * @param {object} payload Request payload
 * @param {string} [nonce] Request nonce
 * @param {function} callback `{string}` err, `{object}` body
 */

HttpClient.prototype.createSignedBody = function(payload, nonce, callback) {
    if (typeof callback === 'undefined') {
        callback = nonce;
        nonce = null;
    }

    var self = this;

    /* Get JWK */
    self.getJwk(function(err, jwk) {
        if (err) return callback(err);

        /* Request header */
        var header = {
            alg: 'RS256',
            jwk: jwk
        };

        /* Request payload */
        var protectedOpts = {};

        if (nonce) {
            debug('Using nonce: %s', nonce);
            protectedOpts.nonce = nonce;
        }

        var payload64 = helper.b64encode(JSON.stringify(payload));
        var protected = merge(true, header, protectedOpts);
        var protected64 = helper.b64encode(JSON.stringify(protected));

        /* Signature */
        var signer = crypto.createSign('RSA-SHA256')
            .update(fmt('%s.%s', protected64, payload64), 'utf8');

        var signature64 = helper.b64escape(signer.sign(self.accountKey, 'base64'));

        /* Request data */
        callback(null, {
            header: header,
            protected: protected64,
            payload: payload64,
            signature: signature64
        });
    });
};

/**
 * Signed HTTP request
 *
 * https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#request-authentication
 *
 * @param {string} uri HTTP URI
 * @param {string} method HTTP method
 * @param {object} payload Request payload
 * @param {function} callback `{string}` err, `{object}` resp, `{object}` body
 */

HttpClient.prototype.signedRequest = function(uri, method, payload, callback) {
    var self = this;

    async.waterfall([
        /* Get nonce */
        self.getNonce.bind(self),

        /* Create signed body */
        async.apply(self.createSignedBody.bind(self), payload),

        /* Send request */
        function(data, done) {
            self.request(uri, method, { body: data }, done);
        }
    ], callback);
};

/* Expose client */
module.exports = HttpClient;
<|endoftext|>


<commit_before>var os = Npm.require('os');

SystemModel = function () {
  var self = this;
  this.startTime = Ntp._now();
  try {
    var usage = (typeof KadiraBinaryDeps != 'undefined')?
      KadiraBinaryDeps.usage: Npm.require('usage');
    this.usageLookup = Meteor._wrapAsync(usage.lookup.bind(usage));
  } catch(ex) {
    console.error('Kadira: usage npm module loading failed - ', ex.message);
  }
}

_.extend(SystemModel.prototype, KadiraModel.prototype);

SystemModel.prototype.buildPayload = function() {
  var metrics = {};
  var now = Ntp._now();
  metrics.startTime = Kadira.syncedDate.syncTime(this.startTime);
  metrics.endTime = Kadira.syncedDate.syncTime(now);

  metrics.sessions = _.keys(Meteor.default_server.sessions).length;
  metrics.memory = process.memoryUsage().rss / (1024*1024);

  if(this.usageLookup && !this._dontTrackUsage) {
    try {
      metrics.pcpu = this.usageLookup(process.pid, {keepHistory: true}).cpu;
      // this metric will be added soon. So we just need to make it reserved
      metrics.cputime = -1;
    } catch(ex) {
      if(/Unsupported OS/.test(ex.message)) {
        this._dontTrackUsage = true;
        var message =
          "kadira: we can't track CPU usage in this OS. " +
          "But it will work when you deploy your app!"
        console.warn(message);
      } else {
        throw ex;
      }
    }
  }

  this.startTime = now;
  return {systemMetrics: [metrics]};
};
<commit_msg>Track new sessions created<commit_after>var os = Npm.require('os');

SystemModel = function () {
  var self = this;
  this.startTime = Ntp._now();
  this.newSessions = 0;
  try {
    var usage = (typeof KadiraBinaryDeps != 'undefined')?
      KadiraBinaryDeps.usage: Npm.require('usage');
    this.usageLookup = Meteor._wrapAsync(usage.lookup.bind(usage));
  } catch(ex) {
    console.error('Kadira: usage npm module loading failed - ', ex.message);
  }

  Meteor.onConnection(function () {
    self.newSessions++;
  });
}

_.extend(SystemModel.prototype, KadiraModel.prototype);

SystemModel.prototype.buildPayload = function() {
  var metrics = {};
  var now = Ntp._now();
  metrics.startTime = Kadira.syncedDate.syncTime(this.startTime);
  metrics.endTime = Kadira.syncedDate.syncTime(now);

  metrics.sessions = _.keys(Meteor.default_server.sessions).length;
  metrics.memory = process.memoryUsage().rss / (1024*1024);
  metrics.newSessions = this.newSessions;
  this.newSessions = 0;

  if(this.usageLookup && !this._dontTrackUsage) {
    try {
      metrics.pcpu = this.usageLookup(process.pid, {keepHistory: true}).cpu;
      // this metric will be added soon. So we just need to make it reserved
      metrics.cputime = -1;
    } catch(ex) {
      if(/Unsupported OS/.test(ex.message)) {
        this._dontTrackUsage = true;
        var message =
          "kadira: we can't track CPU usage in this OS. " +
          "But it will work when you deploy your app!"
        console.warn(message);
      } else {
        throw ex;
      }
    }
  }

  this.startTime = now;
  return {systemMetrics: [metrics]};
};
<|endoftext|>