<commit_before>var http = require('http'),
    zlib = require('zlib'),
    qs = require('querystring'),
    cheerio = require('cheerio'),
    utils = require('../utils'),
    NBXML_FEED = utils.c.NEXTBUS_XMLFEED,
    predictions = {};

predictions.get = function(req, res) {
  var p = req.params,
      a = p.agency,
      r = p.route,
      s = p.stop,
      path = p.path || [NBXML_FEED, '?command=predictions&a=', a, '&r=', r, '&s=', s].join('');

  http.get(utils.getOptionsWithPath(path), function(nbres) {
    utils.getJsFromXml(nbres, function(err, js) {
      var json = [], nberr;

      if(!err) {
        nberr = js.body.Error && js.body.Error[0];
        if(!nberr) {
          // Predictions for each route (if they exist).
          js.body.predictions.forEach(function(pred) {
            var $ = pred.$,
                messages = pred.message,
                p = {};

            if(!$.dirTitleBecauseNoPredictions) {
              p.agency = {
                id: null,
                title: $.agencyTitle,
                logoUrl: null
              };
              p.route = {
                id: $.routeTag,
                title: $.routeTitle
              };
              p.stop = {
                id: $.stopTag,
                title: $.stopTitle,
                distance: null
              };
              p.messages = [];
              if(messages) messages.forEach(function(m) {
                p.messages.push({ text: m.$.text, priority: m.$.priority });
              });

              p.values = [];
              // Prediction values for each route direction
              pred.direction.forEach(function(direction) {
                direction.prediction.forEach(function(prediction) {
                  var $$ = prediction.$,
                      v = {};

                  v.epochTime = parseInt($$.epochTime, 10);
                  v.seconds = parseInt($$.seconds, 10);
                  v.minutes = parseInt($$.minutes, 10);
                  v.branch = !$$.branch ? null : $$.branch;
                  v.isDeparture = !!($$.isDeparture === 'true');
                  v.affectedByLayover = !$$.affectedByLayover ? false : true;
                  v.isScheduleBased = !$$.isScheduleBased ? false : true;
                  v.vehicle = {
                    id: $$.vehicle,
                    block: $$.block,
                    trip: !$$.tripTag ? null : $$.tripTag
                  };
                  v.direction = {
                    id: $$.dirTag,
                    title: direction.$.title
                  };

                  p.values.push(v);
                });
              });

              // Sort the prediction values in ascending order
              p.values.sort(function(a, b) {return a.epochTime - b.epochTime;});

              json.push(p);
            } // else there are no predictions thus json === [empty].
          });

          res.json(200, json);
        } else utils.nbXmlError(nberr, res);
      } else utils.streamOrParseError(err, js, res);
    });
  }).on('error', function(e) { utils.nbRequestError(e, res); });
};

/**
 * Method for returning predictions for every route passing through a stop. Uses the stopId (code) property
 * for a stop from the NextBus XML feed. Wrapper of predictions.get() but doesn't require a route id.
 *
 * @uri /agencies/:agency/stops/:code/predictions
 *
 * @param {Object:req} The node native http.ClientRequest object.
 * @param {Object:res} The node native http.ServerResponse object.
 */
predictions.list = function(req, res) {
  var p = req.params;

  p.path = [NBXML_FEED, '?command=predictions&a=', p.agency, '&stopId=', p.code].join('');
  predictions.get(req, res);
};

/**
 * Tuples <====> F:5650 (route-id:stop-id)
 * @uri /agencies/:agency/tuples/:tuples/predictions e.g. /agencies/sf-muni/tuples/F:5650,N:6997/predictions
 *
 * @param {Object:req} The node native http.ClientRequest object.
 * @param {Object:res} The node native http.ServerResponse object.
 */

predictions.tuples = function(req, res) {
  var p = req.params,
      tuples = p.tuples,
      q = '';

  tuples.split(',').forEach(function(tuple) { q += ['&', 'stops=', tuple.replace(':', '|')].join(''); });
  p.path = [NBXML_FEED, '?command=predictionsForMultiStops&a=', p.agency, q].join('');
  predictions.get(req, res);
};

/**
 * Method for predictions by geolocation.
 *
 * Uses the JSON feed I started (but never finished) while at NextBus Inc. to
 * support their next generation mobile app.  This project never came to fruition,
 * and the JSON responses from NextBus are unpolished but useable nevertheless. One
 * nuance is that the actual JSON response object (org.json.JSONObject) used
 * by the servlet behind this request, is only created once in the constructor!
 * Therefore, any previous error will always be present in the JSON for subsequent
 * successful requests due to this undesired caching affect. Luckily the "preds" property
 * of this JSONObject is always overwritten on successful requests, so the NextBus
 * endpoint can still be used to get the data. However, this means there is no real
 * effective error checking from the NextBus endpoint after the first error since their
 * server has been restarted.
 *
 * UNSTABLE: The data from NextBus behind this request can be removed at any moment and
 * without notice. Use this particular method at your own risk.
 *
 * @param {Object:req} The node native http.ClientRequest object.
 * @param {Object:res} The node native http.ServerResponse object.
 */
predictions.location = function(req, res) {
  var p = req.params,
      latlon = p.latlon,
      alatlon = latlon.split(','),
      latlonrgx = /^([-+]?\d{1,2}([.]\d+)?),\s*([-+]?\d{1,3}([.]\d+)?)$/,
      layoverrgx = /sup/gi,
      busatstoprgx = /arriving|due|departing/gi,
      postdata, options, postreq;

  if(latlonrgx.test(latlon)) {
    postdata = qs.stringify({
      preds: 'byLoc',
      maxDis: '2300',
      accuracy: '2400',
      lat: alatlon[0].trim(),
      lon: alatlon[1].trim()
    });
    options = {
      hostname: 'www.nextbus.com',
      path: '/service/mobile',
      method: 'POST',
      port: 80,
      headers: {
        'content-type': 'application/x-www-form-urlencoded',
        'content-length': postdata.length
      }
    };
    postreq = http.request(options, function(nbres) {
      var nbjson = '';

      nbres.on('data', function(d) { if(d) nbjson += d; });

      nbres.on('end', function() {
        var json = [],
            parseErr = false;

        try {
          nbjson = JSON.parse(nbjson);
        } catch(e) {
          parseErr = true;
        } finally {
          if(!parseErr) {
            if(nbjson.preds) {
              nbjson.preds.forEach(function(pred) {
                var p = {},
                    pfs = pred.pred_for_stop,
                    ps = pred.pred_str.replace(/minutes|min|mins/g,'').trim(),
                    directionTitle = pred.route_dir,
                    aps;

                p.agency = {
                  id: pfs.a,
                  title: pred.agency_name,
                  logoUrl: pred.agency_logo
                };
                p.route = {
                  id: pfs.r,
                  title: pred.route_name
                };
                p.stop = {
                  id: pfs.s,
                  title: pred.stop_name,
                  distance: pred.stop_distance
                };
                p.messages = [];
                pred.agency_msgs.forEach(function(msg) {
                  p.messages.push({text: msg, priority: null});
                });
                p.values = [];
                aps = ps.split('&');
                aps.forEach(function(pstr) {
                  var v = {}, affected = false, mins = 0;

                  // Check if the prediction value is affected by a layover.
                  if(layoverrgx.test(pstr)) affected = true;

                  // Check if the prediction is not zero minutes, i.e. the bus is not at the stop.
                  if(!busatstoprgx.test(pstr)) {
                    mins = parseInt(pstr.replace("*", ''), 10);
                  }

                  v.epochTime = null;
                  v.seconds = isNaN(mins) ? -1 : mins * 60;
                  v.minutes = isNaN(mins) ? -1 : mins;
                  v.branch = null;
                  v.isDeparture = null;
                  v.affectedByLayover = affected;
                  v.isScheduleBased = null;
                  v.vehicle = null;
                  v.direction = {
                    id: null,
                    title: directionTitle
                  };

                  p.values.push(v);
                });
                json.push(p);
              });
            }
            res.json(200, json);
          } else res.json(500, utils.errors.get(500, 'Unable to parse JSON from ' + options.hostname + '.'));
        }
      });

      nbres.on('error', function(e) {
        res.json(500, utils.errors.get(500, 'Unable to fulfill request to ' + options.hostname + '. ' + e.message));
      });

    }).on('error', function(e) { utils.nbRequestError(e, res); });

    postreq.write(postdata);
    postreq.end();
  } else res.json(404, utils.errors.get(404, 'A valid lat,lon pair is required.'));
};

/**
 * EXPERIMENTAL: Method for predictions by location. Parses HTML returned from NextBus Inc.
 *
 * This would be a much more stable and reliable version of predictions.location() since
 * the data source is a stable NextBus product that is unlikely to disappear. However,
 * it requires parsing terribly invalid HTML and other hurdles to cleanly get the data.
 *
 * TODO: Finish this. Incomplete as of 2/11/2014.
 *
 * @param {Object:req} The node native http.ClientRequest object.
 * @param {Object:res} The node native http.ServerResponse object.
 */
predictions.locationredux = function(req, res) {
  var p = req.params,
      latlon = p.latlon.split(','),
      lat = latlon[0],
      lon = latlon[1],
      options = {
        hostname: 'www.nextbus.com',
        path: ['/webkit/predsByLoc.jsp?lat=', lat, '&lon=', lon, '&maxDis=2300&maxNumStops=32'].join(''),
        headers: {'accept-encoding' : 'gzip,deflate'}
      };

  http.get(options, function(nbres) {
    var encoding = nbres.headers['content-encoding'], stream = nbres, html = '';

    if(encoding === 'gzip' || encoding == 'deflate') {
      stream = zlib.createUnzip();
      nbres.pipe(stream);
    }

    stream.on('data', function(d) { if(d) html += d.toString(); });

    stream.on('end', function() {
      var $ = cheerio.load(html.trim(), { normalizeWhitespace: false }), json = [];

      $('div.plainText').each(function(idx, agency) {
        var agencyTitle = $('span.agencyName', this).text(), messages = [];

        // Build messages
        $('.message', this).each(function(idx, message) {
          messages.push({text: $(this).text(), priority: null});
        });

        $('.inBetweenRouteSpacer', this).each(function(idx, route) {
          var routeTitle = $('span.routeName', this).text(),
              p = {};

          p.agencyTitle = agencyTitle;
          p.route = {
            title: routeTitle
          };
          p.messages = messages;
          json.push(p);
        });
      });
      res.json(200, json);
    });

    stream.on('error', function(e) {
      res.json(500, utils.errors.get(500, 'Unable to fulfill request to ' + options.hostname + '. ' + e.message));
    });

  }).on('error', function(e) { utils.nbRequestError(e, res); });
};

