<commit_before>/*
 * Copyright 2012 The Toolkitchen Authors. All rights reserved.
 * Use of this source code is goverened by a BSD-style
 * license that can be found in the LICENSE file.
 */

// TODO(arv): Make these local.

var Node_prototype = {
  appendChild: findMethod(Node, 'appendChild'),
  removeChild: findMethod(Node, 'removeChild'),
  insertBefore: findMethod(Node, 'insertBefore'),
  parentNode: findDescriptor(Node, 'parentNode'),
  firstChild: findDescriptor(Node, 'firstChild'),
  lastChild: findDescriptor(Node, 'lastChild'),
  previousSibling: findDescriptor(Node, 'previousSibling'),
  nextSibling: findDescriptor(Node, 'nextSibling'),

  textContent: findDescriptor(Node, 'textContent')
};

var HTMLElement_prototype = {
  innerHTML: findDescriptor(HTMLElement, 'innerHTML'),
  outerHTML: findDescriptor(HTMLElement, 'outerHTML')
};

var getShadowOwnerAndInvalidate;

(function() {

  var shadowOwnerTable = new SideTable('shadowOwner');

  function oneOf(object, propertyNames) {
    for (var i = 0; i < propertyNames.length; i++) {
      if (propertyNames[i] in object)
        return object[propertyNames[i]].bind(object);
    }
  }

  var request = oneOf(window, [
    'requestAnimationFrame',
    'mozRequestAnimationFrame',
    'webkitRequestAnimationFrame',
    'setTimeout'
  ]);

  function ShadowOwner(host) {
    this.host = host;
    this.dirty = false;
  }
  ShadowOwner.prototype = {
    invalidate: function() {
      if (!this.dirty) {
        this.dirty = true;
        ShadowOwner.pending.push(this);
        ShadowOwner.scheduleRender();
      }
    },
    render: function() {
      if (this.dirty) {
        render(this.host);
        this.dirty = false;
      }
    }
  };

  ShadowOwner.pending = [];

  ShadowOwner.timer = null;

  ShadowOwner.scheduleRender = function() {
    if (ShadowOwner.timer)
      return;
    ShadowOwner.timer = request(ShadowOwner.renderAllPending, 0);
  };

  ShadowOwner.renderAllPending = function() {
    ShadowOwner.timer = null;
    ShadowOwner.pending.forEach(function(owner) {
      owner.render();
    });
    ShadowOwner.pending = [];
  };

  function findShadowHost(wrapper) {
    if (!wrapper)
      return null;
    if (wrapper.node.__shadowHost__)
      return wrapper.node.__shadowHost__;
    if (wrapper.node.__shadowRoot__)
      return wrapper.node;
    return findShadowHost(wrapper.parentNode);
  }

  function getShadowOwner(node) {
    if (!node)
      return null;
    var shadowHost;
    if (node.__shadowHost__) {
      shadowHost = node.__shadowHost__;
    } else if (node.__shadowRoot__) {
      shadowHost = node;
    } else {
      var wrapper = getExistingWrapper(node);
      if (!wrapper)
        return null;
      var shadowHost = findShadowHost(wrapper);
      if (!shadowHost)
        return null;
    }

    var shadowOwner = shadowOwnerTable.get(shadowHost);
    if (!shadowOwner) {
      shadowOwner = new ShadowOwner(shadowHost);
      shadowOwnerTable.set(shadowHost, shadowOwner);
    }
    return shadowOwner;
  }

  getShadowOwnerAndInvalidate = function getShadowOwnerAndInvalidate(node) {
    var shadowOwner = getShadowOwner(node);
    if (shadowOwner)
      shadowOwner.invalidate();
    return shadowOwner;
  };

  overrideMethod(Node, 'appendChild', function(node) {
    var shadowOwner = getShadowOwnerAndInvalidate(this);
    var nodeShadowOwner = getShadowOwnerAndInvalidate(node);
    if (shadowOwner || nodeShadowOwner)
      return unwrap(wrap(this).appendChild(wrap(node)));
    return Node_prototype.appendChild.call(this, node);
  });

  overrideMethod(Node, 'removeChild', function(node) {
    var shadowOwner = getShadowOwnerAndInvalidate(this);
    var nodeShadowOwner = getShadowOwnerAndInvalidate(node);
    if (shadowOwner || nodeShadowOwner)
      return unwrap(wrap(this).removeChild(wrap(node)));
    return Node_prototype.removeChild.call(this, node);
  });

  overrideMethod(Node, 'insertBefore', function(node, ref) {
    var shadowOwner = getShadowOwnerAndInvalidate(this);
    var nodeShadowOwner = getShadowOwnerAndInvalidate(node);
    var refShadowOwner = getShadowOwnerAndInvalidate(ref);
    if (shadowOwner || nodeShadowOwner || refShadowOwner)
      return unwrap(wrap(this).insertBefore(wrap(node), wrap(ref)));
    return Node_prototype.insertBefore.call(this, node, ref);
  });

  function methodShouldInvalidate(ctor, methodName) {
    var originalFunction = findMethod(ctor, methodName);
    overrideMethod(ctor, methodName, function() {
      getShadowOwnerAndInvalidate(this);
      return originalFunction.apply(this, arguments);
    });
  }

  methodShouldInvalidate(Element, 'setAttribute');
  methodShouldInvalidate(Element, 'setAttributeNS');
  methodShouldInvalidate(Element, 'setAttributeNode');
  methodShouldInvalidate(Element, 'setAttributeNodeNS');
  methodShouldInvalidate(Element, 'removeAttribute');
  methodShouldInvalidate(Element, 'removeAttributeNode');
  methodShouldInvalidate(Element, 'removeAttributeNS');

  function overrideNodeGetter(propertyName) {
    overrideGetter(Node, propertyName, function() {
      var shadowOwner = getShadowOwner(this);
      if (shadowOwner)
        return unwrap(wrap(this)[propertyName]);
      return Node_prototype[propertyName].get.call(this);
    });
  }

  overrideNodeGetter('parentNode');
  overrideNodeGetter('firstChild');
  overrideNodeGetter('lastChild');
  overrideNodeGetter('previousSibling');
  overrideNodeGetter('nextSibling');

  overrideDescriptor(Node, 'textContent', {
    get: function() {
      if (this instanceof CharacterData)
        return Node_prototype.textContent.get.call(this);

      // TODO(arv): This should fallback to Node_prototype.textContent if there
      // are no shadow trees below or above the context node.
      var s = '';
      for (var child = this.firstChild; child; child = child.nextSibling) {
        s += child.textContent;
      }
      return s;
    },
    set: function(value) {
      if (this instanceof CharacterData)
        return Node_prototype.textContent.set.call(this, value);

      var shadowOwner = getShadowOwnerAndInvalidate(this);
      if (!shadowOwner || this instanceof CharacterData) {
        Node_prototype.textContent.set.call(this, value);
      } else {
        var wrapper = wrap(this);
        wrapper.removeAllChildNodes();
        if (value !== '') {
          var textNode = this.ownerDocument.createTextNode(value);
          wrapper.appendChild(wrap(textNode));
        }
      }
    }
  });

  function getterNeedsReflow(ctor, propertyName) {
    var originalDescriptor = findDescriptor(ctor, propertyName);
    if (originalDescriptor) {
      overrideGetter(ctor, propertyName, function() {
        ShadowOwner.renderAllPending();
        return originalDescriptor.get.call(this);
      });
    }
  }

  getterNeedsReflow(Element, 'clientHeight');
  getterNeedsReflow(Element, 'clientWidth');
  getterNeedsReflow(Element, 'offsetHeight');
  getterNeedsReflow(Element, 'offsetWidth');
  getterNeedsReflow(Element, 'scrollHeight');
  getterNeedsReflow(Element, 'scrollWidth');
  // IE
  getterNeedsReflow(Element, 'currentStyle');

  function accessorNeedsReflow(ctor, propertyName) {
    var originalDescriptor = findDescriptor(ctor, propertyName);
    if (originalDescriptor) {
      overrideDescriptor(ctor, propertyName,{
        get: function() {
          ShadowOwner.renderAllPending();
          return originalDescriptor.get.call(this);
        },
        set: function(value) {
          ShadowOwner.renderAllPending();
          originalDescriptor.set.call(this, value);
        }
      });
    }
  }

  accessorNeedsReflow(Element, 'scrollLeft');
  accessorNeedsReflow(Element, 'scrollTop');

  function methodNeedsReflow(ctor, name) {
    var originalFunction = findMethod(ctor, name);
    overrideMethod(ctor, name, function() {
      ShadowOwner.renderAllPending();
      return originalFunction.apply(this, arguments);
    });
  }

  methodNeedsReflow(Window, 'getComputedStyle');
  methodNeedsReflow(Element, 'getBoundingClientRect');
  methodNeedsReflow(Element, 'getClientRects');
  methodNeedsReflow(HTMLElement, 'scrollIntoView');

  /////////////////////////////////////////////////////////////////////////////
  // innerHTML and outerHTML

  var escapeRegExp = /&|<|"/g;

  function escapeReplace(c) {
    switch (c) {
      case '&':
        return '&';
      case '<':
        return '<';
      case '"':
        return '"'
    }
  }

  function escape(s) {
    return s.replace(escapeRegExp, escapeReplace);
  }

  // http://www.whatwg.org/specs/web-apps/current-work/#void-elements
  var voidElements = {
    'area': true,
    'base': true,
    'br': true,
    'col': true,
    'command': true,
    'embed': true,
    'hr': true,
    'img': true,
    'input': true,
    'keygen': true,
    'link': true,
    'meta': true,
    'param': true,
    'source': true,
    'track': true,
    'wbr': true
  };

  function getOuterHTML(node) {
    switch (node.nodeType) {
      case Node.ELEMENT_NODE:
        var tagName = node.tagName.toLowerCase();
        var s = '<' + tagName;
        var attrs = node.attributes;
        for (var i = 0, attr; attr = attrs[i]; i++) {
          s += ' ' + attr.name + '="' + escape(attr.value) + '"';
        }
        s += '>';
        if (voidElements[tagName])
          return s;

        return s + getInnerHTML(node) + '</' + tagName + '>';
      case Node.TEXT_NODE:
        return escape(node.nodeValue);
      default:
        throw new Error('not implemented');
    }
  }

  function getInnerHTML(node) {
    var s = '';
    for (var child = node.firstChild; child; child = child.nextSibling) {
      s += getOuterHTML(child);
    }
    return s;
  }

  overrideDescriptor(HTMLElement, 'innerHTML', {
    get: function() {
      // TODO(arv): This should fallback to HTMLElement_prototype.innerHTML if there
      // are no shadow trees below or above the context node.
      return getInnerHTML(this);
    },
    set: function(value) {
      var shadowOwner = getShadowOwnerAndInvalidate(this);
      if (!shadowOwner) {
        HTMLElement_prototype.innerHTML.set.call(this, value);
      } else {
        var wrapper = wrap(this);
        wrapper.removeAllChildNodes();
        var tempElement = this.ownerDocument.createElement(this.tagName);
        HTMLElement_prototype.innerHTML.set.call(tempElement, value);
        var firstChild;
        while (firstChild = Node_prototype.firstChild.get.call(tempElement)) {
          wrapper.appendChild(wrap(firstChild));
        }
      }
    }
  });

  overrideDescriptor(HTMLElement, 'outerHTML', {
    get: function() {
      // TODO(arv): This should fallback to HTMLElement_prototype.outerHTML if there
      // are no shadow trees below or above the context node.
      return getOuterHTML(this);
    },
    set: function(value) {
      var shadowOwner = getShadowOwnerAndInvalidate(this);
      if (!shadowOwner) {
        Node_prototype.outerHTML.set.call(this, value);
      } else {
        throw new Error('not implemented');
      }
    }
  });

  // TODO(arv): JsShadowDom should have this getter.
  Object.defineProperty(DocumentFragment.prototype, 'innerHTML', {
    get: function() {
      return getInnerHTML(this);
    },
    set: function(value) {
      this.textContent = '';
      var tmp = this.ownerDocument.createElement('div');
      tmp.innerHTML = value;
      var child;
      while (child = tmp.firstChild) {
        this.appendChild(child);
      }
      ;
    },
    enumerable: true,
    configurable: true
  });

  function reflectAttribute(name) {
    return {
      get: function() {
        return this.getAttribute(name);
      },
      set: function(value) {
        this.setAttribute(name, value);
      },
      enumerable: true,
      configurable: true
    };
  }

  // TODO(arv): Introduce HTMLContentElement
  Object.defineProperty(HTMLUnknownElement.prototype, 'select',
                        reflectAttribute('select'));

})();
<commit_msg>Override nextElementSibling etc<commit_after>/*
 * Copyright 2012 The Toolkitchen Authors. All rights reserved.
 * Use of this source code is goverened by a BSD-style
 * license that can be found in the LICENSE file.
 */

