<commit_before>var Util;

(function(scope, Util) {
    'use strict';

    var rendy;

    if (typeof module === 'object' && module.exports) {
        rendy           = require('rendy');
        module.exports  = new CloudFuncProto(Util);
    } else {
        rendy           = window.rendy;
        scope.CloudFunc = new CloudFuncProto(Util);
    }

    function CloudFuncProto() {
        var CloudFunc               = this,
            Entity                  = new entityProto(),
            FS;

        /* КОНСТАНТЫ (общие для клиента и сервера)*/

        /* название программы */
        this.NAME                   = 'Cloud Commander';

        /* если в ссылке будет эта строка - в браузере js отключен */
        this.FS    =   FS           = '/fs';

        this.apiURL                 = '/api/v1';
        this.MAX_FILE_SIZE          = 500 * 1024;
        this.Entity                 = Entity;

        function entityProto() {
            var Entities = {
                ' ': ' ',
                '<'  : '<',
                '>'   : '>'
            };

            this.encode = function(str) {
                Object.keys(Entities).forEach(function(code) {
                    var char    = Entities[code],
                        reg     = RegExp(char, 'g');

                    str = str.replace(reg, code);
                });

                return str;
            };

            this.decode = function(str) {
                Object.keys(Entities).forEach(function(code) {
                    var char    = Entities[code],
                        reg     = RegExp(code, 'g');

                    str = str.replace(reg, char);
                });

                return str;
            };
        }

        this.formatMsg              = function(msg, name, status) {
            if (!status)
                status = 'ok';

            if (name)
                name = '("' + name + '")';
            else
                name = '';

            msg = msg + ': ' + status + name;

            return msg;
        };

        /** Функция возвращает заголовок веб страницы
         * @pPath
         */
        this.getTitle               = function(pPath) {
            if (!CloudFunc.Path)
                CloudFunc.Path = '/';

            return  CloudFunc.NAME + ' - ' + (pPath || CloudFunc.Path);

        };

        /** Функция получает адреса каждого каталога в пути
         * возвращаеться массив каталогов
         * @param url -  адрес каталога
         */
        function getPathLink(url, template) {
            var namesRaw, names, length,
                pathHTML    = '',
                path        = '/';

            if (!url)
                throw(Error('url could not be empty!'));

            if (!template)
                throw(Error('template could not be empty!'));

            namesRaw    = url.split('/')
                             .slice(1, -1),

            names       = [].concat('/', namesRaw),

            length      = names.length - 1;

            names.forEach(function(name, index) {
                var slash       = '',
                    isLast      = index === length;

                if (index)
                    path        += name + '/';

                if (index && isLast) {
                    pathHTML    += name + '/';
                } else {
                    if (index)
                        slash = '/';

                    pathHTML    += rendy(template, {
                        path: path,
                        name: name,
                        slash: slash
                    });
                }
            });

            return pathHTML;
        }

        /**
         * Функция строит таблицу файлв из JSON-информации о файлах
         * @param params - информация о файлах
         *
         */
        this.buildFromJSON          = function(params) {
            var file, i, n, type, attribute, size, owner, mode,
                dotDot, link, dataName,
                linkResult,
                template        = params.template,
                templateFile    = template.file,
                templateLink    = template.link,
                json            = params.data,
                files           = json.files,
                path            = json.path,

                /*
                 * Строим путь каталога в котором мы находимся
                 * со всеми подкаталогами
                 */
                htmlPath        = getPathLink(path, template.pathLink),

                fileTable       = rendy(template.path, {
                    link        : FS + path,
                    fullPath    : path,
                    path        : htmlPath
                }),

                header         = rendy(templateFile, {
                    tag         : 'div',
                    attribute   : '',
                    className   : 'fm-header',
                    type        : '',
                    name        : 'name',
                    size        : 'size',
                    owner       : 'owner',
                    mode        : 'mode'
                });

            fileTable          += header;

            /* сохраняем путь */
            CloudFunc.Path      = path;

            fileTable           += '
';
            /* Если мы не в корне */
            if (path !== '/') {
                /* убираем последний слеш и каталог в котором мы сейчас находимся*/
                dotDot          = path.substr(path, path.lastIndexOf('/'));
                dotDot          = dotDot.substr(dotDot, dotDot.lastIndexOf('/'));
                /* Если предыдущий каталог корневой */
                if (dotDot === '')
                    dotDot = '/';

                link            = FS + dotDot;

                linkResult      = rendy(template.link, {
                    link        : link,
                    title       : '..',
                    name        : '..'
                });

                dataName        = 'data-name="js-file-.." ',
                attribute       = 'draggable="true" ' + dataName,
                /* Сохраняем путь к каталогу верхнего уровня*/
                fileTable += rendy(template.file, {
                    tag         : 'li',
                    attribute   : attribute,
                    className   : '',
                    type        : 'directory',
                    name        : linkResult,
                    size        : '<dir>',
                    owner       : '.',
                    mode        : '--- --- ---'
                });
            }

            n = files.length;
            for (i = 0; i < n; i++) {
                file            = files[i];
                link            = FS + path + file.name;

                if (file.size === 'dir') {
                    type        = 'directory';
                    attribute   = '';
                    size        = '<dir>';
                } else {
                    type        = 'text-file';
                    attribute   = 'target="_blank" ';
                    size        = file.size;
                }

                owner   = file.owner || 'root';
                mode    = file.mode;

                linkResult  = rendy(templateLink, {
                    link        : link,
                    title       : file.name,
                    name        : Entity.encode(file.name),
                    attribute   : attribute
                });

                dataName        = 'data-name="js-file-' + file.name + '" ';
                attribute       = 'draggable="true" ' + dataName;

                fileTable += rendy(templateFile, {
                    tag         : 'li',
                    attribute   : attribute,
                    className   : '',
                    /* Если папка - выводим пиктограмму папки   *
                     * В противоположном случае - файла         */
                    type        : type,
                    name        : linkResult,
                    size        : size,
                    owner       : owner,
                    mode        : mode
                });
            }

            fileTable          += '
';

            return fileTable;
        };
     }
})(this, Util);
<commit_msg>refactor(cloudfunc) rm Util<commit_after>(function(global) {
    'use strict';

    var rendy;

    if (typeof module === 'object' && module.exports) {
        rendy               = require('rendy');
        module.exports      = new CloudFuncProto();
    } else {
        rendy               = window.rendy;
        global.CloudFunc    = new CloudFuncProto();
    }

    function CloudFuncProto() {
        var CloudFunc               = this,
            Entity                  = new entityProto(),
            FS;

        /* КОНСТАНТЫ (общие для клиента и сервера)*/

        /* название программы */
        this.NAME                   = 'Cloud Commander';

        /* если в ссылке будет эта строка - в браузере js отключен */
        this.FS    =   FS           = '/fs';

        this.apiURL                 = '/api/v1';
        this.MAX_FILE_SIZE          = 500 * 1024;
        this.Entity                 = Entity;

        function entityProto() {
            var Entities = {
                ' ': ' ',
                '<'  : '<',
                '>'   : '>'
            };

            this.encode = function(str) {
                Object.keys(Entities).forEach(function(code) {
                    var char    = Entities[code],
                        reg     = RegExp(char, 'g');

                    str = str.replace(reg, code);
                });

                return str;
            };

            this.decode = function(str) {
                Object.keys(Entities).forEach(function(code) {
                    var char    = Entities[code],
                        reg     = RegExp(code, 'g');

                    str = str.replace(reg, char);
                });

                return str;
            };
        }

        this.formatMsg              = function(msg, name, status) {
            if (!status)
                status = 'ok';

            if (name)
                name = '("' + name + '")';
            else
                name = '';

            msg = msg + ': ' + status + name;

            return msg;
        };

        /** Функция возвращает заголовок веб страницы
         * @pPath
         */
        this.getTitle               = function(pPath) {
            if (!CloudFunc.Path)
                CloudFunc.Path = '/';

            return  CloudFunc.NAME + ' - ' + (pPath || CloudFunc.Path);

        };

        /** Функция получает адреса каждого каталога в пути
         * возвращаеться массив каталогов
         * @param url -  адрес каталога
         */
        function getPathLink(url, template) {
            var namesRaw, names, length,
                pathHTML    = '',
                path        = '/';

            if (!url)
                throw(Error('url could not be empty!'));

            if (!template)
                throw(Error('template could not be empty!'));

            namesRaw    = url.split('/')
                             .slice(1, -1),

            names       = [].concat('/', namesRaw),

            length      = names.length - 1;

            names.forEach(function(name, index) {
                var slash       = '',
                    isLast      = index === length;

                if (index)
                    path        += name + '/';

                if (index && isLast) {
                    pathHTML    += name + '/';
                } else {
                    if (index)
                        slash = '/';

                    pathHTML    += rendy(template, {
                        path: path,
                        name: name,
                        slash: slash
                    });
                }
            });

            return pathHTML;
        }

        /**
         * Функция строит таблицу файлв из JSON-информации о файлах
         * @param params - информация о файлах
         *
         */
        this.buildFromJSON          = function(params) {
            var file, i, n, type, attribute, size, owner, mode,
                dotDot, link, dataName,
                linkResult,
                template        = params.template,
                templateFile    = template.file,
                templateLink    = template.link,
                json            = params.data,
                files           = json.files,
                path            = json.path,

                /*
                 * Строим путь каталога в котором мы находимся
                 * со всеми подкаталогами
                 */
                htmlPath        = getPathLink(path, template.pathLink),

                fileTable       = rendy(template.path, {
                    link        : FS + path,
                    fullPath    : path,
                    path        : htmlPath
                }),

                header         = rendy(templateFile, {
                    tag         : 'div',
                    attribute   : '',
                    className   : 'fm-header',
                    type        : '',
                    name        : 'name',
                    size        : 'size',
                    owner       : 'owner',
                    mode        : 'mode'
                });

            fileTable          += header;

            /* сохраняем путь */
            CloudFunc.Path      = path;

            fileTable           += '
';
            /* Если мы не в корне */
            if (path !== '/') {
                /* убираем последний слеш и каталог в котором мы сейчас находимся*/
                dotDot          = path.substr(path, path.lastIndexOf('/'));
                dotDot          = dotDot.substr(dotDot, dotDot.lastIndexOf('/'));
                /* Если предыдущий каталог корневой */
                if (dotDot === '')
                    dotDot = '/';

                link            = FS + dotDot;

                linkResult      = rendy(template.link, {
                    link        : link,
                    title       : '..',
                    name        : '..'
                });

                dataName        = 'data-name="js-file-.." ',
                attribute       = 'draggable="true" ' + dataName,
                /* Сохраняем путь к каталогу верхнего уровня*/
                fileTable += rendy(template.file, {
                    tag         : 'li',
                    attribute   : attribute,
                    className   : '',
                    type        : 'directory',
                    name        : linkResult,
                    size        : '<dir>',
                    owner       : '.',
                    mode        : '--- --- ---'
                });
            }

            n = files.length;
            for (i = 0; i < n; i++) {
                file            = files[i];
                link            = FS + path + file.name;

                if (file.size === 'dir') {
                    type        = 'directory';
                    attribute   = '';
                    size        = '<dir>';
                } else {
                    type        = 'text-file';
                    attribute   = 'target="_blank" ';
                    size        = file.size;
                }

                owner   = file.owner || 'root';
                mode    = file.mode;

                linkResult  = rendy(templateLink, {
                    link        : link,
                    title       : file.name,
                    name        : Entity.encode(file.name),
                    attribute   : attribute
                });

                dataName        = 'data-name="js-file-' + file.name + '" ';
                attribute       = 'draggable="true" ' + dataName;

                fileTable += rendy(templateFile, {
                    tag         : 'li',
                    attribute   : attribute,
                    className   : '',
                    /* Если папка - выводим пиктограмму папки   *
                     * В противоположном случае - файла         */
                    type        : type,
                    name        : linkResult,
                    size        : size,
                    owner       : owner,
                    mode        : mode
                });
            }

            fileTable          += '
';

            return fileTable;
        };
     }
})(this);
<|endoftext|>