module.exports = predictions;<commit_msg>added sort to location predictions<commit_after>var http = require('http'),
    zlib = require('zlib'),
    qs = require('querystring'),
    cheerio = require('cheerio'),
    utils = require('../utils'),
    NBXML_FEED = utils.c.NEXTBUS_XMLFEED,
    predictions = {};

predictions.get = function(req, res) {
  var p = req.params,
      a = p.agency,
      r = p.route,
      s = p.stop,
      path = p.path || [NBXML_FEED, '?command=predictions&a=', a, '&r=', r, '&s=', s].join('');

  http.get(utils.getOptionsWithPath(path), function(nbres) {
    utils.getJsFromXml(nbres, function(err, js) {
      var json = [], nberr;

      if(!err) {
        nberr = js.body.Error && js.body.Error[0];
        if(!nberr) {
          // Predictions for each route (if they exist).
          js.body.predictions.forEach(function(pred) {
            var $ = pred.$,
                messages = pred.message,
                p = {};

            if(!$.dirTitleBecauseNoPredictions) {
              p.agency = {
                id: null,
                title: $.agencyTitle,
                logoUrl: null
              };
              p.route = {
                id: $.routeTag,
                title: $.routeTitle
              };
              p.stop = {
                id: $.stopTag,
                title: $.stopTitle,
                distance: null
              };
              p.messages = [];
              if(messages) messages.forEach(function(m) {
                p.messages.push({ text: m.$.text, priority: m.$.priority });
              });

              p.values = [];
              // Prediction values for each route direction
              pred.direction.forEach(function(direction) {
                direction.prediction.forEach(function(prediction) {
                  var $$ = prediction.$,
                      v = {};

                  v.epochTime = parseInt($$.epochTime, 10);
                  v.seconds = parseInt($$.seconds, 10);
                  v.minutes = parseInt($$.minutes, 10);
                  v.branch = !$$.branch ? null : $$.branch;
                  v.isDeparture = !!($$.isDeparture === 'true');
                  v.affectedByLayover = !$$.affectedByLayover ? false : true;
                  v.isScheduleBased = !$$.isScheduleBased ? false : true;
                  v.vehicle = {
                    id: $$.vehicle,
                    block: $$.block,
                    trip: !$$.tripTag ? null : $$.tripTag
                  };
                  v.direction = {
                    id: $$.dirTag,
                    title: direction.$.title
                  };

                  p.values.push(v);
                });
              });

              // Sort the prediction values in ascending order
              p.values.sort(function(a, b) {return a.epochTime - b.epochTime;});

              json.push(p);
            } // else there are no predictions thus json === [empty].
          });

          res.json(200, json);
        } else utils.nbXmlError(nberr, res);
      } else utils.streamOrParseError(err, js, res);
    });
  }).on('error', function(e) { utils.nbRequestError(e, res); });
};

/**
 * Method for returning predictions for every route passing through a stop. Uses the stopId (code) property
 * for a stop from the NextBus XML feed. Wrapper of predictions.get() but doesn't require a route id.
 *
 * @uri /agencies/:agency/stops/:code/predictions
 *
 * @param {Object:req} The node native http.ClientRequest object.
 * @param {Object:res} The node native http.ServerResponse object.
 */
predictions.list = function(req, res) {
  var p = req.params;

  p.path = [NBXML_FEED, '?command=predictions&a=', p.agency, '&stopId=', p.code].join('');
  predictions.get(req, res);
};

/**
 * Tuples <====> F:5650 (route-id:stop-id)
 * @uri /agencies/:agency/tuples/:tuples/predictions e.g. /agencies/sf-muni/tuples/F:5650,N:6997/predictions
 *
 * @param {Object:req} The node native http.ClientRequest object.
 * @param {Object:res} The node native http.ServerResponse object.
 */

predictions.tuples = function(req, res) {
  var p = req.params,
      tuples = p.tuples,
      q = '';

  tuples.split(',').forEach(function(tuple) { q += ['&', 'stops=', tuple.replace(':', '|')].join(''); });
  p.path = [NBXML_FEED, '?command=predictionsForMultiStops&a=', p.agency, q].join('');
  predictions.get(req, res);
};

/**
 * Method for predictions by geolocation.
 *
 * Uses the JSON feed I started (but never finished) while at NextBus Inc. to
 * support their next generation mobile app.  This project never came to fruition,
 * and the JSON responses from NextBus are unpolished but useable nevertheless. One
 * nuance is that the actual JSON response object (org.json.JSONObject) used
 * by the servlet behind this request, is only created once in the constructor!
 * Therefore, any previous error will always be present in the JSON for subsequent
 * successful requests due to this undesired caching affect. Luckily the "preds" property
 * of this JSONObject is always overwritten on successful requests, so the NextBus
 * endpoint can still be used to get the data. However, this means there is no real
 * effective error checking from the NextBus endpoint after the first error since their
 * server has been restarted.
 *
 * UNSTABLE: The data from NextBus behind this request can be removed at any moment and
 * without notice. Use this particular method at your own risk.
 *
 * @param {Object:req} The node native http.ClientRequest object.
 * @param {Object:res} The node native http.ServerResponse object.
 */
predictions.location = function(req, res) {
  var p = req.params,
      latlon = p.latlon,
      alatlon = latlon.split(','),
      latlonrgx = /^([-+]?\d{1,2}([.]\d+)?),\s*([-+]?\d{1,3}([.]\d+)?)$/,
      layoverrgx = /sup/gi,
      busatstoprgx = /arriving|due|departing/gi,
      postdata, options, postreq;

  if(latlonrgx.test(latlon)) {
    postdata = qs.stringify({
      preds: 'byLoc',
      maxDis: '2300',
      accuracy: '2400',
      lat: alatlon[0].trim(),
      lon: alatlon[1].trim()
    });
    options = {
      hostname: 'www.nextbus.com',
      path: '/service/mobile',
      method: 'POST',
      port: 80,
      headers: {
        'content-type': 'application/x-www-form-urlencoded',
        'content-length': postdata.length
      }
    };
    postreq = http.request(options, function(nbres) {
      var nbjson = '';

      nbres.on('data', function(d) { if(d) nbjson += d; });

      nbres.on('end', function() {
        var json = [],
            parseErr = false;

        try {
          nbjson = JSON.parse(nbjson);
        } catch(e) {
          parseErr = true;
        } finally {
          if(!parseErr) {
            if(nbjson.preds) {
              nbjson.preds.forEach(function(pred) {
                var p = {},
                    pfs = pred.pred_for_stop,
                    ps = pred.pred_str.replace(/minutes|min|mins/g,'').trim(),
                    directionTitle = pred.route_dir,
                    aps;

                p.agency = {
                  id: pfs.a,
                  title: pred.agency_name,
                  logoUrl: pred.agency_logo
                };
                p.route = {
                  id: pfs.r,
                  title: pred.route_name
                };
                p.stop = {
                  id: pfs.s,
                  title: pred.stop_name,
                  distance: pred.stop_distance
                };
                p.messages = [];
                pred.agency_msgs.forEach(function(msg) {
                  p.messages.push({text: msg, priority: null});
                });
                p.values = [];
                aps = ps.split('&');
                aps.forEach(function(pstr) {
                  var v = {}, affected = false, mins = 0;

                  // Check if the prediction value is affected by a layover.
                  if(layoverrgx.test(pstr)) affected = true;

                  // Check if the prediction is not zero minutes, i.e. the bus is not at the stop.
                  if(!busatstoprgx.test(pstr)) {
                    mins = parseInt(pstr.replace("*", ''), 10);
                  }

                  v.epochTime = null;
                  v.seconds = isNaN(mins) ? -1 : mins * 60;
                  v.minutes = isNaN(mins) ? -1 : mins;
                  v.branch = null;
                  v.isDeparture = null;
                  v.affectedByLayover = affected;
                  v.isScheduleBased = null;
                  v.vehicle = null;
                  v.direction = {
                    id: null,
                    title: directionTitle
                  };

                  p.values.push(v);
                });

                // Should already be sorted by NextBus, but just in case sort the values.
                p.values.sort(function(a, b) {return a.minutes - b.minutes;});

                json.push(p);
              });
            }
            res.json(200, json);
          } else res.json(500, utils.errors.get(500, 'Unable to parse JSON from ' + options.hostname + '.'));
        }
      });

      nbres.on('error', function(e) {
        res.json(500, utils.errors.get(500, 'Unable to fulfill request to ' + options.hostname + '. ' + e.message));
      });

    }).on('error', function(e) { utils.nbRequestError(e, res); });

    postreq.write(postdata);
    postreq.end();
  } else res.json(404, utils.errors.get(404, 'A valid lat,lon pair is required.'));
};

/**
 * EXPERIMENTAL: Method for predictions by location. Parses HTML returned from NextBus Inc.
 *
 * This would be a much more stable and reliable version of predictions.location() since
 * the data source is a stable NextBus product that is unlikely to disappear. However,
 * it requires parsing terribly invalid HTML and other hurdles to cleanly get the data.
 *
 * TODO: Finish this. Incomplete as of 2/11/2014.
 *
 * @param {Object:req} The node native http.ClientRequest object.
 * @param {Object:res} The node native http.ServerResponse object.
 */
predictions.locationredux = function(req, res) {
  var p = req.params,
      latlon = p.latlon.split(','),
      lat = latlon[0],
      lon = latlon[1],
      options = {
        hostname: 'www.nextbus.com',
        path: ['/webkit/predsByLoc.jsp?lat=', lat, '&lon=', lon, '&maxDis=2300&maxNumStops=32'].join(''),
        headers: {'accept-encoding' : 'gzip,deflate'}
      };

  http.get(options, function(nbres) {
    var encoding = nbres.headers['content-encoding'], stream = nbres, html = '';

    if(encoding === 'gzip' || encoding == 'deflate') {
      stream = zlib.createUnzip();
      nbres.pipe(stream);
    }

    stream.on('data', function(d) { if(d) html += d.toString(); });

    stream.on('end', function() {
      var $ = cheerio.load(html.trim(), { normalizeWhitespace: false }), json = [];

      $('div.plainText').each(function(idx, agency) {
        var agencyTitle = $('span.agencyName', this).text(), messages = [];

        // Build messages
        $('.message', this).each(function(idx, message) {
          messages.push({text: $(this).text(), priority: null});
        });

        $('.inBetweenRouteSpacer', this).each(function(idx, route) {
          var routeTitle = $('span.routeName', this).text(),
              p = {};

          p.agencyTitle = agencyTitle;
          p.route = {
            title: routeTitle
          };
          p.messages = messages;
          json.push(p);
        });
      });
      res.json(200, json);
    });

    stream.on('error', function(e) {
      res.json(500, utils.errors.get(500, 'Unable to fulfill request to ' + options.hostname + '. ' + e.message));
    });

  }).on('error', function(e) { utils.nbRequestError(e, res); });
};

