<commit_before>var url       = require('url');
var qs        = require('querystring');
var http      = require('http');
var EventVat  = require('eventvat');
var request   = require('request');
var Streamify = require('streamify');
var Hash      = require('hashish');
var FORMATS   = require('./formats');
var parseTime = require('./util').parseTime;

var INFO_URL = 'http://www.youtube.com/get_video_info?hl=en_US&video_id=';
var KEYS_TO_SPLIT = [
  'keywords'
, 'fmt_list'
, 'fexp'
, 'watermark'
, 'ad_channel_code_overlay'
];

// cache for recently looked up video infos
var cache = new EventVat({ autoexpire: 30 });

/**
 * Checks cache to see if info for this video has been fetched recently.
 * If not, get it, then download the video once the info is found.
 *
 * @param (string) link
 * @param (Object) options
 * @return (ReadableStream)
 */
function ytdl(link, options) {
  var id = url.parse(link, true).query.v;
  var stream = new Streamify({ superCtor: http.ClientResponse });

  options = options || {};
  options.quality = 'highest';

  if (cache.exists(id)) {
    var info = cache.get(id);
    process.nextTick(function() {
      download(link, options, stream, info);
    });

  } else {
    ytdl.getInfo(link, options, function(err, info) {
      if (err) {
        stream.emit('error', err);
        stream.readable = false;
        return;
      }

      download(link, options, stream, info);
    });
  }

  return stream;
}

/**
 * Downloads a video.
 *
 * @param (string) link
 * @param (Object) options
 * @param (ReadableStream) stream
 * @param (Object) info
 * @return (ReadableStream)
 */
function download(link, options, stream, info) {
  var formats = info.formats;
  if (options.filter) {
    formats = formats.filter(options.filter);
  }

  var format;
  switch (options.quality) {
    case 'highest':
      format = formats[0];
      break;

    case 'lowest':
      format = formats[formats.length - 1];
      break;

    default:
      format = formats.filter(function(format) {
        return format.itag === options.quality;
      })[0];

  }

  if (!format) {
    stream.emit('error', new Error('No such format found'));
    return;
  }

  format = Hash.copy(format);
  var url = format.url;
  var requestOptions = Hash.copy(options);

  if (options.start) {
    url = url + '&begin=' + parseTime(options.start);
  }

  format.url = requestOptions.url = url;

  // start downloading the video
  var res = request(requestOptions);
  res.on('response', function(res) {
    format.size = res.headers['content-length'];
    stream.emit('info', info, format);
  });
  stream.resolve(res);
}

/**
 * Gets info from a video.
 *
 * @param (string) link
 * @param (Object) requestOptions
 * @param (Function(Error, Object)) callback
 */
ytdl.getInfo = function getInfo(link, requestOptions, callback) {
  if (typeof requestOptions === 'function') {
    callback = requestOptions;
    requestOptions = {};
  } else {
    requestOptions = Hash.copy(requestOptions);
  }

  var id = url.parse(link, true).query.v;
  if (!id) {
    process.nextTick(function() {
      callback(new Error('Video ID not found'));
    });
    return;
  }

  requestOptions.url = INFO_URL + id;

  request(requestOptions, function(err, res, body) {
    if (err) return callback(err);
    if (res.statusCode !== 200) {
      return callback(new Error('Video not found: ' + url));
    }

    var info = { id: id };
    var info = qs.parse(body);

    if (info.status === 'fail') {
      callback(new Error('Error ' + info.errorcode + ': ' + info.reason));;
      return;
    }

    // split some keys by commas
    KEYS_TO_SPLIT.forEach(function(key) {
      info[key] = info[key].split(',').filter(function(v) { return v !== ''; });
    });

    // convert some strings to javascript numbers and booleans
    info = Hash.map(info, function(val, key) {
      var intVal = parseInt(val);
      var floatVal = parseFloat(val);

      if (intVal.toString() === val) {
        return intVal;
      } else if (floatVal.toString() === val) {
        return floatVal;
      } else if (val === 'True') {
        return true;
      } else if (val === 'False') {
        return false;
      } else {
        return val;
      }
    });

    // a few more manual data cleansing
    var fmt_map = info.url_encoded_fmt_stream_map =
      qs.parse(info.url_encoded_fmt_stream_map);

    fmt_map.itag = fmt_map.itag
      .map(function(itag) {
        var split = itag.split(',');
        return { itag: split[0], url: split[1] ? split[1].slice(4) : null };
      })
      ;

    info.fmt_list = info.fmt_list.map(function(fmt) {
      return fmt.split('/');
    });

    info.formats = info.fmt_list
      .map(function(format, i) {
        var itag = format[0];
        format = FORMATS[itag];

        if (!format) {
          err = new Error('Unrecognized format itag: ' + itag);
          return;
        }

        format               = Hash.copy(format);
        format.quality       = fmt_map.quality[i];
        format.fallback_host = fmt_map.fallback_host[i];
        format.type          = fmt_map.type[i];
        format.url           = fmt_map.itag[i].url;

        return format;
      })
      .filter(function(format) {
        return !!format.url;
      })
      ;

    if (err) {
      callback(err);
    }

    info.video_verticals = info.video_verticals
      .slice(1, -1)
      .split(', ')
      .filter(function(val) { return val !== ''; })
      .map(function(val) { return parseInt(val); })
      ;

    cache.set(id, info);
    callback(null, info);
  });
}

/**
 * export
 */
module.exports = ytdl;
<commit_msg>safety check<commit_after>var url       = require('url');
var qs        = require('querystring');
var http      = require('http');
var EventVat  = require('eventvat');
var request   = require('request');
var Streamify = require('streamify');
var Hash      = require('hashish');
var FORMATS   = require('./formats');
var parseTime = require('./util').parseTime;

var INFO_URL = 'http://www.youtube.com/get_video_info?hl=en_US&video_id=';
var KEYS_TO_SPLIT = [
  'keywords'
, 'fmt_list'
, 'fexp'
, 'watermark'
, 'ad_channel_code_overlay'
];

// cache for recently looked up video infos
var cache = new EventVat({ autoexpire: 30 });

/**
 * Checks cache to see if info for this video has been fetched recently.
 * If not, get it, then download the video once the info is found.
 *
 * @param (string) link
 * @param (Object) options
 * @return (ReadableStream)
 */
function ytdl(link, options) {
  var id = url.parse(link, true).query.v;
  var stream = new Streamify({ superCtor: http.ClientResponse });

  options = options || {};
  options.quality = 'highest';

  if (cache.exists(id)) {
    var info = cache.get(id);
    process.nextTick(function() {
      download(link, options, stream, info);
    });

  } else {
    ytdl.getInfo(link, options, function(err, info) {
      if (err) {
        stream.emit('error', err);
        stream.readable = false;
        return;
      }

      download(link, options, stream, info);
    });
  }

  return stream;
}

/**
 * Downloads a video.
 *
 * @param (string) link
 * @param (Object) options
 * @param (ReadableStream) stream
 * @param (Object) info
 * @return (ReadableStream)
 */
function download(link, options, stream, info) {
  var formats = info.formats;
  if (options.filter) {
    formats = formats.filter(options.filter);
  }

  var format;
  switch (options.quality) {
    case 'highest':
      format = formats[0];
      break;

    case 'lowest':
      format = formats[formats.length - 1];
      break;

    default:
      format = formats.filter(function(format) {
        return format.itag === options.quality;
      })[0];

  }

  if (!format) {
    stream.emit('error', new Error('No such format found'));
    return;
  }

  format = Hash.copy(format);
  var url = format.url;
  var requestOptions = Hash.copy(options);

  if (options.start) {
    url = url + '&begin=' + parseTime(options.start);
  }

  format.url = requestOptions.url = url;

  // start downloading the video
  var res = request(requestOptions);
  res.on('response', function(res) {
    format.size = res.headers['content-length'];
    stream.emit('info', info, format);
  });
  stream.resolve(res);
}

/**
 * Gets info from a video.
 *
 * @param (string) link
 * @param (Object) requestOptions
 * @param (Function(Error, Object)) callback
 */
ytdl.getInfo = function getInfo(link, requestOptions, callback) {
  if (typeof requestOptions === 'function') {
    callback = requestOptions;
    requestOptions = {};
  } else {
    requestOptions = Hash.copy(requestOptions);
  }

  var id = url.parse(link, true).query.v;
  if (!id) {
    process.nextTick(function() {
      callback(new Error('Video ID not found'));
    });
    return;
  }

  requestOptions.url = INFO_URL + id;

  request(requestOptions, function(err, res, body) {
    if (err) return callback(err);
    if (res.statusCode !== 200) {
      return callback(new Error('Video not found: ' + url));
    }

    var info = { id: id };
    var info = qs.parse(body);

    if (info.status === 'fail') {
      callback(new Error('Error ' + info.errorcode + ': ' + info.reason));;
      return;
    }

    // split some keys by commas
    KEYS_TO_SPLIT.forEach(function(key) {
      if (!info[key]) return;
      info[key] = info[key].split(',').filter(function(v) { return v !== ''; });
    });

    // convert some strings to javascript numbers and booleans
    info = Hash.map(info, function(val, key) {
      var intVal = parseInt(val);
      var floatVal = parseFloat(val);

      if (intVal.toString() === val) {
        return intVal;
      } else if (floatVal.toString() === val) {
        return floatVal;
      } else if (val === 'True') {
        return true;
      } else if (val === 'False') {
        return false;
      } else {
        return val;
      }
    });

    // a few more manual data cleansing
    var fmt_map = info.url_encoded_fmt_stream_map =
      qs.parse(info.url_encoded_fmt_stream_map);

    fmt_map.itag = fmt_map.itag
      .map(function(itag) {
        var split = itag.split(',');
        return { itag: split[0], url: split[1] ? split[1].slice(4) : null };
      })
      ;

    info.fmt_list = info.fmt_list.map(function(fmt) {
      return fmt.split('/');
    });

    info.formats = info.fmt_list
      .map(function(format, i) {
        var itag = format[0];
        format = FORMATS[itag];

        if (!format) {
          err = new Error('Unrecognized format itag: ' + itag);
          return;
        }

        format               = Hash.copy(format);
        format.quality       = fmt_map.quality[i];
        format.fallback_host = fmt_map.fallback_host[i];
        format.type          = fmt_map.type[i];
        format.url           = fmt_map.itag[i].url;

        return format;
      })
      .filter(function(format) {
        return !!format.url;
      })
      ;

    if (err) {
      callback(err);
    }

    info.video_verticals = info.video_verticals
      .slice(1, -1)
      .split(', ')
      .filter(function(val) { return val !== ''; })
      .map(function(val) { return parseInt(val); })
      ;

    cache.set(id, info);
    callback(null, info);
  });
}

/**
 * export
 */
module.exports = ytdl;
<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var assert = require('assert');
var Parser = require('stream-parser');
var inherits = require('util').inherits;
var Transform = require('stream').Transform;

// node v0.8.x compat
if (!Transform) Transform = require('readable-stream/transform');

/**
 * Module exports.
 */

module.exports = Throttle;

function Throttle (opts) {
  if (!(this instanceof Throttle)) return new Throttle(opts);

  if ('number' == typeof opts) opts = { bps: opts };
  if (!opts) opts = {};
  if (null == opts.lowWaterMark) opts.lowWaterMark = 0;
  if (null == opts.highWaterMark) opts.highWaterMark = 0;
  if (null == opts.bps) throw new Error('must pass a "bps" bytes-per-second option');
  if (null == opts.chunkSize) opts.chunkSize = opts.bps / 10 | 0; // better size?

  Transform.call(this, opts);

  this.bps = opts.bps;
  this.chunkSize = opts.chunkSize;

  this.totalBytes = 0;
  this.startTime = Date.now();

  this._passthroughChunk();
}
inherits(Throttle, Transform);

