<commit_before>'use strict';

/*
LookJS is under the MIT license.
Created by Elena Torro
https://elenatorro.com
*/

var Look = {

  // -- Private --------------------------------------------------------------
  _setAttributes :
    function(element, attributes) {
      for (var name in attributes) {
        element.setAttribute(name, attributes[name]);
      }
    },

 _setContent :
  function(element, content) {
    content.forEach(function(object) {
      Look.create(object,element);
    });
  },

  _for :
    function(collection, attribute, content, position) {
      if (collection.length != 0) {
        for (var i in collection) {
          if (collection[i].getAttribute(attribute) === content) return collection[i];
        }
      }
      return false;
    },

    // -- Public ---------------------------------------------------------------

  create :
    function(object, container) {
      var element = this.newElement(object.tag);
      this._setAttributes(element, object.attr);
      if (object.content) this._setContent(element, object.content);
      container ? container.appendChild(element) : document.body.appendChild(element);
      return element;
    },

  createMany :
    function(times, object, container) {
      var i = 0;
      for (i; i < times; i++) {
        Look.create(object, container);
      }
    },

  copy :
    function(from, to) {
      return this.create(Look._clone(from), to);
    },

  _clone :
    function(object) {
      var newObject = object;
      newObject['id'] = newObject['id'] + '_copy';
      return newObject;
    },

    // -- document operations substitution ---------------------------------

  newElement :
    function(tag) {
      return document.createElement(tag);
    },

  getBy : {
    id :
      function(id) {
        return document.getElementById(id);
      },
    className :
      function(name, at) {
        if (at) return document.getElementsByClassName(name)[at];
        return document.getElementsByClassName(name);
      },
    tagName :
    function(name, at) {
      if (at) return document.getElementsByTagName(name)[at];
      return document.getElementsByTagName(name);
    },
    href :
      function(link, at) {
        if (at) return Look._for(document.getElementsByTagName('a'), 'href', link, at);
        return Look._for(document.getElementsByTagName('a'), 'href', link);
      },
    src :
      function(link, at) {
        if (at) return Look._for(document.getElementsByTagName('img'), 'src', link, at);
        return Look._for(document.getElementsByTagName('img'), 'src', link);
      }
    },

    // -- Localization ---------------------------------------------------------

setText : {
  to : {
    oneElement : function(element, text) {
      element.textContent = text;
    },

    manyElements : function(elements, texts, language) {
      var i = 0;
      for (i; i < elements.length; i++) {
        elements[i].textContent = texts[i][language];
      }
    }
  }
},

changeLanguage :
  function(language, localizer) {
    if (localizer['id']) {
      for (var element in localizer['id']) {
        Look.setText.to.oneElement(Look.getBy.id(element), localizer['id'][element][language]);
      }
    };

    if (localizer['class']) {
      for (var element in localizer['class']) {
        Look.setText.to.manyElements(Look.getBy.className(element), localizer['class'][element], language);
      }
    };

    if (localizer['tag']) {
      for (var element in localizer['tag']) {
        Look.setText.to.manyElements(Look.getBy.tagName(element), localizer['tag'][element], language);
      }
    };

  }
}
<commit_msg>added allClass and allTag for translations<commit_after>'use strict';

/*
LookJS is under the MIT license.
Created by Elena Torro
https://elenatorro.com
*/

var Look = {

  // -- Private --------------------------------------------------------------
  _setAttributes :
    function(element, attributes) {
      for (var name in attributes) {
        element.setAttribute(name, attributes[name]);
      }
    },

 _setContent :
  function(element, content) {
    content.forEach(function(object) {
      Look.create(object,element);
    });
  },

  _for :
    function(collection, attribute, content, position) {
      if (collection.length != 0) {
        for (var i in collection) {
          if (collection[i].getAttribute(attribute) === content) return collection[i];
        }
      }
      return false;
    },

    // -- Public ---------------------------------------------------------------

  create :
    function(object, container) {
      var element = this.newElement(object.tag);
      this._setAttributes(element, object.attr);
      if (object.content) this._setContent(element, object.content);
      container ? container.appendChild(element) : document.body.appendChild(element);
      return element;
    },

  createMany :
    function(times, object, container) {
      var i = 0;
      for (i; i < times; i++) {
        Look.create(object, container);
      }
    },

  copy :
    function(from, to) {
      return this.create(Look._clone(from), to);
    },

  _clone :
    function(object) {
      var newObject = object;
      newObject['id'] = newObject['id'] + '_copy';
      return newObject;
    },

    // -- document operations substitution ---------------------------------

  newElement :
    function(tag) {
      return document.createElement(tag);
    },

  getBy : {
    id :
      function(id) {
        return document.getElementById(id);
      },
    className :
      function(name, at) {
        if (at) return document.getElementsByClassName(name)[at];
        return document.getElementsByClassName(name);
      },
    tagName :
    function(name, at) {
      if (at) return document.getElementsByTagName(name)[at];
      return document.getElementsByTagName(name);
    },
    href :
      function(link, at) {
        if (at) return Look._for(document.getElementsByTagName('a'), 'href', link, at);
        return Look._for(document.getElementsByTagName('a'), 'href', link);
      },
    src :
      function(link, at) {
        if (at) return Look._for(document.getElementsByTagName('img'), 'src', link, at);
        return Look._for(document.getElementsByTagName('img'), 'src', link);
      }
    },

    // -- Localization ---------------------------------------------------------

setText : {
  to : {
    oneElement :
      function(element, text) {
        element.textContent = text;
      },
    manyElements :
      function(elements, texts, language) {
        var i = 0;
        for (i; i < elements.length; i++) {
          elements[i].textContent = texts[i][language];
        }
      },
    allElements :
      function(elements, text) {
        var i = 0;
        for (i; i < elements.length; i++) {
          elements[i].textContent = text;
        }
      }
    }
},

changeLanguage :
  function(language, localizer) {
    if (localizer['id']) {
      for (var element in localizer['id']) {
        Look.setText.to.oneElement(Look.getBy.id(element), localizer['id'][element][language]);
      }
    };

    if (localizer['class']) {
      for (var element in localizer['class']) {
        Look.setText.to.manyElements(Look.getBy.className(element), localizer['class'][element], language);
      }
    };

    if (localizer['tag']) {
      for (var element in localizer['tag']) {
        Look.setText.to.manyElements(Look.getBy.tagName(element), localizer['tag'][element], language);
      }
    };

    if (localizer['allClass']) {
      for (var element in localizer['allClass']) {
        Look.setText.to.allElements(Look.getBy.className(element), localizer['allClass'][element][language]);
      }
    };

    if (localizer['allTag']) {
      for (var element in localizer['allTag']) {
        Look.setText.to.allElements(Look.getBy.className(element), localizer['allTag'][element][language]);
      }
    };
  }
}
<|endoftext|>


<commit_before>/**
 * Copyright 2013-2014, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule ReactComponent
 */

"use strict";

var ReactOwner = require('ReactOwner');
var ReactRef = require('ReactRef');

var invariant = require('invariant');

function attachRef(ref, component, owner) {
  if (ref instanceof ReactRef) {
    ReactRef.attachRef(ref, component);
  } else {
    ReactOwner.addComponentAsRefTo(component, ref, owner);
  }
}

function detachRef(ref, component, owner) {
  if (ref instanceof ReactRef) {
    ReactRef.detachRef(ref, component);
  } else {
    ReactOwner.removeComponentAsRefFrom(component, ref, owner);
  }
}

/**
 * Components are the basic units of composition in React.
 *
 * Every component accepts a set of keyed input parameters known as "props" that
 * are initialized by the constructor. Once a component is mounted, the props
 * can be mutated using `setProps` or `replaceProps`.
 *
 * Every component is capable of the following operations:
 *
 *   `mountComponent`
 *     Initializes the component, renders markup, and registers event listeners.
 *
 *   `receiveComponent`
 *     Updates the rendered DOM nodes to match the given component.
 *
 *   `unmountComponent`
 *     Releases any resources allocated by this component.
 *
 * Components can also be "owned" by other components. Being owned by another
 * component means being constructed by that component. This is different from
 * being the child of a component, which means having a DOM representation that
 * is a child of the DOM representation of that component.
 *
 * @class ReactComponent
 */
var ReactComponent = {

  /**
   * Injected module that provides ability to mutate individual properties.
   * Injected into the base class because many different subclasses need access
   * to this.
   *
   * @internal
   */
  BackendIDOperations: null,

  /**
   * Base functionality for every ReactComponent constructor. Mixed into the
   * `ReactComponent` prototype, but exposed statically for easy access.
   *
   * @lends {ReactComponent.prototype}
   */
  Mixin: {

    /**
     * Base constructor for all React components.
     *
     * Subclasses that override this method should make sure to invoke
     * `ReactComponent.Mixin.construct.call(this, ...)`.
     *
     * @param {ReactElement} element
     * @internal
     */
    construct: function(element) {
      // We keep the old element and a reference to the pending element
      // to track updates.
      this._currentElement = element;
      this._mountIndex = 0;
    },

    /**
     * Initializes the component, renders markup, and registers event listeners.
     *
     * NOTE: This does not insert any nodes into the DOM.
     *
     * Subclasses that override this method should make sure to invoke
     * `ReactComponent.Mixin.mountComponent.call(this, ...)`.
     *
     * @param {string} rootID DOM ID of the root node.
     * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
     * @return {?string} Rendered markup to be inserted into the DOM.
     * @internal
     */
    mountComponent: function(rootID, transaction, context) {
      var ref = this._currentElement.ref;
      if (ref != null) {
        var owner = this._currentElement._owner;
        attachRef(ref, this, owner);
      }
      // Effectively: return '';
    },

    /**
     * Releases any resources allocated by `mountComponent`.
     *
     * NOTE: This does not remove any nodes from the DOM.
     *
     * Subclasses that override this method should make sure to invoke
     * `ReactComponent.Mixin.unmountComponent.call(this)`.
     *
     * @internal
     */
    unmountComponent: function() {
      var ref = this._currentElement.ref;
      if (ref != null) {
        detachRef(ref, this, this._currentElement._owner);
      }
    },

    /**
     * Updates the component's currently mounted representation.
     *
     * @param {ReactReconcileTransaction} transaction
     * @param {object} prevElement
     * @param {object} nextElement
     * @internal
     */
    updateComponent: function(transaction, prevElement, nextElement, context) {
      // If either the owner or a `ref` has changed, make sure the newest owner
      // has stored a reference to `this`, and the previous owner (if different)
      // has forgotten the reference to `this`. We use the element instead
      // of the public this.props because the post processing cannot determine
      // a ref. The ref conceptually lives on the element.

      // TODO: Should this even be possible? The owner cannot change because
      // it's forbidden by shouldUpdateReactComponent. The ref can change
      // if you swap the keys of but not the refs. Reconsider where this check
      // is made. It probably belongs where the key checking and
      // instantiateReactComponent is done.

      if (nextElement._owner !== prevElement._owner ||
          nextElement.ref !== prevElement.ref) {
        if (prevElement.ref != null) {
          detachRef(prevElement.ref, this, prevElement._owner);
        }
        // Correct, even if the owner is the same, and only the ref has changed.
        if (nextElement.ref != null) {
          attachRef(nextElement.ref, this, nextElement._owner);
        }
      }
    },

    /**
     * Get the publicly accessible representation of this component - i.e. what
     * is exposed by refs and returned by React.render. Can be null for
     * stateless components.
     *
     * @return {?ReactComponent} the actual sibling Component.
     * @internal
     */
    getPublicInstance: function() {
      invariant(
        false,
        'getPublicInstance should never be called on the base class. It must ' +
        'be overriden.'
      );
    }
  }
};