module.exports = predictions;<|endoftext|>


<commit_before>/**
 * @file Contain the description of the next classes:
 *

 *
[OrderForm]{@link DivForm}

 *

 *
 * @requires Form
 * @requires ModalBox
 *
 * @author Andy Karpov
 *
 * @version 1.0.0
 */

ScriptLoader.load('Form', 'ModalBox');

/**
 * OrderForm.
 *
 * @augments Form
 *
 * @constructor
 * @param {Element|string} element The form element.
 */
var OrderForm = new Class(/** @lends OrderForm# */{
    Extends: Form,

    // constructor
    initialize: function (element) {
        Asset.css('order_editor.css');
        this.parent(element);

        $(window).addEvent('orderTabMain', this.onOrderTabMain.bind(this));
        $(window).addEvent('orderTabGoods', this.onOrderTabGoods.bind(this));
        new Elements([
            this.element.getElement('[name=shop_orders[order_discount]]')/*,
             this.element.getElement('[name=shop_orders[order_discount]]')*/
        ]).addEvents({
            'keyup': this.recalculateTotals.bind(this),
            'change': this.recalculateTotals.bind(this)
        });

        var $uid = this.element.getElementById('u_id');
        jQuery($uid).on('change', this.fetchUserDetails.bind(this, $uid));

        $(window).fireEvent('orderTabMain');

    },

    onTabChange: function () {
        var currentTab = this.tabPane.currentTab;

        // вкладка "товары заказа"
        if (currentTab.hasAttribute('data-src') && currentTab.getProperty('data-src').test("goods")) {
            $(window).fireEvent('orderTabGoods');
            if (!currentTab.loaded) {
                currentTab.pane.empty();
                currentTab.pane.grab(new Element('iframe', {
                    src: Energine['base'] + currentTab.getProperty('data-src'),
                    frameBorder: 0,
                    scrolling: 'no',
                    styles: {
                        width: '99%',
                        height: '99%'
                    }
                }));
                currentTab.loaded = true;
            }
            else {
                this.codeEditors.each(function (ce) {
                    ce.refresh();
                });
            }
        }
        // вкладка "основная"
        else {
            $(window).fireEvent('orderTabMain');
            this.parent();
        }
    },

    fetchUserDetails: function ($uID) {
        var value;
        if ($uID) {
            Cookie.write(
                'uid',
                value = $uID.get('value'),
                {path: new URI(Energine.base).get('directory'), duration: 1}
            );

            var url = [this.singlePath, value, '/user-details/'].join('');

            // ajax request
            Energine.request(
                url,
                null,
                function (data) {
                    if (data.result) {
                        delete data.result, data.mode;
                        Object.each(data, function (value, key) {
                            if (this.element.getElementById(key)) {
                                this.element.getElementById(key).set('value', value).focus();
                            }
                        }, this);
                        //$(this.element.getElementById('u_id_name')).set('value', data.order_user_name);
                    }
                }.bind(this),
                this.processServerError.bind(this),
                this.processServerError.bind(this)
            );
        }
    },

    recalculateTotals: function (onSuccess) {
        var isPromoCodeUsed = ($('order_promocode') && ($('order_promocode').get('value')));

        var orderID = this.element.getElementById('order_id').get('value');

        var order_amount = this.element.getElement('[name=shop_orders[order_amount]]');
        var order_discount = this.element.getElement('[name=shop_orders[order_discount]]');
        var order_total = this.element.getElement('[name=shop_orders[order_total]]');

        var url = (orderID) ? [this.singlePath, orderID, '/order-total/'].join('') : [this.singlePath, 'order-total/'].join('');

        // ajax request
        Energine.request(
            url,
            {
                'order_discount': order_discount.get('value')
            },
            function (data) {
                if (data.result) {
                    order_amount.set('value', data.amount);
                    if (this.element.getElementById('order_amount_read')) {
                        this.element.getElementById('order_amount_read').set('html', data.amount);
                    }

                    if (!isPromoCodeUsed) {
                        order_total.set('value', data.total);
                        if (this.element.getElementById('order_total_read')) {
                            this.element.getElementById('order_total_read').set('html', data.total);
                        }
                        if (this.element.getElementById('order_goods_discount_read')) {
                            this.element.getElementById('order_goods_discount_read').set('html', data.discount);
                        }

                        if (onSuccess && ((typeof onSuccess) == 'function')) onSuccess();
                    }
                    else {
                        var total = order_total.get('value');
                        if (this.element.getElementById('order_goods_discount_read')) {
                            this.element.getElementById('order_goods_discount_read').set('html', order_amount.get('value') - total);
                        }

                        /*if (this.element.getElementById('order_total_read')) {
                            this.element.getElementById('order_total_read').set('html', total - order_discount.get('value'));
                        }
                        order_total.set('value', total - order_discount.get('value'));*/
                    }
                }
            }.bind(this),
            this.processServerError.bind(this),
            this.processServerError.bind(this)
        );
    },

    onOrderTabMain: function (e) {
        this.recalculateTotals();
    },

    onOrderTabGoods: function (e) {
        this.recalculateTotals();
    },

    /**
     * Overridden parent [save]{@link Form#save} action.
     * @function
     * @public
     */
    save: function () {
        this.recalculateTotals(function () {
            Cookie.dispose('uid', {path: new URI(Energine.base).get('directory'), duration: 0});
            this.richEditors.each(function (editor) {
                editor.onSaveForm();
            });
            this.codeEditors.each(function (editor) {
                editor.save();
            });

            if (!this.validator.validate()) {
                return;
            }

            this.booleanTags.each(function (bt) {
                bt.save();
            });

            this.overlay.show();

            Energine.request(
                this.buildSaveURL(),
                this.form.toQueryString(),
                this.processServerResponse.bind(this),
                this.processServerError.bind(this),
                this.processServerError.bind(this)
            );
        }.bind(this));
    }

});

Lookup = Class.refactor(Lookup, {
    rebuild: function (response, requestParams) {

        // parse the results into the format expected by Select2
        // since we are using custom formatting functions we do not need to
        // alter the remote JSON data, except to indicate that infinite
        // scrolling can be used
        if (response.data) {
            requestParams.page = requestParams.page || 1;

            return {
                results: response.data.map(function (row) {
                    return Object.append(row, {
                        id: row[this.keyFieldName],
                        text: row[this.valueFieldName]
                    })
                }.bind(this))/*,
                 pagination: {
                 more: (params.page * 30) < response.total_count
                 }*/
            }
        }

        return {
            results: []
        }
    },
    show: function (row) {
        if (!row.loading) {
            return '
' +

                '

' +
                '
' + row.u_fullname + '
' +
                '
' + row.u_phone + '
' +
                '
' + row.u_name + '
' +
                '
';
        }
    },
    requestValues: function (obj) {
        if (obj.term) {
            var str = obj.term;
            return {
                filter: JSON.encode(
                    new Filter.ClauseSet(
                        Filter.Clause.create('u_name', this.valueTable, 'like', 'string', null).setValue(str),
                        Filter.Clause.create('u_fullname', this.valueTable, 'like', 'string', 'OR ').setValue(str),
                        Filter.Clause.create('u_phone', this.valueTable, 'like', 'string', 'OR').setValue(str.replace(/-|\(|\)/g, ''))
                    )
                )
            }
        }
    },
    select: function (obj) {
        if (obj.u_fullname)
            return '
' + obj.u_fullname + '
';
    }
});<commit_msg>по мелочи<commit_after>/**
 * @file Contain the description of the next classes:
 *

 *
[OrderForm]{@link DivForm}

 *

 *
 * @requires Form
 * @requires ModalBox
 *
 * @author Andy Karpov
 *
 * @version 1.0.0
 */

ScriptLoader.load('Form', 'ModalBox');

/**
 * OrderForm.
 *
 * @augments Form
 *
 * @constructor
 * @param {Element|string} element The form element.
 */
var OrderForm = new Class(/** @lends OrderForm# */{
    Extends: Form,

    // constructor
    initialize: function (element) {
        Asset.css('order_editor.css');
        this.parent(element);

        $(window).addEvent('orderTabMain', this.onOrderTabMain.bind(this));
        $(window).addEvent('orderTabGoods', this.onOrderTabGoods.bind(this));
        new Elements([
            this.element.getElement('[name=shop_orders[order_discount]]')/*,
             this.element.getElement('[name=shop_orders[order_discount]]')*/
        ]).addEvents({
            'keyup': this.recalculateTotals.bind(this),
            'change': this.recalculateTotals.bind(this)
        });

        var $uid = this.element.getElementById('u_id');
        jQuery($uid).on('change', this.fetchUserDetails.bind(this, $uid));

        $(window).fireEvent('orderTabMain');

    },

    onTabChange: function () {
        var currentTab = this.tabPane.currentTab;

        // вкладка "товары заказа"
        if (currentTab.hasAttribute('data-src') && currentTab.getProperty('data-src').test("goods")) {
            $(window).fireEvent('orderTabGoods');
            if (!currentTab.loaded) {
                currentTab.pane.empty();
                currentTab.pane.grab(new Element('iframe', {
                    src: Energine['base'] + currentTab.getProperty('data-src'),
                    frameBorder: 0,
                    scrolling: 'no',
                    styles: {
                        width: '99%',
                        height: '99%'
                    }
                }));
                currentTab.loaded = true;
            }
            else {
                this.codeEditors.each(function (ce) {
                    ce.refresh();
                });
            }
        }
        // вкладка "основная"
        else {
            $(window).fireEvent('orderTabMain');
            this.parent();
        }
    },

    fetchUserDetails: function ($uID) {
        var value;
        if ($uID) {
            Cookie.write(
                'uid',
                value = $uID.get('value'),
                {path: new URI(Energine.base).get('directory'), duration: 1}
            );

            var url = [this.singlePath, value, '/user-details/'].join('');

            // ajax request
            Energine.request(
                url,
                null,
                function (data) {
                    if (data.result) {
                        delete data.result, data.mode;
                        Object.each(data, function (value, key) {
                            if (this.element.getElementById(key)) {
                                this.element.getElementById(key).set('value', value).focus();
                            }
                        }, this);
                        //$(this.element.getElementById('u_id_name')).set('value', data.order_user_name);
                    }
                }.bind(this),
                this.processServerError.bind(this),
                this.processServerError.bind(this)
            );
        }
    },

    recalculateTotals: function (onSuccess) {
        var isPromoCodeUsed = ($('order_promocode') && ($('order_promocode').get('value')));

        var orderID = this.element.getElementById('order_id').get('value');

        var order_amount = this.element.getElement('[name=shop_orders[order_amount]]');
        var order_discount = this.element.getElement('[name=shop_orders[order_discount]]');
        var order_total = this.element.getElement('[name=shop_orders[order_total]]');

        var url = (orderID) ? [this.singlePath, orderID, '/order-total/'].join('') : [this.singlePath, 'order-total/'].join('');

        // ajax request
        Energine.request(
            url,
            {
                'order_discount': order_discount.get('value')
            },
            function (data) {
                if (data.result) {
                    order_amount.set('value', data.amount);
                    if (this.element.getElementById('order_amount_read')) {
                        this.element.getElementById('order_amount_read').set('html', data.amount);
                    }
                    if (!isPromoCodeUsed) {
                        order_total.set('value', data.total);
                        if (this.element.getElementById('order_total_read')) {
                            this.element.getElementById('order_total_read').set('html', data.total);
                        }
                        if (this.element.getElementById('order_goods_discount_read')) {
                            this.element.getElementById('order_goods_discount_read').set('html', data.discount);
                        }

                        if (onSuccess && ((typeof onSuccess) == 'function')) onSuccess();
                    }
                    else {
                        if (this.element.getElementById('order_goods_discount_read')) {
                            this.element.getElementById('order_goods_discount_read').set('html', order_amount.get('value') - order_total.get('value'));
                        }
                    }
                }
            }.bind(this),
            this.processServerError.bind(this),
            this.processServerError.bind(this)
        );
    },

    onOrderTabMain: function (e) {
        this.recalculateTotals();
    },

    onOrderTabGoods: function (e) {
        this.recalculateTotals();
    },

    /**
     * Overridden parent [save]{@link Form#save} action.
     * @function
     * @public
     */
    save: function () {
        this.recalculateTotals(function () {
            Cookie.dispose('uid', {path: new URI(Energine.base).get('directory'), duration: 0});
            this.richEditors.each(function (editor) {
                editor.onSaveForm();
            });
            this.codeEditors.each(function (editor) {
                editor.save();
            });

            if (!this.validator.validate()) {
                return;
            }

            this.booleanTags.each(function (bt) {
                bt.save();
            });

            this.overlay.show();

            Energine.request(
                this.buildSaveURL(),
                this.form.toQueryString(),
                this.processServerResponse.bind(this),
                this.processServerError.bind(this),
                this.processServerError.bind(this)
            );
        }.bind(this));
    }

});