<commit_before>// Backbone React Component
// ========================
//
//     Backbone.React.Component v0.8.0-beta.1
//
//     (c) 2014 "Magalhas" José Magalhães <magalhas@gmail.com>
//     Backbone.React.Component can be freely distributed under the MIT license.
//
//
// `Backbone.React.Component` is a mixin that glues [Backbone](http://backbonejs.org/)
// models and collections into [React](http://facebook.github.io/react/) components.
//
// When the component is mounted, a wrapper starts listening to models and
// collections changes to automatically set your component state and achieve UI
// binding through reactive updates.
//
//
//
// Basic Usage
//
//     var MyComponent = React.createClass({
//       mixins: [Backbone.React.Component.mixin],
//       render: function () {
//         return
{this.state.foo}
;
//       }
//     });
//     var model = new Backbone.Model({foo: 'bar'});
//     React.render(<MyComponent model={model} />, document.body);

(function (root, factory) {
  // Universal module definition
  if (typeof define === 'function' && define.amd) {
    define(['react', 'backbone', 'underscore'], factory);
  } else if (typeof module !== 'undefined' && module.exports) {
    module.exports = factory(require('react'), require('backbone'), require('underscore'));
  } else {
    factory(root.React, root.Backbone, root._);
  }
}(this, function (React, Backbone, _) {
  'use strict';
  if (!Backbone.React) {
    Backbone.React = {};
  }
  if (!Backbone.React.Component) {
    Backbone.React.Component = {};
  }
  // Mixin used in all component instances. Exported through `Backbone.React.Component.mixin`.
  Backbone.React.Component.mixin = {
    // Types of the context passed to child components. Only
    // `hasParentBackboneMixin` is required all of the others are optional.
    childContextTypes: {
      hasParentBackboneMixin: React.PropTypes.bool.isRequired,
      parentModel: React.PropTypes.any,
      parentCollection: React.PropTypes.any
    },
    // Types of the context received from the parent component. All of them are
    // optional.
    contextTypes: {
      hasParentBackboneMixin: React.PropTypes.bool,
      parentModel: React.PropTypes.any,
      parentCollection: React.PropTypes.any
    },
    // Passes data to our child components.
    getChildContext: function () {
      return {
        hasParentBackboneMixin: true,
        parentModel: this.getModel(),
        parentCollection: this.getCollection()
      };
    },
    // Sets `this.el` and `this.$el` when the component mounts.
    componentDidMount: function () {
      this.setElement(this.getDOMNode());
    },
    // Sets `this.el` and `this.$el` when the component updates.
    componentDidUpdate: function () {
      this.setElement(this.getDOMNode());
    },
    // When the component gets the initial state, instance a `Wrapper` to take
    // care of models and collections binding with `this.state`.
    getInitialState: function () {
      var initialState = {};

      if (!this.wrapper) {
        this.wrapper = new Wrapper(this, initialState);
      }

      return initialState;
    },
    // When the component mounts, instance a `Wrapper` to take care
    // of models and collections binding with `this.state`.
    componentWillMount: function () {
      if (!this.wrapper) {
        this.wrapper = new Wrapper(this);
      }
    },
    // When the component unmounts, dispose listeners and delete
    // `this.wrapper` reference.
    componentWillUnmount: function () {
      if (this.wrapper) {
        this.wrapper.stopListening();
        delete this.wrapper;
      }
    },
    // In order to allow passing nested models and collections as reference we
    // filter `nextProps.model` and `nextProps.collection`.
    componentWillReceiveProps: function (nextProps) {
      var model = nextProps.model;
      var collection = nextProps.collection;

      if (this.wrapper.model && model) {
        if (this.wrapper.model !== model) {
          this.wrapper.stopListening();
          this.wrapper = new Wrapper(this, void 0, nextProps);
        }
      } else if (model) {
        this.wrapper = new Wrapper(this, void 0, nextProps);
      }

      if (this.wrapper.collection && collection) {
        if (this.wrapper.collection !== collection) {
          this.wrapper.stopListening();
          this.wrapper = new Wrapper(this, void 0, nextProps);
        }
      } else if (collection) {
        this.wrapper = new Wrapper(this, void 0, nextProps);
      }
    },
    // Shortcut to `@$el.find` if jQuery ins present, else if fallbacks to DOM
    // native `querySelector`. Inspired by `Backbone.View`.
    $: function () {
      var els;

      if (this.$el) {
        els = this.$el.find.apply(this.$el, arguments);
      } else {
        var el = this.getDOMNode();
        els = el.querySelector.apply(el, arguments);
      }

      return els;
    },
    // Grabs the collection from `@wrapper.collection` or `@context.parentCollection`
    getCollection: function () {
      return this.wrapper.collection || this.context.parentCollection;
    },
    // Grabs the model from `@wrapper.model` or `@context.parentModel`
    getModel: function () {
      return this.wrapper.model || this.context.parentModel;
    },
    // Sets a DOM element to render/mount this component on this.el and this.$el.
    setElement: function (el) {
      if (el && Backbone.$ && el instanceof Backbone.$) {
        if (el.length > 1) {
          throw new Error('You can only assign one element to a component');
        }
        this.el = el[0];
        this.$el = el;
      } else if (el) {
        this.el = el;
        if (Backbone.$) {
          this.$el = Backbone.$(el);
        }
      }
      return this;
    }
  };
  // Binds models and collections to a `React.Component`. It mixes `Backbone.Events`.
  function Wrapper (component, initialState, nextProps) {
    // 1:1 relation with the `component`
    this.component = component;
    // Use `nextProps` or `component.props` and grab `model` and `collection`
    // from there
    var props = nextProps || component.props || {};
    if (component.overrideModel && typeof component.overrideModel === 'function'){
      // Define overrideModel() method on your `React class` to programatically supply a model object
      // Will override `this.props.model`
      props.model = component.overrideModel();
    }
    if (component.overrideCollection && typeof component.overrideCollection === 'function'){
      // Define overrideCollection() method on your `React class` to programatically supply a collection object
      // Will override `this.props.collection`
      props.collection = component.overrideCollection();
    }
    var model = props.model, collection = props.collection;
    // Check if `props.model` is a `Backbone.Model` or an hashmap of them
    if (typeof model !== 'undefined' && (model.attributes ||
        typeof model === 'object' && _.values(model)[0].attributes)) {
      // The model(s) bound to this component
      this.model = model;
      // Set model(s) attributes on `initialState` for the first render
      this.setStateBackbone(model, void 0, initialState);
    }
    // Check if `props.collection` is a `Backbone.Collection` or an hashmap of them
    if (typeof collection !== 'undefined' && (collection.models ||
        typeof collection === 'object' && _.values(collection)[0].models)) {
      // The collection(s) bound to this component
      this.collection = collection;
      // Set collection(s) models on `initialState` for the first render
      this.setStateBackbone(collection, void 0, initialState);
    }

    // Start listeners
    this.startModelListeners();
    this.startCollectionListeners();
  }
  // Mixing `Backbone.Events` into `Wrapper.prototype`
  _.extend(Wrapper.prototype, Backbone.Events, {
    // Sets `this.state` when a model/collection request results in error. It delegates
    // to `this.setState`. It listens to `Backbone.Model#error` and `Backbone.Collection#error`.
    onError: function (modelOrCollection, res, options) {
      // Set state only if there's no silent option
      if (!options.silent) {
        this.component.setState({
          isRequesting: false,
          hasError: true,
          error: res
        });
      }
    },
    onInvalid: function (model, res, options) {
      if (!options.silent) {
        this.component.setState({
          isInvalid: true
        });
      }
    },
    // Sets `this.state` when a model/collection request starts. It delegates to
    // `this.setState`. It listens to `Backbone.Model#request` and
    // `Backbone.Collection#request`.
    onRequest: function (modelOrCollection, xhr, options) {
      // Set `state` only if there's no silent option
      if (!options.silent) {
        this.component.setState({
          isRequesting: true,
          hasError: false,
          isInvalid: false
        });
      }
    },
    // Sets `this.state` when a model/collection syncs. It delegates to `this.setState`.
    // It listens to `Backbone.Model#sync` and `Backbone.Collection#sync`
    onSync: function (modelOrCollection, res, options) {
      // Calls `setState` only if there's no silent option
      if (!options.silent) {
        this.component.setState({isRequesting: false});
      }
    },
    // Used internally to set `this.collection` or `this.model` on `this.state`. Delegates to
    // `this.setState`. It listens to `Backbone.Collection` events such as `add`, `remove`,
    // `change`, `sort`, `reset` and to `Backbone.Model` `change`.
    setStateBackbone: function (modelOrCollection, key, target) {
      if (!(modelOrCollection.models || modelOrCollection.attributes)) {
        for (key in modelOrCollection)
            this.setStateBackbone(modelOrCollection[key], key, target);
        return;
      }
      this.setState.apply(this, arguments);
    },
    // Sets a model, collection or object into state by delegating to `this.component.setState`.
    setState: function (modelOrCollection, key, target) {
      var state = {};
      var newState = modelOrCollection.toJSON ? modelOrCollection.toJSON() : modelOrCollection;

      if (key) {
        state[key] = newState;
      } else if (modelOrCollection instanceof Backbone.Collection) {
        state.collection = newState;
      } else {
        state.model = newState;
      }

      if (target) {
        _.extend(target, state);
      } else {
        this.component.setState(state);
      }
    },
    // Binds the component to any collection changes.
    startCollectionListeners: function (collection, key) {
      if (!collection) collection = this.collection;
      if (collection) {
        if (collection.models)
          this
            .listenTo(collection, 'add remove change sort reset',
              _.partial(this.setStateBackbone, collection, key, void 0))
            .listenTo(collection, 'error', this.onError)
            .listenTo(collection, 'request', this.onRequest)
            .listenTo(collection, 'sync', this.onSync);
        else if (typeof collection === 'object')
          for (key in collection)
            if (collection.hasOwnProperty(key))
              this.startCollectionListeners(collection[key], key);
      }
    },
    // Binds the component to any model changes.
    startModelListeners: function (model, key) {
      if (!model) model = this.model;
      if (model) {
        if (model.attributes)
          this
            .listenTo(model, 'change',
              _.partial(this.setStateBackbone, model, key, void 0))
            .listenTo(model, 'error', this.onError)
            .listenTo(model, 'request', this.onRequest)
            .listenTo(model, 'sync', this.onSync)
            .listenTo(model, 'invalid', this.onInvalid);
        else if (typeof model === 'object')
          for (key in model)
            this.startModelListeners(model[key], key);
      }
    }
  });

  // Expose `Backbone.React.Component.mixin`.
  return Backbone.React.Component.mixin;
}));
//
<commit_msg>change overrideModel and overrideCollection so that it keeps props immutable<commit_after>// Backbone React Component
// ========================
//
//     Backbone.React.Component v0.8.0-beta.1
//
//     (c) 2014 "Magalhas" José Magalhães <magalhas@gmail.com>
//     Backbone.React.Component can be freely distributed under the MIT license.
//
//
// `Backbone.React.Component` is a mixin that glues [Backbone](http://backbonejs.org/)
// models and collections into [React](http://facebook.github.io/react/) components.
//
// When the component is mounted, a wrapper starts listening to models and
// collections changes to automatically set your component state and achieve UI
// binding through reactive updates.
//
//
//
// Basic Usage
//
//     var MyComponent = React.createClass({
//       mixins: [Backbone.React.Component.mixin],
//       render: function () {
//         return
{this.state.foo}
;
//       }
//     });
//     var model = new Backbone.Model({foo: 'bar'});
//     React.render(<MyComponent model={model} />, document.body);