module.exports = ReactComponent;
<commit_msg>Add additional field for ART reconciler<commit_after>/**
 * Copyright 2013-2014, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule ReactComponent
 */

"use strict";

var ReactOwner = require('ReactOwner');
var ReactRef = require('ReactRef');

var invariant = require('invariant');

function attachRef(ref, component, owner) {
  if (ref instanceof ReactRef) {
    ReactRef.attachRef(ref, component);
  } else {
    ReactOwner.addComponentAsRefTo(component, ref, owner);
  }
}

function detachRef(ref, component, owner) {
  if (ref instanceof ReactRef) {
    ReactRef.detachRef(ref, component);
  } else {
    ReactOwner.removeComponentAsRefFrom(component, ref, owner);
  }
}

/**
 * Components are the basic units of composition in React.
 *
 * Every component accepts a set of keyed input parameters known as "props" that
 * are initialized by the constructor. Once a component is mounted, the props
 * can be mutated using `setProps` or `replaceProps`.
 *
 * Every component is capable of the following operations:
 *
 *   `mountComponent`
 *     Initializes the component, renders markup, and registers event listeners.
 *
 *   `receiveComponent`
 *     Updates the rendered DOM nodes to match the given component.
 *
 *   `unmountComponent`
 *     Releases any resources allocated by this component.
 *
 * Components can also be "owned" by other components. Being owned by another
 * component means being constructed by that component. This is different from
 * being the child of a component, which means having a DOM representation that
 * is a child of the DOM representation of that component.
 *
 * @class ReactComponent
 */
var ReactComponent = {

  /**
   * Injected module that provides ability to mutate individual properties.
   * Injected into the base class because many different subclasses need access
   * to this.
   *
   * @internal
   */
  BackendIDOperations: null,

  /**
   * Base functionality for every ReactComponent constructor. Mixed into the
   * `ReactComponent` prototype, but exposed statically for easy access.
   *
   * @lends {ReactComponent.prototype}
   */
  Mixin: {

    /**
     * Base constructor for all React components.
     *
     * Subclasses that override this method should make sure to invoke
     * `ReactComponent.Mixin.construct.call(this, ...)`.
     *
     * @param {ReactElement} element
     * @internal
     */
    construct: function(element) {
      // We keep the old element and a reference to the pending element
      // to track updates.
      this._currentElement = element;
      // These two fields are used by the DOM and ART diffing algorithms
      // respectively. Instead of using expandos on components, we should be
      // storing the state needed by the diffing algorithms elsewhere.
      this._mountIndex = 0;
      this._mountImage = null;
    },

    /**
     * Initializes the component, renders markup, and registers event listeners.
     *
     * NOTE: This does not insert any nodes into the DOM.
     *
     * Subclasses that override this method should make sure to invoke
     * `ReactComponent.Mixin.mountComponent.call(this, ...)`.
     *
     * @param {string} rootID DOM ID of the root node.
     * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
     * @return {?string} Rendered markup to be inserted into the DOM.
     * @internal
     */
    mountComponent: function(rootID, transaction, context) {
      var ref = this._currentElement.ref;
      if (ref != null) {
        var owner = this._currentElement._owner;
        attachRef(ref, this, owner);
      }
      // Effectively: return '';
    },

    /**
     * Releases any resources allocated by `mountComponent`.
     *
     * NOTE: This does not remove any nodes from the DOM.
     *
     * Subclasses that override this method should make sure to invoke
     * `ReactComponent.Mixin.unmountComponent.call(this)`.
     *
     * @internal
     */
    unmountComponent: function() {
      var ref = this._currentElement.ref;
      if (ref != null) {
        detachRef(ref, this, this._currentElement._owner);
      }
    },

    /**
     * Updates the component's currently mounted representation.
     *
     * @param {ReactReconcileTransaction} transaction
     * @param {object} prevElement
     * @param {object} nextElement
     * @internal
     */
    updateComponent: function(transaction, prevElement, nextElement, context) {
      // If either the owner or a `ref` has changed, make sure the newest owner
      // has stored a reference to `this`, and the previous owner (if different)
      // has forgotten the reference to `this`. We use the element instead
      // of the public this.props because the post processing cannot determine
      // a ref. The ref conceptually lives on the element.

      // TODO: Should this even be possible? The owner cannot change because
      // it's forbidden by shouldUpdateReactComponent. The ref can change
      // if you swap the keys of but not the refs. Reconsider where this check
      // is made. It probably belongs where the key checking and
      // instantiateReactComponent is done.

      if (nextElement._owner !== prevElement._owner ||
          nextElement.ref !== prevElement.ref) {
        if (prevElement.ref != null) {
          detachRef(prevElement.ref, this, prevElement._owner);
        }
        // Correct, even if the owner is the same, and only the ref has changed.
        if (nextElement.ref != null) {
          attachRef(nextElement.ref, this, nextElement._owner);
        }
      }
    },

    /**
     * Get the publicly accessible representation of this component - i.e. what
     * is exposed by refs and returned by React.render. Can be null for
     * stateless components.
     *
     * @return {?ReactComponent} the actual sibling Component.
     * @internal
     */
    getPublicInstance: function() {
      invariant(
        false,
        'getPublicInstance should never be called on the base class. It must ' +
        'be overriden.'
      );
    }
  }
};

module.exports = ReactComponent;
<|endoftext|>


<commit_before>(function() {

  function replicate(src, target, opts, callback, replicateRet) {

    fetchCheckpoint(src, target, function(checkpoint) {
      var results = [];
      var completed = false;
      var pending = 0;
      var last_seq = 0;
      var continuous = opts.continuous || false;
      var result = {
        ok: true,
        start_time: new Date(),
        docs_read: 0,
        docs_written: 0
      };

      function isCompleted() {
        if (completed && pending === 0) {
          result.end_time = new Date();
          writeCheckpoint(src, target, last_seq, function() {
            call(callback, null, result);
          });
        }
      }

      var changes = src.changes({
        continuous: continuous,
        since: checkpoint,
        onChange: function(change) {
          results.push(change);
          result.docs_read++;
          pending++;
          var diff = {};
          diff[change.id] = change.changes.map(function(x) { return x.rev; });
          target.revsDiff(diff, function(err, diffs) {
            for (var id in diffs) {
              diffs[id].missing.map(function(rev) {
                src.get(id, {revs: true, rev: rev}, function(err, doc) {
                  target.bulkDocs({docs: [doc]}, {new_edits: false}, function() {
                    result.docs_written++;
                    pending--;
                    isCompleted();
                  });
                });
              });
            }
          });
        },
        complete: function(err, res) {
          if (res) {
            last_seq = res.last_seq;
          }
          completed = true;
          isCompleted();
        }
      });
      replicateRet.cancel = changes.cancel;
    });
  }

  function toPouch(db, callback) {
    if (typeof db === 'string') {
      return new Pouch(db, callback);
    }
    callback(null, db);
  }

  Pouch.replicate = function(src, target, opts, callback) {
    var replicateRet = {};
    toPouch(src, function(_, src) {
      toPouch(target, function(_, target) {
        replicate(src, target, opts, callback, replicateRet);
      });
    });
    return replicateRet;
  };

}).call(this);<commit_msg>fix race in cancel replication<commit_after>(function() {

  function replicate(src, target, opts, callback, replicateRet) {

    fetchCheckpoint(src, target, function(checkpoint) {
      var results = [];
      var completed = false;
      var pending = 0;
      var last_seq = 0;
      var continuous = opts.continuous || false;
      var result = {
        ok: true,
        start_time: new Date(),
        docs_read: 0,
        docs_written: 0
      };

      function isCompleted() {
        if (completed && pending === 0) {
          result.end_time = new Date();
          writeCheckpoint(src, target, last_seq, function() {
            call(callback, null, result);
          });
        }
      }

      if (replicateRet.cancelled) {
        return;
      }

      var changes = src.changes({
        continuous: continuous,
        since: checkpoint,
        onChange: function(change) {
          results.push(change);
          result.docs_read++;
          pending++;
          var diff = {};
          diff[change.id] = change.changes.map(function(x) { return x.rev; });
          target.revsDiff(diff, function(err, diffs) {
            for (var id in diffs) {
              diffs[id].missing.map(function(rev) {
                src.get(id, {revs: true, rev: rev}, function(err, doc) {
                  target.bulkDocs({docs: [doc]}, {new_edits: false}, function() {
                    result.docs_written++;
                    pending--;
                    isCompleted();
                  });
                });
              });
            }
          });
        },
        complete: function(err, res) {
          if (res) {
            last_seq = res.last_seq;
          }
          completed = true;
          isCompleted();
        }
      });
      if (opts.continuous) {
        replicateRet.cancel = changes.cancel;
      }
    });
  }

  function toPouch(db, callback) {
    if (typeof db === 'string') {
      return new Pouch(db, callback);
    }
    callback(null, db);
  }

  Pouch.replicate = function(src, target, opts, callback) {
    var ret = function() {
      this.cancelled = false;
      this.cancel = function() {
        this.cancelled = true;
      }
    }
    var replicateRet = new ret();
    toPouch(src, function(_, src) {
      toPouch(target, function(_, target) {
        replicate(src, target, opts, callback, replicateRet);
      });
    });
    return replicateRet;
  };

}).call(this);<|endoftext|>


<commit_before>var Promise = require('bluebird'),
    util = require('./util'),
    Contact = require('./Contact');

// Constants
var K = 20;                 // maximum number of nodes per k-bucket.
var ALPHA = 3;              // number of nodes to consult simultaneously during node lookups
var LOOKUP_TIMEOUT = 10000; // milliseconds

/**
 * A node for the distributed hash table
 * @constructor
 * @param {String} name
 */
function Node (name) {
  if (!(this instanceof Node)) {
    throw new SyntaxError('Constructor must be called with the new operator');
  }

  if (!name || typeof name !== 'string') {
    throw new TypeError('Parameter name must be a non-empty string');
  }

  this.name   = name;
  this.id     = util.sha1(name, {asBytes: true });  // SHA-1 hash of the name, as an array with 20 bytes
  this.idText = util.sha1text(name);                // SHA-1 hash of the name, as a string with 40 hex characters

  this.buckets = [];    // k-buckets 0 <= i <= 160.
                        // Each k-bucket contains an array with up to k contacts,
                        // ordered from least-recently seen node to
                        // most-recently seen node.
}

/**
 * Store or update a contact in the k-buckets of this node
 * @param {Contact} contact
 * @return {Promise.<>} Returns a promise which resolves then the contact is
 *                      stored, or rejects when an error occurred.
 */