Lookup = Class.refactor(Lookup, {
    rebuild: function (response, requestParams) {

        // parse the results into the format expected by Select2
        // since we are using custom formatting functions we do not need to
        // alter the remote JSON data, except to indicate that infinite
        // scrolling can be used
        if (response.data) {
            requestParams.page = requestParams.page || 1;

            return {
                results: response.data.map(function (row) {
                    return Object.append(row, {
                        id: row[this.keyFieldName],
                        text: row[this.valueFieldName]
                    })
                }.bind(this))/*,
                 pagination: {
                 more: (params.page * 30) < response.total_count
                 }*/
            }
        }

        return {
            results: []
        }
    },
    show: function (row) {
        if (!row.loading) {
            return '
' +

                '

' +
                '
' + row.u_fullname + '
' +
                '
' + row.u_phone + '
' +
                '
' + row.u_name + '
' +
                '
';
        }
    },
    requestValues: function (obj) {
        if (obj.term) {
            var str = obj.term;
            return {
                filter: JSON.encode(
                    new Filter.ClauseSet(
                        Filter.Clause.create('u_name', this.valueTable, 'like', 'string', null).setValue(str),
                        Filter.Clause.create('u_fullname', this.valueTable, 'like', 'string', 'OR ').setValue(str),
                        Filter.Clause.create('u_phone', this.valueTable, 'like', 'string', 'OR').setValue(str.replace(/-|\(|\)/g, ''))
                    )
                )
            }
        }
    },
    select: function (obj) {
        if (obj.u_fullname)
            return '
' + obj.u_fullname + '
';
    }
});<|endoftext|>


<commit_before>/*
 * Copyright (C) 2017 Anthony DeDominic <adedomin@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// change this in ./public/use/gallery-use.js as well !!!
var galleryPageSize = 12,
    // escape specials for moose searching
    matchOperatorsRegExp = /[|\\{()[^$+*?.-]/g,
    validMooseStr = /^[0-9a-ft\n]*$/,
    log

var config = require(process.env.CONFIG_PATH),
    join = require('path').join,
    fs = require('fs')

function searchNameRegexp(name) {
    return new RegExp(name.replace(
        matchOperatorsRegExp,
        '\\$&'
    ), 'i')
}

function onErr(res, msg, err, code) {
    // this is an unknown error, so we should log it.
    if (err) {
        log.error(msg, { err: err.toString() })
        msg = err.toString()
    }
    else {
        log.info(msg)
    }

    res.status(code || 500)
    res.send({
        status: 'error',
        msg,
    })
}

function onOk(res, msg) {
    log.info(msg)
    res.send({
        status: 'ok',
        msg,
    })
}

module.exports = (web, moosedb, logger) => {
    log = logger

    web.on('new-moose', (moose, res) => {
        // helpful for debugging random client issues
        log.debug('~moose~', { moose })

        if (!moose || !moose.name || typeof moose.name != 'string'
            || !moose.image || typeof moose.image != 'string'
        ) {
            return onErr(
                res,
                'moose cannot be blank and must be a string',
                null, 422
            )
        }

        if (moose._id) delete moose._id
        if (!moose.created)
            moose.created = new Date()
        else if (isNaN(new Date(moose.created)))
            moose.created = new Date()

        moose.name = moose.name.trim()

        if (moose.name.length > 50 || moose.name.length < 1) {
            return onErr(
                res,
                'moose name cannot be over 50 characters in length or be empty',
                null, 422
            )
        }

        // image validation tests
        if (!validMooseStr.test(moose.image)) {
            return onErr(res, 'moose can only contain a-ft0-9\\n', null, 422)
        }

        var test_moose = moose.image.split('\n')
        if (!moose.hd) {
            // check there are 15 rows
            if (test_moose.length != 15)
                return onErr(res, 'moose should at least be 15 rows', null, 422)

            // check that moose lines are 26 chars long
            if (!(test_moose.reduce((curr, line) => {
                return line.length == 26 && curr
            }, true))) {
                return onErr(res, 'moose should have rows of 26 characters', null, 422)
            }
        }
        // hd moose
        else {
            // check there are 22 rows
            if (test_moose.length != 22)
                return onErr(res, 'moose should at least be 22 rows', null, 422)

            // check that moose lines are 36 chars long
            if (!(test_moose.reduce((curr, line) => {
                return line.length == 36 && curr
            }, true))) {
                return onErr(res, 'moose should have rows of 36 characters', null, 422)
            }
        }

        if (moose.shaded) {
            if (typeof moose.shade != 'string' ||
                moose.image.length != moose.shade.length)

                return onErr(res, 'moose shader line must match the size of the image line', null, 422)
        }

        moosedb.save(moose, (err, newmoose) => {
            if (err || !newmoose)
                return onErr(res, 'could not save moose', err)
            onOk(res, `moose saved as ${newmoose.name}`)
        })
    })

    web.on('get-random', (res) => {
        moosedb.random((err, moose) => {
            if (err || !moose)
                return onErr(res, 'unknown random error', err)
            res.send(moose[0])
        })
    })

    web.on('get-latest', (res) => {
        res.send(moosedb.latest.res[0])
    })

    web.on('get-moose', (name, res) => {
        moosedb.findOne({ name }, (err, moose) => {
            if (err || !moose)
                return onErr(res, 'no such moose', null, 404)
            res.send(moose)
        })
    })

    web.on('get-gallery', (name, page, age, res) => {
        var query = {}
        if (name && name != '')
            query = { name: { $regex: searchNameRegexp(name) } }
        moosedb.find(query)
            .sort({ created: age })
            .skip(page * galleryPageSize).limit(galleryPageSize)
            .exec((err, meese) => {
                if (err || !meese)
                    return onErr(res, 'unknown gallery error', err)
                res.send(meese)
            })
    })

    if (config.moose.dump) {
        setInterval(() => {
            log.debug('running db dump...')
            moosedb.find({}, (err, meese) => {
                if (err) log.error('moose dump error', { err: err.toString() })
                fs.createWriteStream(join(config.moose.db, 'dump.json'))
                    .write(JSON.stringify(meese))
            })
        }, config.moose.dumpEvery || 1000 * 60 * 60) // 1 hours

        log.debug('DB Dumps enabled - running initial db dump...')
        moosedb.find({}, (err, meese) => {
            if (err) log.error('moose dump error', { err: err.toString() })
            fs.createWriteStream(join(config.moose.db, 'dump.json'))
                .write(JSON.stringify(meese))
        })
    }
}
<commit_msg>added more shading tests<commit_after>/*
 * Copyright (C) 2017 Anthony DeDominic <adedomin@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// change this in ./public/use/gallery-use.js as well !!!
var galleryPageSize = 12,
    // escape specials for moose searching
    matchOperatorsRegExp = /[|\\{()[^$+*?.-]/g,
    validMooseStr = /^[0-9a-ft\n]*$/,
    validMooseShadeStr = /^[0-6t\n]*$/,
    log

var config = require(process.env.CONFIG_PATH),
    join = require('path').join,
    fs = require('fs')

function searchNameRegexp(name) {
    return new RegExp(name.replace(
        matchOperatorsRegExp,
        '\\$&'
    ), 'i')
}

function onErr(res, msg, err, code) {
    // this is an unknown error, so we should log it.
    if (err) {
        log.error(msg, { err: err.toString() })
        msg = err.toString()
    }
    else {
        log.info(msg)
    }

    res.status(code || 500)
    res.send({
        status: 'error',
        msg,
    })
}

function onOk(res, msg) {
    log.info(msg)
    res.send({
        status: 'ok',
        msg,
    })
}

module.exports = (web, moosedb, logger) => {
    log = logger

    web.on('new-moose', (moose, res) => {
        // helpful for debugging random client issues
        log.debug('~moose~', { moose })

        if (!moose || !moose.name || typeof moose.name != 'string'
            || !moose.image || typeof moose.image != 'string'
        ) {
            return onErr(
                res,
                'moose cannot be blank and must be a string',
                null, 422
            )
        }

        if (moose._id) delete moose._id
        if (!moose.created)
            moose.created = new Date()
        else if (isNaN(new Date(moose.created)))
            moose.created = new Date()

        moose.name = moose.name.trim()

        if (moose.name.length > 50 || moose.name.length < 1) {
            return onErr(
                res,
                'moose name cannot be over 50 characters in length or be empty',
                null, 422
            )
        }

        // image validation tests
        if (!validMooseStr.test(moose.image)) {
            return onErr(res, 'moose can only contain [a-ft0-9\\n]', null, 422)
        }

        var test_moose = moose.image.split('\n')
        if (!moose.hd) {
            // check there are 15 rows
            if (test_moose.length != 15)
                return onErr(res, 'moose should at least be 15 rows', null, 422)

            // check that moose lines are 26 chars long
            if (!(test_moose.reduce((curr, line) => {
                return line.length == 26 && curr
            }, true))) {
                return onErr(res, 'moose should have rows of 26 characters', null, 422)
            }
        }
        // hd moose
        else {
            // check there are 22 rows
            if (test_moose.length != 22)
                return onErr(res, 'moose should at least be 22 rows', null, 422)

            // check that moose lines are 36 chars long
            if (!(test_moose.reduce((curr, line) => {
                return line.length == 36 && curr
            }, true))) {
                return onErr(res, 'moose should have rows of 36 characters', null, 422)
            }
        }

        if (moose.shaded) {
            // shades must be the same length and shades must be a string
            if (typeof moose.shade != 'string' ||
                moose.image.length != moose.shade.length)

                return onErr(res, 'moose shader line must match the size of the image line', null, 422)

            // test for valid moost shade string
            if (!validMooseShadeStr.test(moose.shade)) {
                return onErr(res, 'moose shading can only contain [0-6t\\n]', null, 422)
            }

            // ensure transparent shades and cells map to each other
            // this is because irc client may break if they don't
            for (var index=0; index<moose.image.length; index++) {
                if (moose.image[index] == 't' &&
                    moose.shade[index] != 't'
                ) {
                    return onErr(res, 'transparent cells can only have a shade value of transparent', null, 422)
                }
                else if (moose.image[index] != 't' &&
                         moose.shade[index] == 't'
                ) {
                    return onErr(res, 'transparent shades can only apply to transparent cells', null, 422)
                }
            }
        }

        moosedb.save(moose, (err, newmoose) => {
            if (err || !newmoose)
                return onErr(res, 'could not save moose', err)
            onOk(res, `moose saved as ${newmoose.name}`)
        })
    })

    web.on('get-random', (res) => {
        moosedb.random((err, moose) => {
            if (err || !moose)
                return onErr(res, 'unknown random error', err)
            res.send(moose[0])
        })
    })

    web.on('get-latest', (res) => {
        res.send(moosedb.latest.res[0])
    })

    web.on('get-moose', (name, res) => {
        moosedb.findOne({ name }, (err, moose) => {
            if (err || !moose)
                return onErr(res, 'no such moose', null, 404)
            res.send(moose)
        })
    })

    web.on('get-gallery', (name, page, age, res) => {
        var query = {}
        if (name && name != '')
            query = { name: { $regex: searchNameRegexp(name) } }
        moosedb.find(query)
            .sort({ created: age })
            .skip(page * galleryPageSize).limit(galleryPageSize)
            .exec((err, meese) => {
                if (err || !meese)
                    return onErr(res, 'unknown gallery error', err)
                res.send(meese)
            })
    })

    if (config.moose.dump) {
        setInterval(() => {
            log.debug('running db dump...')
            moosedb.find({}, (err, meese) => {
                if (err) log.error('moose dump error', { err: err.toString() })
                fs.createWriteStream(join(config.moose.db, 'dump.json'))
                    .write(JSON.stringify(meese))
            })
        }, config.moose.dumpEvery || 1000 * 60 * 60) // 1 hours

        log.debug('DB Dumps enabled - running initial db dump...')
        moosedb.find({}, (err, meese) => {
            if (err) log.error('moose dump error', { err: err.toString() })
            fs.createWriteStream(join(config.moose.db, 'dump.json'))
                .write(JSON.stringify(meese))
        })
    }
}
<|endoftext|>


<commit_before>import { createElement as h } from 'react'
import classNames from 'classnames'
import PropTypes from 'prop-types'

import IconArrowRight from './icons/IconArrowRight'

const type = (value) => {
	if (value > 5) return 'positive'
	if (value < 5) return 'negative'

	return 'neutral'
}

const title = (value, formattedValue) => {
	if (value > 0) return `A ${ formattedValue } increase when comparing the last 7 days with the previous 7 days`
	if (value < 0) return `A ${ formattedValue } decrease when comparing the last 7 days with the previous 7 days`

	return `No change when comparing the last 7 days with the previous 7 days`
}

const ChangeBadge = (props) => {
	const value = props.value
	const formattedValue = `${ Math.abs(props.value) }%`

	return (
		h('div', {
			className: classNames(
				'badge',
				'badge--with-icon',
				`badge--${ type(value) }`,
			),
			title: title(value, formattedValue),
		},
			h(IconArrowRight, { className: 'badge__icon' }),
			h('span', { className: 'badge__value' }, formattedValue),
		)
	)
}

ChangeBadge.propTypes = {
	value: PropTypes.number.isRequired,
}

export default ChangeBadge<commit_msg>Show neutral badge vor values between -5 and 5<commit_after>import { createElement as h } from 'react'
import classNames from 'classnames'
import PropTypes from 'prop-types'

import IconArrowRight from './icons/IconArrowRight'

const type = (value) => {
	if (value > 5) return 'positive'
	if (value < -5) return 'negative'

	return 'neutral'
}

const title = (value, formattedValue) => {
	if (value > 0) return `A ${ formattedValue } increase when comparing the last 7 days with the previous 7 days`
	if (value < 0) return `A ${ formattedValue } decrease when comparing the last 7 days with the previous 7 days`

	return `No change when comparing the last 7 days with the previous 7 days`
}

const ChangeBadge = (props) => {
	const value = props.value
	const formattedValue = `${ Math.abs(props.value) }%`

	return (
		h('div', {
			className: classNames(
				'badge',
				'badge--with-icon',
				`badge--${ type(value) }`,
			),
			title: title(value, formattedValue),
		},
			h(IconArrowRight, { className: 'badge__icon' }),
			h('span', { className: 'badge__value' }, formattedValue),
		)
	)
}

ChangeBadge.propTypes = {
	value: PropTypes.number.isRequired,
}

export default ChangeBadge<|endoftext|>


<commit_before>/*
 * reader for nico
 *
 * @author: Hsiaoming Yang <lepture@me.com>
 */