(function (root, factory) {
  // Universal module definition
  if (typeof define === 'function' && define.amd) {
    define(['react', 'backbone', 'underscore'], factory);
  } else if (typeof module !== 'undefined' && module.exports) {
    module.exports = factory(require('react'), require('backbone'), require('underscore'));
  } else {
    factory(root.React, root.Backbone, root._);
  }
}(this, function (React, Backbone, _) {
  'use strict';
  if (!Backbone.React) {
    Backbone.React = {};
  }
  if (!Backbone.React.Component) {
    Backbone.React.Component = {};
  }
  // Mixin used in all component instances. Exported through `Backbone.React.Component.mixin`.
  Backbone.React.Component.mixin = {
    // Types of the context passed to child components. Only
    // `hasParentBackboneMixin` is required all of the others are optional.
    childContextTypes: {
      hasParentBackboneMixin: React.PropTypes.bool.isRequired,
      parentModel: React.PropTypes.any,
      parentCollection: React.PropTypes.any
    },
    // Types of the context received from the parent component. All of them are
    // optional.
    contextTypes: {
      hasParentBackboneMixin: React.PropTypes.bool,
      parentModel: React.PropTypes.any,
      parentCollection: React.PropTypes.any
    },
    // Passes data to our child components.
    getChildContext: function () {
      return {
        hasParentBackboneMixin: true,
        parentModel: this.getModel(),
        parentCollection: this.getCollection()
      };
    },
    // Sets `this.el` and `this.$el` when the component mounts.
    componentDidMount: function () {
      this.setElement(this.getDOMNode());
    },
    // Sets `this.el` and `this.$el` when the component updates.
    componentDidUpdate: function () {
      this.setElement(this.getDOMNode());
    },
    // When the component gets the initial state, instance a `Wrapper` to take
    // care of models and collections binding with `this.state`.
    getInitialState: function () {
      var initialState = {};

      if (!this.wrapper) {
        this.wrapper = new Wrapper(this, initialState);
      }

      return initialState;
    },
    // When the component mounts, instance a `Wrapper` to take care
    // of models and collections binding with `this.state`.
    componentWillMount: function () {
      if (!this.wrapper) {
        this.wrapper = new Wrapper(this);
      }
    },
    // When the component unmounts, dispose listeners and delete
    // `this.wrapper` reference.
    componentWillUnmount: function () {
      if (this.wrapper) {
        this.wrapper.stopListening();
        delete this.wrapper;
      }
    },
    // In order to allow passing nested models and collections as reference we
    // filter `nextProps.model` and `nextProps.collection`.
    componentWillReceiveProps: function (nextProps) {
      var model = nextProps.model;
      var collection = nextProps.collection;

      if (this.wrapper.model && model) {
        if (this.wrapper.model !== model) {
          this.wrapper.stopListening();
          this.wrapper = new Wrapper(this, void 0, nextProps);
        }
      } else if (model) {
        this.wrapper = new Wrapper(this, void 0, nextProps);
      }

      if (this.wrapper.collection && collection) {
        if (this.wrapper.collection !== collection) {
          this.wrapper.stopListening();
          this.wrapper = new Wrapper(this, void 0, nextProps);
        }
      } else if (collection) {
        this.wrapper = new Wrapper(this, void 0, nextProps);
      }
    },
    // Shortcut to `@$el.find` if jQuery ins present, else if fallbacks to DOM
    // native `querySelector`. Inspired by `Backbone.View`.
    $: function () {
      var els;

      if (this.$el) {
        els = this.$el.find.apply(this.$el, arguments);
      } else {
        var el = this.getDOMNode();
        els = el.querySelector.apply(el, arguments);
      }

      return els;
    },
    // Grabs the collection from `@wrapper.collection` or `@context.parentCollection`
    getCollection: function () {
      return this.wrapper.collection || this.context.parentCollection;
    },
    // Grabs the model from `@wrapper.model` or `@context.parentModel`
    getModel: function () {
      return this.wrapper.model || this.context.parentModel;
    },
    // Sets a DOM element to render/mount this component on this.el and this.$el.
    setElement: function (el) {
      if (el && Backbone.$ && el instanceof Backbone.$) {
        if (el.length > 1) {
          throw new Error('You can only assign one element to a component');
        }
        this.el = el[0];
        this.$el = el;
      } else if (el) {
        this.el = el;
        if (Backbone.$) {
          this.$el = Backbone.$(el);
        }
      }
      return this;
    }
  };
  // Binds models and collections to a `React.Component`. It mixes `Backbone.Events`.
  function Wrapper (component, initialState, nextProps) {
    // 1:1 relation with the `component`
    this.component = component;
    // Use `nextProps` or `component.props` and grab `model` and `collection`
    // from there
    var props = nextProps || component.props || {};
    var model, collection;

    if (component.overrideModel && typeof component.overrideModel === 'function'){
      // Define overrideModel() method on your `React class` to programatically supply a model object
      // Will override `this.props.model`
      model = component.overrideModel();
    } else {
      model = props.model;
    }

    if (component.overrideCollection && typeof component.overrideCollection === 'function'){
      // Define overrideCollection() method on your `React class` to programatically supply a collection object
      // Will override `this.props.collection`
      collection = component.overrideCollection();
    } else {
      collection = props.collection;
    }

    // Check if `props.model` is a `Backbone.Model` or an hashmap of them
    if (typeof model !== 'undefined' && (model.attributes ||
        typeof model === 'object' && _.values(model)[0].attributes)) {
      // The model(s) bound to this component
      this.model = model;
      // Set model(s) attributes on `initialState` for the first render
      this.setStateBackbone(model, void 0, initialState);
    }
    // Check if `props.collection` is a `Backbone.Collection` or an hashmap of them
    if (typeof collection !== 'undefined' && (collection.models ||
        typeof collection === 'object' && _.values(collection)[0].models)) {
      // The collection(s) bound to this component
      this.collection = collection;
      // Set collection(s) models on `initialState` for the first render
      this.setStateBackbone(collection, void 0, initialState);
    }

    // Start listeners
    this.startModelListeners();
    this.startCollectionListeners();
  }
  // Mixing `Backbone.Events` into `Wrapper.prototype`
  _.extend(Wrapper.prototype, Backbone.Events, {
    // Sets `this.state` when a model/collection request results in error. It delegates
    // to `this.setState`. It listens to `Backbone.Model#error` and `Backbone.Collection#error`.
    onError: function (modelOrCollection, res, options) {
      // Set state only if there's no silent option
      if (!options.silent) {
        this.component.setState({
          isRequesting: false,
          hasError: true,
          error: res
        });
      }
    },
    onInvalid: function (model, res, options) {
      if (!options.silent) {
        this.component.setState({
          isInvalid: true
        });
      }
    },
    // Sets `this.state` when a model/collection request starts. It delegates to
    // `this.setState`. It listens to `Backbone.Model#request` and
    // `Backbone.Collection#request`.
    onRequest: function (modelOrCollection, xhr, options) {
      // Set `state` only if there's no silent option
      if (!options.silent) {
        this.component.setState({
          isRequesting: true,
          hasError: false,
          isInvalid: false
        });
      }
    },
    // Sets `this.state` when a model/collection syncs. It delegates to `this.setState`.
    // It listens to `Backbone.Model#sync` and `Backbone.Collection#sync`
    onSync: function (modelOrCollection, res, options) {
      // Calls `setState` only if there's no silent option
      if (!options.silent) {
        this.component.setState({isRequesting: false});
      }
    },
    // Used internally to set `this.collection` or `this.model` on `this.state`. Delegates to
    // `this.setState`. It listens to `Backbone.Collection` events such as `add`, `remove`,
    // `change`, `sort`, `reset` and to `Backbone.Model` `change`.
    setStateBackbone: function (modelOrCollection, key, target) {
      if (!(modelOrCollection.models || modelOrCollection.attributes)) {
        for (key in modelOrCollection)
            this.setStateBackbone(modelOrCollection[key], key, target);
        return;
      }
      this.setState.apply(this, arguments);
    },
    // Sets a model, collection or object into state by delegating to `this.component.setState`.
    setState: function (modelOrCollection, key, target) {
      var state = {};
      var newState = modelOrCollection.toJSON ? modelOrCollection.toJSON() : modelOrCollection;

      if (key) {
        state[key] = newState;
      } else if (modelOrCollection instanceof Backbone.Collection) {
        state.collection = newState;
      } else {
        state.model = newState;
      }

      if (target) {
        _.extend(target, state);
      } else {
        this.component.setState(state);
      }
    },
    // Binds the component to any collection changes.
    startCollectionListeners: function (collection, key) {
      if (!collection) collection = this.collection;
      if (collection) {
        if (collection.models)
          this
            .listenTo(collection, 'add remove change sort reset',
              _.partial(this.setStateBackbone, collection, key, void 0))
            .listenTo(collection, 'error', this.onError)
            .listenTo(collection, 'request', this.onRequest)
            .listenTo(collection, 'sync', this.onSync);
        else if (typeof collection === 'object')
          for (key in collection)
            if (collection.hasOwnProperty(key))
              this.startCollectionListeners(collection[key], key);
      }
    },
    // Binds the component to any model changes.
    startModelListeners: function (model, key) {
      if (!model) model = this.model;
      if (model) {
        if (model.attributes)
          this
            .listenTo(model, 'change',
              _.partial(this.setStateBackbone, model, key, void 0))
            .listenTo(model, 'error', this.onError)
            .listenTo(model, 'request', this.onRequest)
            .listenTo(model, 'sync', this.onSync)
            .listenTo(model, 'invalid', this.onInvalid);
        else if (typeof model === 'object')
          for (key in model)
            this.startModelListeners(model[key], key);
      }
    }
  });

  // Expose `Backbone.React.Component.mixin`.
  return Backbone.React.Component.mixin;
}));
//
<|endoftext|>