Node.prototype.onStoreContact = function onStoreContact (contact) {
  var me = this;
  return new Promise(function (resolve, reject) {
    var i, ii, existingContact;

    if (!(contact instanceof Contact)) {
      reject(new TypeError('Instance of contact expected as parameter contact'));
    }

    // find the right bucket for this contact based on distance to our node
    var index = util.bucketIndex(me.id, contact.id);
    var bucket = me.buckets[index];
    if (!bucket) {
      bucket = [];
      me.buckets[index] = bucket;
    }

    // check if the contact is already listed in the bucket
    var b = null;
    for (i = 0, ii = bucket.length; i < ii; i++) {
      if (bucket[i].id == contact.id) {
        // yeah, found!
        b = i;
        break;
      }
    }

    if (b !== null) {
      // contact is already listed. Move it to the tail of the list (most recent).
      existingContact = bucket.splice(b, 1)[0];
      bucket.push(existingContact);
      resolve();
    }
    else if (bucket.length < K) {
      // bucket isn't yet full, add this new contact to the tail (most recent).
      bucket.push(contact);
      resolve();
    }
    else {
      // bucket is full. Check whether the least-seen contact is still alive
      existingContact = bucket[0];
      existingContact.ping()
          .then(function (alive) {
            // check if nothing changed during our ping
            if (bucket[0] === existingContact) {
              if (alive) {
                // This contact is still alive. Move it to the buckets tail (it's
                // most-recent now), and ignore the new contact.
                bucket.shift();
                bucket.push(existingContact);
              }
              else {
                // The existing contact is dead. Put the new contact back in the bucket
                // instead instead of the dead contact.
                bucket.shift();
                bucket.push(contact);
              }
            }
            resolve();
          })
          .catch(function (err) {
            reject(err);
          });
    }
  });
};

/**
 * Find the k number of closest contacts from this Node.
 * @param {Number[]} id   The id of a node
 * @return {Contact[]}    The k contacts closest to the requested id
 *                        as known by this Node. The returned contacts are
 *                        ordered by distance.
 */
Node.prototype.onFindContact = function onFindContact (id) {
  // Find closest bucket
  var index = util.bucketIndex(this.id, id),
      offset = 0,
      bucket,
      contacts = [];

  // get contacts from the closest bucket
  bucket = this.buckets[index];
  if (bucket) contacts = contacts.concat(bucket);

  // if the initial bucket is not filled, add contacts from surrounding buckets
  // until we have at least K buckets
  while ((contacts.length < K) &&
      (index - offset > 0) &&
      (index + offset < this.buckets.length)) {
    offset++;

    bucket = this.buckets[index - offset];
    if (bucket) contacts = contacts.concat(bucket);

    bucket = this.buckets[index + offset];
    if (bucket) contacts = contacts.concat(bucket);
  }

  // sort the contacts by distance
  // TODO: improve performance by caching the distances
  for (var n = 0; n < 1000; n++) {
    contacts.sort(function (a, b) {
      return util.compare(util.distance(id, a.id), util.distance(id, b.id));
    });
  }

  // limit the results to a maximum of k number of contacts
  if (contacts.length > K) {
    contacts = contacts.slice(0, K);
  }

  // TODO: should return a clone of the contacts
  return contacts;
};

/**
 * Find the closest k number of nodes in the network.
 * Recurses over the known nodes.
 * @param {Number[]} id           The id of a node
 * @return {Promise.<Contact[]>}  The k contacts closest to the requested id
 *                                as found in the whole network. The returned
 *                                contacts are ordered by distance.
 */
Node.prototype.findContact = function findContact (id) {
  var me = this;

  return new Promise(function (resolve, reject) {
    var contacts = me.onFindContact(id);

    var consulted = {}; // map with the contacts already consulted

    /* TODO: implement findContact
    // consult the first ALPHA nodes
    Promise
        .map(contacts.slice(0, ALPHA), function (contact) {
          consulted[contact.idText] = contact;
          return contact.findContact(id);
        })
        .then();
    */
  });
};

// TODO: implement storeValue
// TODO: implement findValue

/**
 * Receive a ping and reply to it
 * @returns {boolean} Returns true when the node is alive.
 */
Node.prototype.onPing = function onPing () {
  return true;
};

/**
 * Join a network via one of the networks nodes
 * @param {Node} node
 */
Node.prototype.join = function join(node) {
  // TODO: implement join
};

/**
 * Leave the currently connected network. The node will empty its k-bucket and
 * returns false on a ping message.
 */
Node.prototype.leave = function leave () {
  // TODO: implement leave correctly once we have real remote nodes (will become async?)

  this.buckets = [];
  this.onPing = function noPing () {
    return false;
  };
};

module.exports = Node;
<commit_msg>cleaned up up some test related stuff in the code<commit_after>var Promise = require('bluebird'),
    util = require('./util'),
    Contact = require('./Contact');

// Constants
var K = 20;                 // maximum number of nodes per k-bucket.
var ALPHA = 3;              // number of nodes to consult simultaneously during node lookups
var LOOKUP_TIMEOUT = 10000; // milliseconds

/**
 * A node for the distributed hash table
 * @constructor
 * @param {String} name
 */
function Node (name) {
  if (!(this instanceof Node)) {
    throw new SyntaxError('Constructor must be called with the new operator');
  }

  if (!name || typeof name !== 'string') {
    throw new TypeError('Parameter name must be a non-empty string');
  }

  this.name   = name;
  this.id     = util.sha1(name);      // SHA-1 hash of the name, as an array with 20 bytes
  this.idText = util.sha1text(name);  // SHA-1 hash of the name, as a string with 40 hex characters

  this.buckets = [];    // k-buckets 0 <= i <= 160.
                        // Each k-bucket contains an array with up to k contacts,
                        // ordered from least-recently seen node to
                        // most-recently seen node.
}

/**
 * Store or update a contact in the k-buckets of this node
 * @param {Contact} contact
 * @return {Promise.<>} Returns a promise which resolves then the contact is
 *                      stored, or rejects when an error occurred.
 */
Node.prototype.onStoreContact = function onStoreContact (contact) {
  var me = this;
  return new Promise(function (resolve, reject) {
    var i, ii, existingContact;

    if (!(contact instanceof Contact)) {
      reject(new TypeError('Instance of contact expected as parameter contact'));
    }

    // find the right bucket for this contact based on distance to our node
    var index = util.bucketIndex(me.id, contact.id);
    var bucket = me.buckets[index];
    if (!bucket) {
      bucket = [];
      me.buckets[index] = bucket;
    }

    // check if the contact is already listed in the bucket
    var b = null;
    for (i = 0, ii = bucket.length; i < ii; i++) {
      if (bucket[i].id == contact.id) {
        // yeah, found!
        b = i;
        break;
      }
    }

    if (b !== null) {
      // contact is already listed. Move it to the tail of the list (most recent).
      existingContact = bucket.splice(b, 1)[0];
      bucket.push(existingContact);
      resolve();
    }
    else if (bucket.length < K) {
      // bucket isn't yet full, add this new contact to the tail (most recent).
      bucket.push(contact);
      resolve();
    }
    else {
      // bucket is full. Check whether the least-seen contact is still alive
      existingContact = bucket[0];
      existingContact.ping()
          .then(function (alive) {
            // check if nothing changed during our ping
            if (bucket[0] === existingContact) {
              if (alive) {
                // This contact is still alive. Move it to the buckets tail (it's
                // most-recent now), and ignore the new contact.
                bucket.shift();
                bucket.push(existingContact);
              }
              else {
                // The existing contact is dead. Put the new contact back in the bucket
                // instead instead of the dead contact.
                bucket.shift();
                bucket.push(contact);
              }
            }
            resolve();
          })
          .catch(function (err) {
            reject(err);
          });
    }
  });
};

/**
 * Find the k number of closest contacts from this Node.
 * @param {Number[]} id   The id of a node
 * @return {Contact[]}    The k contacts closest to the requested id
 *                        as known by this Node. The returned contacts are
 *                        ordered by distance.
 */
Node.prototype.onFindContact = function onFindContact (id) {
  // Find closest bucket
  var index = util.bucketIndex(this.id, id),
      offset = 0,
      bucket,
      contacts = [];

  // get contacts from the closest bucket
  bucket = this.buckets[index];
  if (bucket) contacts = contacts.concat(bucket);

  // if the initial bucket is not filled, add contacts from surrounding buckets
  // until we have at least K buckets
  while ((contacts.length < K) &&
      (index - offset > 0) &&
      (index + offset < this.buckets.length)) {
    offset++;

    bucket = this.buckets[index - offset];
    if (bucket) contacts = contacts.concat(bucket);

    bucket = this.buckets[index + offset];
    if (bucket) contacts = contacts.concat(bucket);
  }

  // sort the contacts by distance
  // TODO: improve performance by caching the distances
  contacts.sort(function (a, b) {
    return util.compare(util.distance(id, a.id), util.distance(id, b.id));
  });

  // limit the results to a maximum of k number of contacts
  if (contacts.length > K) {
    contacts = contacts.slice(0, K);
  }

  // TODO: should return a clone of the contacts
  return contacts;
};

/**
 * Find the closest k number of nodes in the network.
 * Recurses over the known nodes.
 * @param {Number[]} id           The id of a node
 * @return {Promise.<Contact[]>}  The k contacts closest to the requested id
 *                                as found in the whole network. The returned
 *                                contacts are ordered by distance.
 */
Node.prototype.findContact = function findContact (id) {
  var me = this;

  return new Promise(function (resolve, reject) {
    /* steps:
     * 1. Get k closest nodes from this node
     *    2. from the alpha closest nodes:
     *       3. Ask the k closest they know
      *      4. add the received responses and order by distance
     *
     */

    var contacts = me.onFindContact(id);

    var consulted = {}; // map with the contacts already consulted

    // TODO: implement findContact
    // consult the first ALPHA nodes
    Promise
        .map(contacts.slice(0, ALPHA), function (contact) {
          consulted[contact.idText] = contact;
          return contact.findContact(id);
        })
        .then();
  });
};

// TODO: implement storeValue
// TODO: implement findValue

/**
 * Receive a ping and reply to it
 * @returns {boolean} Returns true when the node is alive.
 */
Node.prototype.onPing = function onPing () {
  return true;
};

/**
 * Join a network via one of the networks nodes
 * @param {Node} node
 */
Node.prototype.join = function join(node) {
  // TODO: implement join
};

/**
 * Leave the currently connected network. The node will empty its k-bucket and
 * returns false on a ping message.
 */
Node.prototype.leave = function leave () {
  // TODO: implement leave correctly once we have real remote nodes (will become async?)

  this.buckets = [];
  this.onPing = function noPing () {
    return false;
  };
};

module.exports = Node;
<|endoftext|>


<commit_before>var path = require('path'),
	fs = require('fs'),
	Showdown = require('showdown'),
	markdown = new Showdown.converter().makeHtml,
	express = require('express');

var woods = require('./woods'),
	parsers = require('./parsers'),
	Site = require('./Site');

// Helper function to get the uri or url of the page
var getUri = function(page, url) {
	var uri = page.directory,
		parent = page.parent;
	while (parent) {
		uri = path.join(parent.directory, uri);
		parent = parent.parent;
	}
	var site = url ? page.site.url : page.site.getUri('content');
	return path.join(site, uri);
};

var Page = function(site, parent, directory) {
	this.site = site;
	this.directory = directory;
	this.data = {};
	this.images = [];
	this.resources = [];
	this.parent = parent || null;
	// Only pages with a number prefixed to their
	// directory name are marked as being visible.
	this.visible = /^\/[0-9]/.test(this.directory);

	// Remove numbers in path, since they are only used for order,
	// and replace spaces by dashes:
	var url = getUri(this, true).replace(/\/[0-9]+-/g,'/')
				.replace(/\s/g, '-');
	this.url = url.toLowerCase();
	this.uri = getUri(this);
	site.pageByUrl[this.url] = this;
	this.name = this.directory == ''
			? 'home'
			: path.basename(url).replace(/\/$/, '');

	installRoute(this);
	addChildren(this);
	addResources(this);
	parseContent(this);
};

Page.prototype.getTemplate = function(name) {
	var type = this.data.type,
		types = this.site.types;
	return type && types[type][name]
		? types[type][name]
		: types['default'][name] || function(param) {
			return 'Template not found: ' + name;
		};
};