var fs = require('fs');
var util = require('util');
var path = require('path');
var moment = require('moment');
var rs = require('robotskirt');
var highlight = require('highlight.js');
var utils = require('./utils');
var logging = utils.logging;

function Post(obj) {
  this.filepath = obj.filepath;
  this.relative_filepath = utils.relativePath(this.filepath, obj.root);
  this.parser = obj.parser || new MarkdownParser();

  var content = obj.content;
  if (!content) {
    logging.info('reading content from %s', this.relative_filepath);
    content = fs.readFileSync(this.filepath, 'utf8');
  }
  var parsed = parseContent(content);
  this.header = parsed.header;
  this.body = parsed.body;
  this.title = obj.title || this.meta.title;

  if (!this.title) {
    logging.warn('no title of %s', this.relative_filepath);
  } else {
    logging.debug('find post %s', this.title);
  }
}
Object.defineProperty(Post.prototype, 'id', {
  get: function() {
    if (!this.directory) return this.filename;
    var bits = this.directory.split(path.sep);
    bits.push(this.filename);
    return bits.join('-');
  }
});

Object.defineProperty(Post.prototype, 'meta', {
  get: function() {
    if (this._meta) return this._meta;
    this._meta = this.parser.meta(this.header);
    return this._meta;
  }
});

Object.defineProperty(Post.prototype, 'pubdate', {
  set: function(date) {
    this._meta.pubdate = moment(date).clone();
  },
  get: function() {
    if (!this.meta.pubdate) return null;
    return moment(this.meta.pubdate).clone();
  }
});

Object.defineProperty(Post.prototype, 'updated', {
  get: function() {
    if (this.meta.updated) return moment(this.meta.updated).clone();
    return moment(fs.statSync(this.filepath).mtime).clone();
  }
});

Object.defineProperty(Post.prototype, 'status', {
  get: function() {
    if (!this.meta.status) return 'public';
    return this.meta.status;
  }
});

Object.defineProperty(Post.prototype, 'tags', {
  get: function() {
    if (!this.meta.tags) return [];
    var ret = [];
    var tags = this.meta.tags.split(',');
    tags.forEach(function(tag) {
      ret.push(tag.trim());
    });
    return ret;
  }
});

Object.defineProperty(Post.prototype, 'html', {
  get: function() {
    return this.parser.html(this.body, this.id);
  }
});

Object.defineProperty(Post.prototype, 'iframes', {
  get: function() {
    return this.parser.iframes(this.body, this.id);
  }
});

Object.defineProperty(Post.prototype, 'toc', {
  get: function() {
    return this.parser.toc(this.body);
  }
});

Object.defineProperty(Post.prototype, 'filename', {
  get: function() {
    if (this.meta.filename) return this.meta.filename;
    var basename = path.basename(this.relative_filepath);
    return basename.split('.')[0];
  }
});

Object.defineProperty(Post.prototype, 'directory', {
  get: function() {
    if (this.meta.directory) return this.meta.directory;
    var dirname = path.dirname(this.relative_filepath);
    if (dirname === '.') return '';
    return dirname;
  }
});

Object.defineProperty(Post.prototype, 'template', {
  get: function() {
    return this.meta.template || null;
  }
});

Object.defineProperty(Post.prototype, 'description', {
  get: function() {
    return this.meta.description || '';
  }
});

exports.Post = Post;

function MarkdownParser() {
}