<commit_before>'use strict';

// TODO: support find() by Component class and instance
// TODO: refactor constructor to take a Locator or locatorFn

var util = require('util');
var uuid = require('node-uuid');
var Q = require('q');
var queue = require('./queue');
var Entity = require('./entity');
var AssertionResult = require('./assertion-result');
var ComponentCollection = require('./component-collection');
var extend = require('./utils/extend');
var inherits = require('./utils/inherits');
var evaluator = require('./utils/evaluator');
var bundles = require('./tinto');
var tinto = {};

/**
 * @param {Promise} element
 * @constructor
 * @extends tinto.Entity
 */
tinto.Component = function Component(element) {
  Entity.call(this);

  var self = this;

  this.__id__ = null;
  this._properties = {};
  this._states = {};
  this._element = evaluator.execute(element, function(id) {
    id = this.getAttribute('data-tinto-id') || id;

    this.setAttribute('data-tinto-id', id);

    return id;
  }, uuid.v4()).then(function(id) {
    self.__id__ = id;

    return element;
  });

  this.state('available', available);
  this.state('missing', missing);
};

inherits(tinto.Component, Entity);

/**
 * @param {Object} [protoProps]
 * @param {Object} [staticProps]
 * @returns {function(this:tinto.Component,new:tinto.Component,Promise)}
 */
tinto.Component.extend = function(protoProps, staticProps) {
  return extend.call(this, protoProps, staticProps);
};

/**
 * @param {tinto.Component} component
 * @returns {tinto.Component}
 */
tinto.Component.from = function(component) {
  return new this(component._element);
};

/**
 * @returns {string}
 */
tinto.Component.prototype.toString = function() {
  return '[' + this.constructor.name + ':' + this.__id__ + ']';
};

/**
 * @template T
 * @param {T} type
 * @returns {T}
 */
tinto.Component.prototype.as = function(type) {
  return tinto.Component.from.call(type, this);
};

/**
 * @param {...tinto.Component} components
 * @returns {function() : Promise.<tinto.AssertionResult>}
 */
tinto.Component.prototype.contains = function(components) {
  var self = this;

  components = Array.prototype.slice.call(arguments, 0);

  return function() {
    return Q.all(components.map(function(component) {
      return component._element.then(function(element) {
        return self.execute(function(element) {
          return this.contains(element);
        }, element).then(function(result) {
          return new AssertionResult(result, component.toString());
        });
      });
    }));
  };
};

/**
 * @param {tinto.Component} component
 * @returns {function() : Promise.<tinto.AssertionResult>}
 */
tinto.Component.prototype.equals = function(component) {
  var self = this;

  return function() {
    return component._element.then(function(element) {
      return self.execute(function(element) {
        return this === element;
      }, element).then(function(result) {
        return new AssertionResult(result, component.toString(), self.toString());
      });
    });
  };
};

/**
 * @param {string} name
 * @param {function} [matcher]
 * @param {Array.<*>} [args]
 */
tinto.Component.prototype.state = function(name, matcher, args) {
  support.call(this, 'state', 'states', name, matcher, args, 'State "%s" does not exist');
};

/**
 * @param {string} name
 * @param {function} [matcher]
 * @param {Array.<*>} [args]
 */
tinto.Component.prototype.property = function(name, matcher, args) {
  support.call(this, 'property', 'properties', name, matcher, args, 'Property "%s" does not exist');
};

/**
 * @returns {Promise.<webdriver.WebElement>}
 */
tinto.Component.prototype.getElement = function() {
  return this._element;
};

/**
 * @returns {Promise.<string>}
 */
tinto.Component.prototype.getText = function() {
  return this._element.then(function(element) {
    return element.getText();
  });
};

/**
 * @param {string} name
 * @returns {Promise.<string>}
 */
tinto.Component.prototype.getAttribute = function(name) {
  return this._element.then(function(element) {
    return element.getAttribute(name);
  });
};

/**
 * @returns {tinto.Component}
 */
tinto.Component.prototype.click = function() {
  var self = this;

  queue.push(function() {
    return self._element.then(function(element) {
      return element.click();
    });
  });

  return this;
};

/**
 * @param {...string} keys
 * @returns {tinto.Component}
 */
tinto.Component.prototype.enter = function(keys) {
  var self = this;

  keys = arguments;

  queue.push(function() {
    return self._element.then(function(element) {
      return element.sendKeys.apply(element, keys);
    });
  });
};

/**
 * @param {string|function} locator
 * @returns {tinto.ComponentCollection.<tinto.Component>}
 */
tinto.Component.prototype.find = function(locator) {
  return new ComponentCollection(tinto.Component, evaluator.find(this._element, locator));
};

/**
 * @param {!function(this:tinto.Component)} callback
 * @param {...*} [args]
 * @returns {Promise}
 */
tinto.Component.prototype.execute = function(callback, args) {
  args = Array.prototype.slice.call(arguments, 0);

  return evaluator.execute.apply(evaluator, [this._element].concat(args));
};

/**
 * @private
 * @param {string} type
 * @param {string} pluralType
 * @param {string} name
 * @param {function} matcher
 * @param {Array.<*>} args
 * @param {string} message
 */
function support(type, pluralType, name, matcher, args, message) {
  if (matcher instanceof Array) {
    args = matcher;
    matcher = null;
  }

  if (!matcher && this.__bundle__) {
    matcher = bundles[this.__bundle__][pluralType] && bundles[this.__bundle__][pluralType][name];
  }

  if (!matcher) {
    matcher = bundles.html[pluralType] && bundles.html[pluralType][name];
  }

  if (!matcher) {
    throw new Error(util.format(message, name));
  }

  Entity.prototype[type].call(this, name, matcher, args);
}