Page.prototype.getTemplateLocation = function(name) {
	var templateLocations = this.site.templateLocations,
		type = this.data.type;
	return this.data.type && templateLocations[type][name]
		? templateLocations[type][name]
		: templateLocations['default'][name];
};

Page.prototype.renderTemplate = function(name) {
	var param = getTemplateParam(this);
	return this.getTemplate(name)(param);
};

Page.prototype.getUri = function(file) {
	return path.join(this.uri, file || '');
};

Page.prototype.getUrl = function(file) {
	return path.join(this.url, file || '');
};

Page.prototype.getResourceUrl = function(file) {
	return file && path.join(this.url, 'resources', file);
};

/**
 * The title of the page as defined in its document
 * or, when not defined, the name of its folder.
 *
 * @return {String}
 */
Page.prototype.getTitle = function() {
	return this.data.title || this.name;
};

/**
 * Checks whether this page has any children.
 *
 * @return {Boolean} {@true it is has one or more children}
 */
Page.prototype.hasChildren = function() {
	return this.children.length > 0;
};

/**
 * Returns the first child page of this page,
 * if any.
 *
 * @return {Page}
 */
Page.prototype.getFirstChild = function() {
	return this.children.length && this.children[0];
};

/**
 * Returns the last child page of this page.
 * if any.
 *
 * @return {Page}
 */
Page.prototype.getFirstChild = function() {
	var length = this.children.length;
	return length && this.children[length - 1];
};

/**
 * Checks whether the specified page is the parent of this page.
 *
 * @param {Item} page The page to check against
 * @return {Boolean} {@true if it is the parent of the page}
 */
Page.prototype.isParent = function(page) {
	return this.parent == page;
};

/**
 * Checks whether the specified page is a child of this page.
 *
 * @param {Item} page The page to check against
 * @return {Boolean} {@true it is a child of the page}
 */
Page.prototype.isChild = function(page) {
	return page && page.parent == this;
};

/**
 * Checks if this page is contained within the specified page
 * or one of its parents.
 *
 * @param {Item} page The page to check against
 * @return {Boolean} {@true if it is inside the specified page}
 */
Page.prototype.isDescendant = function(page) {
	var parent = this;
	while (parent = parent.parent) {
		if (parent == page)
			return true;
	}
	return false;
};

/**
 * Checks if the page is an ancestor of the specified page.
 *
 * @param {Item} page the page to check against
 * @return {Boolean} {@true if the page is an ancestor of the specified
 * page}
 */
Page.prototype.isAncestor = function(page) {
	return page ? page.isDescendant(this) : false;
};

/**
 * Checks whether there is a previous page on the same level as this one.
 *
 * @return {Boolean} {@true if the page is an ancestor of the specified
 * page}
 */
Page.prototype.hasPrevious = function() {
	return this.visible && this.index > 0;
};

/**
 * Checks whether there is a next page on the same level as this one.
 *
 * @return {Boolean}
 */
Page.prototype.hasNext = function() {
	return this.visible && this.index + 1 < this.parent.children.length;
};

/**
 * The previous page on the same level as this page.
 *
 * @return {Page}
 */
Page.prototype.getPrevious = function() {
	return this.hasPrevious() ? this.parent.children[this.index - 1] : null;
};

/**
 * The next page on the same level as this page.
 *
 * @return {Page}
 */
Page.prototype.getNext = function(page) {
	return this.hasNext() ? this.parent.children[this.index + 1] : null;
};

/**
 * Check whether this page has images.
 *
 * @return {Boolean}
 */
Page.prototype.hasImages = function(page) {
	return !!this.images.length;
};

/**
 * Check whether this page has resources.
 *
 * @return {Boolean}
 */
Page.prototype.hasResources = function(page) {
	return !!this.resources.length;
};

var getTemplateParam = function(page) {
	return {
		page: page,
		site: page.site,
		markdown: markdown
	};
};

var addResources = function(page) {
	var resourcesDir = page.getUri('resources'),
		images = page.images,
		resources = page.resources;

	var addFile = function(file) {
		if (/jpg|jpeg|png|gif/.test(file)) {
			images.push(file);
		}
		resources.push(file);
	};

	fs.exists(resourcesDir, function(exists) {
		if (!exists) return;
		var routeUrl = page.getUrl('resources');
		woods.express.use(routeUrl, express.static(resourcesDir));
		fs.readdir(resourcesDir, function(err, files) {
			files.forEach(addFile);
		});
	});
};

var addChildren = function(page) {
	// Read the directory and add child pagees (if any):
	var children = page.children = [],
		// TODO: make async:
		files = fs.readdirSync(page.uri),
		index = 0;
	for (var i in files) {
		var name = files[i],
			file = path.resolve(page.uri, name),
			// TODO: make async:
			isDirectory = fs.statSync(file).isDirectory(),
			nameReserved = /^(resources|error)$/.test(name);
		if (isDirectory && !nameReserved) {
			var child = new Page(page.site, page, name);
			// Only add to children list if it is a child
			// - i.e. its name starts in the style of: 01- or 02-
			children.push(child);
			child.index = index++;
		}
	}
};

var parseContent = function(page) {
	// Parse the content file and place values in page.data:
	parsers.parse(page.getUri('content.md'), page);
};

var installRoute = function(page) {
	// Install the path in express:
	woods.express.get(page.url, function(req, res) {
		var template = page.getTemplateLocation('html');
		var param = getTemplateParam(page);
		res.render(template, param);
	});
};

module.exports = Page;
<commit_msg>Some work on Page names and urls.<commit_after>var path = require('path'),
	fs = require('fs'),
	Showdown = require('showdown'),
	markdown = new Showdown.converter().makeHtml,
	express = require('express');

var woods = require('./woods'),
	parsers = require('./parsers'),
	Site = require('./Site');

// Helper function to get the uri or url of the page
var getUri = function(page, url) {
	var uri = '',
		parent = page;
	while (parent) {
		uri = path.join(url ? parent.urlName : parent.directory, uri);
		parent = parent.parent;
	}
	var site = url ? page.site.url : page.site.getUri('content');
	return path.join(site, uri);
};

var Page = function(site, parent, directory) {
	this.site = site;
	this.directory = directory;
	this.name = !parent ? 'home' : directory.replace(/^[0-9]+-/g,'');
	this.urlName = !parent ? '' : this.name.replace(/\s/g, '-').toLowerCase();
	this.data = {};
	this.images = [];
	this.resources = [];
	this.parent = parent || null;
	// Only pages with a number prefixed to their
	// directory name are marked as being visible.
	this.visible = /^\/[0-9]/.test(this.directory);

	this.url = getUri(this, true);
	this.uri = getUri(this);
	site.pageByUrl[this.url] = this;

	installRoute(this);
	addChildren(this);
	addResources(this);
	parseContent(this);
};

Page.prototype.getTemplate = function(name) {
	var type = this.data.type,
		types = this.site.types;
	return type && types[type][name]
		? types[type][name]
		: types['default'][name] || function(param) {
			return 'Template not found: ' + name;
		};
};

Page.prototype.getTemplateLocation = function(name) {
	var templateLocations = this.site.templateLocations,
		type = this.data.type;
	return this.data.type && templateLocations[type][name]
		? templateLocations[type][name]
		: templateLocations['default'][name];
};

Page.prototype.renderTemplate = function(name) {
	var param = getTemplateParam(this);
	return this.getTemplate(name)(param);
};

Page.prototype.getUri = function(file) {
	return path.join(this.uri, file || '');
};

Page.prototype.getUrl = function(file) {
	return path.join(this.url, file || '');
};

Page.prototype.getResourceUrl = function(file) {
	return file && path.join(this.url, 'resources', file);
};

/**
 * The title of the page as defined in its document
 * or, when not defined, the name of its folder.
 *
 * @return {String}
 */
Page.prototype.getTitle = function() {
	return this.data.title || this.name;
};

/**
 * Checks whether this page has any children.
 *
 * @return {Boolean} {@true it is has one or more children}
 */
Page.prototype.hasChildren = function() {
	return this.children.length > 0;
};

/**
 * Returns the first child page of this page,
 * if any.
 *
 * @return {Page}
 */
Page.prototype.getFirstChild = function() {
	return this.children.length && this.children[0];
};

/**
 * Returns the last child page of this page.
 * if any.
 *
 * @return {Page}
 */
Page.prototype.getFirstChild = function() {
	var length = this.children.length;
	return length && this.children[length - 1];
};

/**
 * Checks whether the specified page is the parent of this page.
 *
 * @param {Item} page The page to check against
 * @return {Boolean} {@true if it is the parent of the page}
 */
Page.prototype.isParent = function(page) {
	return this.parent == page;
};

/**
 * Checks whether the specified page is a child of this page.
 *
 * @param {Item} page The page to check against
 * @return {Boolean} {@true it is a child of the page}
 */
Page.prototype.isChild = function(page) {
	return page && page.parent == this;
};

/**
 * Checks if this page is contained within the specified page
 * or one of its parents.
 *
 * @param {Item} page The page to check against
 * @return {Boolean} {@true if it is inside the specified page}
 */
Page.prototype.isDescendant = function(page) {
	var parent = this;
	while (parent = parent.parent) {
		if (parent == page)
			return true;
	}
	return false;
};

/**
 * Checks if the page is an ancestor of the specified page.
 *
 * @param {Item} page the page to check against
 * @return {Boolean} {@true if the page is an ancestor of the specified
 * page}
 */
Page.prototype.isAncestor = function(page) {
	return page ? page.isDescendant(this) : false;
};

/**
 * Checks whether there is a previous page on the same level as this one.
 *
 * @return {Boolean} {@true if the page is an ancestor of the specified
 * page}
 */
Page.prototype.hasPrevious = function() {
	return this.visible && this.index > 0;
};

/**
 * Checks whether there is a next page on the same level as this one.
 *
 * @return {Boolean}
 */
Page.prototype.hasNext = function() {
	return this.visible && this.index + 1 < this.parent.children.length;
};

/**
 * The previous page on the same level as this page.
 *
 * @return {Page}
 */
Page.prototype.getPrevious = function() {
	return this.hasPrevious() ? this.parent.children[this.index - 1] : null;
};

/**
 * The next page on the same level as this page.
 *
 * @return {Page}
 */
Page.prototype.getNext = function(page) {
	return this.hasNext() ? this.parent.children[this.index + 1] : null;
};

/**
 * Check whether this page has images.
 *
 * @return {Boolean}
 */
Page.prototype.hasImages = function(page) {
	return !!this.images.length;
};

/**
 * Check whether this page has resources.
 *
 * @return {Boolean}
 */
Page.prototype.hasResources = function(page) {
	return !!this.resources.length;
};

var getTemplateParam = function(page) {
	return {
		page: page,
		site: page.site,
		markdown: markdown
	};
};

var addResources = function(page) {
	var resourcesDir = page.getUri('resources'),
		images = page.images,
		resources = page.resources;

	var addFile = function(file) {
		if (/jpg|jpeg|png|gif/.test(file)) {
			images.push(file);
		}
		resources.push(file);
	};

	fs.exists(resourcesDir, function(exists) {
		if (!exists) return;
		var routeUrl = page.getUrl('resources');
		woods.express.use(routeUrl, express.static(resourcesDir));
		fs.readdir(resourcesDir, function(err, files) {
			files.forEach(addFile);
		});
	});
};