// TODO(arv): Make these local.

var Node_prototype = {
  appendChild: findMethod(Node, 'appendChild'),
  removeChild: findMethod(Node, 'removeChild'),
  insertBefore: findMethod(Node, 'insertBefore'),
  parentNode: findDescriptor(Node, 'parentNode'),
  firstChild: findDescriptor(Node, 'firstChild'),
  lastChild: findDescriptor(Node, 'lastChild'),
  previousSibling: findDescriptor(Node, 'previousSibling'),
  nextSibling: findDescriptor(Node, 'nextSibling'),

  textContent: findDescriptor(Node, 'textContent')
};

var HTMLElement_prototype = {
  innerHTML: findDescriptor(HTMLElement, 'innerHTML'),
  outerHTML: findDescriptor(HTMLElement, 'outerHTML')
};

var getShadowOwnerAndInvalidate;

(function() {

  var shadowOwnerTable = new SideTable('shadowOwner');

  function oneOf(object, propertyNames) {
    for (var i = 0; i < propertyNames.length; i++) {
      if (propertyNames[i] in object)
        return object[propertyNames[i]].bind(object);
    }
  }

  var request = oneOf(window, [
    'requestAnimationFrame',
    'mozRequestAnimationFrame',
    'webkitRequestAnimationFrame',
    'setTimeout'
  ]);

  function ShadowOwner(host) {
    this.host = host;
    this.dirty = false;
  }
  ShadowOwner.prototype = {
    invalidate: function() {
      if (!this.dirty) {
        this.dirty = true;
        ShadowOwner.pending.push(this);
        ShadowOwner.scheduleRender();
      }
    },
    render: function() {
      if (this.dirty) {
        render(this.host);
        this.dirty = false;
      }
    }
  };

  ShadowOwner.pending = [];

  ShadowOwner.timer = null;

  ShadowOwner.scheduleRender = function() {
    if (ShadowOwner.timer)
      return;
    ShadowOwner.timer = request(ShadowOwner.renderAllPending, 0);
  };

  ShadowOwner.renderAllPending = function() {
    ShadowOwner.timer = null;
    ShadowOwner.pending.forEach(function(owner) {
      owner.render();
    });
    ShadowOwner.pending = [];
  };

  function findShadowHost(wrapper) {
    if (!wrapper)
      return null;
    if (wrapper.node.__shadowHost__)
      return wrapper.node.__shadowHost__;
    if (wrapper.node.__shadowRoot__)
      return wrapper.node;
    return findShadowHost(wrapper.parentNode);
  }

  function getShadowOwner(node) {
    if (!node)
      return null;
    var shadowHost;
    if (node.__shadowHost__) {
      shadowHost = node.__shadowHost__;
    } else if (node.__shadowRoot__) {
      shadowHost = node;
    } else {
      var wrapper = getExistingWrapper(node);
      if (!wrapper)
        return null;
      var shadowHost = findShadowHost(wrapper);
      if (!shadowHost)
        return null;
    }

    var shadowOwner = shadowOwnerTable.get(shadowHost);
    if (!shadowOwner) {
      shadowOwner = new ShadowOwner(shadowHost);
      shadowOwnerTable.set(shadowHost, shadowOwner);
    }
    return shadowOwner;
  }

  getShadowOwnerAndInvalidate = function getShadowOwnerAndInvalidate(node) {
    var shadowOwner = getShadowOwner(node);
    if (shadowOwner)
      shadowOwner.invalidate();
    return shadowOwner;
  };

  overrideMethod(Node, 'appendChild', function(node) {
    var shadowOwner = getShadowOwnerAndInvalidate(this);
    var nodeShadowOwner = getShadowOwnerAndInvalidate(node);
    if (shadowOwner || nodeShadowOwner)
      return unwrap(wrap(this).appendChild(wrap(node)));
    return Node_prototype.appendChild.call(this, node);
  });

  overrideMethod(Node, 'removeChild', function(node) {
    var shadowOwner = getShadowOwnerAndInvalidate(this);
    var nodeShadowOwner = getShadowOwnerAndInvalidate(node);
    if (shadowOwner || nodeShadowOwner)
      return unwrap(wrap(this).removeChild(wrap(node)));
    return Node_prototype.removeChild.call(this, node);
  });

  overrideMethod(Node, 'insertBefore', function(node, ref) {
    var shadowOwner = getShadowOwnerAndInvalidate(this);
    var nodeShadowOwner = getShadowOwnerAndInvalidate(node);
    var refShadowOwner = getShadowOwnerAndInvalidate(ref);
    if (shadowOwner || nodeShadowOwner || refShadowOwner)
      return unwrap(wrap(this).insertBefore(wrap(node), wrap(ref)));
    return Node_prototype.insertBefore.call(this, node, ref);
  });

  function methodShouldInvalidate(ctor, methodName) {
    var originalFunction = findMethod(ctor, methodName);
    overrideMethod(ctor, methodName, function() {
      getShadowOwnerAndInvalidate(this);
      return originalFunction.apply(this, arguments);
    });
  }

  methodShouldInvalidate(Element, 'setAttribute');
  methodShouldInvalidate(Element, 'setAttributeNS');
  methodShouldInvalidate(Element, 'setAttributeNode');
  methodShouldInvalidate(Element, 'setAttributeNodeNS');
  methodShouldInvalidate(Element, 'removeAttribute');
  methodShouldInvalidate(Element, 'removeAttributeNode');
  methodShouldInvalidate(Element, 'removeAttributeNS');

  function overrideNodeGetter(propertyName) {
    overrideGetter(Node, propertyName, function() {
      var shadowOwner = getShadowOwner(this);
      if (shadowOwner)
        return unwrap(wrap(this)[propertyName]);
      return Node_prototype[propertyName].get.call(this);
    });
  }

  overrideNodeGetter('parentNode');
  overrideNodeGetter('firstChild');
  overrideNodeGetter('lastChild');
  overrideNodeGetter('previousSibling');
  overrideNodeGetter('nextSibling');

  function getElementWalker(name) {
    return function() {
      for (var node = this[name];
           node && node.nodeType !== Node.ELEMENT_NODE;
           node = node[name]) {}
      return node;
    };
  }

  overrideGetter(Element, 'previousElementSibling', getElementWalker('previousSibling'));
  overrideGetter(Element, 'nextElementSibling', getElementWalker('nextSibling'));
  overrideGetter(Element, 'firstElementChild', getElementWalker('firstChild'));
  overrideGetter(Element, 'lastElementChild', getElementWalker('lastChild'));
  // parentElement is defined on HTMLElement in IE
  overrideGetter(HTMLElement, 'parentElement', getElementWalker('parentNode'));

  overrideDescriptor(Node, 'textContent', {
    get: function() {
      if (this instanceof CharacterData)
        return Node_prototype.textContent.get.call(this);

      // TODO(arv): This should fallback to Node_prototype.textContent if there
      // are no shadow trees below or above the context node.
      var s = '';
      for (var child = this.firstChild; child; child = child.nextSibling) {
        s += child.textContent;
      }
      return s;
    },
    set: function(value) {
      if (this instanceof CharacterData)
        return Node_prototype.textContent.set.call(this, value);

      var shadowOwner = getShadowOwnerAndInvalidate(this);
      if (!shadowOwner || this instanceof CharacterData) {
        Node_prototype.textContent.set.call(this, value);
      } else {
        var wrapper = wrap(this);
        wrapper.removeAllChildNodes();
        if (value !== '') {
          var textNode = this.ownerDocument.createTextNode(value);
          wrapper.appendChild(wrap(textNode));
        }
      }
    }
  });

  function getterNeedsReflow(ctor, propertyName) {
    var originalDescriptor = findDescriptor(ctor, propertyName);
    if (originalDescriptor) {
      overrideGetter(ctor, propertyName, function() {
        ShadowOwner.renderAllPending();
        return originalDescriptor.get.call(this);
      });
    }
  }

  getterNeedsReflow(Element, 'clientHeight');
  getterNeedsReflow(Element, 'clientWidth');
  getterNeedsReflow(Element, 'offsetHeight');
  getterNeedsReflow(Element, 'offsetWidth');
  getterNeedsReflow(Element, 'scrollHeight');
  getterNeedsReflow(Element, 'scrollWidth');
  // IE
  getterNeedsReflow(Element, 'currentStyle');

  function accessorNeedsReflow(ctor, propertyName) {
    var originalDescriptor = findDescriptor(ctor, propertyName);
    if (originalDescriptor) {
      overrideDescriptor(ctor, propertyName,{
        get: function() {
          ShadowOwner.renderAllPending();
          return originalDescriptor.get.call(this);
        },
        set: function(value) {
          ShadowOwner.renderAllPending();
          originalDescriptor.set.call(this, value);
        }
      });
    }
  }

  accessorNeedsReflow(Element, 'scrollLeft');
  accessorNeedsReflow(Element, 'scrollTop');

  function methodNeedsReflow(ctor, name) {
    var originalFunction = findMethod(ctor, name);
    overrideMethod(ctor, name, function() {
      ShadowOwner.renderAllPending();
      return originalFunction.apply(this, arguments);
    });
  }

  methodNeedsReflow(Window, 'getComputedStyle');
  methodNeedsReflow(Element, 'getBoundingClientRect');
  methodNeedsReflow(Element, 'getClientRects');
  methodNeedsReflow(HTMLElement, 'scrollIntoView');

  /////////////////////////////////////////////////////////////////////////////
  // innerHTML and outerHTML

  var escapeRegExp = /&|<|"/g;

  function escapeReplace(c) {
    switch (c) {
      case '&':
        return '&';
      case '<':
        return '<';
      case '"':
        return '"'
    }
  }

  function escape(s) {
    return s.replace(escapeRegExp, escapeReplace);
  }

  // http://www.whatwg.org/specs/web-apps/current-work/#void-elements
  var voidElements = {
    'area': true,
    'base': true,
    'br': true,
    'col': true,
    'command': true,
    'embed': true,
    'hr': true,
    'img': true,
    'input': true,
    'keygen': true,
    'link': true,
    'meta': true,
    'param': true,
    'source': true,
    'track': true,
    'wbr': true
  };

  function getOuterHTML(node) {
    switch (node.nodeType) {
      case Node.ELEMENT_NODE:
        var tagName = node.tagName.toLowerCase();
        var s = '<' + tagName;
        var attrs = node.attributes;
        for (var i = 0, attr; attr = attrs[i]; i++) {
          s += ' ' + attr.name + '="' + escape(attr.value) + '"';
        }
        s += '>';
        if (voidElements[tagName])
          return s;

        return s + getInnerHTML(node) + '</' + tagName + '>';
      case Node.TEXT_NODE:
        return escape(node.nodeValue);
      default:
        throw new Error('not implemented');
    }
  }

  function getInnerHTML(node) {
    var s = '';
    for (var child = node.firstChild; child; child = child.nextSibling) {
      s += getOuterHTML(child);
    }
    return s;
  }

  overrideDescriptor(HTMLElement, 'innerHTML', {
    get: function() {
      // TODO(arv): This should fallback to HTMLElement_prototype.innerHTML if there
      // are no shadow trees below or above the context node.
      return getInnerHTML(this);
    },
    set: function(value) {
      var shadowOwner = getShadowOwnerAndInvalidate(this);
      if (!shadowOwner) {
        HTMLElement_prototype.innerHTML.set.call(this, value);
      } else {
        var wrapper = wrap(this);
        wrapper.removeAllChildNodes();
        var tempElement = this.ownerDocument.createElement(this.tagName);
        HTMLElement_prototype.innerHTML.set.call(tempElement, value);
        var firstChild;
        while (firstChild = Node_prototype.firstChild.get.call(tempElement)) {
          wrapper.appendChild(wrap(firstChild));
        }
      }
    }
  });

  overrideDescriptor(HTMLElement, 'outerHTML', {
    get: function() {
      // TODO(arv): This should fallback to HTMLElement_prototype.outerHTML if there
      // are no shadow trees below or above the context node.
      return getOuterHTML(this);
    },
    set: function(value) {
      var shadowOwner = getShadowOwnerAndInvalidate(this);
      if (!shadowOwner) {
        Node_prototype.outerHTML.set.call(this, value);
      } else {
        throw new Error('not implemented');
      }
    }
  });

  // TODO(arv): JsShadowDom should have this getter.
  Object.defineProperty(DocumentFragment.prototype, 'innerHTML', {
    get: function() {
      return getInnerHTML(this);
    },
    set: function(value) {
      this.textContent = '';
      var tmp = this.ownerDocument.createElement('div');
      tmp.innerHTML = value;
      var child;
      while (child = tmp.firstChild) {
        this.appendChild(child);
      }
      ;
    },
    enumerable: true,
    configurable: true
  });

  function reflectAttribute(name) {
    return {
      get: function() {
        return this.getAttribute(name);
      },
      set: function(value) {
        this.setAttribute(name, value);
      },
      enumerable: true,
      configurable: true
    };
  }

  // TODO(arv): Introduce HTMLContentElement
  Object.defineProperty(HTMLUnknownElement.prototype, 'select',
                        reflectAttribute('select'));

})();
<|endoftext|>