/**
 * @private
 * @this tinto.Component
 * @returns {Promise.<Boolean>}
 */
function available() {
  return this.is('missing')().then(function(result) {
    return !result.outcome;
  });
}

/**
 * @private
 * @this tinto.Component
 * @returns {Promise.<Boolean>}
 */
function missing() {
  return this._element.then(function() {
    return false;
  }).catch(function() {
    return true;
  });
}

module.exports = tinto.Component;
<commit_msg>code cleanup<commit_after>'use strict';

// TODO: support find() by Component class and instance
// TODO: refactor constructor to take a Locator or locatorFn

var util = require('util');
var uuid = require('node-uuid');
var Q = require('q');
var queue = require('./queue');
var Entity = require('./entity');
var AssertionResult = require('./assertion-result');
var ComponentCollection = require('./component-collection');
var extend = require('./utils/extend');
var inherits = require('./utils/inherits');
var evaluator = require('./utils/evaluator');
var bundles = require('./tinto');
var tinto = {};

/**
 * @param {Promise} element
 * @constructor
 * @extends tinto.Entity
 */
tinto.Component = function Component(element) {
  Entity.call(this);

  var self = this;

  this.__id__ = null;
  this._element = evaluator.execute(element, function(id) {
    id = this.getAttribute('data-tinto-id') || id;

    this.setAttribute('data-tinto-id', id);

    return id;
  }, uuid.v4()).then(function(id) {
    self.__id__ = id;

    return element;
  });

  this.state('available', available);
  this.state('missing', missing);
};

inherits(tinto.Component, Entity);

/**
 * @param {Object} [protoProps]
 * @param {Object} [staticProps]
 * @returns {function(this:tinto.Component,new:tinto.Component,Promise)}
 */
tinto.Component.extend = function(protoProps, staticProps) {
  return extend.call(this, protoProps, staticProps);
};

/**
 * @param {tinto.Component} component
 * @returns {tinto.Component}
 */
tinto.Component.from = function(component) {
  return new this(component._element);
};

/**
 * @returns {string}
 */
tinto.Component.prototype.toString = function() {
  return '[' + this.constructor.name + ':' + this.__id__ + ']';
};

/**
 * @template T
 * @param {T} type
 * @returns {T}
 */
tinto.Component.prototype.as = function(type) {
  return tinto.Component.from.call(type, this);
};

/**
 * @param {...tinto.Component} components
 * @returns {function() : Promise.<tinto.AssertionResult>}
 */
tinto.Component.prototype.contains = function(components) {
  var self = this;

  components = Array.prototype.slice.call(arguments, 0);

  return function() {
    return Q.all(components.map(function(component) {
      return component._element.then(function(element) {
        return self.execute(function(element) {
          return this.contains(element);
        }, element).then(function(result) {
          return new AssertionResult(result, component.toString());
        });
      });
    }));
  };
};

/**
 * @param {tinto.Component} component
 * @returns {function() : Promise.<tinto.AssertionResult>}
 */
tinto.Component.prototype.equals = function(component) {
  var self = this;

  return function() {
    return component._element.then(function(element) {
      return self.execute(function(element) {
        return this === element;
      }, element).then(function(result) {
        return new AssertionResult(result, component.toString(), self.toString());
      });
    });
  };
};

/**
 * @param {string} name
 * @param {function} [matcher]
 * @param {Array.<*>} [args]
 */
tinto.Component.prototype.state = function(name, matcher, args) {
  support.call(this, 'state', 'states', name, matcher, args, 'State "%s" does not exist');
};

/**
 * @param {string} name
 * @param {function} [matcher]
 * @param {Array.<*>} [args]
 */
tinto.Component.prototype.property = function(name, matcher, args) {
  support.call(this, 'property', 'properties', name, matcher, args, 'Property "%s" does not exist');
};

/**
 * @returns {Promise.<webdriver.WebElement>}
 */
tinto.Component.prototype.getElement = function() {
  return this._element;
};

/**
 * @returns {Promise.<string>}
 */
tinto.Component.prototype.getText = function() {
  return this._element.then(function(element) {
    return element.getText();
  });
};

/**
 * @param {string} name
 * @returns {Promise.<string>}
 */
tinto.Component.prototype.getAttribute = function(name) {
  return this._element.then(function(element) {
    return element.getAttribute(name);
  });
};

/**
 * @returns {tinto.Component}
 */
tinto.Component.prototype.click = function() {
  var self = this;

  queue.push(function() {
    return self._element.then(function(element) {
      return element.click();
    });
  });

  return this;
};

/**
 * @param {...string} keys
 * @returns {tinto.Component}
 */
tinto.Component.prototype.enter = function(keys) {
  var self = this;

  keys = arguments;

  queue.push(function() {
    return self._element.then(function(element) {
      return element.sendKeys.apply(element, keys);
    });
  });
};

/**
 * @param {string|function} locator
 * @returns {tinto.ComponentCollection.<tinto.Component>}
 */
tinto.Component.prototype.find = function(locator) {
  return new ComponentCollection(tinto.Component, evaluator.find(this._element, locator));
};

/**
 * @param {!function(this:tinto.Component)} callback
 * @param {...*} [args]
 * @returns {Promise}
 */
tinto.Component.prototype.execute = function(callback, args) {
  args = Array.prototype.slice.call(arguments, 0);

  return evaluator.execute.apply(evaluator, [this._element].concat(args));
};

/**
 * @private
 * @param {string} type
 * @param {string} pluralType
 * @param {string} name
 * @param {function} matcher
 * @param {Array.<*>} args
 * @param {string} message
 */
function support(type, pluralType, name, matcher, args, message) {
  if (matcher instanceof Array) {
    args = matcher;
    matcher = null;
  }

  if (!matcher && this.__bundle__) {
    matcher = bundles[this.__bundle__][pluralType] && bundles[this.__bundle__][pluralType][name];
  }

  if (!matcher) {
    matcher = bundles.html[pluralType] && bundles.html[pluralType][name];
  }

  if (!matcher) {
    throw new Error(util.format(message, name));
  }

  Entity.prototype[type].call(this, name, matcher, args);
}

/**
 * @private
 * @this tinto.Component
 * @returns {Promise.<Boolean>}
 */
function available() {
  return this.is('missing')().then(function(result) {
    return !result.outcome;
  });
}

/**
 * @private
 * @this tinto.Component
 * @returns {Promise.<Boolean>}
 */
function missing() {
  return this._element.then(function() {
    return false;
  }).catch(function() {
    return true;
  });
}

module.exports = tinto.Component;
<|endoftext|>


<commit_before>'use strict';
var BBPromise = require('bluebird');
var requestPromise = require('request-promise');
var logger = require('hoist-logger');
var url = require('url');
var _ = require('lodash');
var errors = require('hoist-errors');

var baseUrl = '.chargify.com';

function ChargifyConnector(settings) {
  logger.info({
    settings: settings
  }, 'constructed chargify-connector');
  this.settings = settings;
}

ChargifyConnector.prototype.get = function (url, queryParams) {
  logger.info('inside hoist-connector-chargify.get');
  return this.request('GET', url, queryParams);
};

ChargifyConnector.prototype.post = function (url, data) {
  logger.info('inside hoist-connector-chargify.post');
  if(!data){
    throw new errors.connector.request.InvalidError('no data specified in post');
  }
  return this.request('POST', url, null, data);
};

ChargifyConnector.prototype.put = function (url, data) {
  logger.info('inside hoist-connector-chargify.put');
  if(!data){
    throw new errors.connector.request.InvalidError('no data specified in put');
  }
  return this.request('PUT', url, null, data);
};

ChargifyConnector.prototype.delete = function (url, queryParams, data) {
  logger.info('inside hoist-connector-chargify.delete');
  return this.request('DELETE', url, queryParams, data);
};

ChargifyConnector.prototype.request = function request(method, path, queryParams, data) {
  if(!path){
    throw new errors.connector.request.InvalidError('no path specified');
  }
  var username = this.settings.apiKey;
  var password = 'x';

  // path = path[path.length -1] === '/'? path.slice(0, -1) : path;
  var options = {
    uri: 'https://' + username + ':' + password + "@" + this.settings.subdomain + baseUrl + path,
    method: method,
    resolveWithFullResponse: true,
  };
  var parsedUrl = url.parse(options.uri, true);
  parsedUrl.search = null;

  if(queryParams) {
    parsedUrl.query = _.extend(parsedUrl.query, queryParams);
  }

  options.uri = url.format(parsedUrl);

  if(method === 'POST' || method === 'PUT') {
    if(typeof data === 'string'){
        options.contentType = 'application/xml'
    } else if (typeof data === 'object') {
      data = JSON.stringify(data);
      options.contentType = 'application/json';
    }
    options.body = data;
  }
  logger.info({
    method: method,
    path: path,
    options: options
  }, 'inside hoist-connector-chargify.request');
  return this.requestPromiseHelper(options)
    .then(function(response) {
      logger.info({
        response: response.body
      }, 'got response hoist-connector-chargify.request');
       return response.body;
    });
};
/* istanbul ignore next */
ChargifyConnector.prototype.requestPromiseHelper = function requestPromiseHelper (options) {
  return BBPromise.resolve(requestPromise(options));
};

module.exports = ChargifyConnector;
<commit_msg>fix jshint<commit_after>'use strict';
var BBPromise = require('bluebird');
var requestPromise = require('request-promise');
var logger = require('hoist-logger');
var url = require('url');
var _ = require('lodash');
var errors = require('hoist-errors');

var baseUrl = '.chargify.com';