/**
 * Mixin `Parser`.
 */

Parser(Throttle.prototype);

/**
 * @api private
 */

Throttle.prototype._passthroughChunk = function () {
  this._passthrough(this.chunkSize, this._onchunk);
  this.totalBytes += this.chunkSize;
};

/**
 * @api private
 */

Throttle.prototype._onchunk = function (output, done) {
  var self = this;
  var totalSeconds = (Date.now() - this.startTime) / 1000;
  var expected = totalSeconds * this.bps;

  function d () {
    self._passthroughChunk();
    done();
  }

  if (this.totalBytes > expected) {
    // Use this byte count to calculate how many seconds ahead we are.
    var remainder = this.totalBytes - expected;
    var sleepTime = remainder / this.bps * 1000;
    //console.error('sleep time: %d', sleepTime);
    if (sleepTime > 0) {
      setTimeout(d, sleepTime);
    } else {
      d();
    }
  }
};
<commit_msg>comment<commit_after>
/**
 * Module dependencies.
 */

var assert = require('assert');
var Parser = require('stream-parser');
var inherits = require('util').inherits;
var Transform = require('stream').Transform;

// node v0.8.x compat
if (!Transform) Transform = require('readable-stream/transform');

/**
 * Module exports.
 */

module.exports = Throttle;

/**
 * The `Throttle` passthrough stream class.
 *
 * @param {Number|Object} opts an options object or the "bps" Number value
 * @api public
 */

function Throttle (opts) {
  if (!(this instanceof Throttle)) return new Throttle(opts);

  if ('number' == typeof opts) opts = { bps: opts };
  if (!opts) opts = {};
  if (null == opts.lowWaterMark) opts.lowWaterMark = 0;
  if (null == opts.highWaterMark) opts.highWaterMark = 0;
  if (null == opts.bps) throw new Error('must pass a "bps" bytes-per-second option');
  if (null == opts.chunkSize) opts.chunkSize = opts.bps / 10 | 0; // better size?

  Transform.call(this, opts);

  this.bps = opts.bps;
  this.chunkSize = opts.chunkSize;

  this.totalBytes = 0;
  this.startTime = Date.now();

  this._passthroughChunk();
}
inherits(Throttle, Transform);

/**
 * Mixin `Parser`.
 */

Parser(Throttle.prototype);

/**
 * @api private
 */

Throttle.prototype._passthroughChunk = function () {
  this._passthrough(this.chunkSize, this._onchunk);
  this.totalBytes += this.chunkSize;
};

/**
 * @api private
 */

Throttle.prototype._onchunk = function (output, done) {
  var self = this;
  var totalSeconds = (Date.now() - this.startTime) / 1000;
  var expected = totalSeconds * this.bps;

  function d () {
    self._passthroughChunk();
    done();
  }

  if (this.totalBytes > expected) {
    // Use this byte count to calculate how many seconds ahead we are.
    var remainder = this.totalBytes - expected;
    var sleepTime = remainder / this.bps * 1000;
    //console.error('sleep time: %d', sleepTime);
    if (sleepTime > 0) {
      setTimeout(d, sleepTime);
    } else {
      d();
    }
  }
};
<|endoftext|>


<commit_before>require('babel-polyfill')
const snakeCase = require('snake-case')
const {
  FusionEmitter,
  ListenerSet,
  validIndexValue,
} = require('./utility.js')

const PROTOCOL_VERSION = 'rethinkdb-fusion-v0'

function responseEvent(id){
  return `response:${id}`
}
function errorEvent(id){
  return `error:${id}`
}

var fusionCount = 0

class Fusion extends FusionEmitter {
  constructor(host, {secure: secure=true}={}){
    super(`Fusion(${fusionCount++})`)

    // Allow calling a fusion object
    let self = name => this.collection(name)
    Object.setPrototypeOf(self, this)

    this.host = host
    this.secure = secure
    this.requestCounter = 0
    this.socket = new FusionSocket(host, secure)
    this.listenerSet = ListenerSet.absorbEmitter(this.socket)
      .onceAndDispose('error', (err) => this.emit('error', err, self))
      .on('connected', () => this.emit('connected', self))
      .on('disconnected', () => this.emit('disconnected', self))
    // send handshake
    this.handshakeResponse = this.socket.getPromise('connected').then(() => {
      let reqId = this.requestCounter++
      this.socket.send({request_id: reqId})
      return this.socket.getPromise(responseEvent(reqId)).then((res) => {
        return res
      })
    }).catch(() => {})
    return self
  }

  dispose(reason='Fusion object disposed'){
    // The listenerSet owns the fusionSocket, so will dispose of it.
    return this.listenerSet.dispose(reason)
  }

  collection(collectionName){
    return new Collection(this, collectionName)
  }

  writeOp(opType, collectionName, documents){
    let command = {data: documents, collection: collectionName}
    return this._send(opType, command).intoCollectingPromise('response')
  }

  query(data){
    return this._send('query', data).intoCollectingPromise('response')
  }

  subscribe(query, updates=true){
    if(updates){
      return this._subscribe(query)
    }else{
      return this._send('query', query)
    }
  }

  endSubscription(requestId){
    return this.handshakeResponse.then(handshake => {
      this.socket.send({request_id: requestId, type: 'end_subscription'})
    })
  }

  _send(type, data){
    let requestId = this.requestCounter++
    let req = {type: type, options: data, request_id: requestId}
    this.handshakeResponse.then((handshake) => {
      this.socket.send(req)
    })
    return new RequestEmitter(requestId, this, 'query')
  }

  // Subscription queries, only handles returning an emitter
  _subscribe(query){
    let requestId = this.requestCounter++
    let req = {type: 'subscribe', options: query, request_id: requestId}
    this.handshakeResponse.then((handshake) => this.socket.send(req))
    return new RequestEmitter(requestId, this, 'subscribe')
  }

}

var socketCount = 0

class FusionSocket extends FusionEmitter {
  // Wraps native websockets in an EventEmitter, and deals with some
  // simple protocol level things like serializing from/to JSON, and
  // emitting events on request_ids
  constructor(host, secure=true){
    super(`FusionSocket(${socketCount++})`)
    let hostString = (secure ? 'wss://' : 'ws://') + host
    this._ws = new WebSocket(hostString, PROTOCOL_VERSION)
    this._openWs = new Promise((resolve, reject) => {
      this._ws.onopen = (event) => {
        this.emit('connected', event)
        resolve(this._ws)
      }
      this._ws.onerror = (event) => {
        this.emit('error', event)
        reject(event)
      }
      this._ws.onclose = (event) => {
        this.emit('disconnected', event)
        reject(event)
      }
    })
    this._ws.onmessage = (event) => {
      let data = JSON.parse(event.data)
      if(data.request_id === undefined){
        this.emit("error", "Request id undefined", data)
      }else if(data.error !== undefined){
        this.emit(errorEvent(data.request_id), data)
      }else {
        this.emit(responseEvent(data.request_id), data)
      }
    }
  }

  send(message){
    if(typeof message !== 'string'){
      message = JSON.stringify(message)
    }
    this._openWs.then((ws) => ws.send(message))
  }

  dispose(reason='FusionSocket disposed'){
    this._ws.close(1000, reason)
    return this.getPromise('disconnected')
  }

}

// These are created for requests, will only get messages for the
// particular request id. Has extra methods for closing the request,
// and for creating promises that resolve or reject based on events
// from this emitter
class RequestEmitter extends FusionEmitter {
  constructor(requestId, fusion, queryType){
    super(`RequestEmitter(${requestId})`)
    this.fusion = fusion
    this.requestId = requestId
    this.queryType = queryType
    this.remoteListeners = ListenerSet.onEmitter(this.fusion.socket)
    //Forwards on fusion events to this emitter's listeners
    this.remoteListeners
      .fwd('connected', this)
      .fwd('disconnected', this)
      .onceAndDispose(errorEvent(requestId), (err) => {
        this.emit('error', err)
      }).on(responseEvent(requestId), (response) => {
        if(Array.isArray(response.data)){
          let emitted = false
          response.data.forEach(changeObj => {
            emitted = this._emitChangeEvent(changeObj)
          })
          if(!emitted){
              this.emit('response', response.data)
          }
        }else if(response.data !== undefined){
          this.emit("response", response.data)
        }
        if(response.state === 'synced'){
          this.emit('synced')
        }else if(response.state === 'complete'){
          this.emit('complete')
        }
      }).disposeOn('error')
  }

  _emitChangeEvent(changeObj){
    if(changeObj.new_val === undefined && changeObj.old_val === undefined){
      return false
    }
    if(changeObj.new_val !== null && changeObj.old_val !== null){
      if(this.listenerCount('changed') === 0){
        this.emit('removed', changeObj.old_val)
        this.emit('added', changeObj.new_val)
      }else{
        this.emit('changed', changeObj.new_val, changeObj.old_val)
      }
      return true
    }else if(changeObj.new_val !== null && changeObj.old_val === null){
      this.emit('added', changeObj.new_val)
      return true
    }else if(changeObj.new_val === null && changeObj.old_val !== null){
      this.emit('removed', changeObj.old_val)
      return true
    }else{
      return false
    }
  }
  dispose(reason=`RequestEmitter for ${this.requestId} disposed`){
    if(this.type === 'subscription'){
      return this.fusion.endSubscription(this.requestId)
        .then(() => this.getPromise('complete'))
        .then(() => this.remoteListeners.dispose(reason))
    }else{
      return this.remoteListeners.dispose(reason)
    }
  }
}

class TermBase {

  constructor(fusion, query, mergeObj){
    this.fusion = fusion
    for(let key in Object.keys(mergeObj)){
      if(key in query){
        throw new Error(`${key} is already defined.`)
      }
    }
    this.query = Object.assign({}, query, mergeObj)
  }

  subscribe(updates=true){
    // should create a changefeed query, return eventemitter
    return this.fusion.subscribe(this.query, updates)
  }

  value(){
    // return promise with no changefeed
    return this.fusion.query(this.query)
  }

  // Extend the object with the specified methods. Will fill in
  // error-raising methods for methods not specified, or if the method
  // has already been added to the query object.
  _extendWith(...keys){
    let methods = {
      findAll: FindAll.method,
      find: Find.method,
      order: Order.method,
      above: Above.method,
      below: Below.method,
      limit: Limit.method,
    }
    for(let key in methods){
      if(key in keys){
        // Check if query object already has it. If so, insert a dummy
        // method that throws an error.
        if(snakeCase(key) in this.query){
          this[key] = function(){
            throw new Error(`${key} has already been called on this query`)
          }
        }else{
          this[key] = methods[key]
        }
      }else{
        this[key] = function(){
          throw new Error(`it is not valid to chain the method ${key} from here`)
        }
      }
    }
  }
}

class Collection extends TermBase {

  constructor(fusion, collectionName){
    super(fusion, {}, {
      collection: collectionName,
    })
    this._extendWith('find', 'findAll', 'order', 'above', 'below', 'limit')
  }

  store(documents){
    return this._writeOp('store', documents)
  }

  upsert(documents){
    return this._writeOp('upsert', documents)
  }

  insert(documents){
    return this._writeOp('insert', documents)
  }

  replace(documents){
    return this._writeOp('replace', documents)
  }

  update(documents){
    return this._writeOp('update', documents)
  }

