<commit_before>/*!@preserve
* OurJS     : Free Blog Engine, Forum System, Website Template and CMS Platform based on Node.JS
* Copyright : Kris Zhang <kris.newghost@gmail.com>
* Homepage  : http://code.ourjs.com
* Project   : https://github.com/newghost/ourjs
* License   : BSD
*/

var fs              = require('fs')
  , path            = require('path')
  , redblade        = require('redblade')
  , utility         = require('./utility')
  , config          = global.CONFIG

/*
分页
*/
var getPagination = function(config, pagerFormat) {
  var interval = 5
    , curPager = config.pager || 0
    , maxPager = config.count / config.pageSize | 0
    , startPos = curPager - (interval / 2 | 0)

  maxPager - startPos < interval && (startPos = maxPager - interval)
  startPos < 1 && (startPos = 1)

  var pagination  = ''
    , paginations = []

  var addPage = function(pager) {
    paginations.push(
      pagerFormat.format(pager, curPager == pager ? 'class="active"' : '', (pager + 1))
    )
  }

  addPage(0)
  startPos > 1 && paginations.push('
…
')
  for (var i = 0; startPos < maxPager && i < 5; i++, startPos++) {
    addPage(startPos)
  }
  startPos < maxPager && paginations.push('
…
')
  //maxPager > 0 && addPage(maxPager)

  pagination = '
{1}
'.format(paginations.length, paginations.join(''))

  return pagination
}

//handle: /templatename/category/pagenumber, etc: /home/all/0, /home, /json/all/0
var showListHandler = function(req, res, url) {
  var params      = app.parseUrl('/:template/:keyword/:pagerNumber', url || req.url)
    , template    = params.template || 'home'
    , keyword     = params.keyword  || ''
    , pageNumber  = parseInt(params.pagerNumber) || 0
    , pageSize    = 20
    , user        = req.session.get('user') || {}

  var where = { isPublic: 1 }

  keyword && (where.keyword = keyword)

  redblade.select('article', where, function(err, articles, count) {
    if (err) {
      res.send(err.toString())
      return
    }

    template.indexOf('rss') > -1 && res.type('xml')
    res.render(template + ".tmpl", {
        articles    : articles
      , keyword     : keyword
      , nextPage    : '/' + template + '/' + keyword + '/' + (pageNumber + 1)
      , pagination  : getPagination({
            pageSize  : pageSize
          , pager     : pageNumber
          , count     : 100000
        }, '
{2}
')
    })

  }, { from: pageNumber * pageSize, to: (pageNumber + 1) * pageSize, desc: true, count: true })
}

//handle detail.tmpl: content of article
var showDetailHandler = function(req, res) {
  var tmpl  = req.url.split('/')[1]     //get the template name
    , id    = req.params.id             //get the object id
    , key   = 'article:' + id
    , user  = req.session.get('user') || {}

  if (id && tmpl) {
    //Does it existing in the Articles?
    redblade.client.hgetall(key, function(err, article) {
      if (article) {
        redblade.client.hincrby(key, 'visitNum', 1)
        redblade.client.hgetall('user:' + article.poster, function(err, posterInfo) {
          res.render(tmpl + ".tmpl", {
              article : article
            , poster  : posterInfo || {}
          })
        })
      } else {
        res.send('没有找到文章')
      }
    })
  } else {
    res.send('参数不完整')
  }
}

//127.0.0.1/ or 127.0.0.1/home/category/pagernumber
app.get(['/home', '/rss'], showListHandler)

//127.0.0.1/article/2340234erer23343[OjbectID]
app.get('/article/:id', showDetailHandler)

module.exports = {
    showListHandler : showListHandler
}<commit_msg>update article.js<commit_after>/*!@preserve
* OurJS     : Free Blog Engine, Forum System, Website Template and CMS Platform based on Node.JS
* Copyright : Kris Zhang <kris.newghost@gmail.com>
* Homepage  : http://code.ourjs.com
* Project   : https://github.com/newghost/ourjs
* License   : BSD
*/

var fs              = require('fs')
  , path            = require('path')
  , redblade        = require('redblade')
  , utility         = require('./utility')
  , config          = global.CONFIG

/*
分页
*/
var getPagination = function(config, pagerFormat) {
  var interval = 5
    , curPager = config.pager || 0
    , maxPager = config.count / config.pageSize | 0
    , startPos = curPager - (interval / 2 | 0)

  maxPager - startPos < interval && (startPos = maxPager - interval)
  startPos < 1 && (startPos = 1)

  var pagination  = ''
    , paginations = []

  var addPage = function(pager) {
    paginations.push(
      pagerFormat.format(pager, curPager == pager ? 'class="active"' : '', (pager + 1))
    )
  }

  addPage(0)
  startPos > 1 && paginations.push('
…
')
  for (var i = 0; startPos < maxPager && i < 5; i++, startPos++) {
    addPage(startPos)
  }
  startPos < maxPager && paginations.push('
…
')
  maxPager > 0 && addPage(maxPager)

  pagination = '
{1}
'.format(paginations.length, paginations.join(''))

  return pagination
}

//handle: /templatename/category/pagenumber, etc: /home/all/0, /home, /json/all/0
var showListHandler = function(req, res, url) {
  var params      = app.parseUrl('/:template/:keyword/:pagerNumber', url || req.url)
    , template    = params.template || 'home'
    , keyword     = params.keyword  || ''
    , pageNumber  = parseInt(params.pagerNumber) || 0
    , pageSize    = 20
    , user        = req.session.get('user') || {}

  var where = { isPublic: 1 }

  keyword && (where.keyword = keyword)

  redblade.select('article', where, function(err, articles, count) {
    if (err) {
      res.send(err.toString())
      return
    }

    template.indexOf('rss') > -1 && res.type('xml')
    res.render(template + ".tmpl", {
        articles    : articles
      , keyword     : keyword
      , nextPage    : '/' + template + '/' + keyword + '/' + (pageNumber + 1)
      , pagination  : getPagination({
            pageSize  : pageSize
          , pager     : pageNumber
          , count     : count
        }, '
{2}
')
    })

  }, { from: pageNumber * pageSize, to: (pageNumber + 1) * pageSize, desc: true })
}

//handle detail.tmpl: content of article
var showDetailHandler = function(req, res) {
  var tmpl  = req.url.split('/')[1]     //get the template name
    , id    = req.params.id             //get the object id
    , key   = 'article:' + id
    , user  = req.session.get('user') || {}

  if (id && tmpl) {
    //Does it existing in the Articles?
    redblade.client.hgetall(key, function(err, article) {
      if (article) {
        redblade.client.hincrby(key, 'visitNum', 1)
        redblade.client.hgetall('user:' + article.poster, function(err, posterInfo) {
          res.render(tmpl + ".tmpl", {
              article : article
            , poster  : posterInfo || {}
          })
        })
      } else {
        res.send('没有找到文章')
      }
    })
  } else {
    res.send('参数不完整')
  }
}

//127.0.0.1/ or 127.0.0.1/home/category/pagernumber
app.get(['/home', '/rss'], showListHandler)

//127.0.0.1/article/2340234erer23343[OjbectID]
app.get('/article/:id', showDetailHandler)

module.exports = {
    showListHandler : showListHandler
}<|endoftext|>


<commit_before>var http            = require('http');
var WebSocketServer = require('ws').Server;
var EventEmitter    = require('events');
var util            = require('util');

var Stomp           = require('./lib/stomp');
var StompUtils      = require('./lib/stomp-utils');

/**
 * STOMP Server configuration
 *
 * @typedef {object} ServerConfig
 * @param {http.Server} server Http server reference
 * @param {function} [debug=function(args) {}] Debug function
 * @param {string} [path=/stomp] WebSocket path
 */

/**
 * @class
 * @augments EventEmitter
 *
 * Create Stomp server with config
 *
 * @param {ServerConfig} config Configuration for STOMP server
 */
var StompServer = function (config) {
  EventEmitter.call(this);

  if (config === undefined) {
    config = {};
  }

  this.conf = {
    server: config.server,
    serverName: config.serverName || 'STOMP-JS/1.1.2',
    path: config.path || '/stomp',
    debug: config.debug || function (args) {
    }
  };

  if (this.conf.server === undefined) {
    throw 'Server is required';
  }

  this.subscribes = [];
  this.frameHandler = new Stomp.FrameHandler(this);
  this.heartBeatConfig = {
    client: 0,
    server: 0
  };

  this.socket = new WebSocketServer({
    server: this.conf.server,
    path: this.conf.path,
    perMessageDeflate: false
  });

  /**
   * Client connecting event, emitted after socket is opened.
   *
   * @event StompServer#connecting
   * @type {object}
   * @property {string} sessionId
   */
  this.socket.on('connection', function (ws) {
    ws.sessionId = StompUtils.genId();
    this.emit('connecting', ws.sessionId);
    this.conf.debug('Connect', ws.sessionId);

    ws.on('message', parseRequest.bind(this, ws));
    ws.on('close', connectionClose.bind(this, ws));
    ws.on('error', function (err) {
      this.conf.debug(err);
      this.emit('error', err);
    }.bind(this));
  }.bind(this));

  //<editor-fold defaultstate="collapsed" desc="Events">

  /**
   * Client connected event, emitted after connection established and negotiated
   *
   * @event StompServer#connected
   * @type {object}
   * @property {string} sessionId
   * @property {object} headers
   */
  this.onClientConnected = function (socket, args) {
    socket.clientHeartBeat = {
      client: args.hearBeat[0],
      server: args.hearBeat[1]
    };
    this.conf.debug('CONNECT', socket.sessionId, socket.clientHeartBeat, args.headers);
    this.emit('connected', socket.sessionId, args.headers);
    return true;
  };

  /**
   * Client disconnected event
   *
   * @event StompServer#disconnected
   * @type {object}
   * @property {string} sessionId
   */
  this.onDisconnect = function (socket, receiptId) {
    connectionClose(socket);
    this.conf.debug('DISCONNECT', socket.sessionId);
    this.emit('disconnected', socket.sessionId);
    this.conf.debug('DISCONNECT', socket.sessionId, receiptId);
    return true;
  };

  /**
   * Event emitted when broker send message to subscribers
   *
   * @event StompServer#send
   * @type {object}
   * @property {string} dest Destination
   * @property {string} frame Message frame
   */
  this.onSend = function (socket, args, callback) {
    var bodyObj = args.frame.body;
    var frame = this.frameSerializer(args.frame);
    var headers = {
      //default headers
      'message-id': StompUtils.genId('msg'),
      'content-type': 'text/plain'
    };

    if (frame.body !== undefined) {
      if (typeof frame.body !== 'string' && !(typeof frame.body === 'object' && frame.body instanceof Buffer)) {
        throw 'Message body is not string';
      }
      frame.headers['content-length'] = frame.body.length;
    }

    if (frame.headers) {
      for (var key in frame.headers) {
        headers[key] = frame.headers[key];
      }
    }

    args.frame = frame;
    this.emit('send', {
      frame: {
        headers: frame.headers,
        body: bodyObj
      },
      dest: args.dest
    });

    this._sendToSubscriptions(socket, args);

    if (callback) {
      callback(true);
    }
  };

  /**
   * Client subscribe event, emitted when client subscribe topic
   *
   * @event StompServer#subscribe
   * @type {object}
   * @property {string} id Subscription id
   * @property {string} sessionId Socket session id
   * @property {string} topic Destination topic
   * @property {string[]} tokens Tokenized topic
   * @property {object} socket Connected socket
   */
  this.onSubscribe = function (socket, args) {
    var sub = {
      id: args.id,
      sessionId: socket.sessionId,
      topic: args.dest,
      tokens: StompUtils.tokenizeDestination(args.dest),
      socket: socket
    };
    this.subscribes.push(sub);
    this.emit('subscribe', sub);
    this.conf.debug('Server subscribe', args.id, args.dest);
    return true;
  };

  /**
   * Client subscribe event, emitted when client unsubscribe topic
   *
   * @event StompServer#unsubscribe
   * @type {object}
   * @property {string} id Subscription id
   * @property {string} sessionId Socket session id
   * @property {string} topic Destination topic
   * @property {string[]} tokens Tokenized topic
   * @property {object} socket Connected socket
   * @return {boolean}
   */
  this.onUnsubscribe = function (socket, subId) {
    for (var t in this.subscribes) {
      var sub = this.subscribes[t];
      if (sub.id === subId && sub.sessionId === socket.sessionId) {
        delete this.subscribes[t];
        this.emit('unsubscribe', sub);
        return true;
      }
    }
    return false;
  };

  //</editor-fold>

  //<editor-fold defaultstate="collapsed" desc="Subscribe & Unsubscribe">

  var selfSocket = {
    sessionId: 'self_1234'
  };

  /**
   * Subscription callback method
   *
   * @callback OnSubscribedMessageCallback
   * @param {string} msg Message body
   * @param {object} headers Message headers
   * @param {string} headers.destination Message destination
   * @param {string} headers.subscription Id of subscription
   * @param {string} headers.message-id Id of message
   * @param {string} headers.content-type Content type
   * @param {string} headers.content-length Content length
   */
  // TODO: Create this method or remove above comment

  /**
   * Subscribe topic
   *
   * @param {string} topic Subscribed destination, wildcard is supported
   * @param {OnSubscribedMessageCallback=} callback Callback function
   * @param {object} headers Optional headers, used by client to provide a subscription ID (headers.id)
   * @return {string} Subscription id, when message is received event with this id is emitted
   * @example
   * stompServer.subscribe('/test.data', function(msg, headers) {});
   * //or alternative
   * var subs_id = stompServer.subscribe('/test.data');
   * stompServer.on(subs_id, function(msg, headers) {});
   */
  this.subscribe = function (topic, callback, headers) {
    var id;
    if (!headers || !headers.id) {
      id = 'self_' + Math.floor(Math.random() * 99999999999);
    } else {
      id = headers.id;
    }
    var sub = {
      topic: topic,
      tokens: StompUtils.tokenizeDestination(topic),
      id: id,
      sessionId: 'self_1234'
    };
    this.subscribes.push(sub);
    this.emit('subscribe', sub);
    if (callback) {
      this.on(id, callback);
    }
    return id;
  };

  /** Unsubscribe topic with subscription id
   *
   * @param {string} id Subscription id
   * @return {boolean} Subscription is deleted
   */
  this.unsubscribe = function (id) {
    this.removeAllListeners(id);
    return this.onUnsubscribe(selfSocket, id);
  };

  //</editor-fold>

  //<editor-fold defaultstate="collapsed" desc="Send">

  /**
   * Send message to matching subscribers.
   *
   * @param {object} socket websocket to send the message on
   * @param {string} args onSend args
   * @private
   */
  this._sendToSubscriptions = function (socket, args) {
    for (var i in this.subscribes) {
      var sub = this.subscribes[i];
      if (socket.sessionId === sub.sessionId) {
        continue;
      }
      var match = this._checkSubMatchDest(sub, args);
      if (match) {
        args.frame.headers.subscription = sub.id;
        args.frame.command = 'MESSAGE';
        var sock = sub.socket;
        if (sock !== undefined) {
          StompUtils.sendFrame(sock, args.frame);
        } else {
          this.emit(sub.id, args.frame.body, args.frame.headers);
        }
      }
    }
  };

  /** Send message to topic
   *
   * @param {string} topic Destination for message
   * @param {object} headers Message headers
   * @param {string} body Message body
   */
  this.send = function (topic, headers, body) {
    var _headers = {};
    if (headers) {
      for (var key in headers) {
        _headers[key] = headers[key];
      }
    }
    var frame = {
      body: body,
      headers: _headers
    };
    var args = {
      dest: topic,
      frame: this.frameParser(frame)
    };
    this.onSend(selfSocket, args);
  };

  //</editor-fold>

  //<editor-fold defaultstate="collapsed" desc="Frames">

  /**
   * Serialize frame to string for send
   *
   * @param {MsgFrame} frame Message frame
   * @return {MsgFrame} modified frame
   * */
  this.frameSerializer = function (frame) {
    if (frame.body !== undefined && frame.headers['content-type'] === 'application/json') {
      frame.body = JSON.stringify(frame.body);
    }
    return frame;
  };

  /**
   * Parse frame to object for reading
   *
   * @param {MsgFrame} frame Message frame
   * @return {MsgFrame} modified frame
   * */
  this.frameParser = function (frame) {
    if (frame.body !== undefined && frame.headers['content-type'] === 'application/json') {
      frame.body = JSON.parse(frame.body);
    }
    return frame;
  };

  //</editor-fold>

  /**
   * Test if the input subscriber has subscribed to the target destination.
   *
   * @param sub the subscriber
   * @param args onSend args
   * @returns {boolean} true if the input subscription matches destination
   * @private
   */
  this._checkSubMatchDest = function (sub, args) {
    var match = true;
    //console.log(args.dest);
    var tokens = StompUtils.tokenizeDestination(args.dest);
    for (var t in tokens) {
      var token = tokens[t];
      if (sub.tokens[t] === undefined ||
        (sub.tokens[t] !== token && sub.tokens[t] !== '*' && sub.tokens[t] !== '**')) {
        match = false;
        break;
      } else if (sub.tokens[t] === '**') {
        break;
      }
    }
    return match;
  };

  function connectionClose(socket) {
    var self = this;
    for (var t in self.subscribes) {
      var sub = self.subscribes[t];
      if (sub.sessionId === socket.sessionId) {
        delete self.subscribes[t];
      }
    }
  }

  function parseRequest(socket, data) {
    var frame = StompUtils.parseFrame(data);
    var cmdFunc = this.frameHandler[frame.command];
    if (cmdFunc) {
      frame = this.frameParser(frame);
      return cmdFunc(socket, frame);
    }
    return 'Command not found';
  }

};

util.inherits(StompServer, EventEmitter);

// Export
module.exports = StompServer;
<commit_msg>Version is now read from package.json<commit_after>var http            = require('http');
var WebSocketServer = require('ws').Server;
var EventEmitter    = require('events');
var util            = require('util');

var Stomp           = require('./lib/stomp');
var StompUtils      = require('./lib/stomp-utils');

const VERSION = require('./package.json').version;

/**
 * STOMP Server configuration
 *
 * @typedef {object} ServerConfig
 * @param {http.Server} server Http server reference
 * @param {function} [debug=function(args) {}] Debug function
 * @param {string} [path=/stomp] WebSocket path
 */

/**
 * @class
 * @augments EventEmitter
 *
 * Create Stomp server with config
 *
 * @param {ServerConfig} config Configuration for STOMP server
 */
var StompServer = function (config) {
  EventEmitter.call(this);

  if (config === undefined) {
    config = {};
  }

  this.conf = {
    server: config.server,
    serverName: config.serverName || 'STOMP-JS/' + VERSION,
    path: config.path || '/stomp',
    debug: config.debug || function (args) {
    }
  };

  if (this.conf.server === undefined) {
    throw 'Server is required';
  }

  this.subscribes = [];
  this.frameHandler = new Stomp.FrameHandler(this);
  this.heartBeatConfig = {
    client: 0,
    server: 0
  };

  this.socket = new WebSocketServer({
    server: this.conf.server,
    path: this.conf.path,
    perMessageDeflate: false
  });

  /**
   * Client connecting event, emitted after socket is opened.
   *
   * @event StompServer#connecting
   * @type {object}
   * @property {string} sessionId
   */
  this.socket.on('connection', function (ws) {
    ws.sessionId = StompUtils.genId();
    this.emit('connecting', ws.sessionId);
    this.conf.debug('Connect', ws.sessionId);

    ws.on('message', parseRequest.bind(this, ws));
    ws.on('close', connectionClose.bind(this, ws));
    ws.on('error', function (err) {
      this.conf.debug(err);
      this.emit('error', err);
    }.bind(this));
  }.bind(this));

  //<editor-fold defaultstate="collapsed" desc="Events">

  /**
   * Client connected event, emitted after connection established and negotiated
   *
   * @event StompServer#connected
   * @type {object}
   * @property {string} sessionId
   * @property {object} headers
   */
  this.onClientConnected = function (socket, args) {
    socket.clientHeartBeat = {
      client: args.hearBeat[0],
      server: args.hearBeat[1]
    };
    this.conf.debug('CONNECT', socket.sessionId, socket.clientHeartBeat, args.headers);
    this.emit('connected', socket.sessionId, args.headers);
    return true;
  };

  /**
   * Client disconnected event
   *
   * @event StompServer#disconnected
   * @type {object}
   * @property {string} sessionId
   */
  this.onDisconnect = function (socket, receiptId) {
    connectionClose(socket);
    this.conf.debug('DISCONNECT', socket.sessionId);
    this.emit('disconnected', socket.sessionId);
    this.conf.debug('DISCONNECT', socket.sessionId, receiptId);
    return true;
  };

  /**
   * Event emitted when broker send message to subscribers
   *
   * @event StompServer#send
   * @type {object}
   * @property {string} dest Destination
   * @property {string} frame Message frame
   */
  this.onSend = function (socket, args, callback) {
    var bodyObj = args.frame.body;
    var frame = this.frameSerializer(args.frame);
    var headers = {
      //default headers
      'message-id': StompUtils.genId('msg'),
      'content-type': 'text/plain'
    };

    if (frame.body !== undefined) {
      if (typeof frame.body !== 'string' && !(typeof frame.body === 'object' && frame.body instanceof Buffer)) {
        throw 'Message body is not string';
      }
      frame.headers['content-length'] = frame.body.length;
    }

    if (frame.headers) {
      for (var key in frame.headers) {
        headers[key] = frame.headers[key];
      }
    }

    args.frame = frame;
    this.emit('send', {
      frame: {
        headers: frame.headers,
        body: bodyObj
      },
      dest: args.dest
    });

    this._sendToSubscriptions(socket, args);

    if (callback) {
      callback(true);
    }
  };

  /**
   * Client subscribe event, emitted when client subscribe topic
   *
   * @event StompServer#subscribe
   * @type {object}
   * @property {string} id Subscription id
   * @property {string} sessionId Socket session id
   * @property {string} topic Destination topic
   * @property {string[]} tokens Tokenized topic
   * @property {object} socket Connected socket
   */
  this.onSubscribe = function (socket, args) {
    var sub = {
      id: args.id,
      sessionId: socket.sessionId,
      topic: args.dest,
      tokens: StompUtils.tokenizeDestination(args.dest),
      socket: socket
    };
    this.subscribes.push(sub);
    this.emit('subscribe', sub);
    this.conf.debug('Server subscribe', args.id, args.dest);
    return true;
  };

  /**
   * Client subscribe event, emitted when client unsubscribe topic
   *
   * @event StompServer#unsubscribe
   * @type {object}
   * @property {string} id Subscription id
   * @property {string} sessionId Socket session id
   * @property {string} topic Destination topic
   * @property {string[]} tokens Tokenized topic
   * @property {object} socket Connected socket
   * @return {boolean}
   */
  this.onUnsubscribe = function (socket, subId) {
    for (var t in this.subscribes) {
      var sub = this.subscribes[t];
      if (sub.id === subId && sub.sessionId === socket.sessionId) {
        delete this.subscribes[t];
        this.emit('unsubscribe', sub);
        return true;
      }
    }
    return false;
  };

  //</editor-fold>

  //<editor-fold defaultstate="collapsed" desc="Subscribe & Unsubscribe">

  var selfSocket = {
    sessionId: 'self_1234'
  };

  /**
   * Subscription callback method
   *
   * @callback OnSubscribedMessageCallback
   * @param {string} msg Message body
   * @param {object} headers Message headers
   * @param {string} headers.destination Message destination
   * @param {string} headers.subscription Id of subscription
   * @param {string} headers.message-id Id of message
   * @param {string} headers.content-type Content type
   * @param {string} headers.content-length Content length
   */
  // TODO: Create this method or remove above comment

  /**
   * Subscribe topic
   *
   * @param {string} topic Subscribed destination, wildcard is supported
   * @param {OnSubscribedMessageCallback=} callback Callback function
   * @param {object} headers Optional headers, used by client to provide a subscription ID (headers.id)
   * @return {string} Subscription id, when message is received event with this id is emitted
   * @example
   * stompServer.subscribe('/test.data', function(msg, headers) {});
   * //or alternative
   * var subs_id = stompServer.subscribe('/test.data');
   * stompServer.on(subs_id, function(msg, headers) {});
   */
  this.subscribe = function (topic, callback, headers) {
    var id;
    if (!headers || !headers.id) {
      id = 'self_' + Math.floor(Math.random() * 99999999999);
    } else {
      id = headers.id;
    }
    var sub = {
      topic: topic,
      tokens: StompUtils.tokenizeDestination(topic),
      id: id,
      sessionId: 'self_1234'
    };
    this.subscribes.push(sub);
    this.emit('subscribe', sub);
    if (callback) {
      this.on(id, callback);
    }
    return id;
  };

  /** Unsubscribe topic with subscription id
   *
   * @param {string} id Subscription id
   * @return {boolean} Subscription is deleted
   */
  this.unsubscribe = function (id) {
    this.removeAllListeners(id);
    return this.onUnsubscribe(selfSocket, id);
  };

  //</editor-fold>

  //<editor-fold defaultstate="collapsed" desc="Send">

  /**
   * Send message to matching subscribers.
   *
   * @param {object} socket websocket to send the message on
   * @param {string} args onSend args
   * @private
   */
  this._sendToSubscriptions = function (socket, args) {
    for (var i in this.subscribes) {
      var sub = this.subscribes[i];
      if (socket.sessionId === sub.sessionId) {
        continue;
      }
      var match = this._checkSubMatchDest(sub, args);
      if (match) {
        args.frame.headers.subscription = sub.id;
        args.frame.command = 'MESSAGE';
        var sock = sub.socket;
        if (sock !== undefined) {
          StompUtils.sendFrame(sock, args.frame);
        } else {
          this.emit(sub.id, args.frame.body, args.frame.headers);
        }
      }
    }
  };

  /** Send message to topic
   *
   * @param {string} topic Destination for message
   * @param {object} headers Message headers
   * @param {string} body Message body
   */
  this.send = function (topic, headers, body) {
    var _headers = {};
    if (headers) {
      for (var key in headers) {
        _headers[key] = headers[key];
      }
    }
    var frame = {
      body: body,
      headers: _headers
    };
    var args = {
      dest: topic,
      frame: this.frameParser(frame)
    };
    this.onSend(selfSocket, args);
  };

  //</editor-fold>

  //<editor-fold defaultstate="collapsed" desc="Frames">

  /**
   * Serialize frame to string for send
   *
   * @param {MsgFrame} frame Message frame
   * @return {MsgFrame} modified frame
   * */
  this.frameSerializer = function (frame) {
    if (frame.body !== undefined && frame.headers['content-type'] === 'application/json') {
      frame.body = JSON.stringify(frame.body);
    }
    return frame;
  };

  /**
   * Parse frame to object for reading
   *
   * @param {MsgFrame} frame Message frame
   * @return {MsgFrame} modified frame
   * */
  this.frameParser = function (frame) {
    if (frame.body !== undefined && frame.headers['content-type'] === 'application/json') {
      frame.body = JSON.parse(frame.body);
    }
    return frame;
  };

  //</editor-fold>

  /**
   * Test if the input subscriber has subscribed to the target destination.
   *
   * @param sub the subscriber
   * @param args onSend args
   * @returns {boolean} true if the input subscription matches destination
   * @private
   */
  this._checkSubMatchDest = function (sub, args) {
    var match = true;
    //console.log(args.dest);
    var tokens = StompUtils.tokenizeDestination(args.dest);
    for (var t in tokens) {
      var token = tokens[t];
      if (sub.tokens[t] === undefined ||
        (sub.tokens[t] !== token && sub.tokens[t] !== '*' && sub.tokens[t] !== '**')) {
        match = false;
        break;
      } else if (sub.tokens[t] === '**') {
        break;
      }
    }
    return match;
  };

  function connectionClose(socket) {
    var self = this;
    for (var t in self.subscribes) {
      var sub = self.subscribes[t];
      if (sub.sessionId === socket.sessionId) {
        delete self.subscribes[t];
      }
    }
  }

  function parseRequest(socket, data) {
    var frame = StompUtils.parseFrame(data);
    var cmdFunc = this.frameHandler[frame.command];
    if (cmdFunc) {
      frame = this.frameParser(frame);
      return cmdFunc(socket, frame);
    }
    return 'Command not found';
  }

};

util.inherits(StompServer, EventEmitter);

// Export
module.exports = StompServer;
<|endoftext|>


<commit_before>"use strict";

/*
{
    type: "csv",
    path: "filename",
    add-timestamp: false,
    delimiter: ",",
    quote: "\"",
    escape: "\"",
    types: {
        updated: "boolean"
    }
    columns: ["RUID", "NAME"]
}
*/

var CONSTANTS = require("../constants.js"),
    helpers = require("../helpers.js"),
    fs = require("fs"),
    csv = require("csv"),
    CSV_STATE = "__csv_state__";

module.exports = {

    // @inheritdoc storage#open
    open: function (config) {
        var ctx = this;

        function getColumns (row) {
            if (config.columns) {
                return config.columns;
            }
            return row;
        }

        ctx.records = {};

        return new Promise(function (resolve, reject) {
            try {
                var stream = fs.createReadStream(config.path, {
                        flags: "r",
                        encoding: "utf8",
                        autoClose: true
                    }),
                    parser = csv.parse({
                        delimiter: config.delimiter || ",",
                        quote: config.quote || "\"",
                        escape: config.escape || "\"",
                        columns: getColumns,
                        relax: true,
                        "skip_empty_lines": true
                    });
                stream.pipe(parser);
                parser.on("readable", function () {
                    var record = parser.read();
                    while (record) {
                        helpers.convertColumns(record, config.types || {});
                        ctx.records[record[CONSTANTS.RECORD_UID]] = record;
                        if (config.verbose) {
                            console.log(JSON.stringify(record));
                        }
                        record = parser.read();
                    }
                });
                parser.on("end", function () {
                    resolve();
                });
            } catch (e) {
                reject(e);
            }
        });
    },

    // @inheritdoc storage#find
    find: function (config, uid) {
        var record = this.records[uid];
        if (record) {
            record[CSV_STATE] = "fetched";
        } else {
            record = null;
        }
        return Promise.resolve(record);
    },

    // @inheritdoc storage#add
    add: function (config, record, updated) {
        var ruid = record[CONSTANTS.RECORD_UID];
        if (updated) {
            record[CSV_STATE] = "updated";
        } else {
            record[CSV_STATE] = "created";
        }
        this.records[ruid] = record;
        if (config.verbose) {
            console.log(ruid + "(" + updated + "): " + JSON.stringify(record));
        }
        return Promise.resolve();
    },

    // @inheritdoc storage#close
    close: function (/*config*/) {
        var ctx = this,
            obsoleteCount = 0,
            totalCount = 0;
        Object.keys(ctx.records).forEach(function (ruid) {
            ++totalCount;
            var record = ctx.records[ruid];
            if (undefined === record[CSV_STATE]) {
                ++obsoleteCount;
            }
        });
        return Promise.resolve({
            "obsolete records": obsoleteCount,
            "total number of records": totalCount
        });
    }

};
<commit_msg>Working on output filename<commit_after>"use strict";

/*
{
    type: "csv",
    path: "filename",
    add-file-timestamp: false,
    delimiter: ",",
    quote: "\"",
    escape: "\"",
    types: {
        updated: "boolean"
    }
    columns: ["RUID", "NAME"]
}
*/

var CONSTANTS = require("../constants.js"),
    helpers = require("../helpers.js"),
    fs = require("fs"),
    path = require("path"),
    csv = require("csv"),
    CSV_STATE = "__csv_state__";

module.exports = {

    // @inheritdoc storage#open
    open: function (config) {
        var ctx = this;

        function getColumns (row) {
            if (config.columns) {
                return config.columns;
            }
            return row;
        }

        ctx.records = {};

        return new Promise(function (resolve, reject) {
            try {
                var stream = fs.createReadStream(config.path, {
                        flags: "r",
                        encoding: "utf8",
                        autoClose: true
                    }),
                    parser = csv.parse({
                        delimiter: config.delimiter || ",",
                        quote: config.quote || "\"",
                        escape: config.escape || "\"",
                        columns: getColumns,
                        relax: true,
                        "skip_empty_lines": true
                    });
                stream.pipe(parser);
                parser.on("readable", function () {
                    var record = parser.read();
                    while (record) {
                        helpers.convertColumns(record, config.types || {});
                        ctx.records[record[CONSTANTS.RECORD_UID]] = record;
                        if (config.verbose) {
                            console.log(JSON.stringify(record));
                        }
                        record = parser.read();
                    }
                });
                parser.on("end", function () {
                    resolve();
                });
            } catch (e) {
                reject(e);
            }
        });
    },

    // @inheritdoc storage#find
    find: function (config, uid) {
        var record = this.records[uid];
        if (record) {
            record[CSV_STATE] = "fetched";
        } else {
            record = null;
        }
        return Promise.resolve(record);
    },

    // @inheritdoc storage#add
    add: function (config, record, updated) {
        var ruid = record[CONSTANTS.RECORD_UID];
        if (updated) {
            record[CSV_STATE] = "updated";
        } else {
            record[CSV_STATE] = "created";
        }
        this.records[ruid] = record;
        if (config.verbose) {
            console.log(ruid + "(" + updated + "): " + JSON.stringify(record));
        }
        return Promise.resolve();
    },

    // @inheritdoc storage#close
    close: function (config) {
        var ctx = this,
            obsoleteCount = 0,
            totalCount = 0,
            statistics,
            outputFileName;
        if (config["add-file-timestamp"]) {
            outputFileName = path.parse(config.path);
            delete outputFileName.base;
            outputFileName.name += "." + helpers.getTimestamp();
            outputFileName = path.format(outputFileName);
        } else {
            outputFileName = config.path;
        }
        Object.keys(ctx.records).forEach(function (ruid) {
            ++totalCount;
            var record = ctx.records[ruid];
            if (undefined === record[CSV_STATE]) {
                ++obsoleteCount;
            }
        });
        statistics = {
            "output filename": outputFileName,
            "obsolete records": obsoleteCount,
            "total number of records": totalCount
        };
        return Promise.resolve(statistics);
    }

};
<|endoftext|>


<commit_before>/**
 * Google Cloud Storage Adapter
 *
 */
var fs = require('fs'),
  path = require('path'),
  gcloud = require('gcloud'),
  debug = require('debug')('backupo'),
  progress = require('progress-stream'),
  ProgressBar = require('progress'),
  mime = require('mime');

module.exports.save = function(file, option, cb) {
  debug('gcs.js');
  var gcs = gcloud.storage({
    projectId: option.projectId,
    keyFilename: option.keyFilename
  });
  var bucket = gcs.bucket(option.bucket);

  var metadata = {};
  metadata.contentType = mime.lookup(file);

  var localReadStream = fs.createReadStream(file);

  var gcs_file = bucket.file(path.basename(file));
  var remoteWriteStream = gcs_file.createWriteStream({
    metadata: metadata
  });
  remoteWriteStream.on('error', function(err) {
    console.log(err);
    return cb(err);
  })
  remoteWriteStream.on('finish', function() {
    console.log(new Date(), file, 'upload to google cloud storage done.');
    return cb(null);
  });
  var stat = fs.statSync(file);
  var bar = new ProgressBar(new Date() + ' uploading [:bar] :percent :etas', {
    complete: '=',
    incomplete: ' ',
    width: 20,
    total: stat.size
  });
  localReadStream.on('data', function (chunk) {
    bar.tick(chunk.length);
  });
  localReadStream.pipe(remoteWriteStream);
}
<commit_msg>Remove unnecessary dependency<commit_after>/**
 * Google Cloud Storage Adapter
 *
 */
var fs = require('fs'),
  path = require('path'),
  gcloud = require('gcloud'),
  debug = require('debug')('backupo'),
  ProgressBar = require('progress'),
  mime = require('mime');

module.exports.save = function(file, option, cb) {
  debug('gcs.js');
  var gcs = gcloud.storage({
    projectId: option.projectId,
    keyFilename: option.keyFilename
  });
  var bucket = gcs.bucket(option.bucket);

  var metadata = {};
  metadata.contentType = mime.lookup(file);

  var localReadStream = fs.createReadStream(file);

  var gcs_file = bucket.file(path.basename(file));
  var remoteWriteStream = gcs_file.createWriteStream({
    metadata: metadata
  });
  remoteWriteStream.on('error', function(err) {
    console.log(err);
    return cb(err);
  })
  remoteWriteStream.on('finish', function() {
    console.log(new Date(), file, 'upload to google cloud storage done.');
    return cb(null);
  });
  var stat = fs.statSync(file);
  var bar = new ProgressBar(new Date() + ' uploading [:bar] :percent :etas', {
    complete: '=',
    incomplete: ' ',
    width: 20,
    total: stat.size
  });
  localReadStream.on('data', function (chunk) {
    bar.tick(chunk.length);
  });
  localReadStream.pipe(remoteWriteStream);
}
<|endoftext|>


<commit_before>import Vuex from 'vuex'

export default () =>
  new Vuex.Store({
    getters: {
      user({ auth }) {
        return auth && auth.user && auth.user.data && auth.user.data.user
      },
      storage({ auth }) {
        return auth && auth.user && auth.user.data && auth.user.data.storage
      }
    }
  })
<commit_msg>Resolve similar blocks error<commit_after>import Vuex from 'vuex'

export default () =>
  new Vuex.Store({
    getters: {
      user({ auth }) {
        return getData(auth, 'user')
      },
      storage({ auth }) {
        return getData(auth, 'storage')
      }
    }
  })

function getData(auth, key) {
  return auth && auth.user && auth.user.data && auth.user.data[key]
}
<|endoftext|>


<commit_before>import React from 'react';
import { storiesOf } from '@storybook/react';
import { number, select, text } from '@storybook/addon-knobs/react';
import { Box, TextBody, COLORS, TINTS } from '../src';

const displayValues = ['inline', 'inline-block', 'block', 'flex', 'inline-flex'];
const justifyContentValues = ['center', 'flex-start', 'flex-end', 'space-around', 'space-between', 'space-evenly'];
const textAlignValues = ['center', 'left', 'right'];

const spacingOptions = {
  range: true,
  min: 0,
  max: 8,
  step: 1,
};

storiesOf('Box', module)
  .addParameters({
    info: {
      propTablesExclude: [TextBody],
    },
  })
  .add('Basic', () => (
    <Box
      backgroundColor={select('Background color', COLORS, 'neutral')}
      backgroundTint={select('Background tint', TINTS, 'light')}
      borderWidth={number('Border width', 0)}
      borderBottomWidth={number('Border bottom width', 0)}
      borderLeftWidth={number('Border left width', 0)}
      borderRightWidth={number('Border right width', 0)}
      borderTopWidth={number('Border top width', 0)}
      borderColor={select('Border color', COLORS, 'neutral')}
      borderTint={select('Border tint', TINTS, 'dark')}
      borderRadius={text('Border radius', undefined)}
      display={select('Display', displayValues, 'block')}
      justifyContent={select('Justify Content', justifyContentValues, 'flex-start')}
      margin={number('Margin', 0, spacingOptions)}
      padding={number('Padding', 3, spacingOptions)}
      textAlign={select('Text align', textAlignValues, 'left')}
    >
      <TextBody>I'm body text inside a Box component</TextBody>
    </Box>
  ));
<commit_msg>Update stories<commit_after>import React from 'react';
import { storiesOf } from '@storybook/react';
import { number, select } from '@storybook/addon-knobs/react';
import { Box, TextBody, COLORS, TINTS } from '../src';

const borderRadiusValues = ['square', 'circle', 'rounded'];
const displayValues = ['inline', 'inline-block', 'block', 'flex', 'inline-flex'];
const justifyContentValues = ['center', 'flex-start', 'flex-end', 'space-around', 'space-between', 'space-evenly'];
const textAlignValues = ['center', 'left', 'right'];

const spacingOptions = {
  range: true,
  min: 0,
  max: 8,
  step: 1,
};

storiesOf('Box', module)
  .addParameters({
    info: {
      propTablesExclude: [TextBody],
    },
  })
  .add('Basic', () => (
    <Box
      backgroundColor={select('Background color', COLORS, 'neutral')}
      backgroundTint={select('Background tint', TINTS, 'light')}
      borderWidth={number('Border width', 0)}
      borderBottomWidth={number('Border bottom width', 0)}
      borderLeftWidth={number('Border left width', 0)}
      borderRightWidth={number('Border right width', 0)}
      borderTopWidth={number('Border top width', 0)}
      borderColor={select('Border color', COLORS, 'neutral')}
      borderTint={select('Border tint', TINTS, 'dark')}
      borderRadius={select('Border radius', borderRadiusValues, 'square')}
      borderTopLeftRadius={select('Border top left radius', borderRadiusValues, 'square')}
      borderTopRightRadius={select('Border top right radius', borderRadiusValues, 'square')}
      borderBottomLeftRadius={select('Border bottom left radius', borderRadiusValues, 'square')}
      borderBottomRightRadius={select('Border bottom right radius', borderRadiusValues, 'square')}
      display={select('Display', displayValues, 'block')}
      justifyContent={select('Justify Content', justifyContentValues, 'flex-start')}
      margin={number('Margin', 0, spacingOptions)}
      padding={number('Padding', 3, spacingOptions)}
      textAlign={select('Text align', textAlignValues, 'left')}
    >
      <TextBody>I'm body text inside a Box component</TextBody>
    </Box>
  ));
<|endoftext|>


<commit_before>'use strict';

const P = require('bluebird');
const HyperSwitch = require('hyperswitch');

const mwUtil = require('../lib/mwUtil');

const HTTPError = HyperSwitch.HTTPError;
const spec = HyperSwitch.utils.loadSpec(`${__dirname}/parsoid.yaml`);

const OPERATIONS = [
    'getHtml',
    'getDataParsoid',
    'getLintErrors',
    'transformHtmlToHtml',
    'transformHtmlToWikitext',
    'transformWikitextToHtml',
    'transformWikitextToLint',
    'transformChangesToWikitext'
];

const invert = (v) => v === 'js' ? 'php' : 'js';

class ParsoidProxy {

    constructor(opts = {}) {
        const modOpts = this._initOpts(opts);
        const jsOpts = Object.assign({}, modOpts);
        const phpOpts = Object.assign({}, modOpts);
        delete jsOpts.php_host;
        phpOpts.host = phpOpts.php_host;
        delete phpOpts.php_host;
        this._initMods(jsOpts, phpOpts);
    }

    _initOpts(opts) {
        const retOpts = Object.assign({}, opts);
        retOpts.host = retOpts.host || retOpts.parsoidHost;
        if (!retOpts.host && !retOpts.php_host) {
            throw new Error('Parsoid proxy: no host option specified!');
        }
        this.options = retOpts.proxy || {};
        // possible values are 'js' and 'php'
        this.default_variant = this.options.default_variant || 'js';
        if (!['js', 'php'].includes(this.default_variant)) {
            throw new Error('Parsoid proxy: valid variants are js and php!');
        }
        // possible values are 'single', 'mirror' and 'split'
        this.mode = this.options.mode || 'single';
        if (!['single', 'mirror', 'split'].includes(this.mode)) {
            throw new Error('Parsoid proxy: valid modes are single, mirror and split!');
        }
        this.percentage = parseFloat(this.options.percentage || 0);
        if (isNaN(this.percentage) || this.percentage < 0 || this.percentage > 100) {
            throw new Error('Parsoid proxy: percentage must a number between 0 and 100!');
        }
        if (this.percentage === 0 && this.mode === 'mirror') {
            // a special case of mirror mode with 0% is in fact the single mode
            this.mode = 'single';
        }
        this.splitRegex = mwUtil.constructRegex(this.options.pattern);
        if (!this.splitRegex && this.mode === 'split') {
            // split mode with no pattern is single mode
            this.mode = 'single';
            this.splitRegex = /^$/;
        } else if (this.mode !== 'split') {
            this.splitRegex = /^$/;
        }
        if (this.mode === 'split') {
            this.percentage = 100;
        }
        this.resources = [];
        delete retOpts.parsoidHost;
        delete retOpts.proxy;
        return retOpts;
    }

    _initMods(jsOpts, phpOpts) {
        if (!phpOpts.host) {
            if (this.mode !== 'single') {
                // php_host was not provided but the config expects
                // both modules to be functional, so error out
                throw new Error('Parsoid proxy: expected both host and php_host options!');
            }
            if (this.default_variant === 'php') {
                phpOpts.host = jsOpts.host;
                delete jsOpts.host;
            }
        }
        if (this.mode === 'mirror') {
            if (this.default_variant === 'php') {
                throw new Error('Parsoid proxy: when mirroring, only js can be the default variant!');
            }
            // js is the default, so don't let php issue dependency update events
            phpOpts.skip_updates = true;
        }
        this.mods = {
            js: this._addMod('js', jsOpts),
            php: this._addMod('php', phpOpts)
        };
    }

    _backendNotSupported() {
        throw new HTTPError({
            status: 400,
            body: {
                type: 'bad_request',
                description: 'Parsoid variant not configured!'
            }
        });
    }

    _addMod(variant, opts) {
        if (opts.host) {
            const mod = require(`./parsoid-${variant}.js`)(opts);
            // we are interested only in the operations and resources
            this.resources = this.resources.concat(mod.resources);
            return mod.operations;
        }
        // return operations that error out if no host is specified
        const ret = {};
        OPERATIONS.forEach((o) => {
            ret[o] = this._backendNotSupported;
        });
        return ret;
    }

    _getStickyVariant(hyper, req) {
        let variant = hyper._rootReq.headers['x-parsoid-variant'] ||
            req.headers['x-parsoid-variant'];
        if (!variant && hyper._rootReq.headers.cookie) {
            const match = /parsoid_variant=([^;]+)/i.exec(hyper._rootReq.headers.cookie);
            if (match) {
                variant = match[1];
            }
        }
        if (!variant) {
            return undefined;
        }
        variant = variant.toLowerCase();
        if (!['js', 'php'].includes(variant)) {
            throw new HTTPError({
                status: 400,
                body: {
                    type: 'bad_request',
                    description: `Parsoid variant ${variant} not configured!`
                }
            });
        }
        return variant;
    }

    _req(variant, operation, hyper, req, setHdr = true, sticky = false) {
        if (setHdr) {
            req.headers = req.headers || {};
            req.headers['x-parsoid-variant'] = variant;
        }
        return this.mods[variant][operation](hyper, req)
        .then((res) => {
            res.headers = res.headers || {};
            res.headers['x-parsoid-variant'] = variant;
            return P.resolve(res);
        }).catch({ status: 404 }, { status: 421 }, (e) => {
            // if we actually get a 421, we might be in trouble, so log it
            if (e.status === 421) {
                hyper.logger.log('warn/parsoidproxy/421', e);
            }
            // if we are in split mode, provide a fallback for transforms except lint
            if (!sticky && this.mode === 'split' && /transform/.test(operation) &&
                    !/Lint/.test(operation)) {
                if (setHdr) {
                    req.headers['x-parsoid-variant'] = invert(variant);
                }
                return this.mods[invert(variant)][operation](hyper, req)
                .then((res) => {
                    res.headers = res.headers || {};
                    res.headers['x-parsoid-variant'] = invert(variant);
                    return P.resolve(res);
                });
            }
            throw e;
        });
    }

    doRequest(operation, hyper, req) {
        // TEMP TEMP TEMP
        // all html2html requests go to JS and are mirrored to PHP
        if (operation === 'transformHtmlToHtml') {
            this._req('php', operation, hyper, req, false, true)
            .catch((e) => hyper.logger.log('info/parsoidproxy/html2html', e));
            return this._req('js', operation, hyper, req, false, true);
        }
        // END TEMP
        let variant = this._getStickyVariant(hyper, req);
        // TEMP: Do not honour the header or cookie for now
        /* if (variant) {
            // the variant has been set explicitly by the client, honour it
            return this._req(variant, operation, hyper, req, true, true);
        } */
        // END TEMP
        // we can safely check simply where to direct the request
        // using splitRegex because it won't match anything for any
        // mode other than split
        if (this.splitRegex.test(req.params.domain)) {
            variant = invert(this.default_variant);
        } else {
            variant = this.default_variant;
        }
        // mirror mode works only for getFormat, since for mirroring
        // tranforms we would need to be sure we have the php output
        // stashed
        // also, if we are in split mode, then we must pretend we are
        // also in 100% mirror mode since we want to keep both
        // variants in storage fresh
        /* Mirroring all the traffic causes too much load to withstand
        if (this.mode !== 'single' && !/transform/.test(operation)) {
            if (Math.round(Math.random() * 100) <= this.percentage) {
                // clone the request and its headers
                const mReq = {
                    method: req.method,
                    uri: req.uri,
                    headers: Object.assign({}, req.headers),
                    query: req.query,
                    body: req.body,
                    params: req.params
                };
                // issue an async request to the second variant and
                // don't wait for the return value
                this._req(invert(variant), operation, hyper, mReq, false)
                .catch((e) => hyper.logger.log(`info/parsoidproxy/${invert(variant)}`, e));
            }
        }*/

        return this._req(variant, operation, hyper, req);
    }

    getOperations() {
        const ret = {};
        OPERATIONS.forEach((o) => {
            ret[o] = this.doRequest.bind(this, o);
        });
        return ret;
    }

}

module.exports = (options = {}) => {
    const ps = new ParsoidProxy(options);
    return {
        spec,
        operations: ps.getOperations(),
        resources: ps.resources
    };
};
<commit_msg>Disable all parsoid-php vs parsoid-js special cases<commit_after>'use strict';

const P = require('bluebird');
const HyperSwitch = require('hyperswitch');

const mwUtil = require('../lib/mwUtil');

const HTTPError = HyperSwitch.HTTPError;
const spec = HyperSwitch.utils.loadSpec(`${__dirname}/parsoid.yaml`);

const OPERATIONS = [
    'getHtml',
    'getDataParsoid',
    'getLintErrors',
    'transformHtmlToHtml',
    'transformHtmlToWikitext',
    'transformWikitextToHtml',
    'transformWikitextToLint',
    'transformChangesToWikitext'
];

const invert = (v) => v === 'js' ? 'php' : 'js';

class ParsoidProxy {

    constructor(opts = {}) {
        const modOpts = this._initOpts(opts);
        const jsOpts = Object.assign({}, modOpts);
        const phpOpts = Object.assign({}, modOpts);
        delete jsOpts.php_host;
        phpOpts.host = phpOpts.php_host;
        delete phpOpts.php_host;
        this._initMods(jsOpts, phpOpts);
    }

    _initOpts(opts) {
        const retOpts = Object.assign({}, opts);
        retOpts.host = retOpts.host || retOpts.parsoidHost;
        if (!retOpts.host && !retOpts.php_host) {
            throw new Error('Parsoid proxy: no host option specified!');
        }
        this.options = retOpts.proxy || {};
        // possible values are 'js' and 'php'
        this.default_variant = this.options.default_variant || 'js';
        if (!['js', 'php'].includes(this.default_variant)) {
            throw new Error('Parsoid proxy: valid variants are js and php!');
        }
        // possible values are 'single', 'mirror' and 'split'
        this.mode = this.options.mode || 'single';
        if (!['single', 'mirror', 'split'].includes(this.mode)) {
            throw new Error('Parsoid proxy: valid modes are single, mirror and split!');
        }
        this.percentage = parseFloat(this.options.percentage || 0);
        if (isNaN(this.percentage) || this.percentage < 0 || this.percentage > 100) {
            throw new Error('Parsoid proxy: percentage must a number between 0 and 100!');
        }
        if (this.percentage === 0 && this.mode === 'mirror') {
            // a special case of mirror mode with 0% is in fact the single mode
            this.mode = 'single';
        }
        this.splitRegex = mwUtil.constructRegex(this.options.pattern);
        if (!this.splitRegex && this.mode === 'split') {
            // split mode with no pattern is single mode
            this.mode = 'single';
            this.splitRegex = /^$/;
        } else if (this.mode !== 'split') {
            this.splitRegex = /^$/;
        }
        if (this.mode === 'split') {
            this.percentage = 100;
        }
        this.resources = [];
        delete retOpts.parsoidHost;
        delete retOpts.proxy;
        return retOpts;
    }

    _initMods(jsOpts, phpOpts) {
        if (!phpOpts.host) {
            if (this.mode !== 'single') {
                // php_host was not provided but the config expects
                // both modules to be functional, so error out
                throw new Error('Parsoid proxy: expected both host and php_host options!');
            }
            if (this.default_variant === 'php') {
                phpOpts.host = jsOpts.host;
                delete jsOpts.host;
            }
        }
        if (this.mode === 'mirror') {
            if (this.default_variant === 'php') {
                throw new Error('Parsoid proxy: when mirroring, only js can be the default variant!');
            }
            // js is the default, so don't let php issue dependency update events
            phpOpts.skip_updates = true;
        }
        this.mods = {
            js: this._addMod('js', jsOpts),
            php: this._addMod('php', phpOpts)
        };
    }

    _backendNotSupported() {
        throw new HTTPError({
            status: 400,
            body: {
                type: 'bad_request',
                description: 'Parsoid variant not configured!'
            }
        });
    }

    _addMod(variant, opts) {
        if (opts.host) {
            const mod = require(`./parsoid-${variant}.js`)(opts);
            // we are interested only in the operations and resources
            this.resources = this.resources.concat(mod.resources);
            return mod.operations;
        }
        // return operations that error out if no host is specified
        const ret = {};
        OPERATIONS.forEach((o) => {
            ret[o] = this._backendNotSupported;
        });
        return ret;
    }

    _req(variant, operation, hyper, req, setHdr = true, sticky = false) {
        if (setHdr) {
            req.headers = req.headers || {};
            req.headers['x-parsoid-variant'] = variant;
        }
        return this.mods[variant][operation](hyper, req)
        .then((res) => {
            res.headers = res.headers || {};
            res.headers['x-parsoid-variant'] = variant;
            return P.resolve(res);
        }).catch({ status: 404 }, { status: 421 }, (e) => {
            // if we actually get a 421, we might be in trouble, so log it
            if (e.status === 421) {
                hyper.logger.log('warn/parsoidproxy/421', e);
            }
            // if we are in split mode, provide a fallback for transforms except lint
            if (!sticky && this.mode === 'split' && /transform/.test(operation) &&
                    !/Lint/.test(operation)) {
                if (setHdr) {
                    req.headers['x-parsoid-variant'] = invert(variant);
                }
                return this.mods[invert(variant)][operation](hyper, req)
                .then((res) => {
                    res.headers = res.headers || {};
                    res.headers['x-parsoid-variant'] = invert(variant);
                    return P.resolve(res);
                });
            }
            throw e;
        });
    }

    doRequest(operation, hyper, req) {
        const variant = this.splitRegex.test(req.params.domain) ?
            invert(this.default_variant) : this.default_variant;
        return this._req(variant, operation, hyper, req);
    }

    getOperations() {
        const ret = {};
        OPERATIONS.forEach((o) => {
            ret[o] = this.doRequest.bind(this, o);
        });
        return ret;
    }

}

module.exports = (options = {}) => {
    const ps = new ParsoidProxy(options);
    return {
        spec,
        operations: ps.getOperations(),
        resources: ps.resources
    };
};
<|endoftext|>


<commit_before>'use strict';

import React from 'react';
import { ActionSheetIOS, View } from 'react-native';

export default class ActionSheet extends React.Component {
  render() {
    return <View style={{ flex: 1 }}>{React.Children.only(this.props.children)}</View>;
  }

  showActionSheetWithOptions(options, onSelect) {
    const iosOptions = {
      ...options,
      // A null title or message on iOS causes a crash
      title: options.title || undefined,
      message: options.message || undefined,
    };

    ActionSheetIOS.showActionSheetWithOptions(iosOptions, onSelect);
  }
}
<commit_msg>Fixed crash on iOS if the icons array contains elements (like vector icons) rather than image source paths.<commit_after>'use strict';

import React from 'react';
import { ActionSheetIOS, View } from 'react-native';

export default class ActionSheet extends React.Component {
  render() {
    return <View style={{ flex: 1 }}>{React.Children.only(this.props.children)}</View>;
  }

  showActionSheetWithOptions(options, onSelect) {
    const iosOptions = {
      ...options,
      // A null title or message on iOS causes a crash
      title: options.title || undefined,
      message: options.message || undefined,
      icons: undefined,
    };

    ActionSheetIOS.showActionSheetWithOptions(iosOptions, onSelect);
  }
}
<|endoftext|>


<commit_before>/**
 * @class Alloy
 * Top-level module for Alloy functions.
 *
 */
var 	   _ = require('alloy/underscore')._,
	Backbone = require('alloy/backbone'),
	STR = require('alloy/string');

exports._ = _;
exports.Backbone = Backbone;

// Ti.Analytics.featureEvent('alloy.start', {
// 	guid: Ti.Platform.id,
// 	tiversion: Ti.version,
// 	osname: Ti.Platform.osname,
// 	osversion: Ti.Platform.version,
// 	locale: Ti.Platform.locale
// });

exports.M = function(name, modelDesc, migrations) {
	var config = modelDesc.config;
    var type = (config.adapter ? config.adapter.type : null) || 'localDefault';
    if (type === 'localDefault') {
    	type = OS_MOBILEWEB ? 'localStorage' : 'sql';
    }

	var adapter = require('alloy/sync/'+type);
    var extendObj = {
		defaults: config.defaults,
        sync: function(method, model, opts) {
			var config = model.config || {};
			var adapterObj = config.adapter || {};
			var type = (config.adapter ? config.adapter.type : null) || 'localDefault';
			if (type === 'localDefault') {
		    	type = OS_MOBILEWEB ? 'localStorage' : 'sql';
		    }

			require('alloy/sync/'+type).sync(model,method,opts);
		}
	};

	var extendClass = {};

	// construct the model based on the current adapter type
	if (migrations) { extendClass.migrations = migrations; }
    if (_.isFunction(adapter.beforeModelCreate)) { config = adapter.beforeModelCreate(config) || config; }
	var Model = Backbone.Model.extend(extendObj, extendClass);
	Model.prototype.config = config;
	if (_.isFunction(adapter.afterModelCreate)) { adapter.afterModelCreate(Model); }

	if (_.isFunction(modelDesc.extendModel)) {
		Model = modelDesc.extendModel(Model) || Model;
	}

	return Model;
};

exports.C = function(name, modelDesc, model) {
    var extendObj = {
		model: model,
        sync: function(method, model, opts) {
			var config = (model.config || {});
			var type = (config.adapter ? config.adapter.type : null) || 'localDefault';
			if (type === 'localDefault') {
		    	type = OS_MOBILEWEB ? 'localStorage' : 'sql';
		    }

			require('alloy/sync/'+type).sync(model,method,opts);
		}
	};

	var Collection = Backbone.Collection.extend(extendObj);
	var config = Collection.prototype.config = model.prototype.config;

	var type = (config.adapter ? config.adapter.type : null) || 'localDefault';
	var adapter = require('alloy/sync/'+type);
	if (_.isFunction(adapter.afterCollectionCreate)) { adapter.afterCollectionCreate(Collection); }

	if (_.isFunction(modelDesc.extendModel)) {
		Collection = modelDesc.extendCollection(Collection) || Collection;
	}

	return Collection;
};

exports.A = function(t, type, parent) {
	_.extend(t,Backbone.Events);

	(function() {

		// we are going to wrap addEventListener and removeEventListener
		// with on, off so we can use the Backbone events
		var al = t.addEventListener,
			rl = t.removeEventListener,
			oo = t.on,
			of = t.off,
			tg = t.trigger,
		   cbs = [],
		   ctx = {};

		if (!al || !rl) {
			return;
		}

		t.on = function(e,cb,context) {
			var wcb = function(evt) {
				try {
					_.bind(tg,ctx,e,evt)();
				}
				catch(E) {
					Ti.API.error("Error triggering '"+e+"' event: "+E);
				}
			};
			cbs[cb]=wcb;

			if (OS_IOS) {
				al(e, wcb);
			} else {
				al.call(t, e, wcb);
			}

			_.bind(oo,ctx,e,cb,context)();
		};

		t.off = function(e,cb,context) {
			var f = cbs[cb];
			if (f) {
				_.bind(of,ctx,e,cb,context)();

				if (OS_IOS) {
					rl(e, f);
				} else {
					rl.call(t, e, f);
				}

				delete cbs[cb];
				f = null;
			}
		};

	})();

	return t;
}

/**
 * @method getWidget
 * Factory method for instantiating a widget controller. Creates and returns an instance of the
 * named widget.
 * @param {String} id Id of widget to instantiate.
 * @param {String} name Name of the view within the widget to instantiate ('widget' by default)
 * @param {*...} [args] Arguments to pass to the widget.
 * @return {Alloy.Controller} Alloy widget controller object.
 *
 * @deprecated 1.0 Use {Alloy#createWidget} instead
 */
exports.getWidget = function(id, name, args) {
	Ti.API.warn('Alloy.getWidget() is deprecated, use Alloy.createWidget() instead.');
	return exports.createWidget(id, name, args);
}

/**
 * @method createWidget
 * Factory method for instantiating a widget controller. Creates and returns an instance of the
 * named widget.
 * @param {String} id Id of widget to instantiate.
 * @param {String} name Name of the view within the widget to instantiate ('widget' by default)
 * @param {*...} [args] Arguments to pass to the widget.
 * @return {Alloy.Controller} Alloy widget controller object.
 */
exports.createWidget = function(id, name, args) {
	return new (require('alloy/widgets/' + id + '/controllers/' + (name || 'widget')))(args);
}

/**
 * @method getController
 * Factory method for instantiating a controller. Creates and returns an instance of the
 * named controller.
 * @param {String} name Name of controller to instantiate.
 * @param {*...} [args] Arguments to pass to the controller.
 * @return {Alloy.Controller} Alloy controller object.
 *
 * @deprecated 1.0 Use {Alloy#createController} instead
 */
exports.getController = function(name, args) {
	Ti.API.warn('Alloy.getController() is deprecated, use Alloy.createController() instead.');
	return exports.createController(name, args);
}

/**
 * @method createController
 * Factory method for instantiating a controller. Creates and returns an instance of the
 * named controller.
 * @param {String} name Name of controller to instantiate.
 * @param {*...} [args] Arguments to pass to the controller.
 * @return {Alloy.Controller} Alloy controller object.
 */
exports.createController = function(name, args) {
	return new (require('alloy/controllers/' + name))(args);
}

/**
 * @method getModel
 * Factory method for instantiating a Backbone Model object. Creates and returns an instance of the
 * named model.
 *
 * See [Backbone.Model](http://backbonejs.org/#Model) in the Backbone.js documentation for
 * information on the methods and properties provided by the Model object.
 * @param {String} name Name of model to instantiate.
 * @param {*...} [args] Arguments to pass to the model.
 * @return {Backbone.Model} Backbone model object.
 *
 * @deprecated 1.0 Use {Alloy#createModel} instead
 */
exports.getModel = function(name, args) {
	Ti.API.warn('Alloy.getModel() is deprecated, use Alloy.createModel() instead.');
	return exports.createModel(name, args);
}

/**
 * @method createModel
 * Factory method for instantiating a Backbone Model object. Creates and returns an instance of the
 * named model.
 *
 * See [Backbone.Model](http://backbonejs.org/#Model) in the Backbone.js documentation for
 * information on the methods and properties provided by the Model object.
 * @param {String} name Name of model to instantiate.
 * @param {*...} [args] Arguments to pass to the model.
 * @return {Backbone.Model} Backbone model object.
 */
exports.createModel = function(name, args) {
	return new (require('alloy/models/' + STR.ucfirst(name)).Model)(args);
}

/**
 * @method getCollection
 * Factory method for instantiating a Backbone collection of model objects. Creates and returns a
 * collection for holding the named type of model objects.
 *
 * See [Backbone.Collection](http://backbonejs.org/#Collection) in the Backbone.js
 * documentation for  information on the methods and  properties provided by the
 * Collection object.
 * @param {String} name Name of model to hold in this collection.
 * @param {*...} [args] Arguments to pass to the collection.
 * @return {Backbone.Collection} Backbone collection object.
 *
 * @deprecated 1.0 Use {Alloy#createCollection} instead
 */
exports.getCollection = function(name, args) {
	Ti.API.warn('Alloy.getCollection() is deprecated, use Alloy.createCollection() instead.');
	return exports.createCollection(name, args);
}

/**
 * @method createCollection
 * Factory method for instantiating a Backbone collection of model objects. Creates and returns a
 * collection for holding the named type of model objects.
 *
 * See [Backbone.Collection](http://backbonejs.org/#Collection) in the Backbone.js
 * documentation for  information on the methods and  properties provided by the
 * Collection object.
 * @param {String} name Name of model to hold in this collection.
 * @param {*...} [args] Arguments to pass to the collection.
 * @return {Backbone.Collection} Backbone collection object.
 */
exports.createCollection = function(name, args) {
	return new (require('alloy/models/' + STR.ucfirst(name)).Collection)(args);
}

function isTabletFallback() {
	return !(Math.min(
		Ti.Platform.displayCaps.platformHeight,
		Ti.Platform.displayCaps.platformWidth
	) < 700);
}

/**
 * @method isTablet
 * Returns `true` if the current device is a tablet.
 * @return {boolean}
 *
 */
exports.isTablet = (function() {
	if (OS_IOS) {
		return Ti.Platform.osname === 'ipad';
	}
	if (OS_ANDROID) {
		try {
			var psc = require('ti.physicalSizeCategory');
			return psc.physicalSizeCategory === 'large' ||
				   psc.physicalSizeCategory === 'xlarge';
		} catch(e) {
			Ti.API.warn('Could not find ti.physicalSizeCategory module, using fallback for Alloy.isTablet');
			return isTabletFallback();
		}
	}
	if (OS_MOBILEWEB) {
		return !(Math.min(
			Ti.Platform.displayCaps.platformHeight,
			Ti.Platform.displayCaps.platformWidth
		) < 400);
	}

	// Last resort. Don't worry, uglifyjs cleans up this dead code if necessary.
	return isTabletFallback();
})();

/**
 * @method isHandheld
 * Returns `true` if the current device is a handheld device (not a tablet).
 * @return {boolean}
 *
 */
exports.isHandheld = !exports.isTablet;
<commit_msg>fixed Backbone eventing for proxies, no more multiple event firing when undesired (ALOY-357)<commit_after>/**
 * @class Alloy
 * Top-level module for Alloy functions.
 *
 */
var 	   _ = require('alloy/underscore')._,
	Backbone = require('alloy/backbone'),
	STR = require('alloy/string');

exports._ = _;
exports.Backbone = Backbone;

// Ti.Analytics.featureEvent('alloy.start', {
// 	guid: Ti.Platform.id,
// 	tiversion: Ti.version,
// 	osname: Ti.Platform.osname,
// 	osversion: Ti.Platform.version,
// 	locale: Ti.Platform.locale
// });

exports.M = function(name, modelDesc, migrations) {
	var config = modelDesc.config;
    var type = (config.adapter ? config.adapter.type : null) || 'localDefault';
    if (type === 'localDefault') {
    	type = OS_MOBILEWEB ? 'localStorage' : 'sql';
    }

	var adapter = require('alloy/sync/'+type);
    var extendObj = {
		defaults: config.defaults,
        sync: function(method, model, opts) {
			var config = model.config || {};
			var adapterObj = config.adapter || {};
			var type = (config.adapter ? config.adapter.type : null) || 'localDefault';
			if (type === 'localDefault') {
		    	type = OS_MOBILEWEB ? 'localStorage' : 'sql';
		    }

			require('alloy/sync/'+type).sync(model,method,opts);
		}
	};

	var extendClass = {};

	// construct the model based on the current adapter type
	if (migrations) { extendClass.migrations = migrations; }
    if (_.isFunction(adapter.beforeModelCreate)) { config = adapter.beforeModelCreate(config) || config; }
	var Model = Backbone.Model.extend(extendObj, extendClass);
	Model.prototype.config = config;
	if (_.isFunction(adapter.afterModelCreate)) { adapter.afterModelCreate(Model); }

	if (_.isFunction(modelDesc.extendModel)) {
		Model = modelDesc.extendModel(Model) || Model;
	}

	return Model;
};

exports.C = function(name, modelDesc, model) {
    var extendObj = {
		model: model,
        sync: function(method, model, opts) {
			var config = (model.config || {});
			var type = (config.adapter ? config.adapter.type : null) || 'localDefault';
			if (type === 'localDefault') {
		    	type = OS_MOBILEWEB ? 'localStorage' : 'sql';
		    }

			require('alloy/sync/'+type).sync(model,method,opts);
		}
	};

	var Collection = Backbone.Collection.extend(extendObj);
	var config = Collection.prototype.config = model.prototype.config;

	var type = (config.adapter ? config.adapter.type : null) || 'localDefault';
	var adapter = require('alloy/sync/'+type);
	if (_.isFunction(adapter.afterCollectionCreate)) { adapter.afterCollectionCreate(Collection); }

	if (_.isFunction(modelDesc.extendModel)) {
		Collection = modelDesc.extendCollection(Collection) || Collection;
	}

	return Collection;
};

exports.A = function(t, type, parent) {
	_.extend(t,Backbone.Events);

	(function() {

		// we are going to wrap addEventListener and removeEventListener
		// with on, off so we can use the Backbone events
		var al = t.addEventListener,
			rl = t.removeEventListener,
			oo = t.on,
			of = t.off,
			tg = t.trigger,
		   cbs = {},
		   ctx = _.extend({}, Backbone.Events);

		if (!al || !rl) {
			return;
		}

		t.on = function(e,cb,context) {
			var wcb = function(evt) {
				try {
					_.bind(tg,ctx,e,evt)();
				}
				catch(E) {
					Ti.API.error("Error triggering '"+e+"' event: "+E);
				}
			};

			if (!cbs[e]) {
				cbs[e] = {};
				if (OS_IOS) {
					al(e, wcb);
				} else {
					al.call(t, e, wcb);
				}
			}
			cbs[e][cb] = wcb;

			_.bind(oo,ctx,e,cb,context)();
		};

		t.off = function(e,cb,context) {
			var f = cbs[e] ? cbs[e][cb] : null;
			if (f) {
				_.bind(of,ctx,e,cb,context)();
				delete cbs[e][cb];

				if (cbs[e].length === 0) {
					delete cbs[e];
					if (OS_IOS) {
						rl(e, f);
					} else {
						rl.call(t, e, f);
					}
				}
				f = null;
			}
		};

	})();

	return t;
}

/**
 * @method getWidget
 * Factory method for instantiating a widget controller. Creates and returns an instance of the
 * named widget.
 * @param {String} id Id of widget to instantiate.
 * @param {String} name Name of the view within the widget to instantiate ('widget' by default)
 * @param {*...} [args] Arguments to pass to the widget.
 * @return {Alloy.Controller} Alloy widget controller object.
 *
 * @deprecated 1.0 Use {Alloy#createWidget} instead
 */
exports.getWidget = function(id, name, args) {
	Ti.API.warn('Alloy.getWidget() is deprecated, use Alloy.createWidget() instead.');
	return exports.createWidget(id, name, args);
}

/**
 * @method createWidget
 * Factory method for instantiating a widget controller. Creates and returns an instance of the
 * named widget.
 * @param {String} id Id of widget to instantiate.
 * @param {String} name Name of the view within the widget to instantiate ('widget' by default)
 * @param {*...} [args] Arguments to pass to the widget.
 * @return {Alloy.Controller} Alloy widget controller object.
 */
exports.createWidget = function(id, name, args) {
	return new (require('alloy/widgets/' + id + '/controllers/' + (name || 'widget')))(args);
}

/**
 * @method getController
 * Factory method for instantiating a controller. Creates and returns an instance of the
 * named controller.
 * @param {String} name Name of controller to instantiate.
 * @param {*...} [args] Arguments to pass to the controller.
 * @return {Alloy.Controller} Alloy controller object.
 *
 * @deprecated 1.0 Use {Alloy#createController} instead
 */
exports.getController = function(name, args) {
	Ti.API.warn('Alloy.getController() is deprecated, use Alloy.createController() instead.');
	return exports.createController(name, args);
}

/**
 * @method createController
 * Factory method for instantiating a controller. Creates and returns an instance of the
 * named controller.
 * @param {String} name Name of controller to instantiate.
 * @param {*...} [args] Arguments to pass to the controller.
 * @return {Alloy.Controller} Alloy controller object.
 */
exports.createController = function(name, args) {
	return new (require('alloy/controllers/' + name))(args);
}

/**
 * @method getModel
 * Factory method for instantiating a Backbone Model object. Creates and returns an instance of the
 * named model.
 *
 * See [Backbone.Model](http://backbonejs.org/#Model) in the Backbone.js documentation for
 * information on the methods and properties provided by the Model object.
 * @param {String} name Name of model to instantiate.
 * @param {*...} [args] Arguments to pass to the model.
 * @return {Backbone.Model} Backbone model object.
 *
 * @deprecated 1.0 Use {Alloy#createModel} instead
 */
exports.getModel = function(name, args) {
	Ti.API.warn('Alloy.getModel() is deprecated, use Alloy.createModel() instead.');
	return exports.createModel(name, args);
}

/**
 * @method createModel
 * Factory method for instantiating a Backbone Model object. Creates and returns an instance of the
 * named model.
 *
 * See [Backbone.Model](http://backbonejs.org/#Model) in the Backbone.js documentation for
 * information on the methods and properties provided by the Model object.
 * @param {String} name Name of model to instantiate.
 * @param {*...} [args] Arguments to pass to the model.
 * @return {Backbone.Model} Backbone model object.
 */
exports.createModel = function(name, args) {
	return new (require('alloy/models/' + STR.ucfirst(name)).Model)(args);
}

/**
 * @method getCollection
 * Factory method for instantiating a Backbone collection of model objects. Creates and returns a
 * collection for holding the named type of model objects.
 *
 * See [Backbone.Collection](http://backbonejs.org/#Collection) in the Backbone.js
 * documentation for  information on the methods and  properties provided by the
 * Collection object.
 * @param {String} name Name of model to hold in this collection.
 * @param {*...} [args] Arguments to pass to the collection.
 * @return {Backbone.Collection} Backbone collection object.
 *
 * @deprecated 1.0 Use {Alloy#createCollection} instead
 */
exports.getCollection = function(name, args) {
	Ti.API.warn('Alloy.getCollection() is deprecated, use Alloy.createCollection() instead.');
	return exports.createCollection(name, args);
}

/**
 * @method createCollection
 * Factory method for instantiating a Backbone collection of model objects. Creates and returns a
 * collection for holding the named type of model objects.
 *
 * See [Backbone.Collection](http://backbonejs.org/#Collection) in the Backbone.js
 * documentation for  information on the methods and  properties provided by the
 * Collection object.
 * @param {String} name Name of model to hold in this collection.
 * @param {*...} [args] Arguments to pass to the collection.
 * @return {Backbone.Collection} Backbone collection object.
 */
exports.createCollection = function(name, args) {
	return new (require('alloy/models/' + STR.ucfirst(name)).Collection)(args);
}

function isTabletFallback() {
	return !(Math.min(
		Ti.Platform.displayCaps.platformHeight,
		Ti.Platform.displayCaps.platformWidth
	) < 700);
}

/**
 * @method isTablet
 * Returns `true` if the current device is a tablet.
 * @return {boolean}
 *
 */
exports.isTablet = (function() {
	if (OS_IOS) {
		return Ti.Platform.osname === 'ipad';
	}
	if (OS_ANDROID) {
		try {
			var psc = require('ti.physicalSizeCategory');
			return psc.physicalSizeCategory === 'large' ||
				   psc.physicalSizeCategory === 'xlarge';
		} catch(e) {
			Ti.API.warn('Could not find ti.physicalSizeCategory module, using fallback for Alloy.isTablet');
			return isTabletFallback();
		}
	}
	if (OS_MOBILEWEB) {
		return !(Math.min(
			Ti.Platform.displayCaps.platformHeight,
			Ti.Platform.displayCaps.platformWidth
		) < 400);
	}

	// Last resort. Don't worry, uglifyjs cleans up this dead code if necessary.
	return isTabletFallback();
})();

/**
 * @method isHandheld
 * Returns `true` if the current device is a handheld device (not a tablet).
 * @return {boolean}
 *
 */
exports.isHandheld = !exports.isTablet;
<|endoftext|>