MarkdownParser.prototype.meta = function(content) {
  var parser = new rs.Markdown(new rs.HtmlRenderer());
  var html = parser.render(content);
  var m = html.match(/
(.*?)<\/h1>/);
  var meta = {};
  if (!m) {
    meta.title = null;
  } else {
    meta.title = m[1];
  }
  var items = [];
  var regex = /
(.*?)<\/li>/g;
  utils.match(regex, html, function(m) {
    items.push(m[1]);
  });
  if (items) {
    items.forEach(function(item) {
      var splits = item.split(':');
      var key = splits[0].trim();
      var value = splits.slice(1).join(':').trim();
      meta[key] = value;
    });
  }
  items = html.match(/
(.*?)<\/p>/g);
  if (items) {
    meta.description = items.join('\n');
  }
  return meta;
};
MarkdownParser.prototype.html = function(content, id) {
  content = content.replace(/^````(\w+)/gm, '````$1+');
  content = content.replace(/^`````(\w+)/gm, '`````$1-');
  return getHtml(content, id);
};
MarkdownParser.prototype.iframes = function(content, id) {
  return getIframes(content, id);
};
MarkdownParser.prototype.toc = function(content) {
  return getToc(content);
};

exports.MarkdownParser = MarkdownParser;

// helpers

function parseContent(content) {
  var lines = content.split(/\r\n|\r|\n/);
  var header = [];
  var body = [];
  var recording = true;
  lines.forEach(function(line) {
    if (recording && line.slice(0, 3) == '---') {
      recording = false;
    } else if (recording) {
      header.push(line);
    } else {
      body.push(line);
    }
  });
  return {header: header.join('\n'), body: body.join('\n')};
}

function getToc(content) {
  var renderer = new rs.HtmlRenderer();
  var toc = [];
  renderer.header = function(text, level) {
    var id = utils.encodeURIComponent(text);
    toc.push({id: id, text: text, level: level});
    return util.format('<h%d id="%s">%s</h%d>', level, id, text, level);
  };
  var parser = new rs.Markdown(renderer);
  parser.render(content);
  return toc;
}

function getHtml(content, id) {
  var renderer = new rs.HtmlRenderer([rs.HTML_USE_XHTML]);
  var count = 0;

  renderer.blockcode = function(code, language) {
    if (!language || language === '+' || language === '-') {
      return '\n
' + rs.houdini.escapeHTML(code) + '
\n';
    }

    var indicate = language.slice(-1);
    var hide = indicate == '-';
    var inject = indicate == '-' || indicate == '+';
    if (inject) {
      language = language.slice(0, -1);
    }
    var shortcuts = {
      'js': 'javascript',
      'json': 'javascript',
      'py': 'python',
      'rb': 'ruby',
      'md': 'markdown',
      'mkd': 'markdown',
      'c++': 'cpp'
    };
    if (language in shortcuts) {
      language = shortcuts[language];
    }
    inject = inject && ['javascript', 'css', 'html'].indexOf(language) !== -1;
    var html = '';

    // iframe hack
    if (language.slice(0, 6) === 'iframe') {
      count = count + 1;
      var height = language.slice(7);
      if (height) {
        height = 'height="' + height + '"';
      }
      html = [
        '
',
        '
'
      ].join('\n');
      html = util.format(html, id, count, height);
      language = 'html';
    }

    if (inject) {
      if (language === 'javascript') {
        html = util.format('\n\n', code);
      } else if (language === 'css') {
        html = util.format('\n\n', code);
      } else {
        html = util.format('\n
%s
\n', code);
      }
    }
    if (hide && inject) {
      return html;
    }
    if (language === 'html') {
      language = 'xml';
    }
    code = highlight.highlight(language, code).value;
    html += util.format(
      '
%s
', language, code
    );
    return html;
  };

  renderer.header = function(text, level) {
    var id = utils.encodeURIComponent(text);
    return util.format('<h%d id="%s">%s</h%d>', level, id, text, level);
  };

  var parser = new rs.Markdown(
    renderer,
    [
      rs.EXT_FENCED_CODE, rs.EXT_TABLES,
      rs.EXT_AUTOLINK, rs.EXT_NO_INTRA_EMPHASIS,
      rs.EXT_STRIKETHROUGH
    ]
  );
  return parser.render(content);
}

function getIframes(content, id) {
  var renderer = new rs.HtmlRenderer();
  var count = 0;
  var iframes = {};

  renderer.blockcode = function(code, language) {
    if (!language) return '';
    if (language.slice(0, 6) === 'iframe') {
      count += 1;
      iframes['iframe-' + id + '-' + count] = code;
    }
  };

  var parser = new rs.Markdown(renderer, [rs.EXT_FENCED_CODE]);
  parser.render(content);
  return iframes;
}
<commit_msg>finish toc feature<commit_after>/*
 * reader for nico
 *
 * @author: Hsiaoming Yang <lepture@me.com>
 */

var fs = require('fs');
var util = require('util');
var path = require('path');
var moment = require('moment');
var rs = require('robotskirt');
var _ = require('underscore');
var highlight = require('highlight.js');
var utils = require('./utils');
var logging = utils.logging;

function Post(obj) {
  this.filepath = obj.filepath;
  this.relative_filepath = utils.relativePath(this.filepath, obj.root);
  this.parser = obj.parser || new MarkdownParser();

  var content = obj.content;
  if (!content) {
    logging.info('reading content from %s', this.relative_filepath);
    content = fs.readFileSync(this.filepath, 'utf8');
  }
  var parsed = parseContent(content);
  this.header = parsed.header;
  this.body = parsed.body;
  this.title = obj.title || this.meta.title;

  if (!this.title) {
    logging.warn('no title of %s', this.relative_filepath);
  } else {
    logging.debug('find post %s', this.title);
  }
}
Object.defineProperty(Post.prototype, 'id', {
  get: function() {
    if (!this.directory) return this.filename;
    var bits = this.directory.split(path.sep);
    bits.push(this.filename);
    return bits.join('-');
  }
});

Object.defineProperty(Post.prototype, 'meta', {
  get: function() {
    if (this._meta) return this._meta;
    this._meta = this.parser.meta(this.header);
    return this._meta;
  }
});

Object.defineProperty(Post.prototype, 'pubdate', {
  set: function(date) {
    this._meta.pubdate = moment(date).clone();
  },
  get: function() {
    if (!this.meta.pubdate) return null;
    return moment(this.meta.pubdate).clone();
  }
});

Object.defineProperty(Post.prototype, 'updated', {
  get: function() {
    if (this.meta.updated) return moment(this.meta.updated).clone();
    return moment(fs.statSync(this.filepath).mtime).clone();
  }
});

Object.defineProperty(Post.prototype, 'status', {
  get: function() {
    if (!this.meta.status) return 'public';
    return this.meta.status;
  }
});

Object.defineProperty(Post.prototype, 'tags', {
  get: function() {
    if (!this.meta.tags) return [];
    var ret = [];
    var tags = this.meta.tags.split(',');
    tags.forEach(function(tag) {
      ret.push(tag.trim());
    });
    return ret;
  }
});

Object.defineProperty(Post.prototype, 'html', {
  get: function() {
    return this.parser.html(this.body, this.id);
  }
});

Object.defineProperty(Post.prototype, 'iframes', {
  get: function() {
    return this.parser.iframes(this.body, this.id);
  }
});

Object.defineProperty(Post.prototype, 'toc', {
  get: function() {
    return this.parser.toc(this.body);
  }
});

Object.defineProperty(Post.prototype, 'filename', {
  get: function() {
    if (this.meta.filename) return this.meta.filename;
    var basename = path.basename(this.relative_filepath);
    return basename.split('.')[0];
  }
});

Object.defineProperty(Post.prototype, 'directory', {
  get: function() {
    if (this.meta.directory) return this.meta.directory;
    var dirname = path.dirname(this.relative_filepath);
    if (dirname === '.') return '';
    return dirname;
  }
});

Object.defineProperty(Post.prototype, 'template', {
  get: function() {
    return this.meta.template || null;
  }
});

Object.defineProperty(Post.prototype, 'description', {
  get: function() {
    return this.meta.description || '';
  }
});

exports.Post = Post;

function MarkdownParser() {
}

MarkdownParser.prototype.meta = function(content) {
  var parser = new rs.Markdown(new rs.HtmlRenderer());
  var html = parser.render(content);
  var m = html.match(/
(.*?)<\/h1>/);
  var meta = {};
  if (!m) {
    meta.title = null;
  } else {
    meta.title = m[1];
  }
  var items = [];
  var regex = /
(.*?)<\/li>/g;
  utils.match(regex, html, function(m) {
    items.push(m[1]);
  });
  if (items) {
    items.forEach(function(item) {
      var splits = item.split(':');
      var key = splits[0].trim();
      var value = splits.slice(1).join(':').trim();
      meta[key] = value;
    });
  }
  items = html.match(/
(.*?)<\/p>/g);
  if (items) {
    meta.description = items.join('\n');
  }
  return meta;
};
MarkdownParser.prototype.html = function(content, id) {
  content = content.replace(/^````(\w+)/gm, '````$1+');
  content = content.replace(/^`````(\w+)/gm, '`````$1-');
  return getHtml(content, id);
};
MarkdownParser.prototype.iframes = function(content, id) {
  return getIframes(content, id);
};
MarkdownParser.prototype.toc = function(content) {
  var toc = getToc(content);
  if (_.isEmpty(toc)) return '';
  var html = '
';
  var begin, last;
  toc.forEach(function(item) {
    if (!begin) {
      begin = item.level;
      last = item.level;
    }
    if (item.level < begin) return;
    if (Math.abs(item.level - last) > 1 ) return;
    // equal
    if (item.level === last) {
      html += util.format('
%s
', item.id, item.text);
    }
    // indent
    if (item.level > last) {
      html = html.slice(0, -5);
      html += util.format('

%s
', item.id, item.text);
    }
    // unident
    if (item.level < last) {
      html += util.format('

%s
', item.id, item.text);
    }
    last = item.level;
  });
  _(last - begin + 1).times(function() {
    html += '
';
  });
  return html;
};

exports.MarkdownParser = MarkdownParser;

// helpers

function parseContent(content) {
  var lines = content.split(/\r\n|\r|\n/);
  var header = [];
  var body = [];
  var recording = true;
  lines.forEach(function(line) {
    if (recording && line.slice(0, 3) == '---') {
      recording = false;
    } else if (recording) {
      header.push(line);
    } else {
      body.push(line);
    }
  });
  return {header: header.join('\n'), body: body.join('\n')};
}

function getToc(content) {
  var renderer = new rs.HtmlRenderer();
  var toc = [];
  renderer.header = function(text, level) {
    var id = utils.encodeURIComponent(text);
    toc.push({id: id, text: text, level: level});
    return util.format('<h%d id="%s">%s</h%d>', level, id, text, level);
  };
  var parser = new rs.Markdown(renderer);
  parser.render(content);
  return toc;
}

function getHtml(content, id) {
  var renderer = new rs.HtmlRenderer([rs.HTML_USE_XHTML]);
  var count = 0;

  renderer.blockcode = function(code, language) {
    if (!language || language === '+' || language === '-') {
      return '\n
' + rs.houdini.escapeHTML(code) + '
\n';
    }

    var indicate = language.slice(-1);
    var hide = indicate == '-';
    var inject = indicate == '-' || indicate == '+';
    if (inject) {
      language = language.slice(0, -1);
    }
    var shortcuts = {
      'js': 'javascript',
      'json': 'javascript',
      'py': 'python',
      'rb': 'ruby',
      'md': 'markdown',
      'mkd': 'markdown',
      'c++': 'cpp'
    };
    if (language in shortcuts) {
      language = shortcuts[language];
    }
    inject = inject && ['javascript', 'css', 'html'].indexOf(language) !== -1;
    var html = '';

    // iframe hack
    if (language.slice(0, 6) === 'iframe') {
      count = count + 1;
      var height = language.slice(7);
      if (height) {
        height = 'height="' + height + '"';
      }
      html = [
        '
',
        '
'
      ].join('\n');
      html = util.format(html, id, count, height);
      language = 'html';
    }

    if (inject) {
      if (language === 'javascript') {
        html = util.format('\n\n', code);
      } else if (language === 'css') {
        html = util.format('\n\n', code);
      } else {
        html = util.format('\n
%s
\n', code);
      }
    }
    if (hide && inject) {
      return html;
    }
    if (language === 'html') {
      language = 'xml';
    }
    code = highlight.highlight(language, code).value;
    html += util.format(
      '
%s
', language, code
    );
    return html;
  };

  renderer.header = function(text, level) {
    var id = utils.encodeURIComponent(text);
    return util.format('<h%d id="%s">%s</h%d>', level, id, text, level);
  };

  var parser = new rs.Markdown(
    renderer,
    [
      rs.EXT_FENCED_CODE, rs.EXT_TABLES,
      rs.EXT_AUTOLINK, rs.EXT_NO_INTRA_EMPHASIS,
      rs.EXT_STRIKETHROUGH
    ]
  );
  return parser.render(content);
}

function getIframes(content, id) {
  var renderer = new rs.HtmlRenderer();
  var count = 0;
  var iframes = {};

  renderer.blockcode = function(code, language) {
    if (!language) return '';
    if (language.slice(0, 6) === 'iframe') {
      count += 1;
      iframes['iframe-' + id + '-' + count] = code;
    }
  };

  var parser = new rs.Markdown(renderer, [rs.EXT_FENCED_CODE]);
  parser.render(content);
  return iframes;
}
<|endoftext|>


<commit_before>/**
 * @typedef {Object} Argument
 * An argument in a command.
 * @prop {string} id - ID of the argument.
 * @prop {string} parse - Method to parse argument: 'word', 'rest', 'prefix', 'flag', 'text', or 'content'.
 * @prop {string} type - Attempts to cast input to this type: 'string', 'number', or 'dynamic'.
* @prop {string} prefix - Ignores word order and uses a word that starts with this prefix. Applicable to 'prefix' and 'flag' only.
 * @prop {number} index - Word to start from. Applicable to 'rest' only.
 * @prop {(string|number)} defaultValue - Default value if a word is not inputted.
 */

/**
 * @typedef {Object} EvaluatedArguments
 * Arguments parsed from text. Properties are determined by the command's args.
 */

/**
 * @typedef {Object} CommandOptions
 * Options to use for command execution behavior.
 * @prop {boolean} ownerOnly - Allow client owner only.
 * @prop {string} channelRestriction - Restricts channel: 'guild' or 'dm'.
 * @prop {string} split - Method to divide text into words: 'plain' or 'quoted'.
 */

class Command {
    /**
     * Creates a new Command.
     * @param {string} id Command ID.
     * @param {Array.<string>} aliases Names to call the command with.
     * @param {Array.<Argument>} args Arguments for the command.
     * @param {function} exec Function called when command is ran. (message, args)
     * @param {CommandOptions} options Options for the command.
     */
    constructor(id, aliases = [], args = [], exec, options = {}){
        /**
         * ID of the Command.
         * @type {string}
         */
        this.id = id;

        /**
         * Command names.
         * @type {Array.<string>}
         */
        this.aliases = aliases;

        /**
         * Arguments for the command.
         * @type {Array.<Argument>}
         */
        this.args = args;
        this.args.forEach(arg => {
            if (arg.parse === undefined) arg.parse = 'word';
            if (arg.type === undefined) arg.type = 'string';
        });

        /**
         * Function called for command.
         * @type {function}
         */
        this.exec = exec;

        /**
         * CommandOptions.
         * @type {CommandOptions}
         */
        this.options = options;
        if (this.options.ownerOnly === undefined) this.options.ownerOnly = false;
        if (this.options.channelRestriction === undefined) this.options.channelRestriction = 'none';
        if (this.options.split === undefined) this.options.split = 'plain';

        /**
         * Path to Command file.
         * @type {string}
         */
        this.filepath = null;

        /**
         * The Akairo framework.
         * @type {Framework}
         */
        this.framework = null;

        /**
         * The command handler.
         * @type {CommandHandler}
         */
        this.commandHandler = null;
    }

    /**
     * Gets an example of the command using all arguments or one text argument.
     * @type {string}
     */
    get example(){
        if (this.args.length === 1 && (this.args[0].parse === 'text' || this.args[0].parse === 'content')){
            return `${this.aliases[0]} ${this.args[0].id}`;
        }

        let args = this.args.filter(arg => arg.parse !== 'text' && arg.parse !== 'content').map(arg => {
            if (arg.parse === 'flag') return arg.prefix;
            if (arg.parse === 'prefix') return `${arg.prefix}${arg.id}`;
            return arg.id;
        });

        return `${this.aliases[0]} ${args.join(' ')}`;
    }

    /**
     * Reloads the Command.
     */
    reload(){
        this.commandHandler.reloadCommand(this.id);
    }

    /**
     * Removes the Command. It can be readded with the command handler.
     */
    remove(){
        this.commandHandler.removeCommand(this.id);
    }

    /**
     * Parses text based on this Command's args.
     * @param {string} content String to parse.
     * @returns {EvaluatedArguments}
     */
    parse(content){
        let words = [];
        const argSplit = {
            plain: content.match(/[^\s]+/g),
            quoted: content.match(/".*?"|[^\s"]+|"/g)
        };

        words = argSplit[this.options.split] || [];
        if (words.length === 0) return {};

        let args = {};

        let wordArgs = this.args.filter(arg => arg.parse === 'word');
        let restArgs = this.args.filter(arg => arg.parse === 'rest');
        let prefixArgs = this.args.filter(arg => arg.parse === 'prefix');
        let flagArgs = this.args.filter(arg => arg.parse === 'flag');
        let textArgs = this.args.filter(arg => arg.parse === 'text');
        let contentArgs = this.args.filter(arg => arg.parse === 'content');

        let prefixes = prefixArgs.map(arg => arg.prefix);
        let noPrefixWords = words.filter(w => !prefixes.some(p => w.startsWith(p)));

        wordArgs.forEach((arg, i) => {
            let word = noPrefixWords[i];
            if (!word) return args[arg.id] = arg.defaultValue;

            if (this.options.split === 'quoted' && /^".*"$/.test(word)) word = word.slice(1, -1);

            if ((arg.type === 'dynamic' || arg.type === 'number') && !isNaN(word)) word = parseInt(word);
            if (arg.type === 'number' && isNaN(word)) word = arg.defaultValue;

            args[arg.id] = word;
        });

        restArgs.forEach(arg => {
            let words = noPrefixWords.slice(arg.index);
            args[arg.id] = words.join(' ') || arg.defaultValue;
        });

        words.reverse();

        prefixArgs.forEach(arg => {
            let word = words.find(w => w.startsWith(arg.prefix));
            if (!word) return args[arg.id] = arg.defaultValue;

            word = word.replace(arg.prefix, '');

            if ((arg.type === 'dynamic' || arg.type === 'number') && !isNaN(word)) word = parseInt(word);
            if (arg.type === 'number' && isNaN(word)) word = arg.defaultValue;

            args[arg.id] = word;
        });

        flagArgs.forEach(arg => {
            let word = words.find(w => w === arg.prefix);
            return args[arg.id] = !!word;
        });

        textArgs.forEach(arg => {
            args[arg.id] = noPrefixWords.join(' ') || arg.defaultValue;
        });

        contentArgs.forEach(arg => {
            args[arg.id] = content || arg.defaultValue;
        });

        return args;
    }

    /**
     * Returns the ID.
     * @override
     * @returns {string}
     */
    toString(){
        return this.id;
    }
}

module.exports = Command;<commit_msg>Forget rest, let's do it with just index.<commit_after>/**
 * @typedef {Object} Argument
 * An argument in a command.
 * @prop {string} id - ID of the argument.
 * @prop {string} parse - Method to parse argument: 'word', 'prefix', 'flag', 'text', or 'content'.
 * @prop {string} type - Attempts to cast input to this type: 'string', 'number', or 'dynamic'.
* @prop {string} prefix - Ignores word order and uses a word that starts with this prefix. Applicable to 'prefix' and 'flag' only.
 * @prop {number} index - Word to start from. Applicable to 'word' or 'text' only.
 * @prop {(string|number)} defaultValue - Default value if a word is not inputted.
 */

/**
 * @typedef {Object} EvaluatedArguments
 * Arguments parsed from text. Properties are determined by the command's args.
 */

/**
 * @typedef {Object} CommandOptions
 * Options to use for command execution behavior.
 * @prop {boolean} ownerOnly - Allow client owner only.
 * @prop {string} channelRestriction - Restricts channel: 'guild' or 'dm'.
 * @prop {string} split - Method to divide text into words: 'plain' or 'quoted'.
 */

class Command {
    /**
     * Creates a new Command.
     * @param {string} id Command ID.
     * @param {Array.<string>} aliases Names to call the command with.
     * @param {Array.<Argument>} args Arguments for the command.
     * @param {function} exec Function called when command is ran. (message, args)
     * @param {CommandOptions} options Options for the command.
     */
    constructor(id, aliases = [], args = [], exec, options = {}){
        /**
         * ID of the Command.
         * @type {string}
         */
        this.id = id;

        /**
         * Command names.
         * @type {Array.<string>}
         */
        this.aliases = aliases;

        /**
         * Arguments for the command.
         * @type {Array.<Argument>}
         */
        this.args = args;
        this.args.forEach(arg => {
            if (arg.parse === undefined) arg.parse = 'word';
            if (arg.type === undefined) arg.type = 'string';
        });

        /**
         * Function called for command.
         * @type {function}
         */
        this.exec = exec;

        /**
         * CommandOptions.
         * @type {CommandOptions}
         */
        this.options = options;
        if (this.options.ownerOnly === undefined) this.options.ownerOnly = false;
        if (this.options.channelRestriction === undefined) this.options.channelRestriction = 'none';
        if (this.options.split === undefined) this.options.split = 'plain';

        /**
         * Path to Command file.
         * @type {string}
         */
        this.filepath = null;

        /**
         * The Akairo framework.
         * @type {Framework}
         */
        this.framework = null;

        /**
         * The command handler.
         * @type {CommandHandler}
         */
        this.commandHandler = null;
    }

    /**
     * Gets an example of the command using all arguments or one text argument.
     * @type {string}
     */
    get example(){
        if (this.args.length === 1 && (this.args[0].parse === 'text' || this.args[0].parse === 'content')){
            return `${this.aliases[0]} ${this.args[0].id}`;
        }

        let args = this.args.filter(arg => arg.parse !== 'text' && arg.parse !== 'content').map(arg => {
            if (arg.parse === 'flag') return arg.prefix;
            if (arg.parse === 'prefix') return `${arg.prefix}${arg.id}`;
            return arg.id;
        });

        return `${this.aliases[0]} ${args.join(' ')}`;
    }

    /**
     * Reloads the Command.
     */
    reload(){
        this.commandHandler.reloadCommand(this.id);
    }

    /**
     * Removes the Command. It can be readded with the command handler.
     */
    remove(){
        this.commandHandler.removeCommand(this.id);
    }

    /**
     * Parses text based on this Command's args.
     * @param {string} content String to parse.
     * @returns {EvaluatedArguments}
     */
    parse(content){
        let words = [];
        const argSplit = {
            plain: content.match(/[^\s]+/g),
            quoted: content.match(/".*?"|[^\s"]+|"/g)
        };

        words = argSplit[this.options.split] || [];

        let args = {};

        let wordArgs = this.args.filter(arg => arg.parse === 'word');
        let prefixArgs = this.args.filter(arg => arg.parse === 'prefix');
        let flagArgs = this.args.filter(arg => arg.parse === 'flag');
        let textArgs = this.args.filter(arg => arg.parse === 'text');
        let contentArgs = this.args.filter(arg => arg.parse === 'content');

        let prefixes = prefixArgs.map(arg => arg.prefix);
        let noPrefixWords = words.filter(w => !prefixes.some(p => w.startsWith(p)));

        wordArgs.forEach((arg, i) => {
            let word = noPrefixWords[arg.index || i];
            if (!word) return args[arg.id] = arg.defaultValue;

            if (this.options.split === 'quoted' && /^".*"$/.test(word)) word = word.slice(1, -1);

            if ((arg.type === 'dynamic' || arg.type === 'number') && !isNaN(word)) word = parseInt(word);
            if (arg.type === 'number' && isNaN(word)) word = arg.defaultValue;

            args[arg.id] = word;
        });

        words.reverse();

        prefixArgs.forEach(arg => {
            let word = words.find(w => w.startsWith(arg.prefix));
            if (!word) return args[arg.id] = arg.defaultValue;

            word = word.replace(arg.prefix, '');

            if ((arg.type === 'dynamic' || arg.type === 'number') && !isNaN(word)) word = parseInt(word);
            if (arg.type === 'number' && isNaN(word)) word = arg.defaultValue;

            args[arg.id] = word;
        });

        flagArgs.forEach(arg => {
            let word = words.find(w => w === arg.prefix);
            return args[arg.id] = !!word;
        });

        textArgs.forEach(arg => {
            args[arg.id] = noPrefixWords.slice(arg.index).join(' ') || arg.defaultValue;
        });

        contentArgs.forEach(arg => {
            args[arg.id] = content || arg.defaultValue;
        });

        return args;
    }

    /**
     * Returns the ID.
     * @override
     * @returns {string}
     */
    toString(){
        return this.id;
    }
}

module.exports = Command;<|endoftext|>


<commit_before>/**
 * hupu live
 * */
var request = require("request");

var util = require("util");
var Channel = require("../channel");
var HOST = "http://g.hupu.com/";

var escape = require('../util').escape;
var cheerio = require("cheerio");

var ChannelHupu = module.exports = function (gameInfo) {
    this.channel = "hupu";
    this.sid = 0;
    this.s_count = 0;

    this.lastFetchTime = +new Date();
    this.fetchLag = 10000;
    Channel.call(this, gameInfo);

};

util.inherits(ChannelHupu, Channel);

ChannelHupu.prototype.startLive = function () {
    this.live();
};
ChannelHupu.prototype.stopLive = function () {
    //TODO
};

ChannelHupu.prototype.live = function () {
    var self = this;
    self.lastFetchTime = +new Date();
    this._buildLiveRequest(function (res) {
        if (self.isOver(res)) {
            self.emit("over");
        } else {
            self._parseLiveData(res, self.lagLive);
        }
    });
};
ChannelHupu.prototype.lagLive = function () {
    var self = this;
    if (+new Date() - self.lastFetchTime > self.fetchLag) {
        self.live();
    } else {
        setTimeout(function () {
            self.live();
        }, self.fetchLag - ( +new Date() - self.lastFetchTime));
    }
};

/**
 * is live finished?
 * */
ChannelHupu.prototype.isOver = function (res) {
    return res === "over";
};

/**
 * resolve the http response
 * @param res{String} http response body
 * @param cb{Function} called when finished
 * */
ChannelHupu.prototype._parseLiveData = function (res, cb) {
    var self = this;
    var $ = cheerio.load("
" + res + "
");

    var trs = $("tr");
    var newSid , datas = [];
    if (trs.length) {
        newSid = trs.eq(0).attr("sid");
        if (newSid && newSid != self.sid) {
            self.sid = newSid;
            self.s_count += trs.length;
            trs.each(function (i, tr) {
                var tds = $(tr).find("td");
                if (tds.length == 4) {
                    datas.unshift({
                        time: escape(tds[0].innerHTML),
                        team: escape(tds[1].innerHTML),
                        content: escape(tds[2].innerHTML),
                        score: escape(tds[3].innerHTML)
                    });
                } else if (tds.length == 1) {
                    datas.unshift({
                        content: escape(tds[0].innerHTML)
                    });
                }
            });
            datas.forEach(function (data) {
                self.emit('data', data);
            });
        }
    }
    cb.call(self);
};

/**
 * build http request to hupu live
 * @param cb{Function} called when request success
 * */
ChannelHupu.prototype._buildLiveRequest = function (cb) {
    var self = this;
    request({
        url: this._buildLiveRequestUrl(),
        headers: {
            'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X; en-us) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53'
        }
    }, function (error, response, body) {
        if (error) {
            self.emit("error", error);
            self.lagLive();
        } else {
            if (response.statusCode == 200) {
                cb(body);
            } else {
                self.emit("error", new Error("remote error " + response.statusCode));
                self.lagLive();
            }
        }
    });
};

/**
 * build hupu live url
 * @return {String}
 * */
ChannelHupu.prototype._buildLiveRequestUrl = function () {
    var gameInfo = this.gameInfo;
    var data = {
        sid: this.sid,
        s_count: this.s_count,
        match_id: gameInfo.gameId,
        homeTeamName: encodeURIComponent(gameInfo.host),
        awayTeamName: encodeURIComponent(gameInfo.visiting)
    };
    var url = HOST + "/node/playbyplay/matchLives" + Object.keys(data).reduce(function (sum, current) {
        return sum + current + "=" + data[current] + "&";
    }, "?");

//    console.log(url)

    return url;
};
<commit_msg>fix live<commit_after>/**
 * hupu live
 * */

var r = require("request");
var request = r.defaults({proxy: "http://16.153.99.10:8080"});

var util = require("util");
var Channel = require("../channel");
var HOST = "http://g.hupu.com/";

var escape = require('../util').escape;
var cheerio = require("cheerio");

var ChannelHupu = module.exports = function (gameInfo) {
    this.channel = "hupu";
    this.sid = 0;
    this.s_count = 0;

    this.lastFetchTime = +new Date();
    this.fetchLag = 10000;
    Channel.call(this, gameInfo);

};

util.inherits(ChannelHupu, Channel);

ChannelHupu.prototype.startLive = function () {
    this.live();
};
ChannelHupu.prototype.stopLive = function () {
    //TODO
};

ChannelHupu.prototype.live = function () {
    var self = this;
    self.lastFetchTime = +new Date();
    this._buildLiveRequest(function (res) {
        if (self.isOver(res)) {
            self.emit("over");
        } else {
            self._parseLiveData(res, self.lagLive);
        }
    });
};
ChannelHupu.prototype.lagLive = function () {
    var self = this;
    if (+new Date() - self.lastFetchTime > self.fetchLag) {
        self.live();
    } else {
        setTimeout(function () {
            self.live();
        }, self.fetchLag - ( +new Date() - self.lastFetchTime));
    }
};

/**
 * is live finished?
 * */
ChannelHupu.prototype.isOver = function (res) {
    return res === "over";
};

/**
 * resolve the http response
 * @param res{String} http response body
 * @param cb{Function} called when finished
 * */
ChannelHupu.prototype._parseLiveData = function (res, cb) {
    var self = this;
    var $ = cheerio.load("
" + res + "
");

    var trs = $("tr");
    var newSid , datas = [];
    if (trs.length) {
        newSid = trs.eq(0).attr("sid");
        if (newSid && newSid != self.sid) {
            self.sid = newSid;
            self.s_count += trs.length;
            trs.each(function (i, tr) {
                var tds = $(tr).find("td");
                if (tds.length == 4) {
                    datas.unshift({
                        time: tds[0].children[0].data,
                        team: tds[1].children[0].data,
                        content: tds[2].children[0].data,
                        score: tds[3].children[0].data
                    });
                } else if (tds.length == 1) {
                    datas.unshift({
                        content: tds[0].children[0].data
                    });
                }
            });
            datas.forEach(function (data) {
                self.emit('data', data);
            });
        }
    }
    cb.call(self);
};

/**
 * build http request to hupu live
 * @param cb{Function} called when request success
 * */
ChannelHupu.prototype._buildLiveRequest = function (cb) {
    var self = this;
    request({
        url: this._buildLiveRequestUrl(),
        headers: {
            'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X; en-us) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53'
        }
    }, function (error, response, body) {
        if (error) {
            self.emit("error", error);
            self.lagLive();
        } else {
            if (response.statusCode == 200) {
                cb(body);
            } else {
                self.emit("error", new Error("remote error " + response.statusCode));
                self.lagLive();
            }
        }
    });
};

/**
 * build hupu live url
 * @return {String}
 * */
ChannelHupu.prototype._buildLiveRequestUrl = function () {
    var gameInfo = this.gameInfo;
    var data = {
        sid: this.sid,
        s_count: this.s_count,
        match_id: gameInfo.gameId,
        homeTeamName: encodeURIComponent(gameInfo.host),
        awayTeamName: encodeURIComponent(gameInfo.visiting)
    };
    var url = HOST + "/node/playbyplay/matchLives" + Object.keys(data).reduce(function (sum, current) {
        return sum + current + "=" + data[current] + "&";
    }, "?");

//    console.log(url)

    return url;
};
<|endoftext|>