  remove(documentOrId){
    if(arguments.length > 1){
      throw new Error("remove takes exactly one argument")
    }
    if(documentOrId === undefined){
      throw new Error("remove must be given an argument")
    }
    if(documentOrId === null){
      throw new Error("remove must receive a non-null argument")
    }
    if(validIndexValue(documentOrId)){
      documentOrId = {id: documentOrId}
    }
    return this._writeOp('remove', [documentOrId]).then(() => undefined)
  }

  removeAll(documentsOrIds){
    if(!Array.isArray(documentsOrIds)){
      throw new Error("removeAll takes an array as an argument")
    }
    if(arguments.length > 1){
      throw new Error("removeAll only takes one argument (an array)")
    }
    documentsOrIds = documentsOrIds.map(item => {
      if(validIndexValue(item)){
        return {id: item}
      }else{
        return item
      }
    })
    return this._writeOp('remove', documentsOrIds).then(() => undefined)
  }

  _writeOp(name, documents){
    if(documents == null){
      throw new Error(`${name} must receive a non-null argument`)
    }else if(!Array.isArray(documents)){
      documents = [documents]
    }else if(documents.length === 0){
      // Don't bother sending no-ops to the server
      return Promise.resolve([])
    }
    return this.fusion.writeOp(name, this._collectionName, documents)
  }
}

class FindAll extends TermBase {
  constructor(fusion, query, fieldValues, allowChaining){
    super(fusion, query, {
      find_all: fieldValues,
    })
    if(allowChaining){
      this._extendWith('order', 'above', 'below', 'limit')
    }else{
      this._extendWith()
    }
  }

  static method(...fieldValues){
    let fieldName = 'id'
    if(arguments.length === 0){
      throw new Error(`findAll must take at least one argument`)
    }
    let allowChaining = arguments.length === 1
    return new FindAll(this.fusion, this.query, fieldValues, allowChaining)
  }
}

class Find extends TermBase {
  constructor(fusion, query, queryObject){
    super(fusion, query, {
      find: queryObject,
    })
    this._extendWith()
  }

  static method(idOrObject){
    let q = validIndexValue(idOrObject) ? {id: idOrObject} : idOrObject
    return new Find(this.fusion, this.query, idOrObject)
  }
}

class Above extends TermBase {
  constructor(fusion, query, valueSpecs, direction){
    super(fusion, query, {
      below: [valueSpecs, direction]
    })
    this._extendWith('findAll', 'order', 'below', 'limit')
  }

  static method(aboveSpec, bound="closed"){
    return new Above(this.fusion, this.query, aboveSpec, bound)
  }
}

class Below extends TermBase {
  constructor(fusion, query, valueSpecs, direction){
    super(fusion, query, {
      above: [valueSpecs, direction],
    })
    this._extendWith('findAll', 'order', 'above', 'limit')
  }

  static method(belowSpec, bound="open"){
    return new Below(this.fusion, this.query, belowSpec, bound)
  }
}

class Order extends TermBase {
  constructor(fusion, query, fields, direction){
    super(fusion, query, {
      order: [fields, direction],
    })
    this._extendWith('findAll', 'above', 'below', 'limit')
  }

  static method(fields, direction='ascending'){
    return new Order(this.fusion, this.query, fields, direction)
  }
}

class Limit extends TermBase {
  constructor(fusion, query, size){
    super(fusion, query, {limit: size})
    this._extendWith()
  }

  static method(size){
    return new Limit(this.fusion, this.query, size)
  }
}

module.exports = Fusion
<commit_msg>Add collectionName back onto write operations<commit_after>require('babel-polyfill')
const snakeCase = require('snake-case')
const {
  FusionEmitter,
  ListenerSet,
  validIndexValue,
} = require('./utility.js')

const PROTOCOL_VERSION = 'rethinkdb-fusion-v0'

function responseEvent(id){
  return `response:${id}`
}
function errorEvent(id){
  return `error:${id}`
}

var fusionCount = 0

class Fusion extends FusionEmitter {
  constructor(host, {secure: secure=true}={}){
    super(`Fusion(${fusionCount++})`)

    // Allow calling a fusion object
    let self = name => this.collection(name)
    Object.setPrototypeOf(self, this)

    this.host = host
    this.secure = secure
    this.requestCounter = 0
    this.socket = new FusionSocket(host, secure)
    this.listenerSet = ListenerSet.absorbEmitter(this.socket)
      .onceAndDispose('error', (err) => this.emit('error', err, self))
      .on('connected', () => this.emit('connected', self))
      .on('disconnected', () => this.emit('disconnected', self))
    // send handshake
    this.handshakeResponse = this.socket.getPromise('connected').then(() => {
      let reqId = this.requestCounter++
      this.socket.send({request_id: reqId})
      return this.socket.getPromise(responseEvent(reqId)).then((res) => {
        return res
      })
    }).catch(() => {})
    return self
  }

  dispose(reason='Fusion object disposed'){
    // The listenerSet owns the fusionSocket, so will dispose of it.
    return this.listenerSet.dispose(reason)
  }

  collection(collectionName){
    return new Collection(this, collectionName)
  }

  writeOp(opType, collectionName, documents){
    let command = {data: documents, collection: collectionName}
    return this._send(opType, command).intoCollectingPromise('response')
  }

  query(data){
    return this._send('query', data).intoCollectingPromise('response')
  }

  subscribe(query, updates=true){
    if(updates){
      return this._subscribe(query)
    }else{
      return this._send('query', query)
    }
  }

  endSubscription(requestId){
    return this.handshakeResponse.then(handshake => {
      this.socket.send({request_id: requestId, type: 'end_subscription'})
    })
  }

  _send(type, data){
    let requestId = this.requestCounter++
    let req = {type: type, options: data, request_id: requestId}
    this.handshakeResponse.then((handshake) => {
      this.socket.send(req)
    })
    return new RequestEmitter(requestId, this, 'query')
  }

  // Subscription queries, only handles returning an emitter
  _subscribe(query){
    let requestId = this.requestCounter++
    let req = {type: 'subscribe', options: query, request_id: requestId}
    this.handshakeResponse.then((handshake) => this.socket.send(req))
    return new RequestEmitter(requestId, this, 'subscribe')
  }

}

var socketCount = 0

class FusionSocket extends FusionEmitter {
  // Wraps native websockets in an EventEmitter, and deals with some
  // simple protocol level things like serializing from/to JSON, and
  // emitting events on request_ids
  constructor(host, secure=true){
    super(`FusionSocket(${socketCount++})`)
    let hostString = (secure ? 'wss://' : 'ws://') + host
    this._ws = new WebSocket(hostString, PROTOCOL_VERSION)
    this._openWs = new Promise((resolve, reject) => {
      this._ws.onopen = (event) => {
        this.emit('connected', event)
        resolve(this._ws)
      }
      this._ws.onerror = (event) => {
        this.emit('error', event)
        reject(event)
      }
      this._ws.onclose = (event) => {
        this.emit('disconnected', event)
        reject(event)
      }
    })
    this._ws.onmessage = (event) => {
      let data = JSON.parse(event.data)
      if(data.request_id === undefined){
        this.emit("error", "Request id undefined", data)
      }else if(data.error !== undefined){
        this.emit(errorEvent(data.request_id), data)
      }else {
        this.emit(responseEvent(data.request_id), data)
      }
    }
  }

  send(message){
    if(typeof message !== 'string'){
      message = JSON.stringify(message)
    }
    this._openWs.then((ws) => ws.send(message))
  }

  dispose(reason='FusionSocket disposed'){
    this._ws.close(1000, reason)
    return this.getPromise('disconnected')
  }

}

// These are created for requests, will only get messages for the
// particular request id. Has extra methods for closing the request,
// and for creating promises that resolve or reject based on events
// from this emitter
class RequestEmitter extends FusionEmitter {
  constructor(requestId, fusion, queryType){
    super(`RequestEmitter(${requestId})`)
    this.fusion = fusion
    this.requestId = requestId
    this.queryType = queryType
    this.remoteListeners = ListenerSet.onEmitter(this.fusion.socket)
    //Forwards on fusion events to this emitter's listeners
    this.remoteListeners
      .fwd('connected', this)
      .fwd('disconnected', this)
      .onceAndDispose(errorEvent(requestId), (err) => {
        this.emit('error', err)
      }).on(responseEvent(requestId), (response) => {
        if(Array.isArray(response.data)){
          let emitted = false
          response.data.forEach(changeObj => {
            emitted = this._emitChangeEvent(changeObj)
          })
          if(!emitted){
              this.emit('response', response.data)
          }
        }else if(response.data !== undefined){
          this.emit("response", response.data)
        }
        if(response.state === 'synced'){
          this.emit('synced')
        }else if(response.state === 'complete'){
          this.emit('complete')
        }
      }).disposeOn('error')
  }

  _emitChangeEvent(changeObj){
    if(changeObj.new_val === undefined && changeObj.old_val === undefined){
      return false
    }
    if(changeObj.new_val !== null && changeObj.old_val !== null){
      if(this.listenerCount('changed') === 0){
        this.emit('removed', changeObj.old_val)
        this.emit('added', changeObj.new_val)
      }else{
        this.emit('changed', changeObj.new_val, changeObj.old_val)
      }
      return true
    }else if(changeObj.new_val !== null && changeObj.old_val === null){
      this.emit('added', changeObj.new_val)
      return true
    }else if(changeObj.new_val === null && changeObj.old_val !== null){
      this.emit('removed', changeObj.old_val)
      return true
    }else{
      return false
    }
  }
  dispose(reason=`RequestEmitter for ${this.requestId} disposed`){
    if(this.type === 'subscription'){
      return this.fusion.endSubscription(this.requestId)
        .then(() => this.getPromise('complete'))
        .then(() => this.remoteListeners.dispose(reason))
    }else{
      return this.remoteListeners.dispose(reason)
    }
  }
}

class TermBase {

  constructor(fusion, query, mergeObj){
    this.fusion = fusion
    for(let key in Object.keys(mergeObj)){
      if(key in query){
        throw new Error(`${key} is already defined.`)
      }
    }
    this.query = Object.assign({}, query, mergeObj)
  }

  subscribe(updates=true){
    // should create a changefeed query, return eventemitter
    return this.fusion.subscribe(this.query, updates)
  }

  value(){
    // return promise with no changefeed
    return this.fusion.query(this.query)
  }

  // Extend the object with the specified methods. Will fill in
  // error-raising methods for methods not specified, or if the method
  // has already been added to the query object.
  _extendWith(...keys){
    let methods = {
      findAll: FindAll.method,
      find: Find.method,
      order: Order.method,
      above: Above.method,
      below: Below.method,
      limit: Limit.method,
    }
    for(let key in methods){
      if(key in keys){
        // Check if query object already has it. If so, insert a dummy
        // method that throws an error.
        if(snakeCase(key) in this.query){
          this[key] = function(){
            throw new Error(`${key} has already been called on this query`)
          }
        }else{
          this[key] = methods[key]
        }
      }else{
        this[key] = function(){
          throw new Error(`it is not valid to chain the method ${key} from here`)
        }
      }
    }
  }
}

class Collection extends TermBase {

  constructor(fusion, collectionName){
    super(fusion, {
      collection: collectionName,
    }, {})
    this._collectionName = collectionName
    this._extendWith('find', 'findAll', 'order', 'above', 'below', 'limit')
  }

  store(documents){
    return this._writeOp('store', documents)
  }

  upsert(documents){
    return this._writeOp('upsert', documents)
  }

  insert(documents){
    return this._writeOp('insert', documents)
  }