var addChildren = function(page) {
	// Read the directory and add child pages (if any):
	var children = page.children = [],
		// TODO: make async:
		files = fs.readdirSync(page.uri),
		index = 0;
	for (var i in files) {
		var name = files[i],
			file = path.resolve(page.uri, name),
			// TODO: make async:
			isDirectory = fs.statSync(file).isDirectory(),
			nameReserved = /^(resources|error)$/.test(name);
		if (isDirectory && !nameReserved) {
			var child = new Page(page.site, page, name);
			// Only add to children list if it is a child
			// - i.e. its name starts in the style of: 01- or 02-
			children.push(child);
			child.index = index++;
		}
	}
};

var parseContent = function(page) {
	// Parse the content file and place values in page.data:
	parsers.parse(page.getUri('content.md'), page);
};

var installRoute = function(page) {
	// Install the path in express:
	woods.express.get(page.url, function(req, res) {
		var template = page.getTemplateLocation('html');
		var param = getTemplateParam(page);
		res.render(template, param);
	});
};

module.exports = Page;
<|endoftext|>


<commit_before>var parsePath = require('./parsePath')
var parseCommand = require('./parseCommand')

module.exports = Path

function Path (content) {
  this.content = content
}

Path.prototype.abs = function () {
  walkThroughCommands(this.content, function (command, x, y) {
    if (command.relative) {
      if (command.x1 != null) command.x1 += x
      if (command.y1 != null) command.y1 += y
      if (command.x2 != null) command.x2 += x
      if (command.y2 != null) command.y2 += y
      if (command.x != null)  command.x  += x
      if (command.y != null)  command.y  += y
      command.relative = false
    }
  })
}

Path.prototype.matrix = function (a, b, c, d, e, f, debug) {
  for (var i=0; i < 6; i++) {
    if (typeof arguments[i] !== 'number')
      throw new TypeError('Matrix transformations require 6 Number arguments.')
  }
  this.convertArcs()
  walkThroughCommands(this.content, function (command, x, y) {
    applyMatrix(command, a, b, c, d, e, f)
  })
}

Path.prototype.convertArcs = function () {
  var args, curves
  walkThroughCommands(this.content, function (command, x, y) {
    if (command.type === 'A') {
      return convertArc(command, x, y)
    }
  })
}

Path.prototype.translate = function (tx, ty) {
  this.matrix(1, 0, 0, 1, tx, ty)
}

Path.prototype.scale = function (sx, sy) {
  this.matrix(sx, 0, 0, sy, 0, 0)
}

Path.prototype.rotate = function (a) {
  a = a * (Math.PI / 180)
  this.matrix(Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0)
}

Path.prototype.skewX = function (a) {
  a = a * (Math.PI / 180)
  this.matrix(1, 0, Math.tan(a), 1, 0, 0)
}

Path.prototype.skewY = function (a) {
  a = a * (Math.PI / 180)
  this.matrix(1, Math.tan(a), 0, 1, 0, 0)
}

Path.prototype.copy = function () {
  var newContent = this.content.map(function (command) {
    var copy = {}
    for (var attr in command) {
      if (command.hasOwnProperty(attr)) copy[attr] = command[attr];
    }
    return copy
  })
  return new Path(newContent)
}

Path.prototype.split = function() {
  var subpaths = [[]]
  var start
  function currentPath() { return subpaths[subpaths.length - 1] }
  function push(command) { currentPath().push(command) }
  function newPath() {
    subpaths.push([])
  }
  this.content.forEach(function (command) {
    if (command.type === 'Z') {
      push(clone(command))
      newPath()
    } else if (command.type === 'M' && currentPath().length > 0) {
      newPath()
      push(clone(command))
      start = command
    } else {
      if (command.type === 'M') {
        start = command
      } else if (currentPath().length === 0) {
        push({ type: 'M', relative: false, x: start.x, y: start.y })
      }
      push(clone(command))
    }
  })
  if (currentPath().length === 0) subpaths.pop()
  return subpaths.map(function (subpath) {
    return new Path(subpath)
  })
}

function clone (command) {
  var clone = {}
  for (var attr in command) {
    if (command.hasOwnProperty(attr)) clone[attr] = command[attr];
  }
  return clone
}

function applyMatrix (cmd, a, b, c, d, e, f) {
  // x' = a*x + c*y + e
  // y' = b*x + d*y + f
  var x = (cmd.x == null ? 0 : cmd.x)
  var y = (cmd.y == null ? 0 : cmd.y)
  if (cmd.x != null) {
    cmd.x = x*a + y*c + e
    if (nearZero(cmd.x)) cmd.x = 0
  }
  if (cmd.y != null) {
    cmd.y = x*b + y*d + f
    if (nearZero(cmd.y)) cmd.y = 0
  }
  if (cmd.x1 != null) {
    cmd.x1 = cmd.x1*a + cmd.y1*c + e
    cmd.y1 = cmd.x1*b + cmd.y1*d + f
    if (nearZero(cmd.x1)) cmd.x1 = 0
    if (nearZero(cmd.y1)) cmd.y1 = 0
  }
  if (cmd.x2 != null) {
    cmd.x2 = cmd.x2*a + cmd.y2*c + e
    cmd.y2 = cmd.x2*b + cmd.y2*d + f
    if (nearZero(cmd.x2)) cmd.x2 = 0
    if (nearZero(cmd.y2)) cmd.y2 = 0
  }
}

function nearZero (num) {
  return Math.abs(num) < 1e-12
}

function convertArc (command, x, y) {
  if (command.relative) {
    command.x += x
    command.y += y
    command.relative = false
  }
  args = a2c(x, y, command.rx, command.ry, command.x_axis_rotation, command.large_arc_flag, command.sweep_flag, command.x, command.y)
  return parseCommand('C', args)
}

a2c = function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
  var PI = Math.PI
  // Borrowed from https://github.com/DmitryBaranovskiy/raphael/blob/4d97d4ff5350bb949b88e6d78b877f76ea8b5e24/raphael.js#L2216-L2304
  // (MIT licensed; http://raphaeljs.com/license.html).
  // --------------------------------------------------------------------------
  // for more information of where this math came from visit:
  // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
  var _120 = PI * 120 / 180,
      rad = PI / 180 * (+angle || 0),
      res = [],
      xy,
      rotate = function (x, y, rad) {
        var X = x * Math.cos(rad) - y * Math.sin(rad),
        Y = x * Math.sin(rad) + y * Math.cos(rad);
        return {x: X, y: Y};
      };
  if (!recursive) {
    xy = rotate(x1, y1, -rad);
    x1 = xy.x;
    y1 = xy.y;
    xy = rotate(x2, y2, -rad);
    x2 = xy.x;
    y2 = xy.y;
    var cos = Math.cos(PI / 180 * angle),
        sin = Math.sin(PI / 180 * angle),
        x = (x1 - x2) / 2,
        y = (y1 - y2) / 2;
    var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
    if (h > 1) {
      h = Math.sqrt(h);
      rx = h * rx;
      ry = h * ry;
    }
    var rx2 = rx * rx,
        ry2 = ry * ry,
        k = (large_arc_flag == sweep_flag ? -1 : 1) *
          Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
        cx = k * rx * y / ry + (x1 + x2) / 2,
        cy = k * -ry * x / rx + (y1 + y2) / 2,
        f1 = Math.asin(((y1 - cy) / ry).toFixed(9)),
        f2 = Math.asin(((y2 - cy) / ry).toFixed(9));

    f1 = x1 < cx ? PI - f1 : f1;
    f2 = x2 < cx ? PI - f2 : f2;
    f1 < 0 && (f1 = PI * 2 + f1);
    f2 < 0 && (f2 = PI * 2 + f2);
    if (sweep_flag && f1 > f2) {
      f1 = f1 - PI * 2;
    }
    if (!sweep_flag && f2 > f1) {
      f2 = f2 - PI * 2;
    }
  } else {
    f1 = recursive[0];
    f2 = recursive[1];
    cx = recursive[2];
    cy = recursive[3];
  }
  var df = f2 - f1;
  if (Math.abs(df) > _120) {
    var f2old = f2,
        x2old = x2,
        y2old = y2;
    f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
    x2 = cx + rx * Math.cos(f2);
    y2 = cy + ry * Math.sin(f2);
    res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
  }
  df = f2 - f1;
  var c1 = Math.cos(f1),
      s1 = Math.sin(f1),
      c2 = Math.cos(f2),
      s2 = Math.sin(f2),
      t = Math.tan(df / 4),
      hx = 4 / 3 * rx * t,
      hy = 4 / 3 * ry * t,
      m1 = [x1, y1],
      m2 = [x1 + hx * s1, y1 - hy * c1],
      m3 = [x2 + hx * s2, y2 - hy * c2],
      m4 = [x2, y2];
  m2[0] = 2 * m1[0] - m2[0];
  m2[1] = 2 * m1[1] - m2[1];
  if (recursive) {
    return [m2, m3, m4].concat(res);
  } else {
    res = [m2, m3, m4].concat(res).join().split(',');
    var newres = [];
    for (var i = 0, ii = res.length; i < ii; i++) {
      newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
    }
    return newres;
  }
}

// Iterate over the list of commands, calling the callback for each one with
// the command and the current absolute x and y.
//
// If the callback returns false, the current command will be filtered out.
// If it returns an array of commands, the current command will be replaced
// with those commands. If it returns null or undefined, nothing happens.
function walkThroughCommands (commands, callback) {
  var x, y, startX, startY, seenZ=false
  var current, replacement, i, numReplaced
  for (i = 0; /* do not cache length! */ i < commands.length; i++) {
    current = commands[i]
    if (seenZ) {
      seenZ = false
      replacement = callback(current, startX, startY)
    } else {
      replacement = callback(current, x, y)
    }
    if (replacement === false) {
      replacement = []
    }
    if (replacement) {
      numReplaced = replace(i, replacement)
      i += numReplaced - 1
      current = commands[i]
    }
    if (current.type === 'Z') {
      seenZ = true
    } else if (current.type === 'M') {
      x = startX = current.x
      y = startY = current.y
    } else {
      if (current.x != null) x = current.x
      if (current.y != null) y = current.y
    }
  }
  function replace (i, replacement) {
    commands.splice.bind(commands, i, 1).apply(commands, replacement)
    return replacement.length
  }
}
<commit_msg>fix global leak<commit_after>var parsePath = require('./parsePath')
var parseCommand = require('./parseCommand')

module.exports = Path

function Path (content) {
  this.content = content
}

Path.prototype.abs = function () {
  walkThroughCommands(this.content, function (command, x, y) {
    if (command.relative) {
      if (command.x1 != null) command.x1 += x
      if (command.y1 != null) command.y1 += y
      if (command.x2 != null) command.x2 += x
      if (command.y2 != null) command.y2 += y
      if (command.x != null)  command.x  += x
      if (command.y != null)  command.y  += y
      command.relative = false
    }
  })
}

Path.prototype.matrix = function (a, b, c, d, e, f, debug) {
  for (var i=0; i < 6; i++) {
    if (typeof arguments[i] !== 'number')
      throw new TypeError('Matrix transformations require 6 Number arguments.')
  }
  this.convertArcs()
  walkThroughCommands(this.content, function (command, x, y) {
    applyMatrix(command, a, b, c, d, e, f)
  })
}

Path.prototype.convertArcs = function () {
  var args, curves
  walkThroughCommands(this.content, function (command, x, y) {
    if (command.type === 'A') {
      return convertArc(command, x, y)
    }
  })
}

Path.prototype.translate = function (tx, ty) {
  this.matrix(1, 0, 0, 1, tx, ty)
}

Path.prototype.scale = function (sx, sy) {
  this.matrix(sx, 0, 0, sy, 0, 0)
}

Path.prototype.rotate = function (a) {
  a = a * (Math.PI / 180)
  this.matrix(Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0)
}