function ChargifyConnector(settings) {
  logger.info({
    settings: settings
  }, 'constructed chargify-connector');
  this.settings = settings;
}

ChargifyConnector.prototype.get = function (url, queryParams) {
  logger.info('inside hoist-connector-chargify.get');
  return this.request('GET', url, queryParams);
};

ChargifyConnector.prototype.post = function (url, data) {
  logger.info('inside hoist-connector-chargify.post');
  if(!data){
    throw new errors.connector.request.InvalidError('no data specified in post');
  }
  return this.request('POST', url, null, data);
};

ChargifyConnector.prototype.put = function (url, data) {
  logger.info('inside hoist-connector-chargify.put');
  if(!data){
    throw new errors.connector.request.InvalidError('no data specified in put');
  }
  return this.request('PUT', url, null, data);
};

ChargifyConnector.prototype.delete = function (url, queryParams, data) {
  logger.info('inside hoist-connector-chargify.delete');
  return this.request('DELETE', url, queryParams, data);
};

ChargifyConnector.prototype.request = function request(method, path, queryParams, data) {
  if(!path){
    throw new errors.connector.request.InvalidError('no path specified');
  }
  var username = this.settings.apiKey;
  var password = 'x';

  // path = path[path.length -1] === '/'? path.slice(0, -1) : path;
  var options = {
    uri: 'https://' + username + ':' + password + "@" + this.settings.subdomain + baseUrl + path,
    method: method,
    resolveWithFullResponse: true,
  };
  var parsedUrl = url.parse(options.uri, true);
  parsedUrl.search = null;

  if(queryParams) {
    parsedUrl.query = _.extend(parsedUrl.query, queryParams);
  }

  options.uri = url.format(parsedUrl);

  if(method === 'POST' || method === 'PUT') {
    if(typeof data === 'string'){
        options.contentType = 'application/xml';
    } else if (typeof data === 'object') {
      data = JSON.stringify(data);
      options.contentType = 'application/json';
    }
    options.body = data;
  }
  logger.info({
    method: method,
    path: path,
    options: options
  }, 'inside hoist-connector-chargify.request');
  return this.requestPromiseHelper(options)
    .then(function(response) {
      logger.info({
        response: response.body
      }, 'got response hoist-connector-chargify.request');
       return response.body;
    });
};
/* istanbul ignore next */
ChargifyConnector.prototype.requestPromiseHelper = function requestPromiseHelper (options) {
  return BBPromise.resolve(requestPromise(options));
};

module.exports = ChargifyConnector;
<|endoftext|>


<commit_before>'use strict';

var df = require('datafixture.js');
var _ = require('lodash');

var WHITESPACE = /\s/;
var NEWLINE = /\n/g;
var GUID = /.*-.*-.*-.*/;
var DECIMAL = /\d*$/;

function value(value) {
    return function () {
        return value;
    };
}

function stringConverter(stringValue) {
    var hasWhitespace = stringValue.match(WHITESPACE);
    var hasNewLine = stringValue.match(NEWLINE);
    if (hasWhitespace && !hasNewLine) {
        return 'lorem:1...' + (hasWhitespace.length + 1);
    } else if (hasWhitespace && hasNewLine) {
        return 'plorem:' + (hasNewLine.length + 1);
    } else if (stringValue.length === 0) {
        return 'lorem:0...1';
    }
    return stringValue.match(GUID) ? 'GUID' : 'lorem:1';
}

function arrayConverter(arrayValue) {
    return function () {
        return _.map(arrayValue, convert);
    };
}

function FallbackConverterBuilder(fallbacks) {
    return {
        thenMatch: function (predicate, converter) {
            return new FallbackConverterBuilder(_.compact(fallbacks).concat([[predicate, converter]]));
        },
        buildWithDefault: function (defaultConverter) {
            return function (value) {
                var converterToUse = _.find(fallbacks, function(conditionAndConverter) {
                    return conditionAndConverter[0](value);
                });
                return (converterToUse ? converterToUse[1] : defaultConverter)(value);
            };
        }
    }
}

function booleanConverter() {
    return function () {
        return df.getRandom(0, 1) === 0;
    };
}

function objectConverter(objectValue) {
    if (_.isDate(objectValue)) {
        return function () {
            return new Date();
        };
    }

    return  _.reduce(objectValue, function (memo, value, name) {
        memo[name] = convert(value);
        return memo;
    }, {});
}

var numberConverter = new FallbackConverterBuilder()
    .thenMatch(_.partial(_.isEqual, Infinity), value(Infinity))
    .thenMatch(_.partial(_.isEqual, -Infinity), value(-Infinity))
    .thenMatch(_.isNaN, value(NaN))
    .buildWithDefault(function(numberValue) {
        var mainRange = numberValue <= 0 ? Math.floor(numberValue) + '...0' : '1...' + Math.ceil(numberValue);
        return mainRange + (numberValue % 1 === 0 ? '' : ':' + DECIMAL.exec(numberValue)[0].length);
    });

var convert = new FallbackConverterBuilder()
    .thenMatch(_.isNull, value(undefined))
    .thenMatch(_.isUndefined, value(undefined))
    .thenMatch(_.isBoolean, booleanConverter)
    .thenMatch(_.isString, stringConverter)
    .thenMatch(_.isArray, arrayConverter)
    .thenMatch(_.isNumber, numberConverter)
    .thenMatch(_.isObject, objectConverter)
    .buildWithDefault(function(value) {
        throw new Error('no converter found for ' + JSON.stringify(value));
    });

module.exports = convert;<commit_msg>refactor of object converter<commit_after>'use strict';

var df = require('datafixture.js');
var _ = require('lodash');

var WHITESPACE = /\s/;
var NEWLINE = /\n/g;
var GUID = /.*-.*-.*-.*/;
var DECIMAL = /\d*$/;

function value(value) {
    return function () {
        return value;
    };
}

function stringConverter(stringValue) {
    var hasWhitespace = stringValue.match(WHITESPACE);
    var hasNewLine = stringValue.match(NEWLINE);
    if (hasWhitespace && !hasNewLine) {
        return 'lorem:1...' + (hasWhitespace.length + 1);
    } else if (hasWhitespace && hasNewLine) {
        return 'plorem:' + (hasNewLine.length + 1);
    } else if (stringValue.length === 0) {
        return 'lorem:0...1';
    }
    return stringValue.match(GUID) ? 'GUID' : 'lorem:1';
}

function arrayConverter(arrayValue) {
    return function () {
        return _.map(arrayValue, convert);
    };
}

function FallbackConverterBuilder(fallbacks) {
    return {
        thenMatch: function (predicate, converter) {
            return new FallbackConverterBuilder(_.compact(fallbacks).concat([[predicate, converter]]));
        },
        buildWithDefault: function (defaultConverter) {
            return function (value) {
                var converterToUse = _.find(fallbacks, function(conditionAndConverter) {
                    return conditionAndConverter[0](value);
                });
                return (converterToUse ? converterToUse[1] : defaultConverter)(value);
            };
        }
    }
}

function booleanConverter() {
    return function () {
        return df.getRandom(0, 1) === 0;
    };
}

var objectConverter = new FallbackConverterBuilder()
    .thenMatch(_.isDate, function() {
        return function () {
            return new Date();
        };
    })
    .buildWithDefault(function(objectValue) {
        return  _.reduce(objectValue, function (memo, value, name) {
            memo[name] = convert(value);
            return memo;
        }, {});
    });

var numberConverter = new FallbackConverterBuilder()
    .thenMatch(_.partial(_.isEqual, Infinity), value(Infinity))
    .thenMatch(_.partial(_.isEqual, -Infinity), value(-Infinity))
    .thenMatch(_.isNaN, value(NaN))
    .buildWithDefault(function(numberValue) {
        var mainRange = numberValue <= 0 ? Math.floor(numberValue) + '...0' : '1...' + Math.ceil(numberValue);
        return mainRange + (numberValue % 1 === 0 ? '' : ':' + DECIMAL.exec(numberValue)[0].length);
    });

var convert = new FallbackConverterBuilder()
    .thenMatch(_.isNull, value(undefined))
    .thenMatch(_.isUndefined, value(undefined))
    .thenMatch(_.isBoolean, booleanConverter)
    .thenMatch(_.isString, stringConverter)
    .thenMatch(_.isArray, arrayConverter)
    .thenMatch(_.isNumber, numberConverter)
    .thenMatch(_.isObject, objectConverter)
    .buildWithDefault(function(value) {
        throw new Error('no converter found for ' + JSON.stringify(value));
    });

module.exports = convert;<|endoftext|>