  replace(documents){
    return this._writeOp('replace', documents)
  }

  update(documents){
    return this._writeOp('update', documents)
  }

  remove(documentOrId){
    if(arguments.length > 1){
      throw new Error("remove takes exactly one argument")
    }
    if(documentOrId === undefined){
      throw new Error("remove must be given an argument")
    }
    if(documentOrId === null){
      throw new Error("remove must receive a non-null argument")
    }
    if(validIndexValue(documentOrId)){
      documentOrId = {id: documentOrId}
    }
    return this._writeOp('remove', [documentOrId]).then(() => undefined)
  }

  removeAll(documentsOrIds){
    if(!Array.isArray(documentsOrIds)){
      throw new Error("removeAll takes an array as an argument")
    }
    if(arguments.length > 1){
      throw new Error("removeAll only takes one argument (an array)")
    }
    documentsOrIds = documentsOrIds.map(item => {
      if(validIndexValue(item)){
        return {id: item}
      }else{
        return item
      }
    })
    return this._writeOp('remove', documentsOrIds).then(() => undefined)
  }

  _writeOp(name, documents){
    if(documents == null){
      throw new Error(`${name} must receive a non-null argument`)
    }else if(!Array.isArray(documents)){
      documents = [documents]
    }else if(documents.length === 0){
      // Don't bother sending no-ops to the server
      return Promise.resolve([])
    }
    return this.fusion.writeOp(name, this._collectionName, documents)
  }
}

class FindAll extends TermBase {
  constructor(fusion, query, fieldValues, allowChaining){
    super(fusion, query, {
      find_all: fieldValues,
    })
    if(allowChaining){
      this._extendWith('order', 'above', 'below', 'limit')
    }else{
      this._extendWith()
    }
  }

  static method(...fieldValues){
    let fieldName = 'id'
    if(arguments.length === 0){
      throw new Error(`findAll must take at least one argument`)
    }
    let allowChaining = arguments.length === 1
    return new FindAll(this.fusion, this.query, fieldValues, allowChaining)
  }
}

class Find extends TermBase {
  constructor(fusion, query, queryObject){
    super(fusion, query, {
      find: queryObject,
    })
    this._extendWith()
  }

  static method(idOrObject){
    let q = validIndexValue(idOrObject) ? {id: idOrObject} : idOrObject
    return new Find(this.fusion, this.query, idOrObject)
  }
}

class Above extends TermBase {
  constructor(fusion, query, valueSpecs, direction){
    super(fusion, query, {
      below: [valueSpecs, direction]
    })
    this._extendWith('findAll', 'order', 'below', 'limit')
  }

  static method(aboveSpec, bound="closed"){
    return new Above(this.fusion, this.query, aboveSpec, bound)
  }
}

class Below extends TermBase {
  constructor(fusion, query, valueSpecs, direction){
    super(fusion, query, {
      above: [valueSpecs, direction],
    })
    this._extendWith('findAll', 'order', 'above', 'limit')
  }

  static method(belowSpec, bound="open"){
    return new Below(this.fusion, this.query, belowSpec, bound)
  }
}

class Order extends TermBase {
  constructor(fusion, query, fields, direction){
    super(fusion, query, {
      order: [fields, direction],
    })
    this._extendWith('findAll', 'above', 'below', 'limit')
  }

  static method(fields, direction='ascending'){
    return new Order(this.fusion, this.query, fields, direction)
  }
}

class Limit extends TermBase {
  constructor(fusion, query, size){
    super(fusion, query, {limit: size})
    this._extendWith()
  }

  static method(size){
    return new Limit(this.fusion, this.query, size)
  }
}

module.exports = Fusion
<|endoftext|>


<commit_before>var request = require('request-promise');

module.exports = function (context, message) {

    let path = '/' + message.social.facebook.id + '?fields=email,first_name,last_name,birthday,locale,location,picture.width(500),about,education,friends,hometown,photos,relationship_status,religion,political,tagged_places,work,posts';

    let options = {
        uri: "https://graph.facebook.com" + path,
        method: 'GET',
        headers: {
            'Authorization': 'OAuth ' + message.social.facebook.token
        }
    };

    request(options)
        .then((response) => {
            let data = message;

            data.response = {
                platform: "facebook",
                type: "profile",
                data: response
            };

            context.bindings.out = data;
        })
        .catch((error) => context.log(error))
        .finally(() => context.done());
}<commit_msg>Converting response to JSON object (from string)<commit_after>var request = require('request-promise');

module.exports = function (context, message) {

    let path = '/' + message.social.facebook.id + '?fields=email,first_name,last_name,birthday,locale,location,picture.width(500),about,education,friends,hometown,photos,relationship_status,religion,political,tagged_places,work,posts';

    let options = {
        uri: "https://graph.facebook.com" + path,
        method: 'GET',
        headers: {
            'Authorization': 'OAuth ' + message.social.facebook.token
        }
    };

    request(options)
        .then((response) => {
            let data = message;

            data.response = {
                platform: "facebook",
                type: "profile",
                data: JSON.parse(response)
            };

            context.bindings.out = data;
        })
        .catch((error) => context.log(error))
        .finally(() => context.done());
}<|endoftext|>


<commit_before>/**
 * Satellizer
 * (c) 2014 Sahat Yalkabov <sahat@me.com>
 * License: MIT
 */

(function(window, angular, undefined) {
  'use strict';

  angular.module('Satellizer', [])
    .provider('$auth', $auth)
    .factory('RunBlock', RunBlock)
    .factory('Local', Local)
    .factory('Oauth2', Oauth2)
    .factory('Oauth1', Oauth1)
    .factory('Popup', Popup)
    .service('Utils', Utils)
    .config(httpInterceptor)
    .run(onRun);

  var config = {
    logoutRedirect: '/',
    loginRedirect: '/',
    loginUrl: '/auth/login',
    signupUrl: '/auth/signup',
    signupRedirect: '/login',
    loginRoute: '/login',
    signupRoute: '/signup',
    user: 'currentUser'
  };

  var providers = {
    google: {
      url: '/auth/google',
      authorizationEndpoint: 'https://accounts.google.com/o/oauth2/auth',
      redirectUri: window.location.origin,
      scope: 'openid profile email',
      scopeDelimiter: ' ',
      requiredUrlParams: ['scope'],
      optionalUrlParams: ['display'],
      display: 'popup',
      type: '2.0',
      popupOptions: {
        width: 452,
        height: 633
      }
    },
    facebook: {
      url: '/auth/facebook',
      authorizationEndpoint: 'https://www.facebook.com/dialog/oauth',
      redirectUri: window.location.origin + '/',
      scope: 'email',
      scopeDelimiter: ',',
      requiredUrlParams: ['display'],
      display: 'popup',
      type: '2.0',
      popupOptions: {
        width: 481,
        height: 269
      }
    },
    linkedin: {
      url: '/auth/linkedin',
      authorizationEndpoint: 'https://www.linkedin.com/uas/oauth2/authorization',
      redirectUri: window.location.origin,
      requiredUrlParams: ['state'],
      scope: [],
      scopeDelimiter: ' ',
      state: 'STATE',
      type: '2.0',
      popupOptions: {
        width: 527,
        height: 582
      }
    },
    twitter: {
      url: '/auth/twitter',
      type: '1.0'
    }
  };

  function $auth() {

    this.config = config;

    this.setProvider = function(params) {
      angular.extend(providers[params.name], params);
    };

    this.addProvider = function(params) {
      providers[params.name] = {};
      angular.extend(providers[params.name], params);
    };

    this.getProvider = function(name) {
      return providers[name];
    };

    this.$get = function($q, $http, Oauth1, Oauth2, Local) {

      var $auth = {};

      $auth.authenticate = function(name) {
        var deferred = $q.defer();
        var provider = (providers[name].type === '1.0') ? Oauth1 : Oauth2;
        provider.open(providers[name]).then(function(response) {
          Local.parseUser(response.token, deferred);
        });
        return deferred.promise;
      };

      $auth.login = function(user) {
        Local.login(user);
      };

      $auth.signup = function(user) {
        Local.signup(user);
      };

      $auth.logout = function() {
        Local.logout();
      };

      $auth.isAuthenticated = function() {
        Local.isAuthenticated();
      };

      return $auth;
    };

  }

  function Local($q, $http, $window, $rootScope, $location) {

    var local = {};

    local.parseUser = function(token, deferred) {
      var payload = JSON.parse($window.atob(token.split('.')[1]));
      $window.localStorage.jwtToken = token;
      $rootScope[config.user] = payload.user;
      $location.path(config.loginRedirect);
      deferred.resolve(payload.user);
    };

    local.login = function(user) {
      var deferred = $q.defer();
      $http.post(config.loginUrl, user)
        .success(function(response) {
          local.parseUser(response.token, deferred);
        })
        .error(function(error) {
          deferred.reject(error);
        });
      return deferred.promise;
    };

    local.signup = function(user) {
      $http.post(config.signupUrl, user).then(function() {
        $location.path(config.signupRedirect);
      });
    };

    local.logout = function() {
      delete $rootScope[config.user];
      delete $window.localStorage.jwtToken;
      $location.path(config.logoutRedirect);
    };

    local.isAuthenticated = function() {
      return Boolean($rootScope.currentUser);
    };

    return local;
  }

  function Popup($q, $interval, $window) {
    var popupWindow = null;
    var polling = null;

    var popup = {};

    popup.popupWindow = popupWindow;

    popup.open = function(url, options) {
      var deferred = $q.defer();
      var optionsString = popup.stringifyOptions(popup.prepareOptions(options));

      popupWindow = $window.open(url, 'Satellizer', optionsString);
      popupWindow.focus();

      popup.postMessageHandler(deferred);
      popup.pollPopup(deferred);

      return deferred.promise;
    };

    popup.pollPopup = function(deferred) {
      polling = $interval(function() {
        if (popupWindow.closed) {
          $interval.cancel(polling);
          deferred.reject('Popup was closed by the user');
        }
      }, 35);
    };

    popup.postMessageHandler = function(deferred) {
      $window.addEventListener('message', function(event) {
        if (event.origin === $window.location.origin) {
          deferred.resolve(event.data);
        }
      }, false);
    };

    popup.prepareOptions = function(options) {
      options = options || {};
      var width = options.width || 500;
      var height = options.height || 500;
      var defaults = {
        width: width,
        height: height,
        left: $window.screenX + (($window.outerWidth - width) / 2),
        top: $window.screenY + (($window.outerHeight - height) / 2.5)
      };
      return angular.extend(defaults, options);
    };

    popup.stringifyOptions = function(options) {
      var parts = [];
      angular.forEach(options, function(value, key) {
        parts.push(key + '=' + value);
      });
      return parts.join(',');
    };

    return popup;
  }

  function Oauth1($q, $http, Popup, Utils) {
    var defaults = {
      url: null,
      name: null,
      popupOptions: null
    };

    var oauth1 = {};

    oauth1.open = function(options) {
      angular.extend(defaults, options);

      var deferred = $q.defer();

      Popup.open(defaults.url).then(function(oauthData) {
        oauth1.exchangeForToken(oauthData).then(function(response) {
          deferred.resolve(response.data);
        });
      });

      return deferred.promise;
    };

    oauth1.exchangeForToken = function(oauthData) {
      oauthData = Utils.buildQueryString(oauthData);
      return $http.get(defaults.url + '?' + oauthData);
    };

    return oauth1;
  }

  function Oauth2($q, $http, Utils, Popup) {
    var defaults = {
      url: null,
      name: null,
      scope: null,
      clientId: null,
      redirectUri: null,
      popupOptions: null,
      requiredUrlParams: null,
      optionalUrlParams: null,
      authorizationEndpoint: null,
      responseType: 'code',
      defaultUrlParams: ['response_type', 'client_id', 'redirect_uri']
    };

    var oauth2 = {};

    oauth2.open = function(options) {
      angular.extend(defaults, options);
      var deferred = $q.defer();
      var url = oauth2.buildUrl();

      Popup.open(url, defaults.popupOptions).then(function(oauthData) {
        oauth2.exchangeForToken(oauthData).then(function(response) {
          deferred.resolve(response.data);
        });
      });

      return deferred.promise;
    };

    oauth2.exchangeForToken = function(oauthData) {
      return $http.post(defaults.url, {
        code: oauthData.code,
        clientId: defaults.clientId,
        redirectUri: defaults.redirectUri
      });
    };

    oauth2.buildUrl = function() {
      var baseUrl = defaults.authorizationEndpoint;
      var qs = oauth2.buildQueryString();
      return [baseUrl, qs].join('?');
    };

    oauth2.buildQueryString = function() {
      var keyValuePairs = [];
      var urlParams = ['defaultUrlParams', 'requiredUrlParams', 'optionalUrlParams'];

      angular.forEach(urlParams, function(params) {
        angular.forEach(defaults[params], function(paramName) {
          var camelizedName = Utils.camelCase(paramName);
          var paramValue = defaults[camelizedName];
          keyValuePairs.push([paramName, encodeURIComponent(paramValue)]);
        });
      });

      return keyValuePairs.map(function(pair) {
        return pair.join('=');
      }).join('&');
    };

    return oauth2;
  }

  function httpInterceptor($httpProvider) {
    $httpProvider.interceptors.push(function($q, $window, $location) {
      return {
        request: function(config) {
          if ($window.localStorage.jwtToken) {
            config.headers.Authorization = 'Bearer ' + $window.localStorage.jwtToken;
          }
          return config;
        },
        responseError: function(response) {
          if (response.status === 401 || response.status === 403) {
            delete $window.localStorage.jwtToken;
            $location.path('/login');
          }
          return $q.reject(response);
        }
      };
    });
  }

  function onRun(RunBlock) {
    RunBlock.run();
  }

  function RunBlock($rootScope, $window, $location, Utils) {
    return {
      run: function() {
        var token = $window.localStorage.jwtToken;
        if (token) {
          var payload = JSON.parse($window.atob(token.split('.')[1]));
          $rootScope.currentUser = payload.user;
        }

        var params = $window.location.search.substring(1);
        var qs = Object.keys($location.search()).length ? $location.search() : Utils.parseQueryString(params);
        if ($window.opener && $window.opener.location.origin === $window.location.origin) {
          if (qs.oauth_token && qs.oauth_verifier) {
            $window.opener.postMessage({ oauth_token: qs.oauth_token, oauth_verifier: qs.oauth_verifier }, '*');
          } else if (qs.code) {
            $window.opener.postMessage({ code: qs.code }, '*');
          }
          $window.close();
        }
      }
    }
  }

  function Utils() {
    this.camelCase = function(name) {
      return name.replace(/([\:\-\_]+(.))/g, function(_, separator, letter, offset) {
        return offset ? letter.toUpperCase() : letter;
      });
    };
    this.parseQueryString = function(keyValue) {
      var obj = { }, key, value;
      angular.forEach((keyValue || '').split('&'), function(keyValue) {
        if (keyValue) {
          value = keyValue.split('=');
          key = decodeURIComponent(value[0]);
          obj[key] = angular.isDefined(value[1]) ? decodeURIComponent(value[1]) : true;
        }
      });
      return obj;
    };
    this.buildQueryString = function(obj) {
      var str = [];
      for (var p in obj) {
        var k = p,
          v = obj[k];
        str.push(angular.isObject(v) ? this.buildQueryString(v, k) : (k) + "=" + encodeURIComponent(v));
      }
      return str.join('&');
    }
  }

})(window, window.angular);