<commit_before>const validator = require('../utils').validator;
const size = require('../utils').constants.size;
const convert = require('../utils/inputConverter').convert;

class Comment {
    constructor(model, user) {
        this._author = user.username;
        this._content = model.comment;
    }

    get id() {
        return this._id;
    }

    get _author() {
        return this.author;
    }

    set _author(value) {
        const name = convert(value);
        if (validator.validateString(name, size.MIN_NAME, size.MAX_NAME)) {
            this.author = name;
        } else {
            throw new Error('Invalid author');
        }
    }

    get _content() {
        return this.content;
    }

    set _content(value) {
        const text = convert(value);
        if (validator.validateString(text, size.MIN_TEXT, size.MAX_TEXT)) {
            this.content = text;
        } else {
            throw new Error('Invalid content');
        }
    }
}

const initComment = (model, user) => {
    return new Comment(model, user);
};

module.exports = { Comment, initComment };
<commit_msg>refactored validations<commit_after>const { validator } = require('../utils/validator');
const convert = require('../utils/inputConverter').convert;

class Comment {
    constructor(model, user) {
        this._author = user.username;
        this._content = model.comment;
    }

    get id() {
        return this._id;
    }

    get _author() {
        return this.author;
    }

    set _author(value) {
        const usernameVal = convert(value);
        validator.validateIfUndefinedOrNull(usernameVal, 'Username');
        validator.validateUsername(usernameVal);
        validator.validateIfEmptyString(usernameVal, 'Username');
        this.author = usernameVal;
    }

    get _content() {
        return this.content;
    }

    set _content(value) {
        const text = convert(value);
        validator.validateIfUndefinedOrNull(text, 'Comment');
        validator.validateTypeOf(text, 'Comment', 'string');
        validator.validateIfEmptyString(text, 'Comment');
        this.content = text;
    }
}

const initComment = (model, user) => {
    return new Comment(model, user);
};

module.exports = { Comment, initComment };
<|endoftext|>


<commit_before>           from.package('poopy.js').wait();
var yarn = from.package('Yarn')    .wait();

return (function(){ var percival;
  percival = new(Object);

  percival.peer = function (target) {
    process.stdio.write(percival.percieve(target) + '\n');
  };

  percival.percieve = function (target) { var stack;
    var stack = new(Array);

    return (function (target) { var me, sub;
      me = arguments.callee;

      if ((index = stack.indexOf(target)) !== -1) {
        return new(Array)( stack.length - index + 1 ).join('.') };

      sub = function (sub) { var result;
        stack.push(target);
        result = me.apply(percival, [sub]);
        stack.pop();
        return result;
      };

      return percival.lensFor(target).apply(target, [sub]);
    })(target);
  };

  percival.lenses = new(Object);

  // The default lens. Applied to all basic JavaScript objects.
  //
  // (Note: Lenses are `apply()`ed, so `this` in this function refers to the
  // object being `probe()`d, not the `percival` object.)
  //--
  // TODO: Indentation and newline handling!
  // TODO: Does this function need to have indentation passed to it?
  percival.lenses[Object.prototype] = function (sub) { var out;
    out = yarn.beget();

    for (key in this) { if (this.hasOwnProperty(key)) {
      out.push(sub( key ), ': ', sub( this[key] ), ', ') }};

    out.pop(); // To get rid of the final comma… I wish I could use `join()`…
    out.unshift('{');
    out.push('}');
    return out;
  };

  // Climbs the prototypal inheritance chain, and returns the first lens
  // associated with one of the objects. Will return the `lens` property, if
  // it’s defined, or the relevant element from `percival.lenses`, if not.
  percival.lensFor = function (object) {
         if (typeof object.lens !== 'undefined') { return object.lens }
    else if (typeof percival.lenses[object] !== 'undefined') {
             return percival.lenses[object] }
      else { return arguments.callee.apply(this, [object['__proto__']]) }
  };

  return percival;
})();
<commit_msg>`yarn`’s constructor is a bit bullshit<commit_after>           from.package('poopy.js').wait();
var yarn = from.package('Yarn')    .wait();