Path.prototype.skewX = function (a) {
  a = a * (Math.PI / 180)
  this.matrix(1, 0, Math.tan(a), 1, 0, 0)
}

Path.prototype.skewY = function (a) {
  a = a * (Math.PI / 180)
  this.matrix(1, Math.tan(a), 0, 1, 0, 0)
}

Path.prototype.copy = function () {
  var newContent = this.content.map(function (command) {
    var copy = {}
    for (var attr in command) {
      if (command.hasOwnProperty(attr)) copy[attr] = command[attr];
    }
    return copy
  })
  return new Path(newContent)
}

Path.prototype.split = function() {
  var subpaths = [[]]
  var start
  function currentPath() { return subpaths[subpaths.length - 1] }
  function push(command) { currentPath().push(command) }
  function newPath() {
    subpaths.push([])
  }
  this.content.forEach(function (command) {
    if (command.type === 'Z') {
      push(clone(command))
      newPath()
    } else if (command.type === 'M' && currentPath().length > 0) {
      newPath()
      push(clone(command))
      start = command
    } else {
      if (command.type === 'M') {
        start = command
      } else if (currentPath().length === 0) {
        push({ type: 'M', relative: false, x: start.x, y: start.y })
      }
      push(clone(command))
    }
  })
  if (currentPath().length === 0) subpaths.pop()
  return subpaths.map(function (subpath) {
    return new Path(subpath)
  })
}

function clone (command) {
  var clone = {}
  for (var attr in command) {
    if (command.hasOwnProperty(attr)) clone[attr] = command[attr];
  }
  return clone
}

function applyMatrix (cmd, a, b, c, d, e, f) {
  // x' = a*x + c*y + e
  // y' = b*x + d*y + f
  var x = (cmd.x == null ? 0 : cmd.x)
  var y = (cmd.y == null ? 0 : cmd.y)
  if (cmd.x != null) {
    cmd.x = x*a + y*c + e
    if (nearZero(cmd.x)) cmd.x = 0
  }
  if (cmd.y != null) {
    cmd.y = x*b + y*d + f
    if (nearZero(cmd.y)) cmd.y = 0
  }
  if (cmd.x1 != null) {
    cmd.x1 = cmd.x1*a + cmd.y1*c + e
    cmd.y1 = cmd.x1*b + cmd.y1*d + f
    if (nearZero(cmd.x1)) cmd.x1 = 0
    if (nearZero(cmd.y1)) cmd.y1 = 0
  }
  if (cmd.x2 != null) {
    cmd.x2 = cmd.x2*a + cmd.y2*c + e
    cmd.y2 = cmd.x2*b + cmd.y2*d + f
    if (nearZero(cmd.x2)) cmd.x2 = 0
    if (nearZero(cmd.y2)) cmd.y2 = 0
  }
}

function nearZero (num) {
  return Math.abs(num) < 1e-12
}

function convertArc (command, x, y) {
  if (command.relative) {
    command.x += x
    command.y += y
    command.relative = false
  }
  args = a2c(x, y, command.rx, command.ry, command.x_axis_rotation, command.large_arc_flag, command.sweep_flag, command.x, command.y)
  return parseCommand('C', args)
}

function a2c (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
  var PI = Math.PI
  // Borrowed from https://github.com/DmitryBaranovskiy/raphael/blob/4d97d4ff5350bb949b88e6d78b877f76ea8b5e24/raphael.js#L2216-L2304
  // (MIT licensed; http://raphaeljs.com/license.html).
  // --------------------------------------------------------------------------
  // for more information of where this math came from visit:
  // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
  var _120 = PI * 120 / 180,
      rad = PI / 180 * (+angle || 0),
      res = [],
      xy,
      rotate = function (x, y, rad) {
        var X = x * Math.cos(rad) - y * Math.sin(rad),
        Y = x * Math.sin(rad) + y * Math.cos(rad);
        return {x: X, y: Y};
      };
  if (!recursive) {
    xy = rotate(x1, y1, -rad);
    x1 = xy.x;
    y1 = xy.y;
    xy = rotate(x2, y2, -rad);
    x2 = xy.x;
    y2 = xy.y;
    var cos = Math.cos(PI / 180 * angle),
        sin = Math.sin(PI / 180 * angle),
        x = (x1 - x2) / 2,
        y = (y1 - y2) / 2;
    var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
    if (h > 1) {
      h = Math.sqrt(h);
      rx = h * rx;
      ry = h * ry;
    }
    var rx2 = rx * rx,
        ry2 = ry * ry,
        k = (large_arc_flag == sweep_flag ? -1 : 1) *
          Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
        cx = k * rx * y / ry + (x1 + x2) / 2,
        cy = k * -ry * x / rx + (y1 + y2) / 2,
        f1 = Math.asin(((y1 - cy) / ry).toFixed(9)),
        f2 = Math.asin(((y2 - cy) / ry).toFixed(9));

    f1 = x1 < cx ? PI - f1 : f1;
    f2 = x2 < cx ? PI - f2 : f2;
    f1 < 0 && (f1 = PI * 2 + f1);
    f2 < 0 && (f2 = PI * 2 + f2);
    if (sweep_flag && f1 > f2) {
      f1 = f1 - PI * 2;
    }
    if (!sweep_flag && f2 > f1) {
      f2 = f2 - PI * 2;
    }
  } else {
    f1 = recursive[0];
    f2 = recursive[1];
    cx = recursive[2];
    cy = recursive[3];
  }
  var df = f2 - f1;
  if (Math.abs(df) > _120) {
    var f2old = f2,
        x2old = x2,
        y2old = y2;
    f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
    x2 = cx + rx * Math.cos(f2);
    y2 = cy + ry * Math.sin(f2);
    res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
  }
  df = f2 - f1;
  var c1 = Math.cos(f1),
      s1 = Math.sin(f1),
      c2 = Math.cos(f2),
      s2 = Math.sin(f2),
      t = Math.tan(df / 4),
      hx = 4 / 3 * rx * t,
      hy = 4 / 3 * ry * t,
      m1 = [x1, y1],
      m2 = [x1 + hx * s1, y1 - hy * c1],
      m3 = [x2 + hx * s2, y2 - hy * c2],
      m4 = [x2, y2];
  m2[0] = 2 * m1[0] - m2[0];
  m2[1] = 2 * m1[1] - m2[1];
  if (recursive) {
    return [m2, m3, m4].concat(res);
  } else {
    res = [m2, m3, m4].concat(res).join().split(',');
    var newres = [];
    for (var i = 0, ii = res.length; i < ii; i++) {
      newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
    }
    return newres;
  }
}

// Iterate over the list of commands, calling the callback for each one with
// the command and the current absolute x and y.
//
// If the callback returns false, the current command will be filtered out.
// If it returns an array of commands, the current command will be replaced
// with those commands. If it returns null or undefined, nothing happens.
function walkThroughCommands (commands, callback) {
  var x, y, startX, startY, seenZ=false
  var current, replacement, i, numReplaced
  for (i = 0; /* do not cache length! */ i < commands.length; i++) {
    current = commands[i]
    if (seenZ) {
      seenZ = false
      replacement = callback(current, startX, startY)
    } else {
      replacement = callback(current, x, y)
    }
    if (replacement === false) {
      replacement = []
    }
    if (replacement) {
      numReplaced = replace(i, replacement)
      i += numReplaced - 1
      current = commands[i]
    }
    if (current.type === 'Z') {
      seenZ = true
    } else if (current.type === 'M') {
      x = startX = current.x
      y = startY = current.y
    } else {
      if (current.x != null) x = current.x
      if (current.y != null) y = current.y
    }
  }
  function replace (i, replacement) {
    commands.splice.bind(commands, i, 1).apply(commands, replacement)
    return replacement.length
  }
}
<|endoftext|>


<commit_before>'use strict';
var Q = require('q');
var _ = require('lodash');
var debug = require('debug')('dynamoose:scan');

var errors = require('./errors');

function Scan (Model, filter, options) {

  this.Model = Model;
  this.options = options || {'all': {'delay': 0, 'max': 1}};

  // [{
  //     name: 'name',
  //     values: ['value', ...],
  //     comparison: 'string'
  //   },
  //    ...
  // ]
  this.filters = {};
  this.buildState = false;
  this.validationError = null;
  if (typeof filter === 'string') {
    this.buildState = filter;
    this.filters[filter] = {name: filter};
  } else if (typeof filter === 'object'){
    if (typeof filter.FilterExpression === 'string') {
      // if filter expression is given, just assign the filter
      this.filters = filter;
    } else {
      this.parseFilterObject(filter);
    }
  }
}