<commit_msg>new setters: facebook, google, linkedin, twitter, oauth1, oauth2<commit_after>/**
 * Satellizer
 * (c) 2014 Sahat Yalkabov <sahat@me.com>
 * License: MIT
 */

(function(window, angular, undefined) {
  'use strict';

  angular.module('Satellizer', [])
    .provider('$auth', $auth)
    .factory('RunBlock', RunBlock)
    .factory('Local', Local)
    .factory('Oauth2', Oauth2)
    .factory('Oauth1', Oauth1)
    .factory('Popup', Popup)
    .service('Utils', Utils)
    .config(httpInterceptor)
    .run(onRun);

  var config = {
    logoutRedirect: '/',
    loginRedirect: '/',
    loginUrl: '/auth/login',
    signupUrl: '/auth/signup',
    signupRedirect: '/login',
    loginRoute: '/login',
    signupRoute: '/signup',
    user: 'currentUser'
  };

  var providers = {
    google: {
      url: '/auth/google',
      authorizationEndpoint: 'https://accounts.google.com/o/oauth2/auth',
      redirectUri: window.location.origin,
      scope: 'openid profile email',
      scopeDelimiter: ' ',
      requiredUrlParams: ['scope'],
      optionalUrlParams: ['display'],
      display: 'popup',
      type: '2.0',
      popupOptions: {
        width: 452,
        height: 633
      }
    },
    facebook: {
      url: '/auth/facebook',
      authorizationEndpoint: 'https://www.facebook.com/dialog/oauth',
      redirectUri: window.location.origin + '/',
      scope: 'email',
      scopeDelimiter: ',',
      requiredUrlParams: ['display'],
      display: 'popup',
      type: '2.0',
      popupOptions: {
        width: 481,
        height: 269
      }
    },
    linkedin: {
      url: '/auth/linkedin',
      authorizationEndpoint: 'https://www.linkedin.com/uas/oauth2/authorization',
      redirectUri: window.location.origin,
      requiredUrlParams: ['state'],
      scope: [],
      scopeDelimiter: ' ',
      state: 'STATE',
      type: '2.0',
      popupOptions: {
        width: 527,
        height: 582
      }
    },
    twitter: {
      url: '/auth/twitter',
      type: '1.0'
    }
  };

  function $auth() {

    this.config = config;

    this.facebook = function(params) {
      angular.extend(providers.facebook, params);
    };

    this.google = function(params) {
      angular.extend(providers.google, params);
    };

    this.linkedin = function(params) {
      angular.extend(providers.linkedin, params);
    };

    this.twitter = function(params) {
      angular.extend(providers.twitter, params);
    };

    this.oauth2 = function(params) {
      providers[params.name] = providers[params.name] || {};
      providers[params.name].type = '2.0';
      angular.extend(providers[params.name], params);
    };

    this.oauth1 = function(params) {
      providers[params.name] = providers[params.name] || {};
      providers[params.name].type = '1.0';
      angular.extend(providers[params.name], params);
    };

    this.setProvider = function(params) {
      angular.extend(providers[params.name], params);
    };

    this.addProvider = function(params) {
      providers[params.name] = {};
      angular.extend(providers[params.name], params);
    };

    this.getProvider = function(name) {
      return providers[name];
    };

    this.$get = function($q, $http, Oauth1, Oauth2, Local) {

      var $auth = {};

      $auth.authenticate = function(name) {
        var deferred = $q.defer();
        var provider = (providers[name].type === '1.0') ? Oauth1 : Oauth2;
        provider.open(providers[name]).then(function(response) {
          Local.parseUser(response.token, deferred);
        });
        return deferred.promise;
      };

      $auth.login = function(user) {
        Local.login(user);
      };

      $auth.signup = function(user) {
        Local.signup(user);
      };

      $auth.logout = function() {
        Local.logout();
      };

      $auth.isAuthenticated = function() {
        Local.isAuthenticated();
      };

      return $auth;
    };

  }

  function Local($q, $http, $window, $rootScope, $location) {

    var local = {};

    local.parseUser = function(token, deferred) {
      var payload = JSON.parse($window.atob(token.split('.')[1]));
      $window.localStorage.jwtToken = token;
      $rootScope[config.user] = payload.user;
      $location.path(config.loginRedirect);
      deferred.resolve(payload.user);
    };

    local.login = function(user) {
      var deferred = $q.defer();
      $http.post(config.loginUrl, user)
        .success(function(response) {
          local.parseUser(response.token, deferred);
        })
        .error(function(error) {
          deferred.reject(error);
        });
      return deferred.promise;
    };

    local.signup = function(user) {
      $http.post(config.signupUrl, user).then(function() {
        $location.path(config.signupRedirect);
      });
    };

    local.logout = function() {
      delete $rootScope[config.user];
      delete $window.localStorage.jwtToken;
      $location.path(config.logoutRedirect);
    };

    local.isAuthenticated = function() {
      return Boolean($rootScope.currentUser);
    };

    return local;
  }

  function Popup($q, $interval, $window) {
    var popupWindow = null;
    var polling = null;

    var popup = {};

    popup.popupWindow = popupWindow;

    popup.open = function(url, options) {
      var deferred = $q.defer();
      var optionsString = popup.stringifyOptions(popup.prepareOptions(options));

      popupWindow = $window.open(url, 'Satellizer', optionsString);
      popupWindow.focus();

      popup.postMessageHandler(deferred);
      popup.pollPopup(deferred);

      return deferred.promise;
    };

    popup.pollPopup = function(deferred) {
      polling = $interval(function() {
        if (popupWindow.closed) {
          $interval.cancel(polling);
          deferred.reject('Popup was closed by the user');
        }
      }, 35);
    };

    popup.postMessageHandler = function(deferred) {
      $window.addEventListener('message', function(event) {
        if (event.origin === $window.location.origin) {
          deferred.resolve(event.data);
        }
      }, false);
    };

    popup.prepareOptions = function(options) {
      options = options || {};
      var width = options.width || 500;
      var height = options.height || 500;
      var defaults = {
        width: width,
        height: height,
        left: $window.screenX + (($window.outerWidth - width) / 2),
        top: $window.screenY + (($window.outerHeight - height) / 2.5)
      };
      return angular.extend(defaults, options);
    };

    popup.stringifyOptions = function(options) {
      var parts = [];
      angular.forEach(options, function(value, key) {
        parts.push(key + '=' + value);
      });
      return parts.join(',');
    };

    return popup;
  }

  function Oauth1($q, $http, Popup, Utils) {
    var defaults = {
      url: null,
      name: null,
      popupOptions: null
    };

    var oauth1 = {};

    oauth1.open = function(options) {
      angular.extend(defaults, options);

      var deferred = $q.defer();

      Popup.open(defaults.url).then(function(oauthData) {
        oauth1.exchangeForToken(oauthData).then(function(response) {
          deferred.resolve(response.data);
        });
      });

      return deferred.promise;
    };

    oauth1.exchangeForToken = function(oauthData) {
      oauthData = Utils.buildQueryString(oauthData);
      return $http.get(defaults.url + '?' + oauthData);
    };

    return oauth1;
  }

  function Oauth2($q, $http, Utils, Popup) {
    var defaults = {
      url: null,
      name: null,
      scope: null,
      clientId: null,
      redirectUri: null,
      popupOptions: null,
      requiredUrlParams: null,
      optionalUrlParams: null,
      authorizationEndpoint: null,
      responseType: 'code',
      defaultUrlParams: ['response_type', 'client_id', 'redirect_uri']
    };

    var oauth2 = {};

    oauth2.open = function(options) {
      angular.extend(defaults, options);
      var deferred = $q.defer();
      var url = oauth2.buildUrl();

      Popup.open(url, defaults.popupOptions).then(function(oauthData) {
        oauth2.exchangeForToken(oauthData).then(function(response) {
          deferred.resolve(response.data);
        });
      });

      return deferred.promise;
    };

    oauth2.exchangeForToken = function(oauthData) {
      return $http.post(defaults.url, {
        code: oauthData.code,
        clientId: defaults.clientId,
        redirectUri: defaults.redirectUri
      });
    };

    oauth2.buildUrl = function() {
      var baseUrl = defaults.authorizationEndpoint;
      var qs = oauth2.buildQueryString();
      return [baseUrl, qs].join('?');
    };

    oauth2.buildQueryString = function() {
      var keyValuePairs = [];
      var urlParams = ['defaultUrlParams', 'requiredUrlParams', 'optionalUrlParams'];

      angular.forEach(urlParams, function(params) {
        angular.forEach(defaults[params], function(paramName) {
          var camelizedName = Utils.camelCase(paramName);
          var paramValue = defaults[camelizedName];
          keyValuePairs.push([paramName, encodeURIComponent(paramValue)]);
        });
      });

      return keyValuePairs.map(function(pair) {
        return pair.join('=');
      }).join('&');
    };

    return oauth2;
  }

  function httpInterceptor($httpProvider) {
    $httpProvider.interceptors.push(function($q, $window, $location) {
      return {
        request: function(config) {
          if ($window.localStorage.jwtToken) {
            config.headers.Authorization = 'Bearer ' + $window.localStorage.jwtToken;
          }
          return config;
        },
        responseError: function(response) {
          if (response.status === 401 || response.status === 403) {
            delete $window.localStorage.jwtToken;
            $location.path('/login');
          }
          return $q.reject(response);
        }
      };
    });
  }

  function onRun(RunBlock) {
    RunBlock.run();
  }

  function RunBlock($rootScope, $window, $location, Utils) {
    return {
      run: function() {
        var token = $window.localStorage.jwtToken;
        if (token) {
          var payload = JSON.parse($window.atob(token.split('.')[1]));
          $rootScope.currentUser = payload.user;
        }

        var params = $window.location.search.substring(1);
        var qs = Object.keys($location.search()).length ? $location.search() : Utils.parseQueryString(params);
        if ($window.opener && $window.opener.location.origin === $window.location.origin) {
          if (qs.oauth_token && qs.oauth_verifier) {
            $window.opener.postMessage({ oauth_token: qs.oauth_token, oauth_verifier: qs.oauth_verifier }, '*');
          } else if (qs.code) {
            $window.opener.postMessage({ code: qs.code }, '*');
          }
          $window.close();
        }
      }
    }
  }

  function Utils() {
    this.camelCase = function(name) {
      return name.replace(/([\:\-\_]+(.))/g, function(_, separator, letter, offset) {
        return offset ? letter.toUpperCase() : letter;
      });
    };
    this.parseQueryString = function(keyValue) {
      var obj = { }, key, value;
      angular.forEach((keyValue || '').split('&'), function(keyValue) {
        if (keyValue) {
          value = keyValue.split('=');
          key = decodeURIComponent(value[0]);
          obj[key] = angular.isDefined(value[1]) ? decodeURIComponent(value[1]) : true;
        }
      });
      return obj;
    };
    this.buildQueryString = function(obj) {
      var str = [];
      for (var p in obj) {
        var k = p,
          v = obj[k];
        str.push(angular.isObject(v) ? this.buildQueryString(v, k) : (k) + "=" + encodeURIComponent(v));
      }
      return str.join('&');
    }
  }

})(window, window.angular);

