Source: digitalocean/util.js

(function() {
  var slice = [].slice;

  module.exports.safeUrl = function() {
    var args = Array.prototype.slice.call(arguments);
    return args.reduce(function(accum, fragment) {
      return accum + '/' + encodeURIComponent(fragment);
    }, '');
  };

  // Based on Humps by Dom Christie
  var decamelizeKeys = function(object) {
    // if we're not an array or object, return the primative
    if (object !== Object(object)) {
      return object;
    }

    var decamelizeString = function(string) {
      var separator = '_';
      var split = /(?=[A-Z])/;

      return string.split(split).join(separator).toLowerCase();
    };


    var output;
    if (object instanceof Array) {
      output = [];
      for(var i = 0, l = object.length; i < l; i++) {
        output.push(decamelizeKeys(object[i]));
      }
    } else {
      output = {};
      for (var key in object) {
        if (object.hasOwnProperty(key)) {
          output[decamelizeString(key)] = decamelizeKeys(object[key]);
        }
      }
    }

    return output;
  };
  module.exports.decamelizeKeys = decamelizeKeys;

  module.exports.extractListArguments = function(args, countPrependedArgs) {
    var startIndex, endIndex, id, params, callback;

    var hasCallback = typeof(args[args.length - 1]) === 'function';

    startIndex = countPrependedArgs;
    if (args.length > 0) {
      endIndex = args.length;
      if (hasCallback) {
        endIndex -= 1;
      }
      params = slice.call(args, startIndex, endIndex);
    } else {
      endIndex = countPrependedArgs;
      params = [];
    }

    if (countPrependedArgs > 0) {
      id = args[0];
    }

    if (hasCallback) {
      callback = args[args.length - 1];
    }

    return {
      identifier: id,
      callback: callback,
      params: params
    };
  };

  /**
   * A class that runs the pagination until the end if necessary.
   *
   * @class ListResponse
   */
  var ListResponse = function(client, initialData, totalLength, requestOptions, successStatuses, successRootKeys, promise) {
    this.currentPage = 1; // default to start at page 1
    // this.perPage = queryParams && queryParams.per_page || 25; // default to 25 per page
    this.totalLength = totalLength;
    // bootstrap with initial data
    this.push.apply(this, initialData);

    this.client = client;
    this.requestOptions = requestOptions;
    this.successStatuses = successStatuses;
    this.successRootKeys = successRootKeys;
    this.promise = promise;
  };
  ListResponse.prototype = Object.create(Array.prototype);

  ListResponse.prototype.concat = function() {
    // not sure why concat doesn't like the subclass, but treats it as an
    // object
    return [].concat.apply(this.slice(0), arguments);
  };

  ListResponse.prototype.all = function(callback) {
    var iterator = this.iterator();
    var iterable = iterator.next();

    var handleNext = function(value) {
      if (callback) {
        setTimeout(function() {
          callback(null, value);
        }, 0);
      }
      iterable = iterator.next();

      // "recurse"
      if (!iterable.done) {
        iterable.value.then(handleNext).catch(handleError);
      }
    };
    var handleError = function(error) {
      if (callback) {
        setTimeout(function() {
          callback(error, null);
        }, 0);
      }
    };

    iterable.value.then(handleNext).catch(handleError);
  };

  /**
   * Implement the iterator protocol. Return an object that responds to next()
   * with two properties: a boolean `done` and `value` with the result of that iteration, which is a promise in this case.
   */
  ListResponse.prototype.iterator = function() {
    var totalIterated = 0;
    var self = this;
    var Promise = this.promise;
    return {
      next: function() {
        if (totalIterated < self.totalLength) {
          var deferred;
          if (totalIterated >= self.length) {
            // Fetch next page
            deferred = self.nextPage().then(function(page) {
              [].push.apply(self, page.slice(0));
            });
          } else {
            deferred = new Promise(function(resolve) {
              return resolve(1);
            });
          }

          deferred = deferred.then(function() {
            var value = self[totalIterated];
            totalIterated += 1;
            return value;
          });

          return {
            value: deferred,
            done: false
          };
        } else {
          return {
            done: true
          };
        }
      }
    };
  };

  // returns a promise
  ListResponse.prototype.nextPage = function() {
    this.currentPage += 1;
    var params = Object.assign({}, this.requestOptions.query);
    params.page = this.currentPage;

    var promise = this.client.get(
      this.requestOptions.path,
      this.requestOptions,
      params,
      this.successStatuses,
      this.successRootKeys,
      false
    );

    return promise;
  };

  module.exports.ListResponse = ListResponse;
}).call(this);