Scan.prototype.exec = function (next) {
  debug('exec scan for ', this.scan);
  if (this.validationError) {
    if (next) {
      next(this.validationError);
    }
    return Q.reject(this.validationError);
  }

  var Model = this.Model;
  var Model$ = Model.$__;
  var schema = Model$.schema;
  var options = this.options;

  var scanReq = { };

  function toModel (item) {
    var model  = new Model();
    model.$__.isNew = false;
    schema.parseDynamo(model, item);

    debug('scan parsed model', model);

    return model;
  }

    function scanByRawFilter() {
      var deferred = Q.defer();
      var dbClient = Model.$__.base.documentClient();
      dbClient.scan(scanReq, function(err, data) {
        if (err) {
          return deferred.reject(err);
        } else {
          if (!data) {
            return deferred.resolve([]);
          }
          return deferred.resolve(data.Items.map(function (item) {
            var model = new Model(item);
            model.$__.isNew = false;
            debug('scan parsed model', model);
            return model;
          }));
        }
      });

      return deferred.promise.nodeify(next);
    }

  if (this.filters && typeof this.filters.FilterExpression === 'string') {
    // use the raw aws filter, which needs to be composed by the developer
    scanReq = this.filters;
    if (!scanReq) {
      scanReq = {};
    }
    if (!scanReq.TableName) {
      scanReq.TableName = Model.$__.name;
    }

    // use the document client in aws-sdk
    return scanByRawFilter();

  } else {
    // default
    scanReq = {
      TableName: Model.$__.name
    };

    if(Object.keys(this.filters).length > 0) {
      scanReq.ScanFilter = {};
      for(var name in this.filters) {
        var filter = this.filters[name];
        var filterAttr = schema.attributes[name];
        scanReq.ScanFilter[name] = {
          AttributeValueList: [],
          ComparisonOperator: filter.comparison
        };

        var isContains = filter.comparison === 'CONTAINS' || filter.comparison === 'NOT_CONTAINS';
        var isListContains = isContains && filterAttr.type.name === 'list';

        if(filter.values) {
          for (var i = 0; i < filter.values.length; i++) {
            var val = filter.values[i];
            scanReq.ScanFilter[name].AttributeValueList.push(
              isListContains ? filterAttr.attributes[0].toDynamo(val, true) : filterAttr.toDynamo(val, true)
            );
          }
        }
      }
    }

    if(options.attributes) {
      scanReq.AttributesToGet = options.attributes;
    }

    if(options.count) {
      scanReq.Select = 'COUNT';
    }

    if(options.counts) {
      scanReq.Select = 'COUNT';
    }

    if(options.limit) {
      scanReq.Limit = options.limit;
    }

    if(options.parallel) {
      scanReq.TotalSegments = options.parallel;
    }

    if(Array.isArray(options.ExclusiveStartKey)) {
      scanReq.TotalSegments = options.ExclusiveStartKey.length;
    } else if(options.ExclusiveStartKey) {
      options.ExclusiveStartKey = [options.ExclusiveStartKey];
    }

    if(options.conditionalOperator) {
      scanReq.ConditionalOperator = options.conditionalOperator;
    }

    if(options.consistent) {
      scanReq.ConsistentRead = true;
    }

  }

  function scanSegment (segment) {
    var deferred = Q.defer();

    var scanOneReq = _.clone(scanReq);

    if(scanOneReq.TotalSegments) {
      scanOneReq.Segment = segment;
    }

    if(options.ExclusiveStartKey) {
      scanOneReq.ExclusiveStartKey = options.ExclusiveStartKey[segment];
    }

    debug('adding scan segement', scanOneReq);

    var models = {}, totalCount = 0, scannedCount = 0, timesScanned = 0, lastKey;
    if (!options.all) {
      options.all = {'delay': 0, 'max': 1};
    }
    scanOne();
    function scanOne() {

      debug('scan request', scanOneReq);
      Model.$__.base.ddb().scan(scanOneReq, function(err, data) {
        if(err) {
          debug('Error returned by scan', err);
          return deferred.reject(err);
        }
        debug('scan response', data);

        if(!Object.keys(data).length) {
          return deferred.resolve();
        }

        try {
          if (options.count) {
            return deferred.resolve(data.Count);
          }
          if (options.counts) {
            var counts = { count: data.Count, scannedCount: data.ScannedCount };
            return deferred.resolve(counts);
          }
          if (data.Items !== undefined) {
            if (!models.length || models.length === 0) {
              models = data.Items.map(toModel);
            } else {
              models = models.concat(data.Items.map(toModel));
            }

            if(options.one) {
              if (!models || models.length === 0) {
                return deferred.resolve();
              }
              return deferred.resolve(models[0]);
            }
            lastKey = data.LastEvaluatedKey;
          }
          totalCount += data.Count;
          scannedCount += data.ScannedCount;
          timesScanned++;

          if ((options.all.max === 0 || timesScanned < options.all.max) && lastKey) {
            // scan.all need to scan again
            scanOneReq.ExclusiveStartKey = lastKey;
            setTimeout(scanOne, options.all.delay * 1000);
          }
          else {
            // completed scan returning models
            models.lastKey = lastKey;
            models.count = totalCount;
            models.scannedCount = scannedCount;
            models.timesScanned = timesScanned;
            deferred.resolve(models);
          }
        } catch (err) {
          deferred.reject(err);
        }
      });
    }

    return deferred.promise;
  }

  function scan () {
    var deferred = Q.defer();

    var totalSegments = scanReq.TotalSegments || 1;
    var scans = [];
    for(var segment = 0; segment < totalSegments; segment++) {
      scans.push(scanSegment(segment));
    }
    Q.all(scans)
    .then(function (results) {
      var models = _.flatten(results);
      var lastKeys = results.map(function (r) {
        return r.lastKey;
      });

      if(lastKeys.length === 1) {
        models.lastKey = lastKeys[0];
      } else if (_.compact(lastKeys).length !== 0){
        models.lastKey = lastKeys;
      }

      models.count = results.reduce(function(acc, r) {
        return acc + r.count;
      }, 0);
      models.scannedCount = results.reduce(function(acc, r) {
        return acc + r.scannedCount;
      }, 0);
      models.timesScanned = results.reduce(function(acc, r) {
        return acc + r.timesScanned;
      }, 0);
      deferred.resolve(models);

    })
    .fail(function (err) {
      deferred.reject(err);
    });

    return deferred.promise.nodeify(next);

  }

  if(Model$.options.waitForActive) {
    return Model$.table.waitForActive().then(scan).catch(function (err) {
      if (next) {
        next(err);
      }
      return Q.reject(err);
    });
  }

  return scan();
};

Scan.prototype.parseFilterObject = function (filter) {

  if (Object.keys(filter).length > 0) {

    for(var filterName in filter) {
      if (filter.hasOwnProperty(filterName)) {

        // Parse AND OR
        if (filterName === 'and' || filterName === 'or') {

          this[filterName]();
          for(var condition in filter[filterName]) {
            if (filter[filterName].hasOwnProperty(condition)) {
              this.parseFilterObject(filter[filterName][condition]);
            }
          }
        } else {

          this.where(filterName);
          var val, comp;

          if (typeof filter[filterName] === 'object' &&
            Object.keys(filter[filterName]).length  === 1) {

            comp = Object.keys(filter[filterName])[0];

            if (comp === 'null') {
              if (!filter[filterName][comp]) {
                comp = 'not_null';
              }
              val = [null];
            } else if (comp === 'in' || comp === 'between') {
              val = filter[filterName][comp];
            } else {
              val = [filter[filterName][comp]];
            }

          } else {
            comp = 'eq';
            val = [filter[filterName]];
          }
          this.compVal(val, comp.toUpperCase());
        }

      }
    }
  }
};

Scan.prototype.and = function() {
  this.options.conditionalOperator = 'AND';
  return this;
};

Scan.prototype.or = function() {
  this.options.conditionalOperator = 'OR';
  return this;
};

Scan.prototype.consistent = function() {
  this.options.consistent = true;
  return this;
};

Scan.prototype.where = function (filter) {
  if (this.validationError) {
    return this;
  }

  if(this.buildState) {
    this.validationError = new errors.ScanError('Invalid scan state; where() must follow comparison');
    return this;
  }
  if(typeof filter === 'string') {
    this.buildState = filter;
    if(this.filters[filter]) {
      this.validationError = new errors.ScanError('Invalid scan state; %s can only be used once', filter);
      return this;
    }
    this.filters[filter] = {name: filter};
  }

  return this;
};
Scan.prototype.filter = Scan.prototype.where;

Scan.prototype.compVal = function (vals, comp) {
  if (this.validationError) {
    return this;
  }

  var permittedComparison =
    [
      'NOT_NULL','NULL','EQ','NE','GE','LT','GT','LE','GE',
      'NOT_CONTAINS','CONTAINS','BEGINS_WITH','IN','BETWEEN'
    ];

  if(!this.buildState) {
    this.validationError = new errors.ScanError('Invalid scan state; %s must follow scan(), where(), or filter()', comp);
    return this;
  }

  if (permittedComparison.indexOf(comp) === -1) {
    this.validationError = new errors.ScanError('Invalid comparison %s', comp);
    return this;
  }

  this.filters[this.buildState].values = vals;
  this.filters[this.buildState].comparison = comp;

  this.buildState = false;
  this.notState = false;

  return this;
};

Scan.prototype.not = function() {
  this.notState = true;
  return this;
};

Scan.prototype.null = function() {
  if(this.notState) {
    return this.compVal(null, 'NOT_NULL');
  } else {
    return this.compVal(null, 'NULL');
  }
};

Scan.prototype.eq = function (val) {

  if(val === '' || val === null || val === undefined){
    return this.null();
  }
  if(this.notState) {
    return this.compVal([val], 'NE');
  } else {
    return this.compVal([val], 'EQ');
  }
};

Scan.prototype.lt = function (val) {
  if(this.notState) {
    return this.compVal([val], 'GE');
  } else {
    return this.compVal([val], 'LT');
  }
};

Scan.prototype.le = function (val) {
  if(this.notState) {
    return this.compVal([val], 'GT');
  } else {
    return this.compVal([val], 'LE');
  }
};

Scan.prototype.ge = function (val) {
  if(this.notState) {
    return this.compVal([val], 'LT');
  } else {
    return this.compVal([val], 'GE');
  }
};

Scan.prototype.gt = function (val) {
  if(this.notState) {
    return this.compVal([val], 'LE');
  } else {
    return this.compVal([val], 'GT');
  }
};

Scan.prototype.contains = function (val) {
  if(this.notState) {
    return this.compVal([val], 'NOT_CONTAINS');
  } else {
    return this.compVal([val], 'CONTAINS');
  }
};

Scan.prototype.beginsWith = function (val) {
  if (this.validationError) {
    return this;
  }
  if(this.notState) {
    this.validationError = new errors.ScanError('Invalid scan state: beginsWith() cannot follow not()');
    return this;
  }
  return this.compVal([val], 'BEGINS_WITH');
};

Scan.prototype.in = function (vals) {
  if (this.validationError) {
    return this;
  }
  if(this.notState) {
    this.validationError = new errors.ScanError('Invalid scan state: in() cannot follow not()');
    return this;
  }

  return this.compVal(vals, 'IN');
};

Scan.prototype.between = function (a, b) {
  if (this.validationError) {
    return this;
  }
  if(this.notState) {
    this.validationError = new errors.ScanError('Invalid scan state: between() cannot follow not()');
    return this;
  }
  return this.compVal([a, b], 'BETWEEN');
};

Scan.prototype.limit = function (limit) {
  this.options.limit = limit;
  return this;
};

Scan.prototype.startAt = function (key) {
  this.options.ExclusiveStartKey = key;
  return this;
};

Scan.prototype.attributes = function (attributes) {
  this.options.attributes = attributes;
  return this;
};

Scan.prototype.count = function () {
  this.options.count = true;
  this.options.select = 'COUNT';
  return this;
};

Scan.prototype.counts = function () {
  this.options.counts = true;
  this.options.select = 'COUNT';
  return this;
};

Scan.prototype.all = function (delay, max) {
  delay = delay || 1;
  max = max || 0;
  this.options.all = {'delay': delay, 'max': max};
  return this;
};

Scan.prototype.parallel = function (numberOfSegments) {
  this.options.parallel = numberOfSegments;
  return this;
};

module.exports = Scan;
<commit_msg>When DocumentClient is used to perform a "RawAWSFilter" the returned items nows have any values of type DynamoDBSet unpacked to normal Array's to keep consistency with default scan behaviour.<commit_after>'use strict';
var Q = require('q');
var _ = require('lodash');
var debug = require('debug')('dynamoose:scan');

var errors = require('./errors');

function Scan (Model, filter, options) {

  this.Model = Model;
  this.options = options || {'all': {'delay': 0, 'max': 1}};

  // [{
  //     name: 'name',
  //     values: ['value', ...],
  //     comparison: 'string'
  //   },
  //    ...
  // ]
  this.filters = {};
  this.buildState = false;
  this.validationError = null;
  if (typeof filter === 'string') {
    this.buildState = filter;
    this.filters[filter] = {name: filter};
  } else if (typeof filter === 'object'){
    if (typeof filter.FilterExpression === 'string') {
      // if filter expression is given, just assign the filter
      this.filters = filter;
    } else {
      this.parseFilterObject(filter);
    }
  }
}