<|endoftext|>


<commit_before>var fs = require('fs');
var pathLib = require('path');
var g = require('../gimlet-api');

describe('gimlet', function() {
  beforeEach(function() {
    var tmpDir = __dirname + "/tmp";
    if (fs.existsSync(tmpDir)) {
      rmdirSyncRecursive(tmpDir);
    }

    fs.mkdirSync(tmpDir);
    process.chdir(tmpDir); // switch working dir to test repo root

    expect(fs.readdirSync(process.cwd()).length).toEqual(0);
  });

  describe('init', function() {
    var expectGimletFilesAndDirectories = function() {
      expect(fs.existsSync(__dirname + "/tmp/.gimlet/hooks/")).toEqual(true);
      expect(fs.existsSync(__dirname + "/tmp/.gimlet/info/")).toEqual(true);
      expect(fs.existsSync(__dirname + "/tmp/.gimlet/logs/")).toEqual(true);
      expect(fs.existsSync(__dirname + "/tmp/.gimlet/objects/")).toEqual(true);
      expect(fs.existsSync(__dirname + "/tmp/.gimlet/refs/")).toEqual(true);
      expect(fs.existsSync(__dirname + "/tmp/.gimlet/refs/")).toEqual(true);
      expect(fs.existsSync(__dirname + "/tmp/.gimlet/refs/heads/")).toEqual(true);
      expect(fs.existsSync(__dirname + "/tmp/.gimlet/refs/remotes/")).toEqual(true);
      expect(fs.existsSync(__dirname + "/tmp/.gimlet/refs/remotes/origin/")).toEqual(true);
      expect(fs.existsSync(__dirname + "/tmp/.gimlet/refs/tags/")).toEqual(true);

      expect(fs.readFileSync(__dirname + "/tmp/.gimlet/HEAD", "utf8"))
        .toEqual("ref: refs/heads/master\n");
    };

    it('should create .gimlet/ and all required dirs', function() {
      g.init();
      expectGimletFilesAndDirectories();
    });

    it('should not change anything if init run twice', function() {
      g.init();
      g.init();
      expectGimletFilesAndDirectories();
    });
  });

  describe('hash-object', function() {
    it('should throw if not in repo', function() {
      expect(function() { g.hash_object(); })
        .toThrow("fatal: Not a gimlet repository (or any of the parent directories): .gimlet");
    });

    it('should return undefined if no file specified', function() {
      g.init();
      expect(g.hash_object()).toBeUndefined();
    });

    it('should throw if file specified does not exist', function() {
      g.init();
      expect(function() { g.hash_object("not-there") })
        .toThrow("fatal: Cannot open 'not-there': No such file or directory");
    });

    it('should return unique (!) hash of contents when file passed with no -w', function() {
      g.init();

      fs.writeFileSync("a.txt", "taoehusnaot uhrs.ochurcaoh. usrcao.h usrc oa.husrc aosr.ot");
      expect(g.hash_object("a.txt")).toEqual("15ee");

      fs.writeFileSync("b.txt", "oetuhntoaehuntao hesuh sano.tuh snato.h usntaho .u");
      expect(g.hash_object("b.txt")).toEqual("1318");
    });

    it('should store blob and return hash when file passed with -w', function() {
      var content = "taoehusnaot uhrs.ochurcaoh. usrcao.h usrc oa.husrc aosr.ot";
      g.init();
      fs.writeFileSync("a.txt", content);
      expect(g.hash_object("a.txt", { w:true })).toEqual("15ee");
      expect(fs.readFileSync(__dirname + "/tmp/.gimlet/objects/15ee", "utf8")).toEqual(content);
    });

    it('should not store blob when -w not passed', function() {
      var content = "taoehusnaot uhrs.ochurcaoh. usrcao.h usrc oa.husrc aosr.ot";
      g.init();
      fs.writeFileSync("a.txt", content);

      var objectPath = __dirname + "/tmp/.gimlet/objects/" + g.hash_object("a.txt");
      expect(fs.existsSync(objectPath, "utf8")).toEqual(false);

      // check that file is stored with -w
      g.hash_object("a.txt", { w: true });
      expect(fs.existsSync(objectPath, "utf8")).toEqual(true);
    });
  });

  describe('add', function() {
    it('should throw if not in repo', function() {
      expect(function() { g.add(); })
        .toThrow("fatal: Not a gimlet repository (or any of the parent directories): .gimlet");
    });

    it('should throw if no pathspec passed', function() {
      g.init();
      expect(function() { g.add(); }).toThrow("Nothing specified, nothing added.");
    });

    describe('pathspec matching', function() {
      it('should throw rel path if in root and pathspec does not match files', function() {
        g.init();
        expect(function() {
          g.add("blah");
        }).toThrow("fatal: pathspec 'blah' did not match any files");
      });

      it('should throw rel path if not in root and pathspec does not match files', function() {
        g.init();
        fs.mkdirSync("1");
        process.chdir("1");
        fs.mkdirSync("2");
        process.chdir("2");
        expect(function() {
          g.add("blah");
        }).toThrow("fatal: pathspec '1/2/blah' did not match any files");
      });
    });
  });

  describe('update-index', function() {
    it('should throw if not in repo', function() {
      expect(function() { g.update_index(); })
        .toThrow("fatal: Not a gimlet repository (or any of the parent directories): .gimlet");
    });

    it('should return undefined if nothing specified', function() {
      g.init();
      expect(g.update_index()).toBeUndefined();
    });

    describe('pathspec stipulations', function() {
      it('should throw if path does not match existing working copy file', function() {
        g.init();
        expect(function() { g.update_index("blah"); })
          .toThrow("error: blah: does not exist\nfatal: Unable to process path blah");
      });

      it('should throw rel path if not in root and pathspec does not match file', function() {
        g.init();
        fs.mkdirSync("1");
        process.chdir("1");
        fs.mkdirSync("2");
        process.chdir("2");
        expect(function() { g.update_index("blah"); })
          .toThrow("error: 1/2/blah: does not exist\nfatal: Unable to process path 1/2/blah");
      });

      it('should throw rel path if not in root and path is dir', function() {
        g.init();
        fs.mkdirSync("1");
        process.chdir("1");
        fs.mkdirSync("2");
        expect(function() { g.update_index("2"); })
          .toThrow("error: 1/2: is a directory - add files inside instead\n" +
                   "fatal: Unable to process path 1/2");
      });
    });

    describe('adding files to index', function() {
      it('should add a file to an empty index and create object', function() {
        g.init();
        fs.writeFileSync("README.md", "this is a readme");
        g.update_index("README.md", { add: true });

        var readmeHash = g.hash_object("README.md");
        expect(fs.readFileSync(pathLib.join(".gimlet/objects", readmeHash), "utf8"))
          .toEqual("this is a readme");

        expect(g.ls_files()[0]).toEqual("README.md");
      });
  describe('ls-files', function() {
    it('should throw if not in repo', function() {
      expect(function() { g.update_index(); })
        .toThrow("fatal: Not a gimlet repository (or any of the parent directories): .gimlet");
    });

    it('should return no files if nothing in index', function() {
      g.init();
      expect(g.ls_files()).toEqual([]);
    });

    it('should return files in index', function() {
      g.init();
      fs.writeFileSync("README1.md", "this is a readme1");
      fs.writeFileSync("README2.md", "this is a readme2");
      g.update_index("README1.md", { add: true });
      g.update_index("README2.md", { add: true });

      expect(g.ls_files()[0]).toEqual("README1.md");
      expect(g.ls_files()[1]).toEqual("README2.md");
    });

    it('should not return files not in index', function() {
      g.init();
      fs.writeFileSync("README1.md", "this is a readme1");
      fs.writeFileSync("README2.md", "this is a readme2");
      g.update_index("README1.md", { add: true });

      expect(g.ls_files()[0]).toEqual("README1.md");
      expect(g.ls_files().length).toEqual(1);
    });

    it('should include full path in returned entries', function() {
      g.init();
      fs.mkdirSync("src");
      fs.writeFileSync("src/README1.md", "this is a readme1");
      g.update_index("src/README1.md", { add: true });

      expect(g.ls_files()[0]).toEqual("src/README1.md");
    });
  });
});

var rmdirSyncRecursive = function(dir) {
  fs.readdirSync(dir).forEach(function(fileName) {
    var filePath = pathLib.join(dir, fileName);
    if (fs.statSync(filePath).isDirectory()) {
      rmdirSyncRecursive(filePath);
    } else {
      fs.unlinkSync(filePath);
    }
  });

  fs.rmdirSync(dir);
};
<commit_msg>Can add multiple files to index<commit_after>var fs = require('fs');
var pathLib = require('path');
var g = require('../gimlet-api');

describe('gimlet', function() {
  beforeEach(function() {
    var tmpDir = __dirname + "/tmp";
    if (fs.existsSync(tmpDir)) {
      rmdirSyncRecursive(tmpDir);
    }

    fs.mkdirSync(tmpDir);
    process.chdir(tmpDir); // switch working dir to test repo root

    expect(fs.readdirSync(process.cwd()).length).toEqual(0);
  });

  describe('init', function() {
    var expectGimletFilesAndDirectories = function() {
      expect(fs.existsSync(__dirname + "/tmp/.gimlet/hooks/")).toEqual(true);
      expect(fs.existsSync(__dirname + "/tmp/.gimlet/info/")).toEqual(true);
      expect(fs.existsSync(__dirname + "/tmp/.gimlet/logs/")).toEqual(true);
      expect(fs.existsSync(__dirname + "/tmp/.gimlet/objects/")).toEqual(true);
      expect(fs.existsSync(__dirname + "/tmp/.gimlet/refs/")).toEqual(true);
      expect(fs.existsSync(__dirname + "/tmp/.gimlet/refs/")).toEqual(true);
      expect(fs.existsSync(__dirname + "/tmp/.gimlet/refs/heads/")).toEqual(true);
      expect(fs.existsSync(__dirname + "/tmp/.gimlet/refs/remotes/")).toEqual(true);
      expect(fs.existsSync(__dirname + "/tmp/.gimlet/refs/remotes/origin/")).toEqual(true);
      expect(fs.existsSync(__dirname + "/tmp/.gimlet/refs/tags/")).toEqual(true);

      expect(fs.readFileSync(__dirname + "/tmp/.gimlet/HEAD", "utf8"))
        .toEqual("ref: refs/heads/master\n");
    };

    it('should create .gimlet/ and all required dirs', function() {
      g.init();
      expectGimletFilesAndDirectories();
    });

    it('should not change anything if init run twice', function() {
      g.init();
      g.init();
      expectGimletFilesAndDirectories();
    });
  });

  describe('hash-object', function() {
    it('should throw if not in repo', function() {
      expect(function() { g.hash_object(); })
        .toThrow("fatal: Not a gimlet repository (or any of the parent directories): .gimlet");
    });

    it('should return undefined if no file specified', function() {
      g.init();
      expect(g.hash_object()).toBeUndefined();
    });

    it('should throw if file specified does not exist', function() {
      g.init();
      expect(function() { g.hash_object("not-there") })
        .toThrow("fatal: Cannot open 'not-there': No such file or directory");
    });

    it('should return unique (!) hash of contents when file passed with no -w', function() {
      g.init();

      fs.writeFileSync("a.txt", "taoehusnaot uhrs.ochurcaoh. usrcao.h usrc oa.husrc aosr.ot");
      expect(g.hash_object("a.txt")).toEqual("15ee");

      fs.writeFileSync("b.txt", "oetuhntoaehuntao hesuh sano.tuh snato.h usntaho .u");
      expect(g.hash_object("b.txt")).toEqual("1318");
    });

    it('should store blob and return hash when file passed with -w', function() {
      var content = "taoehusnaot uhrs.ochurcaoh. usrcao.h usrc oa.husrc aosr.ot";
      g.init();
      fs.writeFileSync("a.txt", content);
      expect(g.hash_object("a.txt", { w:true })).toEqual("15ee");
      expect(fs.readFileSync(__dirname + "/tmp/.gimlet/objects/15ee", "utf8")).toEqual(content);
    });

    it('should not store blob when -w not passed', function() {
      var content = "taoehusnaot uhrs.ochurcaoh. usrcao.h usrc oa.husrc aosr.ot";
      g.init();
      fs.writeFileSync("a.txt", content);

      var objectPath = __dirname + "/tmp/.gimlet/objects/" + g.hash_object("a.txt");
      expect(fs.existsSync(objectPath, "utf8")).toEqual(false);

      // check that file is stored with -w
      g.hash_object("a.txt", { w: true });
      expect(fs.existsSync(objectPath, "utf8")).toEqual(true);
    });
  });

  describe('add', function() {
    it('should throw if not in repo', function() {
      expect(function() { g.add(); })
        .toThrow("fatal: Not a gimlet repository (or any of the parent directories): .gimlet");
    });

    it('should throw if no pathspec passed', function() {
      g.init();
      expect(function() { g.add(); }).toThrow("Nothing specified, nothing added.");
    });

    describe('pathspec matching', function() {
      it('should throw rel path if in root and pathspec does not match files', function() {
        g.init();
        expect(function() {
          g.add("blah");
        }).toThrow("fatal: pathspec 'blah' did not match any files");
      });

      it('should throw rel path if not in root and pathspec does not match files', function() {
        g.init();
        fs.mkdirSync("1");
        process.chdir("1");
        fs.mkdirSync("2");
        process.chdir("2");
        expect(function() {
          g.add("blah");
        }).toThrow("fatal: pathspec '1/2/blah' did not match any files");
      });
    });
  });

  describe('update-index', function() {
    it('should throw if not in repo', function() {
      expect(function() { g.update_index(); })
        .toThrow("fatal: Not a gimlet repository (or any of the parent directories): .gimlet");
    });

    it('should return undefined if nothing specified', function() {
      g.init();
      expect(g.update_index()).toBeUndefined();
    });

    describe('pathspec stipulations', function() {
      it('should throw if path does not match existing working copy file', function() {
        g.init();
        expect(function() { g.update_index("blah"); })
          .toThrow("error: blah: does not exist\nfatal: Unable to process path blah");
      });

      it('should throw rel path if not in root and pathspec does not match file', function() {
        g.init();
        fs.mkdirSync("1");
        process.chdir("1");
        fs.mkdirSync("2");
        process.chdir("2");
        expect(function() { g.update_index("blah"); })
          .toThrow("error: 1/2/blah: does not exist\nfatal: Unable to process path 1/2/blah");
      });

      it('should throw rel path if not in root and path is dir', function() {
        g.init();
        fs.mkdirSync("1");
        process.chdir("1");
        fs.mkdirSync("2");
        expect(function() { g.update_index("2"); })
          .toThrow("error: 1/2: is a directory - add files inside instead\n" +
                   "fatal: Unable to process path 1/2");
      });
    });

    describe('adding files to index', function() {
      it('should add a file to an empty index and create object', function() {
        g.init();
        fs.writeFileSync("README.md", "this is a readme");
        g.update_index("README.md", { add: true });

        var readmeHash = g.hash_object("README.md");
        expect(fs.readFileSync(pathLib.join(".gimlet/objects", readmeHash), "utf8"))
          .toEqual("this is a readme");

        expect(g.ls_files()[0]).toEqual("README.md");
      });

      it('should add file to index with stuff in it', function() {
        g.init();
        fs.writeFileSync("README1.md", "this is a readme1");
        g.update_index("README1.md", { add: true });
        fs.writeFileSync("README2.md", "this is a readme2");
        g.update_index("README2.md", { add: true });

        expect(fs.readFileSync(pathLib.join(".gimlet/objects", g.hash_object("README1.md")),
                               "utf8")).toEqual("this is a readme1");
        expect(fs.readFileSync(pathLib.join(".gimlet/objects", g.hash_object("README2.md")),
                               "utf8")).toEqual("this is a readme2");

        expect(g.ls_files()[0]).toEqual("README1.md");
        expect(g.ls_files()[1]).toEqual("README2.md");
      });
    });
  });

  describe('ls-files', function() {
    it('should throw if not in repo', function() {
      expect(function() { g.update_index(); })
        .toThrow("fatal: Not a gimlet repository (or any of the parent directories): .gimlet");
    });

    it('should return no files if nothing in index', function() {
      g.init();
      expect(g.ls_files()).toEqual([]);
    });

    it('should return files in index', function() {
      g.init();
      fs.writeFileSync("README1.md", "this is a readme1");
      fs.writeFileSync("README2.md", "this is a readme2");
      g.update_index("README1.md", { add: true });
      g.update_index("README2.md", { add: true });

      expect(g.ls_files()[0]).toEqual("README1.md");
      expect(g.ls_files()[1]).toEqual("README2.md");
    });

    it('should not return files not in index', function() {
      g.init();
      fs.writeFileSync("README1.md", "this is a readme1");
      fs.writeFileSync("README2.md", "this is a readme2");
      g.update_index("README1.md", { add: true });

      expect(g.ls_files()[0]).toEqual("README1.md");
      expect(g.ls_files().length).toEqual(1);
    });

    it('should include full path in returned entries', function() {
      g.init();
      fs.mkdirSync("src");
      fs.writeFileSync("src/README1.md", "this is a readme1");
      g.update_index("src/README1.md", { add: true });

      expect(g.ls_files()[0]).toEqual("src/README1.md");
    });
  });
});

var rmdirSyncRecursive = function(dir) {
  fs.readdirSync(dir).forEach(function(fileName) {
    var filePath = pathLib.join(dir, fileName);
    if (fs.statSync(filePath).isDirectory()) {
      rmdirSyncRecursive(filePath);
    } else {
      fs.unlinkSync(filePath);
    }
  });

  fs.rmdirSync(dir);
};
<|endoftext|>


<commit_before>/*jslint indent: 2*/
/*global require: true, module: true*/
(function () {
  'use strict';

  var InvalidInputError = require('./input-error.js'),
    transformationFunctions = require('./transforms'),
    transformationKeys = require('./keys'),
    regex =  /^(\d*)d(\d+|\%)(([\+\-\/\*bw])(\d+))?(([\+\-\/\*])(\d+|(\d*)d(\d+|\%)(([\+\-\/\*bw])(\d+))?))*$/,
    roll;

  roll = function (random) {
    this.random = random || function () {
      return Math.random();
    };
  };

  roll.prototype.validate = function (s) {
    return regex.test(s);
  };

  roll.prototype.parse = function (s) {
    if (!this.validate(s)) {
      throw new InvalidInputError(s);
    }

    var match = regex.exec(s),
      quantity = match[1], // 2d20+3 => 2
      sides = match[2], // 2d20+3 => 20
      hasTransformation = !!match[3], // 2d20+3 => true
      operator,
      transformationParameter,
      transforms = [],
      opIndex = 0,
      segLength,
      segments = s.split(/[\+\-\/\*]/);

 	if (segments[0].indexOf('b') > -1) {
		transforms.push(transformationKeys[match[4]](parseInt(match[5], 10)))
	}
	for (var seg = 1; seg < segments.length; seg++) {
		opIndex += segments[seg - 1].length;
		operator = s[opIndex]; // 2d20+3 => "+"
		opIndex += 1;
		transformationParameter = segments[seg]; // 2d20+3 => 3
		if (transformationParameter.indexOf('d') > -1) {
		  	transforms.push(transformationKeys[operator](parseInt(this.roll(transformationParameter).result, 10)));
		} else {
		    transforms.push(transformationKeys[operator](parseInt(transformationParameter, 10)));
		}
	}

	return {
		quantity: quantity ? parseInt(quantity, 10) : 1,
		sides: sides === '%' ? 100 : parseInt(sides, 10),
		transformations: hasTransformation ? transforms.length > 1 ? transforms : transformationKeys[match[4]](parseInt(match[5], 10)) : ['sum'],
		toString: function () {
		        return s;
		    }
		};
	};
  roll.prototype.roll = function (input) {
    if (!input) {
      throw new InvalidInputError();
    } else if (typeof input === 'string') {
      input = this.parse(input);
    } else if (input instanceof Array) {
      var resultset = [];
      for (var i in input){
       resultset.push(this.roll(input[i]));
      }
      return resultset;
    }

    var rolled = [],
     calculations = [];

    while (rolled.length < input.quantity) {
      rolled.push(Math.floor((this.random() * input.sides) + 1));
    }

    calculations = input.transformations.reduce(function (previous, transformation) {
      var transformationFunction,
        transformationAdditionalParameter;

		    if (typeof transformation === 'function') { // lets you pass something custom in
				              transformationFunction = transformation;
			} else if (typeof transformation === 'string') { // "sum"
				              transformationFunction = transformationFunctions[transformation];
			} else if (transformation instanceof Array) { // ["add", 3]
		              if (transformation[0] instanceof Array) {
		                  transformation = transformation[0];
		              } else if (transformation[1] instanceof Array) {
		                  transformation = transformation[1];
		              }
		              transformationFunction = transformationFunctions[transformation[0]]; // fn for "add"
		              transformationAdditionalParameter = transformation[1];
			};
		   previous.unshift(transformationFunction(previous[0], transformationAdditionalParameter));
		   return previous;
		}, [rolled]);
    return {
      input: input,
      calculations: calculations,
      rolled: calculations[calculations.length - 1],
      result: calculations[0]
    };
  };

  module.exports = roll;
  module.exports.InvalidInputError = InvalidInputError;

}());

<commit_msg>fix nested ternary<commit_after>/*jslint indent: 2*/
/*global require: true, module: true*/
(function () {
  'use strict';

  var InvalidInputError = require('./input-error.js'),
    transformationFunctions = require('./transforms'),
    transformationKeys = require('./keys'),
    regex =  /^(\d*)d(\d+|\%)(([\+\-\/\*bw])(\d+))?(([\+\-\/\*])(\d+|(\d*)d(\d+|\%)(([\+\-\/\*bw])(\d+))?))*$/,
    roll;

  roll = function (random) {
    this.random = random || function () {
      return Math.random();
    };
  };

  roll.prototype.validate = function (s) {
    return regex.test(s);
  };

  roll.prototype.parse = function (s) {
    if (!this.validate(s)) {
      throw new InvalidInputError(s);
    }

    var match = regex.exec(s),
      quantity = match[1], // 2d20+3 => 2
      sides = match[2], // 2d20+3 => 20
      hasTransformation = !!match[3], // 2d20+3 => true
      operator,
      transformationParameter,
      transforms = [],
      opIndex = 0,
      segLength,
      segments = s.split(/[\+\-\/\*]/);

 	if (segments[0].indexOf('b') > -1) {
		transforms.push(transformationKeys[match[4]](parseInt(match[5], 10)))
	}
	for (var seg = 1; seg < segments.length; seg++) {
		opIndex += segments[seg - 1].length;
		operator = s[opIndex]; // 2d20+3 => "+"
		opIndex += 1;
		transformationParameter = segments[seg]; // 2d20+3 => 3
		if (transformationParameter.indexOf('d') > -1) {
		  	transforms.push(transformationKeys[operator](parseInt(this.roll(transformationParameter).result, 10)));
		} else {
		    transforms.push(transformationKeys[operator](parseInt(transformationParameter, 10)));
		}
	}

	return {
		quantity: quantity ? parseInt(quantity, 10) : 1,
		sides: sides === '%' ? 100 : parseInt(sides, 10),
		transformations: hasTransformation || transforms.length ? transforms.length > 1 ? transforms : transformationKeys[match[4]](parseInt(match[5], 10)) : ['sum'],
		toString: function () {
		        return s;
		    }
		};
	};
  roll.prototype.roll = function (input) {
    if (!input) {
      throw new InvalidInputError();
    } else if (typeof input === 'string') {
      input = this.parse(input);
    } else if (input instanceof Array) {
      var resultset = [];
      for (var i in input){
       resultset.push(this.roll(input[i]));
      }
      return resultset;
    }

    var rolled = [],
     calculations = [];

    while (rolled.length < input.quantity) {
      rolled.push(Math.floor((this.random() * input.sides) + 1));
    }

    calculations = input.transformations.reduce(function (previous, transformation) {
      var transformationFunction,
        transformationAdditionalParameter;

		    if (typeof transformation === 'function') { // lets you pass something custom in
				              transformationFunction = transformation;
			} else if (typeof transformation === 'string') { // "sum"
				              transformationFunction = transformationFunctions[transformation];
			} else if (transformation instanceof Array) { // ["add", 3]
		              if (transformation[0] instanceof Array) {
		                  transformation = transformation[0];
		              } else if (transformation[1] instanceof Array) {
		                  transformation = transformation[1];
		              }
		              transformationFunction = transformationFunctions[transformation[0]]; // fn for "add"
		              transformationAdditionalParameter = transformation[1];
			};
		   previous.unshift(transformationFunction(previous[0], transformationAdditionalParameter));
		   return previous;
		}, [rolled]);
    return {
      input: input,
      calculations: calculations,
      rolled: calculations[calculations.length - 1],
      result: calculations[0]
    };
  };

  module.exports = roll;
  module.exports.InvalidInputError = InvalidInputError;

}());

<|endoftext|>


<commit_before>

////////////// db-subscriptions:

Meteor.subscribe('locations');
Meteor.subscribe('roles');
Meteor.subscribe('currentUser');
Meteor.subscribe('files');

// close any verification dialogs still open
Router.onBeforeAction(function() {
	Session.set('verify', false);

	this.next();
});

// Subscribe to list of regions and configure the regions
// This checks client storage for a region setting. When there is no previously
// selected region, we ask the server to do geolocation. If that fails too,
// we just set it to 'all regions'.
regionSub = Meteor.subscribe('regions', function() {
	var useRegion = function(regionId) {
		if (regionId == 'all' || Regions.findOne({ _id: regionId })) {
			Session.set("region", regionId);
			return true;
		}
		return false;
	}

	if (useRegion(localStorage.getItem("region"))) return;

	Meteor.call('autoSelectRegion', function(error, regionId) {
		if (useRegion(regionId)) return;

		// Give up
		useRegion('all');
	});
});

// Try to guess a sensible language
Meteor.startup(function() {
	// Could be built dynamically I guess
	var availableLangs = {
		'ar': 'ar',
		'de': 'de',
		'en': 'en',
		'es': 'es',
		'fr': 'fr',
		'it': 'it',
		'zh': 'zh_TW'
	};

	var useLocale = function(lang) {
		var locale = false;
		if (availableLangs[lang]) {
			locale = availableLangs[lang];
		}
		if (!locale && lang.length > 2) {
			var short = langCandidate.substring(0, 2);
			if (availableLangs[short]) {
				locale = availableLangs[short];
			}
		}
		if (locale) {
			Session.set('locale', locale);
			return true;
		}
		return false;
	}

	// Soon everybody will support this, right?
	var desiredLangs = navigator.languages || [navigator.language];
	desiredLangs.unshift(localStorage.getItem('locale'));
	desiredLangs.push('en'); // fallback

	for (l in desiredLangs) {
		var langCandidate = desiredLangs[l];
		if (useLocale(langCandidate)) break;
	}

	Deps.autorun(function() {
		var desiredLocale = Session.get('locale');

		// Tell moment to switch the locale
		// Also change timeLocale which will invalidate the parts that depend on it
		var setLocale = moment.locale(desiredLocale);
		Session.set('timeLocale', setLocale);
		if (desiredLocale !== setLocale) console.log("Date formatting set to "+setLocale+" because "+desiredLocale+" not available");
	});
});

minuteTime = new ReactiveVar();

// Set up reactive date sources that can be used for updates based on time
function setTimes() {
	var now = new Date();

	now.setSeconds(0);
	now.setMilliseconds(0);
	var old = minuteTime.get();
	if (!old || old.getTime() !== now.getTime()) {
		minuteTime.set(now);
	}
}
setTimes();

// Update interval of five seconds is okay
Meteor.setInterval(setTimes, 1000*5);
<commit_msg>modern browsers now need "Array.prototype.slice.call(desiredLangs)"<commit_after>

////////////// db-subscriptions:

Meteor.subscribe('locations');
Meteor.subscribe('roles');
Meteor.subscribe('currentUser');
Meteor.subscribe('files');

// close any verification dialogs still open
Router.onBeforeAction(function() {
	Session.set('verify', false);

	this.next();
});

// Subscribe to list of regions and configure the regions
// This checks client storage for a region setting. When there is no previously
// selected region, we ask the server to do geolocation. If that fails too,
// we just set it to 'all regions'.
regionSub = Meteor.subscribe('regions', function() {
	var useRegion = function(regionId) {
		if (regionId == 'all' || Regions.findOne({ _id: regionId })) {
			Session.set("region", regionId);
			return true;
		}
		return false;
	}

	if (useRegion(localStorage.getItem("region"))) return;

	Meteor.call('autoSelectRegion', function(error, regionId) {
		if (useRegion(regionId)) return;

		// Give up
		useRegion('all');
	});
});

// Try to guess a sensible language
Meteor.startup(function() {
	// Could be built dynamically I guess
	var availableLangs = {
		'ar': 'ar',
		'de': 'de',
		'en': 'en',
		'es': 'es',
		'fr': 'fr',
		'it': 'it',
		'zh': 'zh_TW'
	};

	var useLocale = function(lang) {
		var locale = false;
		if (availableLangs[lang]) {
			locale = availableLangs[lang];
		}
		if (!locale && lang.length > 2) {
			var short = langCandidate.substring(0, 2);
			if (availableLangs[short]) {
				locale = availableLangs[short];
			}
		}
		if (locale) {
			Session.set('locale', locale);
			return true;
		}
		return false;
	}

	// Soon everybody will support this, right?
	var desiredLangs = navigator.languages || [navigator.language];
	desiredLangs = Array.prototype.slice.call(desiredLangs);
	desiredLangs.unshift(localStorage.getItem('locale'));
	desiredLangs.push('en'); // fallback

	for (l in desiredLangs) {
		var langCandidate = desiredLangs[l];
		if (useLocale(langCandidate)) break;
	}

	Deps.autorun(function() {
		var desiredLocale = Session.get('locale');

		// Tell moment to switch the locale
		// Also change timeLocale which will invalidate the parts that depend on it
		var setLocale = moment.locale(desiredLocale);
		Session.set('timeLocale', setLocale);
		if (desiredLocale !== setLocale) console.log("Date formatting set to "+setLocale+" because "+desiredLocale+" not available");
	});
});

minuteTime = new ReactiveVar();

// Set up reactive date sources that can be used for updates based on time
function setTimes() {
	var now = new Date();

	now.setSeconds(0);
	now.setMilliseconds(0);
	var old = minuteTime.get();
	if (!old || old.getTime() !== now.getTime()) {
		minuteTime.set(now);
	}
}
setTimes();

// Update interval of five seconds is okay
Meteor.setInterval(setTimes, 1000*5);
<|endoftext|>