return (function(){ var percival;
  percival = new(Object);

  percival.peer = function (target) {
    process.stdio.write(percival.percieve(target) + '\n');
  };

  percival.percieve = function (target) { var stack;
    var stack = new(Array);

    return (function (target) { var me, sub;
      me = arguments.callee;

      if ((index = stack.indexOf(target)) !== -1) {
        return new(Array)( stack.length - index + 1 ).join('.') };

      sub = function (sub) { var result;
        stack.push(target);
        result = me.apply(percival, [sub]);
        stack.pop();
        return result;
      };

      return percival.lensFor(target).apply(target, [sub]);
    })(target);
  };

  percival.lenses = new(Object);

  // The default lens. Applied to all basic JavaScript objects.
  //
  // (Note: Lenses are `apply()`ed, so `this` in this function refers to the
  // object being `probe()`d, not the `percival` object.)
  percival.lenses[Object.prototype] = function (sub) { var out;
    out = yarn.beget();

    for (key in this) { if (this.hasOwnProperty(key)) {
      out.push(sub( key ), ': ', sub( this[key] ), ', ') }};

    out.pop(); // To get rid of the final comma… I wish I could use `join()`…
    out.unshift('{');
    out.push('}');
    return out;
  };

  percival.lenses[Number.prototype] = function (sub) {var out;
    out = yarn.beget();
    // out.style('number');
    out.push(this.valueOf());
    return out;
  };

  percival.lenses[String.prototype] = function (sub) { var out;
    out = yarn.beget();
    // out.style('string');
    out.push(
      '"',
      this.valueOf()
        .replace(/\\/g, '\\\\')
        .replace(/\n/g, '\\n')
        .replace(/\"/g, '\\"'),
      '"'
    );
    return out;
  };

  // Climbs the prototypal inheritance chain, and returns the first lens
  // associated with one of the objects. Will return the `lens` property, if
  // it’s defined, or the relevant element from `percival.lenses`, if not.
  percival.lensFor = function (object) {
         if (typeof object.lens !== 'undefined') { return object.lens }
    else if (typeof percival.lenses[object] !== 'undefined') {
             return percival.lenses[object] }
      else { return arguments.callee.apply(this, [object['__proto__']]) }
  };

  return percival;
})();
<|endoftext|>


<commit_before>module.exports = Platform;

function Platform() {

    function get_container() {
        if (is_browser()) return window;
        if (is_node()) return global;
        if (is_phantom()) return phantom;
    }

    function is_node() {
        return typeof global != 'undefined' &&
               typeof global.process != 'undefined' &&
               global.process.title == 'node';
    }

    function is_browser() {
        return typeof window != 'undefined';
    }

    function is_phantom() {
        return typeof phantom != 'undefined';
    }

    return {
        get_container: get_container,
        is_node: is_node,
        is_browser: is_browser,
        is_phantom: is_phantom
    }

}<commit_msg>Better detection of node.js<commit_after>module.exports = Platform;

function Platform() {

    function get_container() {
        if (is_browser()) return window;
        if (is_node()) return global;
        if (is_phantom()) return phantom;
    }

    function is_node() {
        return typeof module != 'undefined' &&
               typeof module.exports != 'undefined';
    }

    function is_browser() {
        return typeof window != 'undefined';
    }

    function is_phantom() {
        return typeof phantom != 'undefined';
    }

    return {
        get_container: get_container,
        is_node: is_node,
        is_browser: is_browser,
        is_phantom: is_phantom
    }

}
<|endoftext|>


<commit_before>var extend = require('util')._extend;
var setEventually = require('./func.js').setEventually;

/**
 * Constructor for Prospect model
 * @param {Object} [data] - setEventually of parameters which will be merged with the created instance
 * @constructor
 * @namespace
 * @property {String} this.profileId - the customer profile Id (related to a specific payment system
 * @property {String} this.customerFirstName - the customer (billed to) first name
 * @property {String} this.customerLastName - the customer (billed to) last name
 * @property {String} this.customerEmail - the customer (billed to) email
 * @property {String} this.billingAddress - the billing address
 * @property {String} this.billingCity - the billing city
 * @property {String} this.billingState - the billing state
 * @property {String} this.billingCountry - the billing country
 * @property {String} this.shippingFirstName - the customer (billed to) first name
 * @property {String} this.shippingLastName - the customer (billed to) first name
 * @property {String} this.shippingAddress - the shipping address
 * @property {String} this.shippingCity - the shipping city
 * @property {String} this.shippingState - the shipping state
 * @property {String} this.shippingCountry - the shipping country
 */
function Prospect(data) {
  var dataOrDefault = data || {};
  extend(this, dataOrDefault);
}

Prospect.prototype.withProfileId = setEventually('profileId');

/**
 * setEventually the customer first name
 * @method
 * @param {String} name - the customer first name
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withCustomerFirstName = setEventually('customerFirstName');

/**
 * setEventually the customer last name
 * @method
 * @param {String} name - the customer last name
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withCustomerLastName = setEventually('customerLastName');

/**
 * setEventually the customer email address
 * @method
 * @param {String} email - the customer email
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withCustomerEmail = setEventually('customerEmail');

/**
 * setEventually the billing address
 * @method
 * @param {String} address - the billing address
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withBillingAddress = setEventually('billingAddress');

/**
 * setEventually the billing city
 * @method
 * @param {String} city - the billing city
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withBillingCity = setEventually('billingCity');

/**
 * setEventually the billing state
 * @method
 * @param {String} state - the billing state
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withBillingState = setEventually('billingState');

/**
 * setEventually the billing country
 * @method
 * @param {String} country - the billing country
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withBillingCountry = setEventually('billingCountry');

/**
 * setEventually the shipping first name
 * @method
 * @param {String} name - the shipping first name
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withShippingFirstName = setEventually('shippingFirstName');

/**
 * setEventually the shipping last name
 * @method
 * @param {String} name - the shipping last name
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withShippingLastName = setEventually('shippingLastName');

/**
 * setEventually the shipping address
 * @method
 * @param {String} address - the shipping address
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withShippingAddress = setEventually('shippingAddress');

/**
 * setEventually the shipping city
 * @method
 * @param {String} city - the shipping city
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withShippingCity = setEventually('shippingCity');

/**
 * setEventually the shipping state
 * @method
 * @param {String} state - the shipping state
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withShippingState = setEventually('shippingState');

/**
 * setEventually the shipping country
 * @method
 * @param {String} country - the shipping country
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withShippingCountry = setEventually('shippingCountry');

/**
 * set eventually the billing zip code
 * @method
 * @params {String} zip - the zip code to set
 * @returns {Prospect} the instance
 */
Prospect.prototype.withBillingZip = setEventually('billingZip');

/**
 * set eventually the shipping zip code
 * @method
 * @params {String} zip - the zip code to set
 * @returns {Prospect} the instance
 */
Prospect.prototype.withShippingZip = setEventually('shippingZip');

/**
 * charge a customer using a specific gateway assuming its profileId refers to a record in the gateway system.
 * This is an simple implementation of the visitor design pattern (the visitor being the gateway)
 * @param {Object} order - the order for the transaction
 * @param {BaseGateway} gateway - the gateway to use
 * @param {Object} option - optional field
 * @returns {Promise} - cf BaseGateway#chargeCustomer
 */
Prospect.prototype.$charge = function (order, gateway, option) {
  return gateway.chargeCustomer(order, this.profileId, option);
};

module.exports = Prospect;

<commit_msg>Extending prospect model (and missing comment/doc).<commit_after>var extend = require('util')._extend;
var setEventually = require('./func.js').setEventually;

/**
 * Constructor for Prospect model
 * @param {Object} [data] - setEventually of parameters which will be merged with the created instance
 * @constructor
 * @namespace
 * @property {String} this.profileId - the customer profile Id (related to a specific payment system
 * @property {String} this.customerFirstName - the customer (billed to) first name
 * @property {String} this.customerLastName - the customer (billed to) last name
 * @property {String} this.customerEmail - the customer (billed to) email
 * @property {String} this.customerPhone - the customer (billed to) phone
 * @property {String} this.billingAddress - the billing address
 * @property {String} this.billingCity - the billing city
 * @property {String} this.billingState - the billing state
 * @property {String} this.billingZip - the billing zipcode
 * @property {String} this.billingCountry - the billing country
 * @property {String} this.shippingFirstName - the customer (billed to) first name
 * @property {String} this.shippingLastName - the customer (billed to) first name
 * @property {String} this.shippingAddress - the shipping address
 * @property {String} this.shippingCity - the shipping city
 * @property {String} this.shippingState - the shipping state
 * @property {String} this.shippingZip - the shipping zipcode
 * @property {String} this.shippingCountry - the shipping country
 */
function Prospect(data) {
  var dataOrDefault = data || {};
  extend(this, dataOrDefault);
}

Prospect.prototype.withProfileId = setEventually('profileId');

/**
 * setEventually the customer first name
 * @method
 * @param {String} name - the customer first name
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withCustomerFirstName = setEventually('customerFirstName');

/**
 * setEventually the customer last name
 * @method
 * @param {String} name - the customer last name
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withCustomerLastName = setEventually('customerLastName');

/**
 * setEventually the customer email address
 * @method
 * @param {String} email - the customer email
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withCustomerEmail = setEventually('customerEmail');

/**
 * setEventually the customer phone
 * @method
 * @param {String} phone - the customer phone
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withCustomerPhone = setEventually('customerPhone');

/**
 * setEventually the billing address
 * @method
 * @param {String} address - the billing address
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withBillingAddress = setEventually('billingAddress');

/**
 * setEventually the billing city
 * @method
 * @param {String} city - the billing city
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withBillingCity = setEventually('billingCity');

/**
 * setEventually the billing state
 * @method
 * @param {String} state - the billing state
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withBillingState = setEventually('billingState');

/**
 * setEventually the billing country
 * @method
 * @param {String} country - the billing country
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withBillingCountry = setEventually('billingCountry');

/**
 * setEventually the shipping first name
 * @method
 * @param {String} name - the shipping first name
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withShippingFirstName = setEventually('shippingFirstName');

/**
 * setEventually the shipping last name
 * @method
 * @param {String} name - the shipping last name
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withShippingLastName = setEventually('shippingLastName');

/**
 * setEventually the shipping address
 * @method
 * @param {String} address - the shipping address
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withShippingAddress = setEventually('shippingAddress');

/**
 * setEventually the shipping city
 * @method
 * @param {String} city - the shipping city
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withShippingCity = setEventually('shippingCity');

/**
 * setEventually the shipping state
 * @method
 * @param {String} state - the shipping state
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withShippingState = setEventually('shippingState');

/**
 * setEventually the shipping country
 * @method
 * @param {String} country - the shipping country
 * @returns {Prospect} returns the instance
 */
Prospect.prototype.withShippingCountry = setEventually('shippingCountry');

/**
 * set eventually the billing zip code
 * @method
 * @params {String} zip - the zip code to set
 * @returns {Prospect} the instance
 */
Prospect.prototype.withBillingZip = setEventually('billingZip');

/**
 * set eventually the shipping zip code
 * @method
 * @params {String} zip - the zip code to set
 * @returns {Prospect} the instance
 */
Prospect.prototype.withShippingZip = setEventually('shippingZip');

/**
 * charge a customer using a specific gateway assuming its profileId refers to a record in the gateway system.
 * This is an simple implementation of the visitor design pattern (the visitor being the gateway)
 * @param {Object} order - the order for the transaction
 * @param {BaseGateway} gateway - the gateway to use
 * @param {Object} option - optional field
 * @returns {Promise} - cf BaseGateway#chargeCustomer
 */
Prospect.prototype.$charge = function (order, gateway, option) {
  return gateway.chargeCustomer(order, this.profileId, option);
};

module.exports = Prospect;

<|endoftext|>


<commit_before>// Copyright (c) 2015, Evan Summers (twitter.com/evanxsummers)
// ISC license, see http://github.com/evanx/redex/LICENSE

const logger = Loggers.create(module.filename);

module.exports = {

   create(rootConfig, config, state, alerter) {

      let that = {};

      const those = {
         async start() {
            if (config.helloDelay) {
               that.startTimeoutId = setTimeout(async () => {
                  alerter.sendAlert('RESTARTED', await those.getReport()).catch(err => {
                     logger.error('send hello:', err.stack);
                  });
               }, config.helloDelay);
            }
            if (config.dailyEnabled) {
               that.dailyIntervalId = setInterval(async () => {
                  var time = new Date();
                  if (time.getHours() === config.hour && time.getMinutes() === config.minute) {
                     logger.info('daily', time.getHours(), time.getMinutes(), config);
                     alerter.sendAlert('DAILY', await those.getReport()).catch(err => {
                        logger.error('send daily:', err);
                     });
                  }
               }, 45000);
            }
         },
         end() {
            if (that.dailyIntervalId) {
               clearInterval(that.dailyIntervalId);
            }
         },
         async getReport() {
            var lines = lodash.map([...state.services], service => {
               if (service.status) {
                  return [service.status, service.url].join(' ');
               } else {
                  return service.url;
               }
            });
            let peakLoad = await Files.readFile('/proc/loadavg');
            let rootDisk = await Files.readFile('/proc/loadavg');
            lines.push(lodash.trim(peakLoad));
            lines.push(lodash.trim(rootDisk));
            return lines.join('\n');
         },
         sendAlert(subject, message) {
            if (false) {
               if (message) {
                  message += '\n\n' + those.getReport();
               } else {
                  message = those.getReport();
               }
            }
            alerter.sendAlert(subject, message);
         }
      }
      return those;
   }
};
<commit_msg>scripts<commit_after>// Copyright (c) 2015, Evan Summers (twitter.com/evanxsummers)
// ISC license, see http://github.com/evanx/redex/LICENSE

const logger = Loggers.create(module.filename);

module.exports = {

   create(rootConfig, config, state, alerter) {

      let that = {};

      const those = {
         async start() {
            if (config.helloDelay) {
               that.startTimeoutId = setTimeout(async () => {
                  alerter.sendAlert('RESTARTED', await those.getReport()).catch(err => {
                     logger.error('send hello:', err.stack);
                  });
               }, config.helloDelay);
            }
            if (config.dailyEnabled) {
               that.dailyIntervalId = setInterval(async () => {
                  var time = new Date();
                  if (time.getHours() === config.hour && time.getMinutes() === config.minute) {
                     logger.info('daily', time.getHours(), time.getMinutes(), config);
                     alerter.sendAlert('DAILY', await those.getReport()).catch(err => {
                        logger.error('send daily:', err);
                     });
                  }
               }, 45000);
            }
         },
         end() {
            if (that.dailyIntervalId) {
               clearInterval(that.dailyIntervalId);
            }
         },
         async getReport() {
            var lines = lodash.map([...state.services], service => {
               if (service.status) {
                  return [service.status, service.url].join(' ');
               } else {
                  return service.name;
               }
            });
            let peakLoad = await Files.readFile('/proc/loadavg');
            let rootDisk = await Files.readFile('/proc/loadavg');
            lines.push(lodash.trim(peakLoad));
            lines.push(lodash.trim(rootDisk));
            return lines.join('\n');
         },
         sendAlert(subject, message) {
            if (message) {
               message += '\n\n' + those.getReport();
            } else {
               message = those.getReport();
            }
            alerter.sendAlert(subject, message);
         }
      }
      return those;
   }
};
<|endoftext|>


<commit_before>var merge = require('react/lib/merge');

/**
 * The Response is used as the `this` value for route functions. It hosts
 * methods for manipulating the server response and application state.
 */
function Response(request, router) {
  this.request = request;
  this.router = router;
  this.state = router.state;
}

Response.prototype.setState = function(state, cb) {
  // The Response's state is basically a window onto the router state, since
  // we don't want to accidentally clobber `view`. This assumes that you only
  // ever change router state through the route context.
  var newState = {viewProps: merge(this.state.viewProps, state)};
  this.router.setState(newState, function() {
    this.state = this.router.state.viewProps;
    if (cb) cb();
  }.bind(this));
};

Response.prototype.setView = function(view, cb) {
  this.router.setState({view: view}, function() {
    this.view = view;
    if (cb) cb();
  }.bind(this));
};

/**
 * Indicate that no view was found for the corresponding request. This has no
 * effect in the browser, but causes a 404 response to be sent on the server.
 * Note that this is different from `unhandled` as it indicates that the UI has
 * been updated.
 */
Response.prototype.notFound = function() {
  // TODO: Should this work as a getter too? Or should we make it chainable?
  this._notFound = true;
};

Response.prototype.doctype = function() {
  return ''; // TODO: THIS!
};

Response.prototype.contentType = function() {
  return ''; // TODO: THIS, ALSO!
};

/**
 * A function decorator that creates a new view from the provided one and the
 * (optional) remaining arguments.
 *
 * @param {function} fn The function whose arguments to transform.
 */
function renderer(fn) {
  return function(view) {
    if (arguments.length > 1) {
      var oldView = view;
      var args = Array.prototype.slice.call(arguments, 1);
      view = function() {
        return oldView.apply(this, args);
      };
    }
    return fn(view);
  };
}

//
// Shortcut methods for rendering a view with props and (optionally) ending the
// request.
//

/**
 * Render the provided view and end the request.
 */
Response.prototype.render = renderer(function(view) {
  this.setView(view);
  this.request.end();
  return this;
});

/**
 * Render the provided view and mark the current state as the initial one.
 */
Response.prototype.renderInitial = renderer(function(view) {
  this.setView(view);
  this.request.endInitial();
  return this;
});

/**
 * Render the provided view.
 */
Response.prototype.renderIntermediate = renderer(function(view) {
  this.setView(view);
  return this;
});

module.exports = Response;
<commit_msg>Fix renderer context<commit_after>var merge = require('react/lib/merge');

/**
 * The Response is used as the `this` value for route functions. It hosts
 * methods for manipulating the server response and application state.
 */
function Response(request, router) {
  this.request = request;
  this.router = router;
  this.state = router.state;
}

Response.prototype.setState = function(state, cb) {
  // The Response's state is basically a window onto the router state, since
  // we don't want to accidentally clobber `view`. This assumes that you only
  // ever change router state through the route context.
  var newState = {viewProps: merge(this.state.viewProps, state)};
  this.router.setState(newState, function() {
    this.state = this.router.state.viewProps;
    if (cb) cb();
  }.bind(this));
};

Response.prototype.setView = function(view, cb) {
  this.router.setState({view: view}, function() {
    this.view = view;
    if (cb) cb();
  }.bind(this));
};

/**
 * Indicate that no view was found for the corresponding request. This has no
 * effect in the browser, but causes a 404 response to be sent on the server.
 * Note that this is different from `unhandled` as it indicates that the UI has
 * been updated.
 */
Response.prototype.notFound = function() {
  // TODO: Should this work as a getter too? Or should we make it chainable?
  this._notFound = true;
};

Response.prototype.doctype = function() {
  return ''; // TODO: THIS!
};

Response.prototype.contentType = function() {
  return ''; // TODO: THIS, ALSO!
};

/**
 * A function decorator that creates a new view from the provided one and the
 * (optional) remaining arguments.
 *
 * @param {function} fn The function whose arguments to transform.
 */
function renderer(fn) {
  return function(view) {
    if (arguments.length > 1) {
      var oldView = view;
      var args = Array.prototype.slice.call(arguments, 1);
      view = function() {
        return oldView.apply(this, args);
      };
    }
    return fn.call(this, view);
  };
}

//
// Shortcut methods for rendering a view with props and (optionally) ending the
// request.
//

/**
 * Render the provided view and end the request.
 */
Response.prototype.render = renderer(function(view) {
  this.setView(view);
  this.request.end();
  return this;
});

/**
 * Render the provided view and mark the current state as the initial one.
 */
Response.prototype.renderInitial = renderer(function(view) {
  this.setView(view);
  this.request.endInitial();
  return this;
});

/**
 * Render the provided view.
 */
Response.prototype.renderIntermediate = renderer(function(view) {
  this.setView(view);
  return this;
});

module.exports = Response;
<|endoftext|>


<commit_before>/**
 * Created by JiaHao on 19/8/15.
 */

var async = require('async');
var Promise = global.Promise || require('bluebird');

var navigation = require('./navigation');
var actions = require('./actions');
var checks = require('./checks');
var getData = require('./getData');

/**
 * A headless browser based on PhantomJS
 *
 * @param {Object} [options]
 * @constructor
 */
function Revenant(options) {
    this.options = options;
    this.initQueue();
}

Revenant.prototype = {
    constructor: Revenant,

    initQueue: function () {
        var self = this;
        const QUEUE_CONCURRENCY = 1;

        self.taskQueue = async.queue(function(parameters, callback) {

            var task = parameters.task;
            var arguments = parameters.argument;

            if (task === navigation.openPage) {

                task(
                    arguments,
                    function (error, page, ph) {
                        self.phantom = ph;
                        self.page = page;

                        page.get('url', function (url) {
                            self.__url = url;

                            if (error) {
                                callback(error);
                                return;
                            }

                            callback();

                        });

                    },
                    self.options
                );
            } else {

                if (!self.page) {
                    callback('Page is not open');
                    return;
                }

                var taskArguments = [self.page, self.phantom, arguments, function (error, page, ph, result) {
                    page.get('url', function (url) {
                        self.__url = url;
                        callback(error, result);
                    });
                }];

                // removes any null elements from the list
                taskArguments = taskArguments.filter(function (element) {
                    return !!element;
                });

                task.apply(this, taskArguments);

            }

        }, QUEUE_CONCURRENCY);
    },

    getUrl: function (callback) {
        var self = this;

        return promiseOrCallback(callback, function (resolve) {
            // if page is available (meaning that the page has been opened or .done() has not been called),
            // get the current url
            if (self.page) {
                self.page.get('url', function (url) {
                    self.__url = url;
                    resolve(url);

                });
            } else {
                // just get the cached url at the end of every method call
                resolve(self.__url);
            }
        });
    },

    /**
     * Private helper function to push a task
     * @param task
     * @param argument
     * @param callback
     * @returns {Promise}
     * @private
     */
    __pushTask: function (task, argument, callback) {
        var self = this;

        var queueArgument = {
            task: task,
            argument: argument
        };

        return promiseOrCallback(callback, function (resolve, reject) {
            self.taskQueue.push(queueArgument, function (error, result) {
                if (error) {
                    reject(error);
                } else {
                    resolve(result);
                }
            });
        });
    },

    openPage: function (url, callback) {
        return this.__pushTask(navigation.openPage, url, callback);
    },

    navigateToUrl: function (url, callback) {
        return this.__pushTask(navigation.navigateToUrl, url, callback);
    },

    takeSnapshot: function(callback) {
        return this.__pushTask(getData.takeSnapshot, null, callback);
    },

    waitForElement: function (selector, callback) {
        return this.__pushTask(checks.elementExists, selector, callback);
    },

    waitForDomString: function (stringQuery, callback) {
        return this.__pushTask(checks.waitForString, stringQuery, callback);
    },

    getInnerHTML: function (selector, callback) {
        return this.__pushTask(getData.getInnerHtmlFromElement, selector, callback);
    },

    changeDropdownIndex: function (selector, value, callback) {
        return this.__pushTask(actions.changeDropdownIndex, [selector, value], callback);
    },

    /**
     *
     * @param {string} selector
     * @param {int} options 0 – callback immediately
     *                      1 – expect ajax, callback when the dom changes
     *                      2 – expect page navigation, callback when the url changes and document is ready
     * @param callback
     */
    clickElement: function (selector, options, callback) {
        return this.__pushTask(actions.clickElement, [selector, options], callback);
    },

    /**
     * Sets the state of a checkbox
     * @param {string} selector
     * @param {boolean} state
     * @param callback
     * @returns {Promise}
     */
    setCheckboxState: function (selector, state, callback) {
        return this.__pushTask(actions.setCheckboxState, [selector, state], callback);
    },

    fillForm: function (selector, value, callback) {
        return this.__pushTask(actions.fillForm, [selector, value], callback);
    },

    getSelectorValue: function (selector, callback) {
        return this.__pushTask(getData.getSelectorValue, selector, callback);
    },

    submitForm: function (callback) {
        return this.__pushTask(actions.submitForm, null, callback);
    },

    /**
     * Kills the phantom process. Does nothing if it has not been started initially
     */
    done: function () {
        // Guard to prevent duplicate call of .exit();
        if (this.phantom) {
            this.phantom.exit();
            this.page = null;
            this.phantom = null;
        }
    }
};

/**
 * Build a promise, with an optional node-style callback attached
 */
function promiseOrCallback (callback, executor) {
    var promise = new Promise(executor);

    // no callback: do not attach
    if (typeof callback !== 'function') {
        return promise;
    }

    return promise.then(function (value) {
        setImmediate(function () {
            callback(null, value);
        });
        return value;
    }, function (error) {
        setImmediate(function () {
            callback(error);
        });
        throw error;
    });
}

module.exports = Revenant;
<commit_msg>Reorder functions grouped by functionality<commit_after>/**
 * Created by JiaHao on 19/8/15.
 */

var async = require('async');
var Promise = global.Promise || require('bluebird');

var navigation = require('./navigation');
var actions = require('./actions');
var checks = require('./checks');
var getData = require('./getData');

/**
 * A headless browser based on PhantomJS
 *
 * @param {Object} [options]
 * @constructor
 */
function Revenant(options) {
    this.options = options;
    this.initQueue();
}

Revenant.prototype = {
    constructor: Revenant,

    initQueue: function () {
        var self = this;
        const QUEUE_CONCURRENCY = 1;

        self.taskQueue = async.queue(function(parameters, callback) {

            var task = parameters.task;
            var arguments = parameters.argument;

            if (task === navigation.openPage) {
                task(
                    arguments,
                    function (error, page, ph) {
                        self.phantom = ph;
                        self.page = page;

                        page.get('url', function (url) {
                            self.__url = url;

                            if (error) {
                                callback(error);
                                return;
                            }

                            callback();

                        });

                    },
                    self.options
                );
            } else {
                if (!self.page) {
                    callback('Page is not open');
                    return;
                }
                var taskArguments = [self.page, self.phantom, arguments, function (error, page, ph, result) {
                    page.get('url', function (url) {
                        self.__url = url;
                        callback(error, result);
                    });
                }];

                // removes any null elements from the list
                taskArguments = taskArguments.filter(function (element) {
                    return !!element;
                });
                task.apply(this, taskArguments);
            }
        }, QUEUE_CONCURRENCY);
    },

    getUrl: function (callback) {
        var self = this;

        return promiseOrCallback(callback, function (resolve) {
            // if page is available (meaning that the page has been opened or .done() has not been called),
            // get the current url
            if (self.page) {
                self.page.get('url', function (url) {
                    self.__url = url;
                    resolve(url);
                });
            } else {
                // just get the cached url at the end of every method call
                resolve(self.__url);
            }
        });
    },

    /**
     * Private helper function to push a task
     * @param task
     * @param argument
     * @param callback
     * @returns {Promise}
     * @private
     */
    __pushTask: function (task, argument, callback) {
        var self = this;
        var queueArgument = {
            task: task,
            argument: argument
        };

        return promiseOrCallback(callback, function (resolve, reject) {
            self.taskQueue.push(queueArgument, function (error, result) {
                if (error) {
                    reject(error);
                } else {
                    resolve(result);
                }
            });
        });
    },

    // navigation

    openPage: function (url, callback) {
        return this.__pushTask(navigation.openPage, url, callback);
    },

    navigateToUrl: function (url, callback) {
        return this.__pushTask(navigation.navigateToUrl, url, callback);
    },

    // checks

    waitForElement: function (selector, callback) {
        return this.__pushTask(checks.elementExists, selector, callback);
    },

    waitForDomString: function (stringQuery, callback) {
        return this.__pushTask(checks.waitForString, stringQuery, callback);
    },

    // get data

    getInnerHTML: function (selector, callback) {
        return this.__pushTask(getData.getInnerHtmlFromElement, selector, callback);
    },

    takeSnapshot: function(callback) {
        return this.__pushTask(getData.takeSnapshot, null, callback);
    },

    getSelectorValue: function (selector, callback) {
        return this.__pushTask(getData.getSelectorValue, selector, callback);
    },

    // actions

    changeDropdownIndex: function (selector, value, callback) {
        return this.__pushTask(actions.changeDropdownIndex, [selector, value], callback);
    },

    /**
     * Clicks an element
     * @param {string} selector
     * @param {int} options 0 – callback immediately
     *                      1 – expect ajax, callback when the dom changes
     *                      2 – expect page navigation, callback when the url changes and document is ready
     * @param callback
     */
    clickElement: function (selector, options, callback) {
        return this.__pushTask(actions.clickElement, [selector, options], callback);
    },

    /**
     * Sets the state of a checkbox
     * @param {string} selector
     * @param {boolean} state
     * @param callback
     * @returns {Promise}
     */
    setCheckboxState: function (selector, state, callback) {
        return this.__pushTask(actions.setCheckboxState, [selector, state], callback);
    },

    fillForm: function (selector, value, callback) {
        return this.__pushTask(actions.fillForm, [selector, value], callback);
    },

    submitForm: function (callback) {
        return this.__pushTask(actions.submitForm, null, callback);
    },

    /**
     * Kills the phantom process. Does nothing if it has not been started initially
     */
    done: function () {
        // Guard to prevent duplicate call of .exit();
        if (this.phantom) {
            this.phantom.exit();
            this.page = null;
            this.phantom = null;
        }
    }
};

/**
 * Build a promise, with an optional node-style callback attached
 */
function promiseOrCallback (callback, executor) {
    var promise = new Promise(executor);

    // no callback: do not attach
    if (typeof callback !== 'function') {
        return promise;
    }

    return promise.then(function (value) {
        setImmediate(function () {
            callback(null, value);
        });
        return value;
    }, function (error) {
        setImmediate(function () {
            callback(error);
        });
        throw error;
    });
}

module.exports = Revenant;
<|endoftext|>


<commit_before>/**
 * S3renity
 * @author Wells Johnston <wells@littlstar.com>
 */

'use strict'

const Batch = require('batch');
const aws = require('aws-sdk');
const awsM = require('mock-aws-s3');
const BatchRequest = require('./BatchRequest');

/**
 * S3renity allows you to run {@link BatchRequest}, as well as interact with s3
 * objects directly through a promise-based api.
 */

class S3renity {

  /**
   * @param {Object} config - Options to initialize s3renity with. If access_key_id
   * and secret_access_key are left out, the aws sdk will attempt
   * to use the computer's default credentials.
   * @param {String} [config.access_key_id=null] AWS Access Key
   * @param {String} [config.secret_access_key=null] AWS Secret Key
   * @param {Integer} [config.max_retries=30] Max retries allowed for aws api requets
   * @param {Integer} [config.timeout=120] Timeout allowed for aws api requests
   * @param {Boolean} [config.verbose=false] Whether to use verbose mode when making requets
   */

  constructor(config) {

    config = config || {};
    this.verbose = config.verbose || false;
    this.show_progress = config.show_progress || false;

    if (config.local_path == null) {

      // use the aws sdk. attempt to use aws credentials in config.  if they
      // are not present, the aws sdk could pick them up in ~/.aws/credentials
      if (config.access_key_id && config.secret_access_key) {
        aws.config.update({
          accessKeyId: config.access_key_id,
          secretAccessKey: config.secret_access_key
        });
      }

      let s3opts = {
        maxRetries: config.max_retries || 10,
        httpOptions: {
          timeout: config.timeout || 1000
        }
      };

      this.s3 = new aws.S3(s3opts);
    } else {

      /* use local files (using mock aws sdk) */
      awsM.config.basePath = config.local_path;
      this.s3 = new awsM.S3();
    }
  }

  /**
   * Creates a new {@link BatchRequest} context.
   *
   * @param {String} bucket - The bucket to use, or a valid s3 path
   * @param {String} prefix - The prefix (folder) to use. Leave null if you
   * gave an s3 path
   * @param {String} [marker] - The key to start from
   * @param {Integer} [limit] Limit the # of items processed in the batch request.
   * @returns {BatchRequest} A new batch request instance.
   */

  context(bucket, prefix, marker, limit) {

    limit = limit || Infinity;
    let deferred = Promise.defer();
    let sources = null;
    let batch = new Batch;

    if (typeof bucket == 'object') {
      sources = bucket;
    } else {
      sources = [{
        bucket: bucket,
        prefix: prefix,
        marker: marker
      }];
    }

    sources.forEach(source => {
      batch.push(done => {

        /* list each source in parallel */
        this.keys(source.bucket, source.prefix, source.marker).then(files => {

          /* format sources and ignore empty files (directories) */
          let sources = files.map(file => {
            return {
              bucket: source.bucket,
              prefix: source.prefix,
              file: file,
              key: `${source.prefix}${file}`
            };
          }).filter(source => source.file.length > 0);

          done(null, sources);
        }).catch(e => {
          done(e);
        });
      });
    });

    if (this.verbose) {
      batch.on('progress', status => {
        console.info('listing keys in context...');
      });
    }

    batch.end((err, sources) => {
      if (err) {
        deferred.reject(err);
      }

      /* flatten the array (of array) of sources and impose limit */
      sources = sources.reduce((prev, cur) => prev.concat(cur), []).slice(0, limit);
      deferred.resolve(sources);
    })

    /* create a new batch request context with a s3renity instance */
    return new BatchRequest(this, deferred.promise);
  }

  /**
   * Gets an object in s3.
   *
   * @param {String} bucket - The bucket to get from
   * @param {String} key - The key of the object to get
   * @param {String} [encoding=utf8] - The encoding
   * @param {Function} [transformer] - If supplied, this function will be
   * run on Object.Body before returning. Useful for dealing with compressed
   * files or weird formats
   * @returns {Promise} The s3 text object.
   */

  get(bucket, key, encoding, transformer) {

    encoding = encoding || 'utf8';
    let deferred = Promise.defer();

    /* default transform is to assume a utf8 encoded text file */
    if (transformer == null) {
      transformer = obj => {
        return obj.Body.toString(encoding);
      };
    }

    this.s3.getObject({
      Bucket: bucket,
      Key: key
    }, (err, object) => {
      if (err) {
        deferred.reject(err);
      } else {
        try {
          deferred.resolve(transformer(object));
          if (this.verbose) {
            console.info(`GET OBJECT s3://${bucket}/${key}`);
          }
        } catch (e) {
          deferred.reject(e);
        }
      }
    });

    return deferred.promise;
  }

  /**
   * Puts a text object in S3.
   *
   * @param {String} bucket - The s3 bucket to use
   * @param {String} key - The key path where the object will be placed
   * @param {String} body - The object body
   * @param {String} [encoding=utf8] - The encoding
   * @return {Promise} Promise that resolves when the object is written to s3
   */

  put(bucket, key, body, encoding) {

    encoding = encoding || 'utf8';
    let deferred = Promise.defer();

    this.s3.putObject({
      Bucket: bucket,
      Key: key,
      Body: body,
      ContentEncoding: encoding
    }, (err, res) => {
      if (err) {
        deferred.reject(err);
      } else {
        if (this.verbose) {
          console.info(`PUT OBJECT s3://${bucket}/${key}`);
        }
        deferred.resolve(res);
      }
    });

    return deferred.promise;
  }

  /**
   * Copies an object in S3.
   *
   * @public
   * @param {String} bucket The source bucket
   * @param {String} key The source key
   * @param {String} targetBucket The target bucket
   * @param {String} targetKey The target key
   * @return {Promise}
   */

  copy(bucket, key, targetBucket, targetKey) {

    let deferred = Promise.defer();

    this.s3.copyObject({
      Bucket: targetBucket,
      Key: targetKey,
      CopySource: `${bucket}/${key}`
    }, (err, res) => {
      if (err) {
        deferred.reject(err);
      } else {
        if (this.verbose) {
          console.info(`COPY OBJECT s3://${bucket}/${key} --> s3://${targetBucket}/${targetKey}`);
        }
        deferred.resolve();
      }
    });

    return deferred.promise;
  }

  /**
   * Moves an object in S3 (copy then delete).
   *
   * @public
   * @param {String} bucket The source bucket
   * @param {String} key The source key
   * @param {String} targetBucket The target bucket
   * @param {String} targetKey The target key
   * @return {Promise}
   */

  move(bucket, key, targetBucket, targetKey) {

    let deferred = Promise.defer();

    this.copy(bucket, key, targetBucket, targetKey).then(() => {
      this.delete(bucket, key).then(() => {
        deferred.resolve();
      }).catch(e => {
        deferred.reject(e);
      });
    }).catch(e => {
      deferred.reject(e);
    });

    return deferred.promise;
  }

  /**
   * Deletes an object or array of objects in S3.
   *
   * @public
   * @param {String} bucket - The bucket
   * @param {String|Array} key - The key to delete
   * @returns {Promise} The key (or array of keys) that was deleted.
   */

  delete(bucket, key) {

    let deferred = Promise.defer();

    this.s3.deleteObject({
      Bucket: bucket,
      Key: key
    }, (err, res) => {
      if (err) {
        deferred.reject(err);
      } else {
        deferred.resolve();
        if (this.verbose) {
          console.info(`DELETE OBJECT s3://${bucket}/${key}`);
        }
      }
    });

    return deferred.promise;
  }

  /**
   * Deletes a list of objects in S3.
   *
   * @private
   * @param {String} bucket - The s3 bucket to use
   * @param {Array} keys - The keys of the objects to delete
   * @returns {Promise}
   */

  deleteObjects(bucket, keys) {

    let deferred = Promise.defer();

    /* creates input with format: { Key: key } required by s3 */
    let input = keys.map(key => {
      return {
        Key: key
      };
    });

    this.s3.deleteObjects({
      Bucket: bucket,
      Delete: {
        Objects: input
      }
    }, (err, res) => {
      if (err) {
        deferred.reject(err);
      } else {
        deferred.resolve(res);
        if (this.verbose) {
          keys.forEach(key => {
            console.info(`DELETE OBJECT s3://${bucket}/${key}`);
          });
        }
      }
    });

    return deferred.promise;
  }

  /**
   * Lists all the s3 objects in the given folder.
   *
   * @param {String} bucket - The bucket
   * @param {String} prefix - The prefix for the folder to list keys for
   * @param {String} [marker] - The key to start listing from, alphabetically
   * @returns {Promise} An array containing all the keys in s3://bucket/prefix.
   */

  list(bucket, prefix, marker) {

    let deferred = Promise.defer();
    let self = this;
    let allKeys = [];
    marker = marker || '';

    recurse(bucket, prefix, marker, err => {
      if (err) {
        deferred.reject(err);
      } else {
        allKeys = allKeys.filter(key => key.Key != prefix);
        deferred.resolve(allKeys)
      }
    });

    function recurse(bucket, prefix, marker, done) {
      self.s3.listObjects({
        Bucket: bucket,
        Prefix: prefix,
        Marker: marker
      }, (err, keys) => {
        if (err) {
          done(err);
        } else {

          if (self.verbose) {
            console.info(`LIST OBJECTS s3://${bucket}/${marker == '' ? prefix : marker}`);
          }

          keys = keys.Contents;
          marker = keys[keys.length - 1].Key.replace(prefix, '');
          allKeys = allKeys.concat(keys);

          if (keys.length < 1000) {
            done();
          } else {
            recurse(bucket, prefix, marker, done);
          }
        }
      });
    };

    return deferred.promise;
  }

  /**
   * Lists all the keys in the given s3 folder.
   *
   * @param {String} bucket - The bucket
   * @param {String} prefix - The prefix for the folder to list keys for
   * @param {String} [marker] - The key to start listing from, alphabetically
   * @returns {Promise} An array containing all the keys in s3://bucket/prefix.
   */

  keys(bucket, prefix, marker) {

    let self = this;
    let allKeys = [];
    let deferred = Promise.defer();
    marker = marker || '';

    recurse(bucket, prefix, marker, err => {
      if (err) {
        deferred.reject(err);
      } else {
        allKeys = allKeys.filter(key => key.length > 0);
        deferred.resolve(allKeys)
      }
    });

    function recurse(bucket, prefix, marker, done) {
      self.s3.listObjects({
        Bucket: bucket,
        Prefix: prefix,
        Marker: marker
      }, (err, keys) => {
        if (err) {
          done(err);
        } else {

          if (self.verbose) {
            console.info(`LIST OBJECTS s3://${bucket}/${marker == '' ? prefix : marker}`);
          }

          marker = keys.Contents[keys.Contents.length - 1].Key;

          keys = keys.Contents.map(key => {
            return key.Key.replace(prefix, '');
          });

          allKeys = allKeys.concat(keys);

          if (keys.length < 1000) {
            done();
          } else {
            recurse(bucket, prefix, marker, done);
          }
        }
      });
    };

    return deferred.promise;
  }
}

module.exports = S3renity;
<commit_msg>fix list method marker<commit_after>/**
 * S3renity
 * @author Wells Johnston <wells@littlstar.com>
 */

'use strict'

const Batch = require('batch');
const aws = require('aws-sdk');
const awsM = require('mock-aws-s3');
const BatchRequest = require('./BatchRequest');

/**
 * S3renity allows you to run {@link BatchRequest}, as well as interact with s3
 * objects directly through a promise-based api.
 */

class S3renity {

  /**
   * @param {Object} config - Options to initialize s3renity with. If access_key_id
   * and secret_access_key are left out, the aws sdk will attempt
   * to use the computer's default credentials.
   * @param {String} [config.access_key_id=null] AWS Access Key
   * @param {String} [config.secret_access_key=null] AWS Secret Key
   * @param {Integer} [config.max_retries=30] Max retries allowed for aws api requets
   * @param {Integer} [config.timeout=120] Timeout allowed for aws api requests
   * @param {Boolean} [config.verbose=false] Whether to use verbose mode when making requets
   */

  constructor(config) {

    config = config || {};
    this.verbose = config.verbose || false;
    this.show_progress = config.show_progress || false;

    if (config.local_path == null) {

      // use the aws sdk. attempt to use aws credentials in config.  if they
      // are not present, the aws sdk could pick them up in ~/.aws/credentials
      if (config.access_key_id && config.secret_access_key) {
        aws.config.update({
          accessKeyId: config.access_key_id,
          secretAccessKey: config.secret_access_key
        });
      }

      let s3opts = {
        maxRetries: config.max_retries || 10,
        httpOptions: {
          timeout: config.timeout || 1000
        }
      };

      this.s3 = new aws.S3(s3opts);
    } else {

      /* use local files (using mock aws sdk) */
      awsM.config.basePath = config.local_path;
      this.s3 = new awsM.S3();
    }
  }

  /**
   * Creates a new {@link BatchRequest} context.
   *
   * @param {String} bucket - The bucket to use, or a valid s3 path
   * @param {String} prefix - The prefix (folder) to use. Leave null if you
   * gave an s3 path
   * @param {String} [marker] - The key to start from
   * @param {Integer} [limit] Limit the # of items processed in the batch request.
   * @returns {BatchRequest} A new batch request instance.
   */

  context(bucket, prefix, marker, limit) {

    limit = limit || Infinity;
    let deferred = Promise.defer();
    let sources = null;
    let batch = new Batch;

    if (typeof bucket == 'object') {
      sources = bucket;
    } else {
      sources = [{
        bucket: bucket,
        prefix: prefix,
        marker: marker
      }];
    }

    sources.forEach(source => {
      batch.push(done => {

        /* list each source in parallel */
        this.keys(source.bucket, source.prefix, source.marker).then(files => {

          /* format sources and ignore empty files (directories) */
          let sources = files.map(file => {
            return {
              bucket: source.bucket,
              prefix: source.prefix,
              file: file,
              key: `${source.prefix}${file}`
            };
          }).filter(source => source.file.length > 0);

          done(null, sources);
        }).catch(e => {
          done(e);
        });
      });
    });

    if (this.verbose) {
      batch.on('progress', status => {
        console.info('listing keys in context...');
      });
    }

    batch.end((err, sources) => {
      if (err) {
        deferred.reject(err);
      }

      /* flatten the array (of array) of sources and impose limit */
      sources = sources.reduce((prev, cur) => prev.concat(cur), []).slice(0, limit);
      deferred.resolve(sources);
    })

    /* create a new batch request context with a s3renity instance */
    return new BatchRequest(this, deferred.promise);
  }

  /**
   * Gets an object in s3.
   *
   * @param {String} bucket - The bucket to get from
   * @param {String} key - The key of the object to get
   * @param {String} [encoding=utf8] - The encoding
   * @param {Function} [transformer] - If supplied, this function will be
   * run on Object.Body before returning. Useful for dealing with compressed
   * files or weird formats
   * @returns {Promise} The s3 text object.
   */

  get(bucket, key, encoding, transformer) {

    encoding = encoding || 'utf8';
    let deferred = Promise.defer();

    /* default transform is to assume a utf8 encoded text file */
    if (transformer == null) {
      transformer = obj => {
        return obj.Body.toString(encoding);
      };
    }

    this.s3.getObject({
      Bucket: bucket,
      Key: key
    }, (err, object) => {
      if (err) {
        deferred.reject(err);
      } else {
        try {
          deferred.resolve(transformer(object));
          if (this.verbose) {
            console.info(`GET OBJECT s3://${bucket}/${key}`);
          }
        } catch (e) {
          deferred.reject(e);
        }
      }
    });

    return deferred.promise;
  }

  /**
   * Puts a text object in S3.
   *
   * @param {String} bucket - The s3 bucket to use
   * @param {String} key - The key path where the object will be placed
   * @param {String} body - The object body
   * @param {String} [encoding=utf8] - The encoding
   * @return {Promise} Promise that resolves when the object is written to s3
   */

  put(bucket, key, body, encoding) {

    encoding = encoding || 'utf8';
    let deferred = Promise.defer();

    this.s3.putObject({
      Bucket: bucket,
      Key: key,
      Body: body,
      ContentEncoding: encoding
    }, (err, res) => {
      if (err) {
        deferred.reject(err);
      } else {
        if (this.verbose) {
          console.info(`PUT OBJECT s3://${bucket}/${key}`);
        }
        deferred.resolve(res);
      }
    });

    return deferred.promise;
  }

  /**
   * Copies an object in S3.
   *
   * @public
   * @param {String} bucket The source bucket
   * @param {String} key The source key
   * @param {String} targetBucket The target bucket
   * @param {String} targetKey The target key
   * @return {Promise}
   */

  copy(bucket, key, targetBucket, targetKey) {

    let deferred = Promise.defer();

    this.s3.copyObject({
      Bucket: targetBucket,
      Key: targetKey,
      CopySource: `${bucket}/${key}`
    }, (err, res) => {
      if (err) {
        deferred.reject(err);
      } else {
        if (this.verbose) {
          console.info(`COPY OBJECT s3://${bucket}/${key} --> s3://${targetBucket}/${targetKey}`);
        }
        deferred.resolve();
      }
    });

    return deferred.promise;
  }

  /**
   * Moves an object in S3 (copy then delete).
   *
   * @public
   * @param {String} bucket The source bucket
   * @param {String} key The source key
   * @param {String} targetBucket The target bucket
   * @param {String} targetKey The target key
   * @return {Promise}
   */

  move(bucket, key, targetBucket, targetKey) {

    let deferred = Promise.defer();

    this.copy(bucket, key, targetBucket, targetKey).then(() => {
      this.delete(bucket, key).then(() => {
        deferred.resolve();
      }).catch(e => {
        deferred.reject(e);
      });
    }).catch(e => {
      deferred.reject(e);
    });

    return deferred.promise;
  }

  /**
   * Deletes an object or array of objects in S3.
   *
   * @public
   * @param {String} bucket - The bucket
   * @param {String|Array} key - The key to delete
   * @returns {Promise} The key (or array of keys) that was deleted.
   */

  delete(bucket, key) {

    let deferred = Promise.defer();

    this.s3.deleteObject({
      Bucket: bucket,
      Key: key
    }, (err, res) => {
      if (err) {
        deferred.reject(err);
      } else {
        deferred.resolve();
        if (this.verbose) {
          console.info(`DELETE OBJECT s3://${bucket}/${key}`);
        }
      }
    });

    return deferred.promise;
  }

  /**
   * Deletes a list of objects in S3.
   *
   * @private
   * @param {String} bucket - The s3 bucket to use
   * @param {Array} keys - The keys of the objects to delete
   * @returns {Promise}
   */

  deleteObjects(bucket, keys) {

    let deferred = Promise.defer();

    /* creates input with format: { Key: key } required by s3 */
    let input = keys.map(key => {
      return {
        Key: key
      };
    });

    this.s3.deleteObjects({
      Bucket: bucket,
      Delete: {
        Objects: input
      }
    }, (err, res) => {
      if (err) {
        deferred.reject(err);
      } else {
        deferred.resolve(res);
        if (this.verbose) {
          keys.forEach(key => {
            console.info(`DELETE OBJECT s3://${bucket}/${key}`);
          });
        }
      }
    });

    return deferred.promise;
  }

  /**
   * Lists all the s3 objects in the given folder.
   *
   * @param {String} bucket - The bucket
   * @param {String} prefix - The prefix for the folder to list keys for
   * @param {String} [marker] - The key to start listing from, alphabetically
   * @returns {Promise} An array containing all the keys in s3://bucket/prefix.
   */

  list(bucket, prefix, marker) {

    let deferred = Promise.defer();
    let self = this;
    let allKeys = [];
    marker = marker || '';

    recurse(bucket, prefix, marker, err => {
      if (err) {
        deferred.reject(err);
      } else {
        allKeys = allKeys.filter(key => key.Key != prefix);
        deferred.resolve(allKeys)
      }
    });

    function recurse(bucket, prefix, marker, done) {
      self.s3.listObjects({
        Bucket: bucket,
        Prefix: prefix,
        Marker: marker
      }, (err, keys) => {
        if (err) {
          done(err);
        } else {

          if (self.verbose) {
            console.info(`LIST OBJECTS s3://${bucket}/${marker == '' ? prefix : marker}`);
          }

          marker = keys.Contents[keys.length - 1].Key;
          allKeys = allKeys.concat(keys.Contents);

          if (keys.length < 1000) {
            done();
          } else {
            recurse(bucket, prefix, marker, done);
          }
        }
      });
    };

    return deferred.promise;
  }

  /**
   * Lists all the keys in the given s3 folder.
   *
   * @param {String} bucket - The bucket
   * @param {String} prefix - The prefix for the folder to list keys for
   * @param {String} [marker] - The key to start listing from, alphabetically
   * @returns {Promise} An array containing all the keys in s3://bucket/prefix.
   */

  keys(bucket, prefix, marker) {

    let self = this;
    let allKeys = [];
    let deferred = Promise.defer();
    marker = marker || '';

    recurse(bucket, prefix, marker, err => {
      if (err) {
        deferred.reject(err);
      } else {
        allKeys = allKeys.filter(key => key.length > 0);
        deferred.resolve(allKeys)
      }
    });

    function recurse(bucket, prefix, marker, done) {
      self.s3.listObjects({
        Bucket: bucket,
        Prefix: prefix,
        Marker: marker
      }, (err, keys) => {
        if (err) {
          done(err);
        } else {

          if (self.verbose) {
            console.info(`LIST OBJECTS s3://${bucket}/${marker == '' ? prefix : marker}`);
          }

          marker = keys.Contents[keys.Contents.length - 1].Key;

          keys = keys.Contents.map(key => {
            return key.Key.replace(prefix, '');
          });

          allKeys = allKeys.concat(keys);

          if (keys.length < 1000) {
            done();
          } else {
            recurse(bucket, prefix, marker, done);
          }
        }
      });
    };

    return deferred.promise;
  }
}

module.exports = S3renity;
<|endoftext|>