<commit_before>'use strict';
var makeRestRequest = require('./make-rest-request');
module.exports = {
    getDatastoreValue: function (options, callback) {
        var url = "/{chumblr}/datastores/{datastoreId}/values/{identifier}/{targetDate}";
        if (!options.targetDate) {
            url = url.replace("{targetDate}", "");
        }
        options.verb = "get";
        options.url = url;
        makeRestRequest(options, callback);
    },
    getDatastoreValues: function (options, callback) {
        var url = "/{chumblr}/datastores/{datastoreId}/values";
        if (options.query) {
            url += "?" + options.query;
        }
        options.verb = "get";
        options.url = url;
        function requestMade(err, result, statusCode, location) {

            return callback(err, result, statusCode, location);
        }

        makeRestRequest(options, requestMade);
    },
    addDatastoreValue: function (options, callback) {
        var url = "/{chumblr}/datastores/{datastoreId}/values";
        options.verb = "post";
        options.url = url;
        makeRestRequest(options, callback);
    },
    setDatastoreValue: function (options, callback) {
        var url = "/{chumblr}/datastores/{datastoreId}/values/{identifier}/{targetDate}";
        if (!options.targetDate) {
            url = url.replace("/{targetDate}", "");
        }
        options.verb = "put";
        options.url = url;
        makeRestRequest(options, callback);
    },
    removeDatastoreValue: function (options, callback) {
        var url = "/{chumblr}/datastores/{datastoreId}/values/{identifier}";
        options.verb = "del";
        options.url = url;
        makeRestRequest(options, callback);
    },
    getDatastores: function (options, callback) {
        options.verb = "get";
        var url = "/{chumblr}/datastores";
        if (options.query) {
            url += "?" + options.query;
        }
        options.url = url;
        makeRestRequest(options, callback);
    },
    getDatastore: function (options, callback) {
        options.verb = "get";
        options.url = "/{chumblr}/datastores/{datastoreId}";
        makeRestRequest(options, callback);
    },
    addDatastore: function (options, callback) {
        options.verb = "post";
        options.url = "/{chumblr}/datastores";
        options.data = options.datastore;
        makeRestRequest(options, callback);
    },
    updateDatastore: function (options, callback) {
        options.verb = "put";
        options.url = "/{chumblr}/datastores/{datastoreId}";
        options.data = options.datastore;
        makeRestRequest(options, callback);
    }
};<commit_msg>adding getDatastoreValuesAggregate<commit_after>'use strict';
var makeRestRequest = require('./make-rest-request');
module.exports = {
    getDatastoreValue: function (options, callback) {
        var url = "/{chumblr}/datastores/{datastoreId}/values/{identifier}/{targetDate}";
        if (!options.targetDate) {
            url = url.replace("{targetDate}", "");
        }
        options.verb = "get";
        options.url = url;
        makeRestRequest(options, callback);
    },
    getDatastoreValues: function (options, callback) {
        var url = "/{chumblr}/datastores/{datastoreId}/values";
        if (options.query) {
            url += "?" + options.query;
        }
        options.verb = "get";
        options.url = url;
        makeRestRequest(options, callback);
    },
    getDatastoreValuesAggregate: function (options, callback) {
        var url = "/{chumblr}/datastores/{datastoreId}/values/aggregate";
        if (options.query) {
            url += "?" + options.query;
        }
        else {
            url += "?";
        }
        if (options.rawQuery) {
            url += "&" + "rawQuery=" + JSON.stringify(options.rawQuery);
        }
        url += "&" + "aggregate=" + options.aggregate;
        options.verb = "get";
        options.url = url;

        makeRestRequest(options, callback);
    },
    addDatastoreValue: function (options, callback) {
        var url = "/{chumblr}/datastores/{datastoreId}/values";
        options.verb = "post";
        options.url = url;
        makeRestRequest(options, callback);
    },
    setDatastoreValue: function (options, callback) {
        var url = "/{chumblr}/datastores/{datastoreId}/values/{identifier}/{targetDate}";
        if (!options.targetDate) {
            url = url.replace("/{targetDate}", "");
        }
        options.verb = "put";
        options.url = url;
        makeRestRequest(options, callback);
    },
    removeDatastoreValue: function (options, callback) {
        var url = "/{chumblr}/datastores/{datastoreId}/values/{identifier}";
        options.verb = "del";
        options.url = url;
        makeRestRequest(options, callback);
    },
    getDatastores: function (options, callback) {
        options.verb = "get";
        var url = "/{chumblr}/datastores";
        if (options.query) {
            url += "?" + options.query;
        }
        options.url = url;
        makeRestRequest(options, callback);
    },
    getDatastore: function (options, callback) {
        options.verb = "get";
        options.url = "/{chumblr}/datastores/{datastoreId}";
        makeRestRequest(options, callback);
    },
    addDatastore: function (options, callback) {
        options.verb = "post";
        options.url = "/{chumblr}/datastores";
        options.data = options.datastore;
        makeRestRequest(options, callback);
    },
    updateDatastore: function (options, callback) {
        options.verb = "put";
        options.url = "/{chumblr}/datastores/{datastoreId}";
        options.data = options.datastore;
        makeRestRequest(options, callback);
    }
};<|endoftext|>


<commit_before>/**
 * @callback Directive~authorize
 *
 * The meteor method context is passed on to this function, including
 * this.userId
 *
 * @throws Meteor.Error
 *
 * @param {{size: Number, type: String, name: String}} file - File to be
 * uploaded
 * @param {Object} [meta] - Meta information provided by the client.
 *
 * @returns Boolean Return true to authorize the requested upload.
 */

/**
 * @typedef {Object} Directive
 *
 * @property {Number} maxSize - Maximum size in bytes
 * @property {(string, Array.<String>, RegExp, null)} allowedFileTypes - MIME
 * types that can be uploaded. If null is passed, then all file types are
 * allowed.
 *
 * @property {Directive~authorize} authorize - Function to determine whether a
 * file-upload is authorized or not.
 *
 * @property {String} [cacheControl] - rfc2616 Cache-Control directive (if
 * applicable to the selected storage service)
 *
 * @property {String} [contentDisposition] - rfc2616 Content-Disposition
 * directive. Defaults to "inline; <uploaded file name>"
 *
 * @property {String}
 */

/**
 * @typedef {Object} FileInfo
 *
 * @property {String} [name] - Given name to the file.
 * @property {Number} size - File-size in bytes.
 * @property {String} [type] - mime type.
 *
 */

/**
 * @typedef {Object} UploadInstructions
 *
 * @property {String} upload - POST URL
 * @property {String} download - Download URL
 * @property {Array.<{name: String, value: Object}>} postData - POST data to be
 * transferred to storage service along with credentials.
 */

/**
 * List of installed directives by name.
 *
 * @type {Object.<string, Directive>}
 * @private
 */

Slingshot._directives = {};

/**
 * Creates file upload directive that defines a set of rule by which a file may
 * be uploaded.
 *
 * @param {string} name - A unique identifier of the directive.
 * @param {Object} service - A storage service to use.
 * @param {Directive} options
 * @returns {Slingshot.Directive}
 */

Slingshot.createDirective = function (name, service, options) {
  if (_.has(Slingshot._directives, name))
    throw new Error("Directive '" + name + "' already exists");

  var restrictions = Slingshot.getRestrictions(name);
  _.defaults(options, restrictions);

  return (Slingshot._directives[name] =
    new Slingshot.Directive(service, options));
};

/**
 * @param {string} name - The unique identifier of the directive to be
 * retrieved.
 * @returns {Slingshot.Directive}
 */

Slingshot.getDirective = function (name) {
  return this._directives[name];
};

/**
 * @param {Object} service
 * @param {Directive} directive
 * @constructor
 */

Slingshot.Directive = function (service, directive) {
  check(this, Slingshot.Directive);

  //service does not have to be a plain-object, so checking fields individually
  check(service.directiveMatch, Object);
  check(service.upload, Function);
  check(service.maxSize, Match.Optional(Number));
  check(service.allowedFileTypes, Match.Optional(matchAllowedFileTypes));

  _.defaults(directive, service.directiveDefault);

  check(directive, _.defaults({
    authorize: Function,
    maxSize: Match.OneOf(Number, null),
    allowedFileTypes: matchAllowedFileTypes,
    cacheControl: Match.Optional(String),
    contentDisposition: Match.Optional(Match.OneOf(String, null))
  }, service.directiveMatch));

  /**
   * @method storageService
   * @returns {Object}
   */

  this.storageService = function () {
    return service;
  };

  /**
   * @private
   * @property {Directive} _directive
   */

  this._directive = directive;
};

_.extend(Slingshot.Directive.prototype, {

  /**
   * @param {{userId: String}} method
   * @param {FileInfo} file
   * @param {Object} [meta]
   *
   * @returns UploadInstructions
   */

  getInstructions: function (method, file, meta) {
    var instructions = this.storageService().upload(method, _.extend({
      contentDisposition: "inline; filename=" + quoteString(file.name, '"')
    }, this._directive), file, meta);

    check(instructions, {
      upload: String,
      download: String,
      postData: [{
        name: String,
        value: Match.OneOf(String, Number, null)
      }]
    });

    return instructions;
  },

 /**
  *
  * @method requestAuthorization
  *
  * @throws Meteor.Error
  *
  * @param {Object} context
  * @param {FileInfo} file
  * @param {Object} [meta]
  *
  * @returns {Boolean}
  */

  requestAuthorization: function (context, file, meta) {
    var validators = Slingshot.Validators,
        restrictions = _.pick(this._directive,
          ['authorize', 'maxSize', 'allowedFileTypes']
        );

    return validators.checkAll(context, file, meta, restrictions);
  }

});

Meteor.methods({
  /**
   * Requests to perform a file upload.
   *
   * @param {String} directiveName
   * @param {FileInfo} file
   * @param {Object} [meta]
   *
   * @returns {UploadInstructions}
   */

  "slingshot/uploadRequest": function (directiveName, file, meta) {
    check(directiveName, String);
    check(file, {
      type: Match.Optional(Match.Where(function (type) {
        check(type, String);
        return !type || /^[^\/]+\/[^\/]+$/.test(type);
      })),
      name: Match.Optional(String),
      size: Match.Where(function (size) {
        check(size, Number);
        return size >= 0;
      })
    });

    if (!file.type)
      delete file.type;

    check(meta, Match.Optional(Match.OneOf(Object, null)));

    var directive = Slingshot.getDirective(directiveName);

    if (!directive) {
      throw new Meteor.Error("Invalid directive",
        "The directive " + directiveName + " does not seem to exist");
    }

    if (!directive.requestAuthorization(this, file, meta)) {
      throw new Meteor.Error("Unauthorized", "You are not allowed to " +
        "upload this file");
    }

    return directive.getInstructions(this, file, meta);
  }
});