Scan.prototype.exec = function (next) {
  debug('exec scan for ', this.scan);
  if (this.validationError) {
    if (next) {
      next(this.validationError);
    }
    return Q.reject(this.validationError);
  }

  var Model = this.Model;
  var Model$ = Model.$__;
  var schema = Model$.schema;
  var options = this.options;

  var scanReq = { };

  function toModel (item) {
    var model  = new Model();
    model.$__.isNew = false;
    schema.parseDynamo(model, item);

    debug('scan parsed model', model);

    return model;
  }

    function scanByRawFilter() {
      var deferred = Q.defer();
      var dbClient = Model.$__.base.documentClient();
      var AWSSet = dbClient.createSet([1, 2, 3]).constructor;

      dbClient.scan(scanReq, function(err, data) {
        if (err) {
          return deferred.reject(err);
        } else {
          if (!data) {
            return deferred.resolve([]);
          }
          return deferred.resolve(data.Items.map(function (item) {
            var model;

            Object.keys(item).forEach(function (prop) {
              if (item.hasOwnProperty(prop) && item[prop] instanceof AWSSet) {
                item[prop] = item[prop].values;
              }
            });

            model = new Model(item);
            model.$__.isNew = false;
            debug('scan parsed model', model);
            return model;
          }));
        }
      });

      return deferred.promise.nodeify(next);
    }

  if (this.filters && typeof this.filters.FilterExpression === 'string') {
    // use the raw aws filter, which needs to be composed by the developer
    scanReq = this.filters;
    if (!scanReq) {
      scanReq = {};
    }
    if (!scanReq.TableName) {
      scanReq.TableName = Model.$__.name;
    }

    // use the document client in aws-sdk
    return scanByRawFilter();

  } else {
    // default
    scanReq = {
      TableName: Model.$__.name
    };

    if(Object.keys(this.filters).length > 0) {
      scanReq.ScanFilter = {};
      for(var name in this.filters) {
        var filter = this.filters[name];
        var filterAttr = schema.attributes[name];
        scanReq.ScanFilter[name] = {
          AttributeValueList: [],
          ComparisonOperator: filter.comparison
        };

        var isContains = filter.comparison === 'CONTAINS' || filter.comparison === 'NOT_CONTAINS';
        var isListContains = isContains && filterAttr.type.name === 'list';

        if(filter.values) {
          for (var i = 0; i < filter.values.length; i++) {
            var val = filter.values[i];
            scanReq.ScanFilter[name].AttributeValueList.push(
              isListContains ? filterAttr.attributes[0].toDynamo(val, true) : filterAttr.toDynamo(val, true)
            );
          }
        }
      }
    }

    if(options.attributes) {
      scanReq.AttributesToGet = options.attributes;
    }

    if(options.count) {
      scanReq.Select = 'COUNT';
    }

    if(options.counts) {
      scanReq.Select = 'COUNT';
    }

    if(options.limit) {
      scanReq.Limit = options.limit;
    }

    if(options.parallel) {
      scanReq.TotalSegments = options.parallel;
    }

    if(Array.isArray(options.ExclusiveStartKey)) {
      scanReq.TotalSegments = options.ExclusiveStartKey.length;
    } else if(options.ExclusiveStartKey) {
      options.ExclusiveStartKey = [options.ExclusiveStartKey];
    }

    if(options.conditionalOperator) {
      scanReq.ConditionalOperator = options.conditionalOperator;
    }

    if(options.consistent) {
      scanReq.ConsistentRead = true;
    }

  }

  function scanSegment (segment) {
    var deferred = Q.defer();

    var scanOneReq = _.clone(scanReq);

    if(scanOneReq.TotalSegments) {
      scanOneReq.Segment = segment;
    }

    if(options.ExclusiveStartKey) {
      scanOneReq.ExclusiveStartKey = options.ExclusiveStartKey[segment];
    }

    debug('adding scan segement', scanOneReq);

    var models = {}, totalCount = 0, scannedCount = 0, timesScanned = 0, lastKey;
    if (!options.all) {
      options.all = {'delay': 0, 'max': 1};
    }
    scanOne();
    function scanOne() {

      debug('scan request', scanOneReq);
      Model.$__.base.ddb().scan(scanOneReq, function(err, data) {
        if(err) {
          debug('Error returned by scan', err);
          return deferred.reject(err);
        }
        debug('scan response', data);

        if(!Object.keys(data).length) {
          return deferred.resolve();
        }

        try {
          if (options.count) {
            return deferred.resolve(data.Count);
          }
          if (options.counts) {
            var counts = { count: data.Count, scannedCount: data.ScannedCount };
            return deferred.resolve(counts);
          }
          if (data.Items !== undefined) {
            if (!models.length || models.length === 0) {
              models = data.Items.map(toModel);
            } else {
              models = models.concat(data.Items.map(toModel));
            }

            if(options.one) {
              if (!models || models.length === 0) {
                return deferred.resolve();
              }
              return deferred.resolve(models[0]);
            }
            lastKey = data.LastEvaluatedKey;
          }
          totalCount += data.Count;
          scannedCount += data.ScannedCount;
          timesScanned++;

          if ((options.all.max === 0 || timesScanned < options.all.max) && lastKey) {
            // scan.all need to scan again
            scanOneReq.ExclusiveStartKey = lastKey;
            setTimeout(scanOne, options.all.delay * 1000);
          }
          else {
            // completed scan returning models
            models.lastKey = lastKey;
            models.count = totalCount;
            models.scannedCount = scannedCount;
            models.timesScanned = timesScanned;
            deferred.resolve(models);
          }
        } catch (err) {
          deferred.reject(err);
        }
      });
    }

    return deferred.promise;
  }

  function scan () {
    var deferred = Q.defer();

    var totalSegments = scanReq.TotalSegments || 1;
    var scans = [];
    for(var segment = 0; segment < totalSegments; segment++) {
      scans.push(scanSegment(segment));
    }
    Q.all(scans)
    .then(function (results) {
      var models = _.flatten(results);
      var lastKeys = results.map(function (r) {
        return r.lastKey;
      });

      if(lastKeys.length === 1) {
        models.lastKey = lastKeys[0];
      } else if (_.compact(lastKeys).length !== 0){
        models.lastKey = lastKeys;
      }

      models.count = results.reduce(function(acc, r) {
        return acc + r.count;
      }, 0);
      models.scannedCount = results.reduce(function(acc, r) {
        return acc + r.scannedCount;
      }, 0);
      models.timesScanned = results.reduce(function(acc, r) {
        return acc + r.timesScanned;
      }, 0);
      deferred.resolve(models);

    })
    .fail(function (err) {
      deferred.reject(err);
    });

    return deferred.promise.nodeify(next);

  }

  if(Model$.options.waitForActive) {
    return Model$.table.waitForActive().then(scan).catch(function (err) {
      if (next) {
        next(err);
      }
      return Q.reject(err);
    });
  }

  return scan();
};

Scan.prototype.parseFilterObject = function (filter) {

  if (Object.keys(filter).length > 0) {

    for(var filterName in filter) {
      if (filter.hasOwnProperty(filterName)) {

        // Parse AND OR
        if (filterName === 'and' || filterName === 'or') {

          this[filterName]();
          for(var condition in filter[filterName]) {
            if (filter[filterName].hasOwnProperty(condition)) {
              this.parseFilterObject(filter[filterName][condition]);
            }
          }
        } else {

          this.where(filterName);
          var val, comp;

          if (typeof filter[filterName] === 'object' &&
            Object.keys(filter[filterName]).length  === 1) {

            comp = Object.keys(filter[filterName])[0];

            if (comp === 'null') {
              if (!filter[filterName][comp]) {
                comp = 'not_null';
              }
              val = [null];
            } else if (comp === 'in' || comp === 'between') {
              val = filter[filterName][comp];
            } else {
              val = [filter[filterName][comp]];
            }

          } else {
            comp = 'eq';
            val = [filter[filterName]];
          }
          this.compVal(val, comp.toUpperCase());
        }

      }
    }
  }
};

Scan.prototype.and = function() {
  this.options.conditionalOperator = 'AND';
  return this;
};

Scan.prototype.or = function() {
  this.options.conditionalOperator = 'OR';
  return this;
};

Scan.prototype.consistent = function() {
  this.options.consistent = true;
  return this;
};

Scan.prototype.where = function (filter) {
  if (this.validationError) {
    return this;
  }

  if(this.buildState) {
    this.validationError = new errors.ScanError('Invalid scan state; where() must follow comparison');
    return this;
  }
  if(typeof filter === 'string') {
    this.buildState = filter;
    if(this.filters[filter]) {
      this.validationError = new errors.ScanError('Invalid scan state; %s can only be used once', filter);
      return this;
    }
    this.filters[filter] = {name: filter};
  }

  return this;
};
Scan.prototype.filter = Scan.prototype.where;

Scan.prototype.compVal = function (vals, comp) {
  if (this.validationError) {
    return this;
  }

  var permittedComparison =
    [
      'NOT_NULL','NULL','EQ','NE','GE','LT','GT','LE','GE',
      'NOT_CONTAINS','CONTAINS','BEGINS_WITH','IN','BETWEEN'
    ];

  if(!this.buildState) {
    this.validationError = new errors.ScanError('Invalid scan state; %s must follow scan(), where(), or filter()', comp);
    return this;
  }

  if (permittedComparison.indexOf(comp) === -1) {
    this.validationError = new errors.ScanError('Invalid comparison %s', comp);
    return this;
  }

  this.filters[this.buildState].values = vals;
  this.filters[this.buildState].comparison = comp;

  this.buildState = false;
  this.notState = false;

  return this;
};

Scan.prototype.not = function() {
  this.notState = true;
  return this;
};

Scan.prototype.null = function() {
  if(this.notState) {
    return this.compVal(null, 'NOT_NULL');
  } else {
    return this.compVal(null, 'NULL');
  }
};

Scan.prototype.eq = function (val) {

  if(val === '' || val === null || val === undefined){
    return this.null();
  }
  if(this.notState) {
    return this.compVal([val], 'NE');
  } else {
    return this.compVal([val], 'EQ');
  }
};

Scan.prototype.lt = function (val) {
  if(this.notState) {
    return this.compVal([val], 'GE');
  } else {
    return this.compVal([val], 'LT');
  }
};

Scan.prototype.le = function (val) {
  if(this.notState) {
    return this.compVal([val], 'GT');
  } else {
    return this.compVal([val], 'LE');
  }
};

Scan.prototype.ge = function (val) {
  if(this.notState) {
    return this.compVal([val], 'LT');
  } else {
    return this.compVal([val], 'GE');
  }
};

Scan.prototype.gt = function (val) {
  if(this.notState) {
    return this.compVal([val], 'LE');
  } else {
    return this.compVal([val], 'GT');
  }
};

Scan.prototype.contains = function (val) {
  if(this.notState) {
    return this.compVal([val], 'NOT_CONTAINS');
  } else {
    return this.compVal([val], 'CONTAINS');
  }
};

Scan.prototype.beginsWith = function (val) {
  if (this.validationError) {
    return this;
  }
  if(this.notState) {
    this.validationError = new errors.ScanError('Invalid scan state: beginsWith() cannot follow not()');
    return this;
  }
  return this.compVal([val], 'BEGINS_WITH');
};

Scan.prototype.in = function (vals) {
  if (this.validationError) {
    return this;
  }
  if(this.notState) {
    this.validationError = new errors.ScanError('Invalid scan state: in() cannot follow not()');
    return this;
  }

  return this.compVal(vals, 'IN');
};

Scan.prototype.between = function (a, b) {
  if (this.validationError) {
    return this;
  }
  if(this.notState) {
    this.validationError = new errors.ScanError('Invalid scan state: between() cannot follow not()');
    return this;
  }
  return this.compVal([a, b], 'BETWEEN');
};

Scan.prototype.limit = function (limit) {
  this.options.limit = limit;
  return this;
};

Scan.prototype.startAt = function (key) {
  this.options.ExclusiveStartKey = key;
  return this;
};

Scan.prototype.attributes = function (attributes) {
  this.options.attributes = attributes;
  return this;
};

Scan.prototype.count = function () {
  this.options.count = true;
  this.options.select = 'COUNT';
  return this;
};

Scan.prototype.counts = function () {
  this.options.counts = true;
  this.options.select = 'COUNT';
  return this;
};

Scan.prototype.all = function (delay, max) {
  delay = delay || 1;
  max = max || 0;
  this.options.all = {'delay': delay, 'max': max};
  return this;
};

Scan.prototype.parallel = function (numberOfSegments) {
  this.options.parallel = numberOfSegments;
  return this;
};

module.exports = Scan;
<|endoftext|>