function quoteString(string, quotes) {
  return quotes + string.replace(quotes, '\\' + quotes) + quotes;
}
<commit_msg>#22: No content-disposition when there is no name.<commit_after>/**
 * @callback Directive~authorize
 *
 * The meteor method context is passed on to this function, including
 * this.userId
 *
 * @throws Meteor.Error
 *
 * @param {{size: Number, type: String, name: String}} file - File to be
 * uploaded
 * @param {Object} [meta] - Meta information provided by the client.
 *
 * @returns Boolean Return true to authorize the requested upload.
 */

/**
 * @typedef {Object} Directive
 *
 * @property {Number} maxSize - Maximum size in bytes
 * @property {(string, Array.<String>, RegExp, null)} allowedFileTypes - MIME
 * types that can be uploaded. If null is passed, then all file types are
 * allowed.
 *
 * @property {Directive~authorize} authorize - Function to determine whether a
 * file-upload is authorized or not.
 *
 * @property {String} [cacheControl] - rfc2616 Cache-Control directive (if
 * applicable to the selected storage service)
 *
 * @property {String} [contentDisposition] - rfc2616 Content-Disposition
 * directive. Defaults to "inline; <uploaded file name>"
 *
 * @property {String}
 */

/**
 * @typedef {Object} FileInfo
 *
 * @property {String} [name] - Given name to the file.
 * @property {Number} size - File-size in bytes.
 * @property {String} [type] - mime type.
 *
 */

/**
 * @typedef {Object} UploadInstructions
 *
 * @property {String} upload - POST URL
 * @property {String} download - Download URL
 * @property {Array.<{name: String, value: Object}>} postData - POST data to be
 * transferred to storage service along with credentials.
 */

/**
 * List of installed directives by name.
 *
 * @type {Object.<string, Directive>}
 * @private
 */

Slingshot._directives = {};

/**
 * Creates file upload directive that defines a set of rule by which a file may
 * be uploaded.
 *
 * @param {string} name - A unique identifier of the directive.
 * @param {Object} service - A storage service to use.
 * @param {Directive} options
 * @returns {Slingshot.Directive}
 */

Slingshot.createDirective = function (name, service, options) {
  if (_.has(Slingshot._directives, name))
    throw new Error("Directive '" + name + "' already exists");

  var restrictions = Slingshot.getRestrictions(name);
  _.defaults(options, restrictions);

  return (Slingshot._directives[name] =
    new Slingshot.Directive(service, options));
};

/**
 * @param {string} name - The unique identifier of the directive to be
 * retrieved.
 * @returns {Slingshot.Directive}
 */

Slingshot.getDirective = function (name) {
  return this._directives[name];
};

/**
 * @param {Object} service
 * @param {Directive} directive
 * @constructor
 */

Slingshot.Directive = function (service, directive) {
  check(this, Slingshot.Directive);

  //service does not have to be a plain-object, so checking fields individually
  check(service.directiveMatch, Object);
  check(service.upload, Function);
  check(service.maxSize, Match.Optional(Number));
  check(service.allowedFileTypes, Match.Optional(matchAllowedFileTypes));

  _.defaults(directive, service.directiveDefault);

  check(directive, _.defaults({
    authorize: Function,
    maxSize: Match.OneOf(Number, null),
    allowedFileTypes: matchAllowedFileTypes,
    cacheControl: Match.Optional(String),
    contentDisposition: Match.Optional(Match.OneOf(String, null))
  }, service.directiveMatch));

  /**
   * @method storageService
   * @returns {Object}
   */

  this.storageService = function () {
    return service;
  };

  /**
   * @private
   * @property {Directive} _directive
   */

  this._directive = directive;
};

_.extend(Slingshot.Directive.prototype, {

  /**
   * @param {{userId: String}} method
   * @param {FileInfo} file
   * @param {Object} [meta]
   *
   * @returns UploadInstructions
   */

  getInstructions: function (method, file, meta) {
    var instructions = this.storageService().upload(method, _.extend({
      contentDisposition: file.name &&  "inline; filename=" +
        quoteString(file.name, '"')
    }, this._directive), file, meta);

    check(instructions, {
      upload: String,
      download: String,
      postData: [{
        name: String,
        value: Match.OneOf(String, Number, null)
      }]
    });

    return instructions;
  },

 /**
  *
  * @method requestAuthorization
  *
  * @throws Meteor.Error
  *
  * @param {Object} context
  * @param {FileInfo} file
  * @param {Object} [meta]
  *
  * @returns {Boolean}
  */

  requestAuthorization: function (context, file, meta) {
    var validators = Slingshot.Validators,
        restrictions = _.pick(this._directive,
          ['authorize', 'maxSize', 'allowedFileTypes']
        );

    return validators.checkAll(context, file, meta, restrictions);
  }

});

Meteor.methods({
  /**
   * Requests to perform a file upload.
   *
   * @param {String} directiveName
   * @param {FileInfo} file
   * @param {Object} [meta]
   *
   * @returns {UploadInstructions}
   */

  "slingshot/uploadRequest": function (directiveName, file, meta) {
    check(directiveName, String);
    check(file, {
      type: Match.Optional(Match.Where(function (type) {
        check(type, String);
        return !type || /^[^\/]+\/[^\/]+$/.test(type);
      })),
      name: Match.Optional(String),
      size: Match.Where(function (size) {
        check(size, Number);
        return size >= 0;
      })
    });

    if (!file.type)
      delete file.type;

    check(meta, Match.Optional(Match.OneOf(Object, null)));

    var directive = Slingshot.getDirective(directiveName);

    if (!directive) {
      throw new Meteor.Error("Invalid directive",
        "The directive " + directiveName + " does not seem to exist");
    }

    if (!directive.requestAuthorization(this, file, meta)) {
      throw new Meteor.Error("Unauthorized", "You are not allowed to " +
        "upload this file");
    }

    return directive.getInstructions(this, file, meta);
  }
});

function quoteString(string, quotes) {
  return quotes + string.replace(quotes, '\\' + quotes) + quotes;
}
<|endoftext|>


<commit_before>var split = require('split');
var async = require('async');
var fastFeed = require('fast-feed');
var request = require('request');
var config = require('../../config.json');
var debug = require('debug')('fetch-app');

// Sets up stdin for receiving data.

process.stdin.resume();
process.stdin.setEncoding('utf8');

// Wait for incoming request.

process.stdin.pipe(split()).on('data', function(line) {

    var feeds = JSON.parse(line);

    // Fetch all feeds and exit then.

    fetchAll(feeds, function() {

        process.exit(0);
    });
});

// Parses given feed after
// fixups.

function parse(feed, cb) {

    // http://feeds.feedburner.com/smuscom
    // Has HTML as XML.

    feed = feed.replace(/allowfullscreen>/g, '>');

    fastFeed.parse(feed, { content: false }, cb);
}

// Fetches single url and parses result.
// Calls cb at the end.

function fetch(feed, cb) {

    async.waterfall([

        // Fetch feed.

        function(cb) {

            debug('Fetching ' + feed.url);

            request({

                url: feed.url,
                timeout: config.timeout,
                strictSSL: false,
                maxRedirects: 5

            }, cb);

        },

        // Parse feed.

        function(res, body, cb) {

            parse(body, cb);

        }

    ], cb);
}

// Fetches all given urls.
// Does it so by parallel requests.

function fetchAll(feeds, cb) {

    debug('Using ' + config.requests + ' concurrent requests');

    var queue = async.queue(function(feed, cb) {

        fetch(feed, function(err, result) {

            if (err) {

                console.log(JSON.stringify({

                    type: 'error',
                    data: {

                        url: feed.url,
                        uuid: feed.uuid,
                        err: err.message

                    }

                }));

            } else {

                result.uuid = feed.uuid;
                result.url = feed.url;

                console.log(JSON.stringify({

                    type: 'feed',
                    data: result

                }));
            }

            cb();
        });

    }, config.requests);

    queue.drain = cb;
    queue.push(feeds);
};
<commit_msg>Use callback in line-based output in fetcher<commit_after>var split = require('split');
var async = require('async');
var fastFeed = require('fast-feed');
var request = require('request');
var config = require('../../config.json');
var debug = require('debug')('fetch-app');

// Sets up stdin for receiving data.

process.stdin.resume();
process.stdin.setEncoding('utf8');

// Wait for incoming request.

process.stdin.pipe(split()).on('data', function(line) {

    var feeds = JSON.parse(line);

    // Fetch all feeds and exit then.

    fetchAll(feeds, function() {

        process.exit(0);
    });
});

// Parses given feed after
// fixups.

function parse(feed, cb) {

    // http://feeds.feedburner.com/smuscom
    // Has HTML as XML.

    feed = feed.replace(/allowfullscreen>/g, '>');

    fastFeed.parse(feed, { content: false }, cb);
}

// Fetches single url and parses result.
// Calls cb at the end.

function fetch(feed, cb) {

    async.waterfall([

        // Fetch feed.

        function(cb) {

            debug('Fetching ' + feed.url);

            request({

                url: feed.url,
                timeout: config.timeout,
                strictSSL: false,
                maxRedirects: 5

            }, cb);

        },

        // Parse feed.

        function(res, body, cb) {

            parse(body, cb);

        }

    ], cb);
}

// Fetches all given urls.
// Does it so by parallel requests.

function fetchAll(feeds, cb) {

    debug('Using ' + config.requests + ' concurrent requests');

    var queue = async.queue(function(feed, cb) {

        fetch(feed, function(err, result) {

            if (err) {

                process.stdout.write(JSON.stringify({

                    type: 'error',
                    data: {

                        url: feed.url,
                        uuid: feed.uuid,
                        err: err.message

                    }

                }), cb);

            } else {

                result.uuid = feed.uuid;
                result.url = feed.url;

                process.stdout.write(JSON.stringify({

                    type: 'feed',
                    data: result

                }), cb);
            }
        });

    }, config.requests);

    queue.drain = cb;
    queue.push(feeds);
};
<|endoftext|>