<commit_before>/**
 * imagemagick.js - image magick helper
 *
 * each method requires an object with options:
 *  - src:     path to source image
 *  - dst:     path to destination image
 *  - width:   width of resized image (or cropped image)
 *  - height:  height of resized image (or cropped image)
 *  - x:       x offset for cropping (default is 0)
 *  - y:       y offset for cropping (default to 0)
 *  - quality: quality of processed image, 1 to 100 (default is 92)
 *  - gravity: crop position, one of NorthWest, North, NorthEast, West, Center, East, SouthWest, South, SouthEast (default is Center)
 */

var sprintf = require('util').format,
  log = require('sslog'),
  lru = require("lru-cache"),
  child = require('child_process')
  playButtonPath = __dirname + '/../public/play-button.svg';

// create lru cache for im_info responses
var im_info_lru = lru(10240); // 10240 max items

// add slashes to escaped characters
function addslashes(string) {
  return string.
  replace(/\\/g, '\\\\').
  replace(/\u0008/g, '\\b').
  replace(/\t/g, '\\t').
  replace(/\n/g, '\\n').
  replace(/\f/g, '\\f').
  replace(/\r/g, '\\r');
  //      replace(/'/g, '\\\'').
  //      replace(/"/g, '\\"');
}

// custom exec
function exec(command, options, callback) {
  // make sure we have a proper callback passed
  if (typeof callback !== 'function') {
    callback = options;
    options = null;
  }

  // if it still not a function, just create a dummy one
  if (typeof callback !== 'function') callback = function() {};

  var start = Date.now();
  child.exec(command, options, function(err, stdout, stderr) {
    callback(err, stdout, stderr);
    log.verbose(sprintf('executed: "%s", took: %d ms', addslashes(command), Date.now() - start));
  });
}

// get basic information about an image file
// also use lru cache to avoid spawning identify process each time
function im_info(file, force, callback) {
  // make sure we have a proper callback passed
  if (typeof callback !== 'function') {
    callback = force;
    force = false;
  }

  // if it still not function, make a dummy function of it
  if (typeof callback !== 'function') callback = function() {};

  // %z = depth, %m = type, %w = width, %h = height, %b = filesize in bytes, %f = filename
  var imcmd = 'identify -format "%m\n%z\n%w\n%h\n%b\n%f" ' + file,
    cached = im_info_lru.get(imcmd);

  if (cached && !force) {
    callback(null, cached);
  } else {
    exec(imcmd, function(err, stdout, stderr) {
      // do some sanity checks
      if (err) {
        return callback(err);
      }

      if (/^identify:/.test(stderr)) {
        return callback(new Error(stderr));
      }

      if (!stdout) {
        return callback(new Error('got empty output from "identify" command'));
      }

      var lines = stdout.split('\n'),
        info = {
          type: lines[0],
          depth: lines[1],
          width: lines[2],
          height: lines[3],
          size: lines[4],
          name: lines[5]
        };

      im_info_lru.set(imcmd, info); // save to cache
      callback(null, info);
    });
  }
}

// resize an image
function im_resize(options, force, callback) {
  // make sure we have a proper callback passed
  if (typeof callback !== 'function') {
    callback = force;
    force = false;
  }

  // if it still not function, make a dummy function of it
  if (typeof callback !== 'function') callback = function() {};

  if (!options.src || !options.dst) {
    return callback(new Error('src and dst must not be ommited'));
  }

  if (!options.width && !options.height) {
    return callback(new Error('one of width or height must be specified'));
  }

  // we deal only with numeric sizes
  if (isNaN(options.width) || isNaN(options.height)) {
    return callback(new Error('width/height must be numeric'));
  }

  // basic adjustment to width and height; the operators %, ^, and !
  options.adjustment = options.adjustment || '';

  // prepare width and height
  options.width = options.width || '';
  options.height = options.height || '';

  // default is 92 to avoid downsampling chroma channels
  options.quality = options.quality || 92;

  var imcmd = sprintf('convert -interlace Plane %s -resize "%sx%s"%s -quality %d %s', options.src, options.width, options.height, options.adjustment, options.quality, options.dst);
  exec(imcmd, function(err, stdout, stderr) {
    if (err) return callback(err);
    if(options.overlay === 'play'){
      im_add_play_button(options, force, callback);
    } else {
      im_info(options.dst, force, callback);
    }
  });
}

// add play button to an image
function im_add_play_button(options, force, callback) {
  // make sure we have a proper callback passed
  if (typeof callback !== 'function') {
    callback = force;
    force = false;
  }

  // if it still not function, make a dummy function of it
  if (typeof callback !== 'function') callback = function() {};

  if (!options.dst) {
    return callback(new Error('dst must not be ommited'));
  }

  var imcmd = sprintf('composite -gravity center -background none %s %s %s', playButtonPath, options.dst, options.dst);
  exec(imcmd, function(err, stdout, stderr) {
    if (err) return callback(err);
    im_info(options.dst, force, callback);
  });
}

// crop an image
function im_crop(options, force, callback) {
  // make sure we have a proper callback passed
  if (typeof callback !== 'function') {
    callback = force;
    force = false;
  }

  // if it still not function, make a dummy function of it
  if (typeof callback !== 'function') callback = function() {};

  if (!options.src || !options.dst) {
    return callback(new Error('src and dst must not be ommited'));
  }

  if (!options.width || isNaN(options.width)) {
    return callback(new Error('width must be specified and must it be numeric'));
  }

  options.height = (!options.height || isNaN(options.height)) ? options.width : options.height;
  options.x = (!options.x || isNaN(options.x)) ? 0 : options.x;
  options.y = (!options.y || isNaN(options.y)) ? 0 : options.y;
  options.gravity = options.gravity || 'Center';

  // default is 92 to avoid downsampling chroma channels
  options.quality = options.quality || 92;

  var imcmd = sprintf('convert %s -gravity %s -crop %sx%s+%d+%d -quality %d %s', options.src, options.gravity, options.width, options.height, options.x, options.y, options.quality, options.dst);
  exec(imcmd, function(err, stdout, stderr) {
    if (err) return callback(err);
    im_info(options.dst, force, callback);
  });

}

// resize without keeping aspect ratio
function im_stretch(options, force, callback) {
  // make sure we have a proper callback passed
  if (typeof callback !== 'function') {
    callback = force;
    force = false;
  }

  // if it still not function, make a dummy function of it
  if (typeof callback !== 'function') callback = function() {};

  if (!options.width || !options.height || options.width <= 0 || options.height <= 0) {
    return callback(new Error('both width and height must be specified, and must greater than 0'));
  }

  options.adjustment = '!';
  im_resize(options, force, callback);
}

// resize an image on smallest dimension to x, then crop center weighted
function im_chop(options, force, callback) {
  // make sure we have a proper callback passed
  if (typeof callback !== 'function') {
    callback = force;
    force = false;
  }

  // if it still not function, make a dummy function of it
  if (typeof callback !== 'function') callback = function() {};

  if (!options.src || !options.dst) {
    return callback(new Error('src and dst must not be ommited'));
  }

  if (!options.width && !options.height) {
    return callback(new Error('one of width or height must be specified'));
  }

  // we deal only with numeric sizes
  if (isNaN(options.width) || isNaN(options.height)) {
    return callback(new Error('width/height must be numeric'));
  }

  // basic adjustment to width and height; the operators %, ^, and !
  options.adjustment = options.adjustment || '';

  // prepare width and height
  options.width = options.width || '';
  options.height = options.height || '';

  // default is 92 to avoid downsampling chroma channels
  options.quality = options.quality || 92;

  // single line chop example aka scaledownandcrop scalencrop
  // convert myPhoto.jpg -resize 200x200^ -gravity Center -crop 200x200+0+0 +repage myThumb.png

  var imcmd = sprintf('convert %s -resize "%sx%s^" -gravity center -crop %sx%s+0+0 -quality %d %s', options.src, options.width, options.height, options.width, options.height, options.quality, options.dst);
  console.log(imcmd);
  exec(imcmd, function(err, stdout, stderr) {
    if (err) return callback(err);
    im_info(options.dst, force, callback);
  });
}

// export all the stuff
module.exports = {
  info: im_info,
  crop: im_crop,
  resize: im_resize,
  stretch: im_stretch,
  chop: im_chop
};
<commit_msg>added progressive jpeg option to other routes<commit_after>/**
 * imagemagick.js - image magick helper
 *
 * each method requires an object with options:
 *  - src:     path to source image
 *  - dst:     path to destination image
 *  - width:   width of resized image (or cropped image)
 *  - height:  height of resized image (or cropped image)
 *  - x:       x offset for cropping (default is 0)
 *  - y:       y offset for cropping (default to 0)
 *  - quality: quality of processed image, 1 to 100 (default is 92)
 *  - gravity: crop position, one of NorthWest, North, NorthEast, West, Center, East, SouthWest, South, SouthEast (default is Center)
 */

var sprintf = require('util').format,
  log = require('sslog'),
  lru = require("lru-cache"),
  child = require('child_process')
  playButtonPath = __dirname + '/../public/play-button.svg';

// create lru cache for im_info responses
var im_info_lru = lru(10240); // 10240 max items

// add slashes to escaped characters
function addslashes(string) {
  return string.
  replace(/\\/g, '\\\\').
  replace(/\u0008/g, '\\b').
  replace(/\t/g, '\\t').
  replace(/\n/g, '\\n').
  replace(/\f/g, '\\f').
  replace(/\r/g, '\\r');
  //      replace(/'/g, '\\\'').
  //      replace(/"/g, '\\"');
}

// custom exec
function exec(command, options, callback) {
  // make sure we have a proper callback passed
  if (typeof callback !== 'function') {
    callback = options;
    options = null;
  }

  // if it still not a function, just create a dummy one
  if (typeof callback !== 'function') callback = function() {};

  var start = Date.now();
  child.exec(command, options, function(err, stdout, stderr) {
    callback(err, stdout, stderr);
    log.verbose(sprintf('executed: "%s", took: %d ms', addslashes(command), Date.now() - start));
  });
}

// get basic information about an image file
// also use lru cache to avoid spawning identify process each time
function im_info(file, force, callback) {
  // make sure we have a proper callback passed
  if (typeof callback !== 'function') {
    callback = force;
    force = false;
  }

  // if it still not function, make a dummy function of it
  if (typeof callback !== 'function') callback = function() {};

  // %z = depth, %m = type, %w = width, %h = height, %b = filesize in bytes, %f = filename
  var imcmd = 'identify -format "%m\n%z\n%w\n%h\n%b\n%f" ' + file,
    cached = im_info_lru.get(imcmd);

  if (cached && !force) {
    callback(null, cached);
  } else {
    exec(imcmd, function(err, stdout, stderr) {
      // do some sanity checks
      if (err) {
        return callback(err);
      }

      if (/^identify:/.test(stderr)) {
        return callback(new Error(stderr));
      }

      if (!stdout) {
        return callback(new Error('got empty output from "identify" command'));
      }

      var lines = stdout.split('\n'),
        info = {
          type: lines[0],
          depth: lines[1],
          width: lines[2],
          height: lines[3],
          size: lines[4],
          name: lines[5]
        };

      im_info_lru.set(imcmd, info); // save to cache
      callback(null, info);
    });
  }
}

// resize an image
function im_resize(options, force, callback) {
  // make sure we have a proper callback passed
  if (typeof callback !== 'function') {
    callback = force;
    force = false;
  }

  // if it still not function, make a dummy function of it
  if (typeof callback !== 'function') callback = function() {};

  if (!options.src || !options.dst) {
    return callback(new Error('src and dst must not be ommited'));
  }

  if (!options.width && !options.height) {
    return callback(new Error('one of width or height must be specified'));
  }

  // we deal only with numeric sizes
  if (isNaN(options.width) || isNaN(options.height)) {
    return callback(new Error('width/height must be numeric'));
  }

  // basic adjustment to width and height; the operators %, ^, and !
  options.adjustment = options.adjustment || '';

  // prepare width and height
  options.width = options.width || '';
  options.height = options.height || '';

  // default is 92 to avoid downsampling chroma channels
  options.quality = options.quality || 92;

  var imcmd = sprintf('convert -interlace Plane %s -resize "%sx%s"%s -quality %d %s', options.src, options.width, options.height, options.adjustment, options.quality, options.dst);
  exec(imcmd, function(err, stdout, stderr) {
    if (err) return callback(err);
    if(options.overlay === 'play'){
      im_add_play_button(options, force, callback);
    } else {
      im_info(options.dst, force, callback);
    }
  });
}

// add play button to an image
function im_add_play_button(options, force, callback) {
  // make sure we have a proper callback passed
  if (typeof callback !== 'function') {
    callback = force;
    force = false;
  }

  // if it still not function, make a dummy function of it
  if (typeof callback !== 'function') callback = function() {};

  if (!options.dst) {
    return callback(new Error('dst must not be ommited'));
  }

  var imcmd = sprintf('composite -gravity center -background none %s %s %s', playButtonPath, options.dst, options.dst);
  exec(imcmd, function(err, stdout, stderr) {
    if (err) return callback(err);
    im_info(options.dst, force, callback);
  });
}

// crop an image
function im_crop(options, force, callback) {
  // make sure we have a proper callback passed
  if (typeof callback !== 'function') {
    callback = force;
    force = false;
  }

  // if it still not function, make a dummy function of it
  if (typeof callback !== 'function') callback = function() {};

  if (!options.src || !options.dst) {
    return callback(new Error('src and dst must not be ommited'));
  }

  if (!options.width || isNaN(options.width)) {
    return callback(new Error('width must be specified and must it be numeric'));
  }

  options.height = (!options.height || isNaN(options.height)) ? options.width : options.height;
  options.x = (!options.x || isNaN(options.x)) ? 0 : options.x;
  options.y = (!options.y || isNaN(options.y)) ? 0 : options.y;
  options.gravity = options.gravity || 'Center';

  // default is 92 to avoid downsampling chroma channels
  options.quality = options.quality || 92;

  var imcmd = sprintf('convert -interlace Plane %s -gravity %s -crop %sx%s+%d+%d -quality %d %s', options.src, options.gravity, options.width, options.height, options.x, options.y, options.quality, options.dst);
  exec(imcmd, function(err, stdout, stderr) {
    if (err) return callback(err);
    im_info(options.dst, force, callback);
  });

}

// resize without keeping aspect ratio
function im_stretch(options, force, callback) {
  // make sure we have a proper callback passed
  if (typeof callback !== 'function') {
    callback = force;
    force = false;
  }

  // if it still not function, make a dummy function of it
  if (typeof callback !== 'function') callback = function() {};

  if (!options.width || !options.height || options.width <= 0 || options.height <= 0) {
    return callback(new Error('both width and height must be specified, and must greater than 0'));
  }

  options.adjustment = '!';
  im_resize(options, force, callback);
}

// resize an image on smallest dimension to x, then crop center weighted
function im_chop(options, force, callback) {
  // make sure we have a proper callback passed
  if (typeof callback !== 'function') {
    callback = force;
    force = false;
  }

  // if it still not function, make a dummy function of it
  if (typeof callback !== 'function') callback = function() {};

  if (!options.src || !options.dst) {
    return callback(new Error('src and dst must not be ommited'));
  }

  if (!options.width && !options.height) {
    return callback(new Error('one of width or height must be specified'));
  }

  // we deal only with numeric sizes
  if (isNaN(options.width) || isNaN(options.height)) {
    return callback(new Error('width/height must be numeric'));
  }

  // basic adjustment to width and height; the operators %, ^, and !
  options.adjustment = options.adjustment || '';

  // prepare width and height
  options.width = options.width || '';
  options.height = options.height || '';

  // default is 92 to avoid downsampling chroma channels
  options.quality = options.quality || 92;

  // single line chop example aka scaledownandcrop scalencrop
  // convert myPhoto.jpg -resize 200x200^ -gravity Center -crop 200x200+0+0 +repage myThumb.png

  var imcmd = sprintf('convert -interlace Plane %s -resize "%sx%s^" -gravity center -crop %sx%s+0+0 -quality %d %s', options.src, options.width, options.height, options.width, options.height, options.quality, options.dst);
  console.log(imcmd);
  exec(imcmd, function(err, stdout, stderr) {
    if (err) return callback(err);
    im_info(options.dst, force, callback);
  });
}

// export all the stuff
module.exports = {
  info: im_info,
  crop: im_crop,
  resize: im_resize,
  stretch: im_stretch,
  chop: im_chop
};
<|endoftext|>


<commit_before>/*
  Copyright Jesús Pérez <jesusprubio@gmail.com>

  This code may only be used under the GPLv3 license found at
  http://www.gnu.org/licenses/gpl-3.0.txt.
*/

'use strict';

const utils = require('../utils');
const errMsgs = require('../utils/errorMsgs');

const Promise = utils.Promise;
const protocols = utils.requireDir(module, '../protocols');
const dbg = utils.dbg(__filename);
const logger = require('../../bin/lib/logger');

const defaultOpts = {
  proto: 'http',
  // TODO: Set in run time the default for each protocol (only as default if not set)
  rport: 8080,
  users: ['0000', '0001', '0002'],
  passwords: ['0000', '0001', '0002'],
  userAsPass: true,
  // TODO: Keep an eye and use a secure value here.
  // TODO: Take from cfg file.
  concurrency: 100,
  delay: 0,
  timeout: 5000,
};

module.exports = (rhost, opts = {}) =>
  new Promise((resolve, reject) => {
    if (!rhost) {
      reject(new Error(errMsgs.paramReq));

      return;
    }

    dbg(`Starting, IP: ${rhost}, options`, opts);

    if ((rhost && !utils.validator.isIP(rhost)) ||
        (opts.proto && !utils.includes(utils.keys(protocols), opts.proto)) ||
        // ".toString()" because "validator" only works over strings.
        (opts.rport && !utils.validator.isPort(opts.rport.toString())) ||
        (opts.users && !utils.isArray(opts.users)) ||
        (opts.passwords && !utils.isArray(opts.passwords)) ||
        (opts.userAsPass && !utils.validator.isBoolean(opts.userAsPass.toString())) ||
        (opts.concurrency && !utils.validator.isNumeric(opts.concurrency.toString())) ||
        (opts.delay && !utils.validator.isNumeric(opts.delay.toString())) ||
        (opts.timeout && !utils.validator.isNumeric(opts.timeout.toString()))) {
      // TODO: Add the param which failed to the error.
      reject(new Error(errMsgs.paramBad));

      return;
    }

    dbg('Check passed');
    const finalOpts = utils.defaults(opts, defaultOpts);
    dbg('Final options', finalOpts);

    // Adding the usernames as passwords if required.
    // The "[]" for protos with only one variable (ie: TFTP).
    let finalPasswords = finalOpts.passwords || [];
    if (finalOpts.userAsPass) { finalPasswords = utils.concat(finalPasswords, finalOpts.users); }

    dbg('Final options', finalOpts);
    dbg('Preparing an iterator for:', { users: finalOpts.users, passwords: finalPasswords });
    // TODO: Add a public util to get an array from a dic (each line)
    // We need an iterable to avoid a full break with huge ones (GC).
    const credPairs = new utils.ProductIterable(finalOpts.users, finalPasswords);

    const valids = [];
    // Running a promise which resolves to an array with all the results.
    Promise.map(
      credPairs,
      // Requests (promises) generator.
      // TODO: Check without "delay" to see if it has better performance.
      (credPair) => { // eslint-disable-line arrow-body-style
        return new Promise((resolveM, rejectM) => {
          dbg('Starting for', { credPair });

          const toPrint = `${credPair[0]} / ${credPair[1]}`;
          protocols[finalOpts.proto].brute(rhost, credPair, finalOpts).delay(finalOpts.delay)
          .then((res) => {
            dbg('Response received', { credPair });
            // TODO: Do not print here! Do it in the client.
            // But we need to emit an event from here before.
            logger.highlight(`${toPrint} -> ok`);

            if (res.authed) {
              let trimmed = credPair;
              // To avoid return thinks like ['filename', null] (ie: TFTP brute)
              if (!credPair[1]) { trimmed = credPair[0]; }

              valids.push(trimmed);
            } else {
              logger.info(`${toPrint} -> not ok`);
            }
            resolveM();
          })
          .catch(err => rejectM(err));
        });
      }
      ,
      { concurrency: finalOpts.concurrency }
    )
    .then(() => resolve(valids))
    .catch(err => reject(err));
  });
<commit_msg>Fix in the logger for brute forcing.<commit_after>/*
  Copyright Jesús Pérez <jesusprubio@gmail.com>

  This code may only be used under the GPLv3 license found at
  http://www.gnu.org/licenses/gpl-3.0.txt.
*/

'use strict';

const utils = require('../utils');
const errMsgs = require('../utils/errorMsgs');

const Promise = utils.Promise;
const protocols = utils.requireDir(module, '../protocols');
const dbg = utils.dbg(__filename);
const logger = require('../../bin/lib/logger');

const defaultOpts = {
  proto: 'http',
  // TODO: Set in run time the default for each protocol (only as default if not set)
  rport: 8080,
  users: ['0000', '0001', '0002'],
  passwords: ['0000', '0001', '0002'],
  userAsPass: true,
  // TODO: Keep an eye and use a secure value here.
  // TODO: Take from cfg file.
  concurrency: 100,
  delay: 0,
  timeout: 5000,
};

module.exports = (rhost, opts = {}) =>
  new Promise((resolve, reject) => {
    if (!rhost) {
      reject(new Error(errMsgs.paramReq));

      return;
    }

    dbg(`Starting, IP: ${rhost}, options`, opts);

    if ((rhost && !utils.validator.isIP(rhost)) ||
        (opts.proto && !utils.includes(utils.keys(protocols), opts.proto)) ||
        // ".toString()" because "validator" only works over strings.
        (opts.rport && !utils.validator.isPort(opts.rport.toString())) ||
        (opts.users && !utils.isArray(opts.users)) ||
        (opts.passwords && !utils.isArray(opts.passwords)) ||
        (opts.userAsPass && !utils.validator.isBoolean(opts.userAsPass.toString())) ||
        (opts.concurrency && !utils.validator.isNumeric(opts.concurrency.toString())) ||
        (opts.delay && !utils.validator.isNumeric(opts.delay.toString())) ||
        (opts.timeout && !utils.validator.isNumeric(opts.timeout.toString()))) {
      // TODO: Add the param which failed to the error.
      reject(new Error(errMsgs.paramBad));

      return;
    }

    dbg('Check passed');
    const finalOpts = utils.defaults(opts, defaultOpts);
    dbg('Final options', finalOpts);

    // Adding the usernames as passwords if required.
    // The "[]" for protos with only one variable (ie: TFTP).
    let finalPasswords = finalOpts.passwords || [];
    if (finalOpts.userAsPass) { finalPasswords = utils.concat(finalPasswords, finalOpts.users); }

    dbg('Final options', finalOpts);
    dbg('Preparing an iterator for:', { users: finalOpts.users, passwords: finalPasswords });
    // TODO: Add a public util to get an array from a dic (each line)
    // We need an iterable to avoid a full break with huge ones (GC).
    const credPairs = new utils.ProductIterable(finalOpts.users, finalPasswords);

    const valids = [];
    // Running a promise which resolves to an array with all the results.
    Promise.map(
      credPairs,
      // Requests (promises) generator.
      // TODO: Check without "delay" to see if it has better performance.
      (credPair) => { // eslint-disable-line arrow-body-style
        return new Promise((resolveM, rejectM) => {
          dbg('Starting for', { credPair });

          const toPrint = `${credPair[0]} / ${credPair[1]}`;
          protocols[finalOpts.proto].brute(rhost, credPair, finalOpts).delay(finalOpts.delay)
          .then((res) => {
            dbg('Response received', { credPair });
            // TODO: Do not print here! Do it in the client.
            // But we need to emit an event from here before.

            if (res.authed) {
              let trimmed = credPair;
              // To avoid return thinks like ['filename', null] (ie: TFTP brute)
              if (!credPair[1]) { trimmed = credPair[0]; }

              logger.highlight(`${toPrint} -> ok`);
              valids.push(trimmed);
            } else {
              logger.info(`${toPrint} -> not ok`);
            }
            resolveM();
          })
          .catch(err => rejectM(err));
        });
      }
      ,
      { concurrency: finalOpts.concurrency }
    )
    .then(() => resolve(valids))
    .catch(err => reject(err));
  });
<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var request = require('hopjs-request');

module.exports = function (config) {
  var baseUrl = 'https://api.telegram.org/bot' + config.token;
  var pollTimeout = config.timeout ? config.timeout : 86400;
  var offset = null;

  // List of handler methods specified by the user
  var callbacks = {};
  this.on = function (command, callback) {
    if (callback) {
      if (command) callbacks[command] = callback;
      else callbacks._default = callback;
    }
  };

  // Start checking for updates
  this.init = function (update_id) {
    // Allow a custom update_id
    if (update_id) offset = update_id;
    // If there are updates, handle them
    this.getUpdates(offset, null, pollTimeout, handleUpdates);
  };

  function handleUpdates(body) {
    body.result.forEach(function (result, index, results) {
      // Handle text messages
      if (result.message.text) {
        var command = result.message.text.split(' ')[0];
        if (callbacks[command]) callbacks[command](result);
        else if (callbacks._default) callbacks._default(result);
        else console.error('ERROR: No method to handle this message:\n' + result);
      }
      // TODO: Handle _joinGroup and similar messages
      // Update offset
      offset = result.update_id + 1;
    });
    // Check again after handling updates
    this.init();
  }

  // Generic method to send HTTPS requests
  function sendRequest(params, callback) {
    request.post(params, function (err, res, body) {
      if (err) console.error(err);
      if (callback) callback(JSON.parse(body));
    });
  }

  // API methods implemented in JavaScript
  this.getMe = function (callback) {
    sendRequest({
      url: baseUrl + '/getMe',
    }, callback);
  };

  this.sendMessage = function (chat_id, text, disable_web_page_preview, reply_to_message_id, reply_markup, callback) {
    sendRequest({
      url: baseUrl + '/sendMessage',
      form: {
        chat_id: chat_id,
        text: text,
        disable_web_page_preview: disable_web_page_preview,
        reply_to_message_id: reply_to_message_id,
        reply_markup: reply_markup
      }
    }, callback);
  };

  this.forwardMessage = function (chat_id, from_chat_id, message_id, callback) {
    sendRequest({
      url: baseUrl + '/forwardMessage',
      form: {
        chat_id: chat_id,
        from_chat_id: from_chat_id,
        message_id: message_id
      }
    }, callback);
  };

  this.sendPhoto = function (chat_id, photo, caption, reply_to_message_id, reply_markup, callback) {
    sendRequest({
      url: baseUrl + '/sendPhoto',
      formData: {
        chat_id: chat_id,
        photo: photo,
        caption: caption,
        reply_to_message_id: reply_to_message_id,
        reply_markup: reply_markup
      }
    }, callback);
  };

  this.sendAudio = function (chat_id, audio, reply_to_message_id, reply_markup, callback) {
    sendRequest({
      url: baseUrl + '/sendAudio',
      formData: {
        chat_id: chat_id,
        audio: audio,
        reply_to_message_id: reply_to_message_id,
        reply_markup: reply_markup
      }
    }, callback);
  };

  this.sendDocument = function (chat_id, document, reply_to_message_id, reply_markup, callback) {
    sendRequest({
      url: baseUrl + '/sendDocument',
      formData: {
        chat_id: chat_id,
        document: document,
        reply_to_message_id: reply_to_message_id,
        reply_markup: reply_markup
      }
    }, callback);
  };

  this.sendSticker = function (chat_id, sticker, reply_to_message_id, reply_markup, callback) {
    sendRequest({
      url: baseUrl + '/sendSticker',
      formData: {
        chat_id: chat_id,
        sticker: sticker,
        reply_to_message_id: reply_to_message_id,
        reply_markup: reply_markup
      }
    }, callback);
  };

  this.sendVideo = function (chat_id, video, reply_to_message_id, reply_markup, callback) {
    sendRequest({
      url: baseUrl + '/sendVideo',
      formData: {
        chat_id: chat_id,
        video: video,
        reply_to_message_id: reply_to_message_id,
        reply_markup: reply_markup
      }
    }, callback);
  };

  this.sendLocation = function (chat_id, latitude, longitude, reply_to_message_id, reply_markup) {
    sendRequest({
      url: baseUrl + '/sendLocation',
      form: {
        chat_id: chat_id,
        latitude: latitude,
        longitude: longitude,
        reply_to_message_id: reply_to_message_id,
        reply_markup: reply_markup
      }
    }, callback);
  };

  this.sendChatAction = function (chat_id, action) {
    sendRequest({
      url: baseUrl + '/sendChatAction',
      form: {
        chat_id: chat_id,
        action: action
      }
    }, callback);
  };

  this.getUserProfilePhotos = function (user_id, offset, limit, callback) {
    sendRequest({
      url: baseUrl + '/getUserProfilePhotos',
      form: {
        user_id: user_id,
        offset: offset,
        limit: limit
      }
    }, callback);
  };

  this.getUpdates = function (offset, limit, timeout, callback) {
    sendRequest({
      url: baseUrl + '/getUpdates',
      form: {
        offset: offset,
        limit: limit,
        timeout: timeout
      }
    }, callback);
  };

  // this.setWebhook = function () {};
  return this;
};<commit_msg>Switch to EventEmitter, and other refinements<commit_after>var fs = require('fs');
var path = require('path');
var request = require('hopjs-request');
var util = require('util');

// Constructor for the whole darn thing
var Bub = function (config) {
  var BASE_URL = 'https://api.telegram.org/bot' + config.token;
  var TIMEOUT = config.timeout ? config.timeout : 86400;
  var offset = null;

  // A reference to `this`, required for emitting events
  var self = this;

  // Start checking for updates
  self.init = function (update_id) {
    // Allow a custom update_id
    if (update_id) offset = update_id;
    // If there are updates, handle them
    self.getUpdates(offset, null, TIMEOUT, handleUpdates);
  };

  function handleUpdates(body) {
    body.result.forEach(function (result, index, results) {
      // Handle text messages
      if (result.message.text) {
        var command = result.message.text.split(' ')[0];
        var listeners = util.inspect(self.listeners(command));
        if (listeners !== '[]') self.emit(command, result);
        else self.emit('_default', result);
      }
      // TODO: Handle _joinGroup and similar messages
      // Update offset
      offset = result.update_id + 1;
    });
    // Check again after handling updates
    self.init();
  }

  // Generic method to send HTTPS requests
  function sendRequest(params, callback) {
    request.post(params, function (err, res, body) {
      if (err) console.error(err);
      if (callback) callback(JSON.parse(body));
    });
  }

  // API methods implemented in JavaScript
  self.getMe = function (callback) {
    sendRequest({
      url: BASE_URL + '/getMe',
    }, callback);
  };

  self.sendMessage = function (chat_id, text, disable_web_page_preview, reply_to_message_id, reply_markup, callback) {
    sendRequest({
      url: BASE_URL + '/sendMessage',
      form: {
        chat_id: chat_id,
        text: text,
        disable_web_page_preview: disable_web_page_preview,
        reply_to_message_id: reply_to_message_id,
        reply_markup: reply_markup
      }
    }, callback);
  };

  self.forwardMessage = function (chat_id, from_chat_id, message_id, callback) {
    sendRequest({
      url: BASE_URL + '/forwardMessage',
      form: {
        chat_id: chat_id,
        from_chat_id: from_chat_id,
        message_id: message_id
      }
    }, callback);
  };

  self.sendPhoto = function (chat_id, photo, caption, reply_to_message_id, reply_markup, callback) {
    sendRequest({
      url: BASE_URL + '/sendPhoto',
      formData: {
        chat_id: chat_id,
        photo: photo,
        caption: caption,
        reply_to_message_id: reply_to_message_id,
        reply_markup: reply_markup
      }
    }, callback);
  };

  self.sendAudio = function (chat_id, audio, reply_to_message_id, reply_markup, callback) {
    sendRequest({
      url: BASE_URL + '/sendAudio',
      formData: {
        chat_id: chat_id,
        audio: audio,
        reply_to_message_id: reply_to_message_id,
        reply_markup: reply_markup
      }
    }, callback);
  };

  self.sendDocument = function (chat_id, document, reply_to_message_id, reply_markup, callback) {
    sendRequest({
      url: BASE_URL + '/sendDocument',
      formData: {
        chat_id: chat_id,
        document: document,
        reply_to_message_id: reply_to_message_id,
        reply_markup: reply_markup
      }
    }, callback);
  };

  self.sendSticker = function (chat_id, sticker, reply_to_message_id, reply_markup, callback) {
    sendRequest({
      url: BASE_URL + '/sendSticker',
      formData: {
        chat_id: chat_id,
        sticker: sticker,
        reply_to_message_id: reply_to_message_id,
        reply_markup: reply_markup
      }
    }, callback);
  };

  self.sendVideo = function (chat_id, video, reply_to_message_id, reply_markup, callback) {
    sendRequest({
      url: BASE_URL + '/sendVideo',
      formData: {
        chat_id: chat_id,
        video: video,
        reply_to_message_id: reply_to_message_id,
        reply_markup: reply_markup
      }
    }, callback);
  };

  self.sendLocation = function (chat_id, latitude, longitude, reply_to_message_id, reply_markup) {
    sendRequest({
      url: BASE_URL + '/sendLocation',
      form: {
        chat_id: chat_id,
        latitude: latitude,
        longitude: longitude,
        reply_to_message_id: reply_to_message_id,
        reply_markup: reply_markup
      }
    }, callback);
  };

  self.sendChatAction = function (chat_id, action) {
    sendRequest({
      url: BASE_URL + '/sendChatAction',
      form: {
        chat_id: chat_id,
        action: action
      }
    }, callback);
  };

  self.getUserProfilePhotos = function (user_id, offset, limit, callback) {
    sendRequest({
      url: BASE_URL + '/getUserProfilePhotos',
      form: {
        user_id: user_id,
        offset: offset,
        limit: limit
      }
    }, callback);
  };

  self.getUpdates = function (offset, limit, timeout, callback) {
    sendRequest({
      url: BASE_URL + '/getUpdates',
      form: {
        offset: offset,
        limit: limit,
        timeout: timeout
      }
    }, callback);
  };

  // this.setWebhook = function () {};
  return this;
};

util.inherits(Bub, require('events').EventEmitter);

module.exports = Bub;<|endoftext|>


<commit_before>'use strict';

var interceptor = {
  setup: function setup(done) {
    var NAMESPACE = '__webdriverajax';
    var PKG_PREFIX = '[wdio-intercept-service]: ';

    window[NAMESPACE] = { requests: [] };

    // Some browsers don't support FormData.entries(), so we polyfill that (sigh)
    if (typeof FormData.prototype.entries == 'undefined') {
      polyfillFormDataEntries();
    }

    if (supportsSessionStorage()) {
      window.sessionStorage.removeItem(NAMESPACE);
    }

    if (typeof window.fetch == 'function') {
      replaceFetch();
      if (
        typeof window.Promise === 'undefined' ||
        typeof window.Promise.all !== 'function'
      ) {
        console.error(PKG_PREFIX + 'Fetch API preconditions not met!');
      }
    }

    replaceXHR();

    done(window[NAMESPACE]);

    function replaceFetch() {
      var _fetch = window.fetch;
      window.fetch = function () {
        // Default values if not overwritten
        var request = {
          method: 'GET',
          requestHeaders: {},
          requestBody: undefined,
          url: '',
        };
        var input = arguments[0];
        var init = arguments[1];
        if (typeof input == 'string') {
          request.url = input;
        } else if (input instanceof URL) {
          request.url = input.href;
        } else {
          if (input instanceof Request) {
            // Request object
            var clonedRequest = input.clone();
            request.requestBody = clonedRequest.text();
            request.url = clonedRequest.url;
            request.requestHeaders = parseHeaders(clonedRequest.headers);
            request.method = clonedRequest.method;
          } else {
            console.error(PKG_PREFIX + 'Unhandled input type to fetch API!');
            request.requestBody = input.body;
          }
        }
        if (init) {
          if (typeof init.body !== 'undefined') request.requestBody = init.body;
          if (typeof init.method !== 'undefined') request.method = init.method;
          request.requestHeaders = parseHeaders(init.headers);
        }
        addPendingRequest(request);

        return _fetch.apply(window, arguments).then(function (response) {
          // TODO: We could clone it multiple times and check for all type variations of body
          var clonedResponse = response.clone();
          var responsePromise = clonedResponse.text();

          // After decoding the request's body (which may have come from Request#text())
          // and the response body, we can store the completed request.
          Promise.all([request.requestBody, responsePromise]).then(function (
            results
          ) {
            completeFetchRequest(request, {
              requestBody: results[0],
              body: results[1],
              statusCode: clonedResponse.status,
              headers: parseHeaders(clonedResponse.headers),
            });
          });

          // Forward the original response to the application on the current tick.
          return response;
        });
      };
    }

    function replaceXHR() {
      var originalOpen = XMLHttpRequest.prototype.open;
      var originalSend = XMLHttpRequest.prototype.send;
      var originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
      XMLHttpRequest.prototype.open = function () {
        this.lastMethod = arguments[0];
        this.lastURL = arguments[1];
        originalOpen.apply(this, arguments);
      };
      XMLHttpRequest.prototype.send = function () {
        var req = {
          method: this.lastMethod.toUpperCase(),
          requestHeaders: this.lastRequestHeader || {},
          requestBody: parsePayload(arguments[0]),
          url: this.lastURL.toString(),
        };
        addPendingRequest(req);
        originalSend.apply(this, arguments);

        var _this = this;
        this.addEventListener('load', function () {
          req.statusCode = _this.status;
          req.headers = _this.getAllResponseHeaders();
          // The body may need to be further processed, or may be ready synchronously.
          var parsed = parseBody(_this, req);
          if (!parsed.deferred) {
            completeXHRRequest(req, parsed.body);
          }
        });
      };
      XMLHttpRequest.prototype.setRequestHeader = function () {
        if (!this.lastRequestHeader) {
          this.lastRequestHeader = {};
        }
        this.lastRequestHeader[arguments[0]] = arguments[1];
        originalSetRequestHeader.apply(this, arguments);
      };
    }

    function parseBody(xhr, request) {
      if (xhr.responseType === 'arraybuffer') {
        return {
          body: new TextDecoder().decode(xhr.response),
        };
      } else if (xhr.responseType === 'blob') {
        // Read the response like a file.
        var fr = new FileReader();
        fr.addEventListener('load', function () {
          completeXHRRequest(request, new TextDecoder().decode(this.result));
        });
        fr.readAsArrayBuffer(xhr.response);
        return { deferred: true };
      }
      // IE9 comp: need xhr.responseText
      return {
        body: xhr.response || xhr.responseText,
      };
    }

    function parseHeaders(headers) {
      if (headers instanceof Headers) {
        var result = {};

        var headersEntries = headers.entries();
        var header = headersEntries.next();
        while (!header.done) {
          result[header.value[0]] = header.value[1];
          header = headersEntries.next();
        }
        return result;
      }
      return headers || {};
    }

    /**
     * Stringify the given XHR payload so it can be parsed as JSON
     * @param {*} payload XHR request body that is sent to the remote server.
     * @returns {string} JSON-parsable representation of the request body
     */
    function parsePayload(payload) {
      if (typeof payload == 'string') {
        return payload;
      }
      if (payload instanceof FormData) {
        var parsed = {};
        var entries = payload.entries();
        var item;
        while (((item = entries.next()), !item.done)) {
          parsed[item.value[0]] = item.value.slice(1);
        }
        return JSON.stringify(parsed);
      }
      if (payload instanceof ArrayBuffer) {
        return String.fromCharCode.apply(null, payload);
      }

      // Just try to convert it to a string, whatever it might be
      try {
        return JSON.stringify(payload);
      } catch (e) {
        console.error(PKG_PREFIX + 'Failed to stringify payload as JSON!', e);
      }
      return '';
    }

    function addPendingRequest(startedRequest) {
      startedRequest.__processed = Date.now();
      window[NAMESPACE].requests.push(startedRequest);
      pushToSessionStorage(startedRequest);
    }

    function completeFetchRequest(startedRequest, completedRequest) {
      // Merge the completed data with the started request.
      startedRequest.requestBody = completedRequest.requestBody;
      startedRequest.body = completedRequest.body;
      startedRequest.headers = completedRequest.headers;
      startedRequest.statusCode = completedRequest.statusCode;
      startedRequest.__fulfilled = Date.now();
      replaceInSessionStorage(startedRequest);
    }

    function completeXHRRequest(startedRequest, responseBody) {
      // Merge the completed data with the started request.
      startedRequest.body = responseBody;
      startedRequest.__fulfilled = Date.now();
      replaceInSessionStorage(startedRequest);
    }

    function getParsedSessionStorage() {
      var rawData = window.sessionStorage.getItem(NAMESPACE);
      if (!rawData) {
        return [];
      }
      try {
        return JSON.parse(rawData);
      } catch (e) {
        throw new Error(
          PKG_PREFIX + 'Could not parse sessionStorage data: ' + e.message
        );
      }
    }

    function supportsSessionStorage() {
      return (
        typeof window.sessionStorage === 'object' &&
        window.sessionStorage !== null &&
        typeof window.sessionStorage.setItem === 'function' &&
        typeof window.sessionStorage.removeItem === 'function'
      );
    }

    function pushToSessionStorage(req) {
      if (!supportsSessionStorage()) {
        return;
      }
      var parsed = getParsedSessionStorage();
      parsed.push(req);
      window.sessionStorage.setItem(NAMESPACE, JSON.stringify(parsed));
    }

    function replaceInSessionStorage(completedRequest) {
      if (!supportsSessionStorage()) {
        return;
      }
      var parsed = getParsedSessionStorage();
      // Unlike requests held in the namespace, no session-stored requests can share object equality
      // with the completed request, due to the string serialization. Instead, we must look for an
      // item with the same "__processed" time. In case multiple requests are added simultaneously,
      // the url and method are used to further disambiguate the serialized requests.
      for (
        var storedRqNumber = 0;
        storedRqNumber < parsed.length;
        ++storedRqNumber
      ) {
        var r = parsed[storedRqNumber];
        if (
          r.__processed === completedRequest.__processed &&
          r.url === completedRequest.url &&
          r.method === completedRequest.method
        ) {
          parsed[storedRqNumber] = completedRequest;
          break;
        }
      }
      window.sessionStorage.setItem(NAMESPACE, JSON.stringify(parsed));
    }

    function polyfillFormDataEntries() {
      var originalAppend = FormData.prototype.append;
      FormData.prototype.append = function () {
        this.__entries = this.__entries || [];
        this.__entries.push(Array.prototype.slice.call(arguments));
        originalAppend.apply(this, arguments);
      };
      FormData.prototype.entries = function () {
        return this.__entries;
      };
    }
  },
  getRequest: function getRequest(index, options) {
    var NAMESPACE = '__webdriverajax';
    var PKG_PREFIX = '[wdio-intercept-service]: ';

    function getFromSessionStorage() {
      var rawData = window.sessionStorage.getItem(NAMESPACE);
      var parsed;
      if (rawData) {
        try {
          parsed = JSON.parse(rawData);
        } catch (e) {
          throw new Error(
            PKG_PREFIX + 'Could not parse sessionStorage data: ' + e.message
          );
        }
      }
      return parsed;
    }
    function getAllRequests() {
      // Session storage will always return an array that can be mutated freely.
      if (window.sessionStorage && window.sessionStorage.getItem) {
        return getFromSessionStorage() || [];
      }
      // But if we have to use the active namespace array, then return a copy of it.
      var shouldClone = window[NAMESPACE].requests;
      return shouldClone ? shouldClone.slice() : [];
    }
    function isComplete(r) {
      return typeof r.__fulfilled === 'number';
    }

    var requests = getAllRequests();

    var shouldSortByEnd = String(options.orderBy).toUpperCase() !== 'START';
    if (shouldSortByEnd) {
      // Sort ascending by time of fulfillment. If not fulfilled yet, sort to the end!
      requests.sort(function (a, b) {
        var hasA = isComplete(a);
        var hasB = isComplete(b);
        if (hasA && hasB) return a.__fulfilled - b.__fulfilled;
        if (hasA) return -1; // Only A is fulfilled, so order A before B
        if (hasB) return 1; // Only B is fulfilled, so order A after B
        return 0; // Preserve ordering of A & B.
      });
    }

    var includePending = Boolean(options.includePending);
    if (index == null) {
      return includePending ? requests : requests.filter(isComplete);
    }

    if (!includePending) {
      // Filter out the pending requests and index only into the completed requests.
      return requests.filter(isComplete)[index];
    }
    return requests[index];
  },

  /**
   * Convenience method that avoids needing to marshall all requests between node & the browser,
   * and instead inspects the active namespace request array for any unfulfilled requests.
   */
  hasPending: function hasPending() {
    var NAMESPACE = '__webdriverajax';
    var allRequests = window[NAMESPACE].requests || [];
    for (var idx = 0; idx < allRequests.length; ++idx) {
      if (typeof allRequests[idx].__fulfilled === 'undefined') return true;
    }
    return false;
  },
};

module.exports = interceptor;
<commit_msg>fix(#196): requestBody are empty when doing a POST+URLSearchParams (#197)<commit_after>'use strict';

var interceptor = {
  setup: function setup(done) {
    var NAMESPACE = '__webdriverajax';
    var PKG_PREFIX = '[wdio-intercept-service]: ';

    window[NAMESPACE] = { requests: [] };

    // Some browsers don't support FormData.entries(), so we polyfill that (sigh)
    if (typeof FormData.prototype.entries == 'undefined') {
      polyfillFormDataEntries();
    }

    if (supportsSessionStorage()) {
      window.sessionStorage.removeItem(NAMESPACE);
    }

    if (typeof window.fetch == 'function') {
      replaceFetch();
      if (
        typeof window.Promise === 'undefined' ||
        typeof window.Promise.all !== 'function'
      ) {
        console.error(PKG_PREFIX + 'Fetch API preconditions not met!');
      }
    }

    replaceXHR();

    done(window[NAMESPACE]);

    function replaceFetch() {
      var _fetch = window.fetch;
      window.fetch = function () {
        // Default values if not overwritten
        var request = {
          method: 'GET',
          requestHeaders: {},
          requestBody: undefined,
          url: '',
        };
        var input = arguments[0];
        var init = arguments[1];
        if (typeof input == 'string') {
          request.url = input;
        } else if (input instanceof URL) {
          request.url = input.href;
        } else {
          if (input instanceof Request) {
            // Request object
            var clonedRequest = input.clone();
            request.requestBody = clonedRequest.text();
            request.url = clonedRequest.url;
            request.requestHeaders = parseHeaders(clonedRequest.headers);
            request.method = clonedRequest.method;
          } else {
            console.error(PKG_PREFIX + 'Unhandled input type to fetch API!');
            request.requestBody = input.body;
          }
        }
        if (init) {
          if (typeof init.body !== 'undefined') request.requestBody = parsePayload(init.body);
          if (typeof init.method !== 'undefined') request.method = init.method;
          request.requestHeaders = parseHeaders(init.headers);
        }
        addPendingRequest(request);

        return _fetch.apply(window, arguments).then(function (response) {
          // TODO: We could clone it multiple times and check for all type variations of body
          var clonedResponse = response.clone();
          var responsePromise = clonedResponse.text();

          // After decoding the request's body (which may have come from Request#text())
          // and the response body, we can store the completed request.
          Promise.all([request.requestBody, responsePromise]).then(function (
            results
          ) {
            completeFetchRequest(request, {
              requestBody: results[0],
              body: results[1],
              statusCode: clonedResponse.status,
              headers: parseHeaders(clonedResponse.headers),
            });
          });

          // Forward the original response to the application on the current tick.
          return response;
        });
      };
    }

    function replaceXHR() {
      var originalOpen = XMLHttpRequest.prototype.open;
      var originalSend = XMLHttpRequest.prototype.send;
      var originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
      XMLHttpRequest.prototype.open = function () {
        this.lastMethod = arguments[0];
        this.lastURL = arguments[1];
        originalOpen.apply(this, arguments);
      };
      XMLHttpRequest.prototype.send = function () {
        var req = {
          method: this.lastMethod.toUpperCase(),
          requestHeaders: this.lastRequestHeader || {},
          requestBody: parsePayload(arguments[0]),
          url: this.lastURL.toString(),
        };
        addPendingRequest(req);
        originalSend.apply(this, arguments);

        var _this = this;
        this.addEventListener('load', function () {
          req.statusCode = _this.status;
          req.headers = _this.getAllResponseHeaders();
          // The body may need to be further processed, or may be ready synchronously.
          var parsed = parseBody(_this, req);
          if (!parsed.deferred) {
            completeXHRRequest(req, parsed.body);
          }
        });
      };
      XMLHttpRequest.prototype.setRequestHeader = function () {
        if (!this.lastRequestHeader) {
          this.lastRequestHeader = {};
        }
        this.lastRequestHeader[arguments[0]] = arguments[1];
        originalSetRequestHeader.apply(this, arguments);
      };
    }

    function parseBody(xhr, request) {
      if (xhr.responseType === 'arraybuffer') {
        return {
          body: new TextDecoder().decode(xhr.response),
        };
      } else if (xhr.responseType === 'blob') {
        // Read the response like a file.
        var fr = new FileReader();
        fr.addEventListener('load', function () {
          completeXHRRequest(request, new TextDecoder().decode(this.result));
        });
        fr.readAsArrayBuffer(xhr.response);
        return { deferred: true };
      }
      // IE9 comp: need xhr.responseText
      return {
        body: xhr.response || xhr.responseText,
      };
    }

    function parseHeaders(headers) {
      if (headers instanceof Headers) {
        var result = {};

        var headersEntries = headers.entries();
        var header = headersEntries.next();
        while (!header.done) {
          result[header.value[0]] = header.value[1];
          header = headersEntries.next();
        }
        return result;
      }
      return headers || {};
    }

    /**
     * Stringify the given XHR payload so it can be parsed as JSON
     * @param {*} payload XHR request body that is sent to the remote server.
     * @returns {string} JSON-parsable representation of the request body
     */
    function parsePayload(payload) {
      if (typeof payload == 'string') {
        return payload;
      }
      if (payload instanceof FormData) {
        var parsed = {};
        var entries = payload.entries();
        var item;
        while (((item = entries.next()), !item.done)) {
          parsed[item.value[0]] = item.value.slice(1);
        }
        return JSON.stringify(parsed);
      }
      if (payload instanceof ArrayBuffer) {
        return String.fromCharCode.apply(null, payload);
      }
      if (payload instanceof URLSearchParams) {
        return payload.toString();
      }

      // Just try to convert it to a string, whatever it might be
      try {
        return JSON.stringify(payload);
      } catch (e) {
        console.error(PKG_PREFIX + 'Failed to stringify payload as JSON!', e);
      }
      return '';
    }

    function addPendingRequest(startedRequest) {
      startedRequest.__processed = Date.now();
      window[NAMESPACE].requests.push(startedRequest);
      pushToSessionStorage(startedRequest);
    }

    function completeFetchRequest(startedRequest, completedRequest) {
      // Merge the completed data with the started request.
      startedRequest.requestBody = completedRequest.requestBody;
      startedRequest.body = completedRequest.body;
      startedRequest.headers = completedRequest.headers;
      startedRequest.statusCode = completedRequest.statusCode;
      startedRequest.__fulfilled = Date.now();
      replaceInSessionStorage(startedRequest);
    }

    function completeXHRRequest(startedRequest, responseBody) {
      // Merge the completed data with the started request.
      startedRequest.body = responseBody;
      startedRequest.__fulfilled = Date.now();
      replaceInSessionStorage(startedRequest);
    }

    function getParsedSessionStorage() {
      var rawData = window.sessionStorage.getItem(NAMESPACE);
      if (!rawData) {
        return [];
      }
      try {
        return JSON.parse(rawData);
      } catch (e) {
        throw new Error(
          PKG_PREFIX + 'Could not parse sessionStorage data: ' + e.message
        );
      }
    }

    function supportsSessionStorage() {
      return (
        typeof window.sessionStorage === 'object' &&
        window.sessionStorage !== null &&
        typeof window.sessionStorage.setItem === 'function' &&
        typeof window.sessionStorage.removeItem === 'function'
      );
    }

    function pushToSessionStorage(req) {
      if (!supportsSessionStorage()) {
        return;
      }
      var parsed = getParsedSessionStorage();
      parsed.push(req);
      window.sessionStorage.setItem(NAMESPACE, JSON.stringify(parsed));
    }

    function replaceInSessionStorage(completedRequest) {
      if (!supportsSessionStorage()) {
        return;
      }
      var parsed = getParsedSessionStorage();
      // Unlike requests held in the namespace, no session-stored requests can share object equality
      // with the completed request, due to the string serialization. Instead, we must look for an
      // item with the same "__processed" time. In case multiple requests are added simultaneously,
      // the url and method are used to further disambiguate the serialized requests.
      for (
        var storedRqNumber = 0;
        storedRqNumber < parsed.length;
        ++storedRqNumber
      ) {
        var r = parsed[storedRqNumber];
        if (
          r.__processed === completedRequest.__processed &&
          r.url === completedRequest.url &&
          r.method === completedRequest.method
        ) {
          parsed[storedRqNumber] = completedRequest;
          break;
        }
      }
      window.sessionStorage.setItem(NAMESPACE, JSON.stringify(parsed));
    }

    function polyfillFormDataEntries() {
      var originalAppend = FormData.prototype.append;
      FormData.prototype.append = function () {
        this.__entries = this.__entries || [];
        this.__entries.push(Array.prototype.slice.call(arguments));
        originalAppend.apply(this, arguments);
      };
      FormData.prototype.entries = function () {
        return this.__entries;
      };
    }
  },
  getRequest: function getRequest(index, options) {
    var NAMESPACE = '__webdriverajax';
    var PKG_PREFIX = '[wdio-intercept-service]: ';

    function getFromSessionStorage() {
      var rawData = window.sessionStorage.getItem(NAMESPACE);
      var parsed;
      if (rawData) {
        try {
          parsed = JSON.parse(rawData);
        } catch (e) {
          throw new Error(
            PKG_PREFIX + 'Could not parse sessionStorage data: ' + e.message
          );
        }
      }
      return parsed;
    }
    function getAllRequests() {
      // Session storage will always return an array that can be mutated freely.
      if (window.sessionStorage && window.sessionStorage.getItem) {
        return getFromSessionStorage() || [];
      }
      // But if we have to use the active namespace array, then return a copy of it.
      var shouldClone = window[NAMESPACE].requests;
      return shouldClone ? shouldClone.slice() : [];
    }
    function isComplete(r) {
      return typeof r.__fulfilled === 'number';
    }

    var requests = getAllRequests();

    var shouldSortByEnd = String(options.orderBy).toUpperCase() !== 'START';
    if (shouldSortByEnd) {
      // Sort ascending by time of fulfillment. If not fulfilled yet, sort to the end!
      requests.sort(function (a, b) {
        var hasA = isComplete(a);
        var hasB = isComplete(b);
        if (hasA && hasB) return a.__fulfilled - b.__fulfilled;
        if (hasA) return -1; // Only A is fulfilled, so order A before B
        if (hasB) return 1; // Only B is fulfilled, so order A after B
        return 0; // Preserve ordering of A & B.
      });
    }

    var includePending = Boolean(options.includePending);
    if (index == null) {
      return includePending ? requests : requests.filter(isComplete);
    }

    if (!includePending) {
      // Filter out the pending requests and index only into the completed requests.
      return requests.filter(isComplete)[index];
    }
    return requests[index];
  },

  /**
   * Convenience method that avoids needing to marshall all requests between node & the browser,
   * and instead inspects the active namespace request array for any unfulfilled requests.
   */
  hasPending: function hasPending() {
    var NAMESPACE = '__webdriverajax';
    var allRequests = window[NAMESPACE].requests || [];
    for (var idx = 0; idx < allRequests.length; ++idx) {
      if (typeof allRequests[idx].__fulfilled === 'undefined') return true;
    }
    return false;
  },
};

module.exports = interceptor;
<|endoftext|>


<commit_before>var async = require("async");
var mongoq = require('mongoq');

var checkStock = function(db,location,items,done){
    var aItems = arrangeItems(items);
    async.eachSeries(aItems,
      function(item,callback){
        var id = mongoq.mongodb.BSONPure.ObjectID.createFromHexString(item._id);
        checkItemInventory(db,location,item,callback);
      },
      function(err){
        done(err,aItems);
      });
};

var getPreviousRecord = function(db,filter,callback){

  db.collection("sales")
  .find(filter).toArray()
      .done(function(data){
      if(data && data.length){
        callback(null,data[0]);
      }
      else{
        callback(null,null);
      }
    });
};
var outStock = function(db,location,items,cb){

  checkStock(db,location,items,function(err,items){
    if(err){
      cb(err);
    }
    else{
      async.eachSeries(items,function(item,callback){
        var id = mongoq.mongodb.BSONPure.ObjectID.createFromHexString(item._id);
        item.quantity = item.quantity * -1;
        db.collection("products")
        .update({_id : id, "inventories._id":location}, {"$inc":{"inventories.$.quantity":item.quantity}}, {safe: true})
        .done(function(data){
          callback();
        })
        .fail( function( err ) {
          item.error_message = "DATABASE_INSERTION_ERROR";
          callback(item);
        });
      },function(err){
        if(err){
          cb(err);
        }
        else{
          cb(items);
        }
      });
    }
  });
};
var inStock = function(db,location,items,cb){
 var aItems = arrangeItems(items);
  async.eachSeries(aItems,function(item,callback){
    var id = mongoq.mongodb.BSONPure.ObjectID.createFromHexString(item._id);
    console.log(id,item.quantity);
    db.collection("products")
    .update({_id : id, "inventories._id":location}, {"$inc":{"inventories.$.quantity":item.quantity}}, {safe: true})
    .done(function(data){
      callback();
    })
    .fail( function( err ) {
      item.error_message = "INSTOCK DATABASE_INSERTION_ERROR";
      callback(item);
    });
  },function(err){
    if(err){
      cb(err);
    }
    else{
      cb(items);
    }
  });
};

var checkItemInventory = function(db,location,item,callback){
  var id = mongoq.mongodb.BSONPure.ObjectID.createFromHexString(item._id);
  db.collection("products")
  .find({
    _id : id,
    inventories : {"$elemMatch" : { _id: location, quantity : {"$gte": item.quantity}}}}).toArray()
      .done(function(data){
      if(data && data.length){
        callback(null,{
          item : item,
          location : location,
          status : "SUFFICIENT_STOCK"
        });
      }
      else{
        callback({
          item : item,
          location : location,
          status : "INSUFFICIENT_STOCK"
        });
      }
    });
};

var subtractItemInventory = function(db,location,item,callback){

};
var mergePackage = function(items){
  var mergePackages = [];
  for(var i in items){
    if(items[i].uom == "Package"){
      for(var i in items[i].package){
        items[i].package[j].quantity = items[i].package[j].quantity * items[i].quantity;
        mergePackages.push(items[i].package[j]);
      }
    }
    else{
      mergePackages.push(items[i]);
    }
  }
  return mergePackages;
};

var mergeItem = function(items){

  var mergeItems = {};
  for(var i in items){
    if(mergeItems[items[i]._id]){
      mergeItems[items[i]._id].quantity+=items[i].quantity;
    }
    else{
      mergeItems[items[i]._id] = items[i];
    }
  }
  var arrMergeItems = [];
  for(var i in mergeItems){
    arrMergeItems.push(mergeItems[i]);
  }
  return arrMergeItems;
  };

  var arrangeItems = function(items){
    return mergeItem(mergePackage(items));
  };

  var process_request = function(req,res,next){
  switch(req.params.object){
    case 'sales' :
        switch(req.body.status.status_code){
          case 'SALES_ORDER_CREATED' :
            if(req.body.sono){
              getPreviousRecord(req.db,{sono:req.body.sono},function(err,order){
                inStock(req.db,order.inventory_location,order.ordered_items,function(err,result){
                  outStock(req.db,req.body.inventory_location,req.body.ordered_items,function(err,result){
                    next();
                  });
                });
              });
            }
            else{
              outStock(req.db,req.body.inventory_location,req.body.ordered_items,function(err,result){
                next();
              });
            }

          break;
          case 'SALES_ORDER_REVISED' :
            getPreviousRecord(req.db,{sono:req.body.sono},function(err,order){
              inStock(req.db,order.inventory_location,order.ordered_items,function(err,result){
                outStock(req.db,req.body.inventory_location,req.body.ordered_items,function(err,result){
                  next();
                });
              });
            });
          break;
          case 'PACKING_CREATED' :
            next();
          break;
          case 'DELIVERY_RECEIPT_APPROVED' :
            next();
          break;
          case 'DELIVERY_RECEIPT_REJECTED' :
            next();
          break;
          case 'PROFORMA_INVOICE_CREATED' :
            if(req.body.pfno){
              async.auto({
                "gpr" : function(cb){

                  getPreviousRecord(req.db,{pfno:req.body.pfno},function(err,result){
                    return cb(err,result);
                  });
                },
                "is" : ['gpr',function(cb,result){
                  var order = result.gpr;

                  inStock(req.db,order.inventory_location,order.ordered_items,cb);
                }],
                "os" : ['is',function(cb,result){
                  cb();
                  //outStock(req.db,req.body.inventory_location,req.body.ordered_items,cb);
                }]

              },function(err,result){
                next();
              });
              // getPreviousRecord(req.db,{pfno:req.body.pfno},function(err,order){
              //   inStock(req.db,order.inventory_location,order.ordered_items,function(err,result){
              //       console.log("chito");
              //     outStock(req.db,req.body.inventory_location,req.body.ordered_items,function(err,result){
              //       next();
              //     });
              //   });
              // });
            }
            else{
              outStock(req.db,req.body.inventory_location,req.body.ordered_items,function(err,result){
                console.log("err :",err,"result :",result);
                next();
              });
            }
          break;
          case 'PROFORMA_INVOICE_APPROVED' :
            next();
          break;
          case 'PROFORMA_INVOICE_REVISED' :
            getPreviousRecord(req.db,req.body.sono,function(err,order){
              inStock(req.db,order.inventory_location,order.ordered_items,function(err,result){
                outStock(req.db,req.body.inventory_location,req.body.ordered_items,function(err,result){
                  next();
                });
              });
            });
          break;
          case 'PROFORMA_INVOICE_REJECTED' :

          break;
          case 'RETURN_APPROVED' :

          break;
          case 'RETURN_CANCEL' :

          break;
          case '' :

          break;
          case '' :

          break;
          default: console.log("no status matched");

        }
    break;
    case 'product' :
      next();
    break;
    case 'consignment' :

    break;
  }
  next();
};

module.exports = {
  process_request : process_request
}
<commit_msg>inventories of proforma<commit_after>var async = require("async");
var mongoq = require('mongoq');

var checkStock = function(db,location,items,done){
    var aItems = arrangeItems(items);
    async.eachSeries(aItems,
      function(item,callback){
        var id = mongoq.mongodb.BSONPure.ObjectID.createFromHexString(item._id);
        checkItemInventory(db,location,item,callback);
      },
      function(err){
        done(err,aItems);
      });
};

var getPreviousRecord = function(db,filter,callback){

  db.collection("sales")
  .find(filter).toArray()
      .done(function(data){
      if(data && data.length){
        callback(null,data[0]);
      }
      else{
        callback(null,null);
      }
    });
};
var outStock = function(db,location,items,cb){

  checkStock(db,location,items,function(err,items){
    if(err){
      cb(err);
    }
    else{
      async.eachSeries(items,function(item,callback){
        var id = mongoq.mongodb.BSONPure.ObjectID.createFromHexString(item._id);
        item.quantity = item.quantity * -1;
        db.collection("products")
        .update({_id : id, "inventories._id":location}, {"$inc":{"inventories.$.quantity":item.quantity}}, {safe: true})
        .done(function(data){
          callback();
        })
        .fail( function( err ) {
          item.error_message = "DATABASE_INSERTION_ERROR";
          callback(item);
        });
      },function(err){
        if(err){
          cb(err);
        }
        else{
          cb(items);
        }
      });
    }
  });
};
var inStock = function(db,location,items,cb){
 var aItems = arrangeItems(items);
  async.eachSeries(aItems,function(item,callback){
    var id = mongoq.mongodb.BSONPure.ObjectID.createFromHexString(item._id);
    console.log(id,item.quantity);
    db.collection("products")
    .update({_id : id, "inventories._id":location}, {"$inc":{"inventories.$.quantity":item.quantity}}, {safe: true})
    .done(function(data){
      callback();
    })
    .fail( function( err ) {
      item.error_message = "INSTOCK DATABASE_INSERTION_ERROR";
      callback(item);
    });
  },function(err){
    if(err){
      cb(err);
    }
    else{
      cb(items);
    }
  });
};

var checkItemInventory = function(db,location,item,callback){
  var id = mongoq.mongodb.BSONPure.ObjectID.createFromHexString(item._id);
  db.collection("products")
  .find({
    _id : id,
    inventories : {"$elemMatch" : { _id: location, quantity : {"$gte": item.quantity}}}}).toArray()
      .done(function(data){
      if(data && data.length){
        callback(null,{
          item : item,
          location : location,
          status : "SUFFICIENT_STOCK"
        });
      }
      else{
        callback({
          item : item,
          location : location,
          status : "INSUFFICIENT_STOCK"
        });
      }
    });
};

var subtractItemInventory = function(db,location,item,callback){

};
var mergePackage = function(items){
  var mergePackages = [];
  for(var i in items){
    if(items[i].uom == "Package"){
      for(var i in items[i].package){
        items[i].package[j].quantity = items[i].package[j].quantity * items[i].quantity;
        mergePackages.push(items[i].package[j]);
      }
    }
    else{
      mergePackages.push(items[i]);
    }
  }
  return mergePackages;
};

var mergeItem = function(items){

  var mergeItems = {};
  for(var i in items){
    if(mergeItems[items[i]._id]){
      mergeItems[items[i]._id].quantity+=items[i].quantity;
    }
    else{
      mergeItems[items[i]._id] = items[i];
    }
  }
  var arrMergeItems = [];
  for(var i in mergeItems){
    arrMergeItems.push(mergeItems[i]);
  }
  return arrMergeItems;
  };

  var arrangeItems = function(items){
    return mergeItem(mergePackage(items));
  };

  var process_request = function(req,res,next){
  switch(req.params.object){
    case 'sales' :
        switch(req.body.status.status_code){
          case 'SALES_ORDER_CREATED' :
            if(req.body.sono){
              getPreviousRecord(req.db,{sono:req.body.sono},function(err,order){
                inStock(req.db,order.inventory_location,order.ordered_items,function(err,result){
                  outStock(req.db,req.body.inventory_location,req.body.ordered_items,function(err,result){
                    next();
                  });
                });
              });
            }
            else{
              outStock(req.db,req.body.inventory_location,req.body.ordered_items,function(err,result){
                next();
              });
            }

          break;
          case 'SALES_ORDER_REVISED' :
            getPreviousRecord(req.db,{sono:req.body.sono},function(err,order){
              inStock(req.db,order.inventory_location,order.ordered_items,function(err,result){
                outStock(req.db,req.body.inventory_location,req.body.ordered_items,function(err,result){
                  next();
                });
              });
            });
          break;
          case 'PACKING_CREATED' :
            next();
          break;
          case 'DELIVERY_RECEIPT_APPROVED' :
            next();
          break;
          case 'DELIVERY_RECEIPT_REJECTED' :
            next();
          break;
          case 'PROFORMA_INVOICE_CREATED' :
            if(req.body.pfno){
              getPreviousRecord(req.db,{pfno:req.body.pfno},function(err,order){
                inStock(req.db,order.inventory_location,order.ordered_items,function(err,result){
                    console.log("chito");
                  outStock(req.db,req.body.inventory_location,req.body.ordered_items,function(err,result){
                    next();
                  });
                });
              });
            }
            else{
              outStock(req.db,req.body.inventory_location,req.body.ordered_items,function(err,result){
                console.log("err :",err,"result :",result);
                next();
              });
            }
          break;
          case 'PROFORMA_INVOICE_APPROVED' :
            next();
          break;
          case 'PROFORMA_INVOICE_REVISED' :
            getPreviousRecord(req.db,req.body.sono,function(err,order){
              inStock(req.db,order.inventory_location,order.ordered_items,function(err,result){
                outStock(req.db,req.body.inventory_location,req.body.ordered_items,function(err,result){
                  next();
                });
              });
            });
          break;
          case 'PROFORMA_INVOICE_REJECTED' :

          break;
          case 'RETURN_APPROVED' :

          break;
          case 'RETURN_CANCEL' :

          break;
          case '' :

          break;
          case '' :

          break;
          default: console.log("no status matched");

        }
    break;
    case 'product' :
      next();
    break;
    case 'consignment' :

    break;
  }
  next();
};

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


<commit_before>/**
 * @author Gilles Coomans <gilles.coomans@gmail.com>
 * elenpi v2

	//________ new api

	done(function(env, obj, string){
		//...
		return string || false;
	})
	terminal(regExp, name || function(env, obj, string, captured){
		//...
		return string || false;
	})
	char(test)
	optional(rule)
	end()

	one(rule || {
		rule:rule,
		?as:Class,
		?set:'name' || function(env, parent, obj){ ... }
	})
	zeroOrOne(rule || {
		rule:rule,
		?as:Class,
		?set:'name' || function(env, parent, obj){ ... }
	})
	oneOf([rules] || {
		rules:[rules],
		?as:Class,
		?set:'name' || function(env, parent, obj){ ... }
	})
	xOrMore({
		rule:rule,
		minimum:int,
		?as:Class,
		?pushTo:'name' || function(env, parent, obj){ ... },
		?separator:rule,
		?maximum:int
	})

	V3 will be a Babelute with same api

 *
 *
 */
(function() {
	var defaultSpaceRegExp = /^[\s\n\r]+/;

	function exec(string, rule, descriptor, parser, env) {
		if (typeof rule === 'string')
			rule = parser.rules[rule];
		var rules = rule._queue;
		for (var i = 0, len = rules.length; i < len; ++i) {
			var current = rules[i];
			if (current.__elenpi__)
				string = exec(string, current, descriptor, parser, env);
			else // is function
				string = current.call(parser, env, descriptor, string);
			if (string === false)
				return false;
			env.soFar = string.length;
		}
		return string;
	};

	function getRule(parser, name) {
		var r = parser.rules[name];
		if (!r)
			throw new Error('elenpi : rules not found : ' + rule);
		return r;
	}

	function Rule() {
		this._queue = [];
		this.__elenpi__ = true;
	};

	Rule.prototype = {
		// base for all rule's handlers
		done: function(callback) {
			this._queue.push(callback);
			return this;
		},
		// for debug purpose
		log: function(title) {
			title = title || '';
			return this.done(function(env, descriptor, string) {
				console.log("elenpi log : ", title, string, descriptor);
				return string;
			});
		},
		use: function(rule) {
			var args = [].slice.call(arguments, 1);
			return this.done(function(env, descriptor, string) {
				if (typeof rule === 'string')
					rule = getRule(this, rule);
				if (rule.__elenpi__)
					return exec(string, rule, descriptor, this, env);
				var r = new Rule();
				rule.apply(r, args);
				return exec(string, r, descriptor, this, env);
			});
		},
		optional: function(rule) {
			return this.done(function(env, descriptor, string) {
				if (typeof rule === 'string')
					rule = getRule(this, rule);
				var newString = exec(string, rule, descriptor, this, env);
				return newString === false ? string : newString;
			});
		},
		terminal: function(reg, set) {
			return this.done(function(env, descriptor, string) {
				if (!string)
					return false;
				var cap = reg.exec(string);
				if (cap) {
					if (set) {
						if (typeof set === 'string')
							descriptor[set] = cap[0];
						else
							return set.call(this, env, descriptor, string.substring(cap[0].length), cap);
					}
					return string.substring(cap[0].length);
				}
				return false;
			});
		},
		char: function(test) {
			return this.done(function(env, descriptor, string) {
				if (!string)
					return false;
				if (string[0] === test)
					return string.substring(1);
				return false;
			});
		},
		xOrMore: function(rule) {
			var options = (typeof rule === 'string' ||  rule.__elenpi__) ? { rule: rule } : rule;
			options.minimum = options.minimum || 0;
			return this.done(function(env, descriptor, string) {
				if (typeof options.rule === 'string')
					options.rule = getRule(this, options.rule);
				var newString = true,
					count = 0;
				while (newString && string) {
					if (options.maximum && options.maximum === count)
						break;
					var newDescriptor = options.as ? options.as(env, descriptor) : (options.pushTo ? {} : descriptor);
					newString = exec(string, options.rule, newDescriptor, this, env);
					if (newString !== false) {
						count++;
						string = newString;
						if (!newDescriptor.skip && options.pushTo)
							if (typeof options.pushTo === 'string') {
								descriptor[options.pushTo] = descriptor[options.pushTo] || [];
								descriptor[options.pushTo].push(newDescriptor);
							} else
								options.pushTo(env, descriptor, newDescriptor);
						if (options.separator && string) {
							newString = exec(string, options.separator, newDescriptor, this, env);
							if (newString !== false)
								string = newString;
						}
					}
				}
				if (count < options.minimum)
					return false;
				return string;
			});
		},
		zeroOrMore: function(rule) {
			return this.xOrMore(rule);
		},
		oneOrMore: function(rule) {
			if (typeof rule === 'string' || rule.__elenpi__)
				rule = {
					rule: rule,
					minimum: 1
				}
			return this.xOrMore(rule);
		},
		zeroOrOne: function(rule) {
			var options = (typeof rule === 'string' ||  rule.__elenpi__) ? { rule: rule } : rule;
			return this.done(function(env, descriptor, string) {
				if (typeof options.rule === 'string')
					options.rule = getRule(this, options.rule);
				var newDescriptor = options.as ? options.as(env, descriptor) : (options.set ? {} : descriptor),
					res = exec(string, options.rule, newDescriptor, this, env);
				if (res !== false) {
					if (!newDescriptor.skip && options.set) {
						if (typeof options.set === 'string')
							descriptor[options.set] = newDescriptor;
						else
							options.set(env, descriptor, newDescriptor);
					}
					string = res;
				}
				return string;
			});
		},
		oneOf: function(rules) {
			var options = (typeof rules === 'string' || rules.__elenpi__) ? { rules: [].slice.call(arguments) } : rules;
			return this.done(function(env, descriptor, string) {
				if (!string)
					return false;
				var count = 0;
				while (count < options.rules.length) {
					var rule = options.rules[count];
					if (typeof rule === 'string')
						rule = getRule(this, rule);
					var newDescriptor = options.as ? options.as(env, descriptor) : (options.set ? {} : descriptor),
						newString = exec(string, rule, newDescriptor, this, env);
					if (newString !== false) {
						if (!newDescriptor.skip && options.set) {
							if (typeof options.set === 'string')
								descriptor[options.set] = newDescriptor;
							else
								options.set(env, descriptor, newDescriptor);
						}
						return newString;
					}
					count++;
				}
				return false;
			});
		},
		one: function(rule) {
			var options = (typeof rule === 'string' ||  (rule && rule.__elenpi__)) ? { rule: rule } : rule;
			return this.done(function(env, descriptor, string) {
				if (typeof options.rule === 'string')
					options.rule = getRule(this, options.rule);
				var newDescriptor = options.as ? options.as(env, descriptor) : (options.set ? {} : descriptor),
					newString = exec(string, options.rule, newDescriptor, this, env);
				if (newString !== false && !newDescriptor.skip && options.set) {
					if (typeof options.set === 'string')
						descriptor[options.set] = newDescriptor;
					else
						options.set(env, descriptor, newDescriptor);
				}
				return newString;
			});
		},
		skip: function() {
			return this.done(function(env, descriptor, string) {
				descriptor.skip = true;
				return string;
			});
		},
		space: function(needed) {
			return this.done(function(env, descriptor, string) {
				if (!string)
					if (needed)
						return false;
					else
						return string;
				var cap = (this.rules.space || defaultSpaceRegExp).exec(string);
				if (cap)
					return string.substring(cap[0].length);
				else if (needed)
					return false;
				return string;
			});
		},
		end: function(needed) {
			return this.done(function(env, descriptor, string) {
				if (!string || !needed)
					return string;
				return false;
			});
		}
	};

	var Parser = function(rules, defaultRule) {
		this.rules = rules;
		this.defaultRule = defaultRule;
	};
	Parser.prototype = {
		exec: function(string, descriptor, rule, env) {
			env = env || {};
			if (!rule)
				rule = this.rules[this.defaultRule];
			return exec(string, rule, descriptor, this, env);
		},
		parse: function(string, rule, descriptor, env) {
			env = env || {};
			descriptor = descriptor || {};
			var ok = this.exec(string, descriptor, rule, env);
			if (ok === false || (ok && ok.length > 0)) {
				var pos = string.length - env.soFar;
				// todo : catch line number
				console.error('elenpi parsing failed : (pos:' + pos + ') near :\n', string.substring(Math.max(pos - 1, 0), pos + 50));
				return false;
			}
			return descriptor;
		}
	};

	var elenpi = {
		r: function() {
			return new Rule();
		},
		Rule: Rule,
		Parser: Parser
	};

	if (typeof module !== 'undefined' && module.exports)
		module.exports = elenpi; // use common js if avaiable
	else this.elenpi = elenpi; // assign to global window
})();
//___________________________________________________
<commit_msg>rewrite v2 for performance<commit_after>/**
 * @author Gilles Coomans <gilles.coomans@gmail.com>
 * elenpi v2

	//________ new api

	done(function(env, obj, string){
		//...
		return string || false;
	})
	terminal(regExp, name || function(env, obj, string, captured){
		//...
		return string || false;
	})
	char(test)
	optional(rule)
	end()

	one(rule || {
		rule:rule,
		?as:Class,
		?set:'name' || function(env, parent, obj){ ... }
	})
	zeroOrOne(rule || {
		rule:rule,
		?as:Class,
		?set:'name' || function(env, parent, obj){ ... }
	})
	oneOf([rules] || {
		rules:[rules],
		?as:Class,
		?set:'name' || function(env, parent, obj){ ... }
	})
	xOrMore({
		rule:rule,
		minimum:int,
		?as:Class,
		?pushTo:'name' || function(env, parent, obj){ ... },
		?separator:rule,
		?maximum:int
	})

	V3 will be a Babelute with same api

 *
 *
 */

(function() {
	var defaultSpaceRegExp = /^[\s\n\r]+/;

	function exec(rule, descriptor, env) {
		if (env.stop || env.error)
			return;
		if (typeof rule === 'string')
			rule = getRule(env.parser, rule);
		// Parser.counts.countExec++;
		var rules = rule._queue,
			current;
		for (var i = 0, len = rules.length; i < len; ++i) {
			current = rules[i];
			if (current.__elenpi__)
				exec(current, descriptor, env);
			else // is function
				current(env, descriptor);
			if (env.error)
				return;
			if (env.soFar > env.string.length)
				env.soFar = env.string.length;
			if (env.stop)
				return;
		}
	};

	function getRule(parser, name) {
		var r = parser.rules[name];
		if (!r)
			throw new Error('elenpi : rules not found : ' + rule);
		return r;
	}

	function Rule() {
		this._queue = [];
		this.__elenpi__ = true;
	};

	Rule.prototype = {
		// base for all rule's handlers
		done: function(callback) {
			this._queue.push(callback);
			return this;
		},
		// for debug purpose
		log: function(title) {
			title = title || '';
			return this.done(function(env, descriptor) {
				console.log("elenpi log : ", title, env, descriptor);
			});
		},
		use: function(rule) {
			var args = [].slice.call(arguments, 1);
			return this.done(function(env, descriptor) {
				if (typeof rule === 'string')
					rule = getRule(env.parser, rule);
				if (rule.__elenpi__) {
					exec(rule, descriptor, env);
					return;
				}
				var r = new Rule();
				rule.apply(r, args);
				exec(r, descriptor, env);
			});
		},
		optional: function(rule) {
			return this.done(function(env, descriptor) {
				var string = env.string;
				exec(rule, descriptor, env);
				if (env.error) {
					env.string = string;
					env.error = false;
				}
			});
		},
		terminal: function(reg, set) {
			return this.done(function(env, descriptor) {
				// console.log('terminal test : ', reg);
				if (!env.string.length) {
					env.error = true;
					return;
				}
				// Parser.counts.countTerminalTest++;
				var cap = reg.exec(env.string);
				// console.log('terminal : ', reg, cap);
				if (cap) {
					// Parser.counts.countTerminalMatched++;
					env.string = env.string.substring(cap[0].length);
					// console.log('terminal cap 0 length : ', cap[0].length);
					// console.log('terminal string length : ', string.length, cap[0]);
					if (set) {
						if (typeof set === 'string')
							descriptor[set] = cap[0];
						else
							set(env, descriptor, cap);
					}
					return;
				}
				env.error = true;
			});
		},
		char: function(test) {
			return this.done(function(env, descriptor) {
				if (!env.string.length || env.string[0] !== test)
					env.error = true;
				else
					env.string = env.string.substring(1);
			});
		},
		xOrMore: function(rule) {
			var opt = (typeof rule === 'string' ||  rule.__elenpi__) ? { rule: rule } : rule;
			opt.minimum = opt.minimum || 0;
			opt.maximum = opt.maximum || Infinity;
			return this.done(function(env, descriptor) {
				var options = opt;
				if (!env.string.length && options.minimum > 0) {
					env.error = true;
					return;
				}
				var string = env.string,
					count = 0,
					rule = options.rule,
					pushTo = options.pushTo,
					pushToString = typeof pushTo === 'string',
					As = options.as,
					separator = options.separator,
					newDescriptor;
				// Parser.counts.countXorMore++;
				while (!env.error && env.string.length && count < options.maximum) {

					// Parser.counts.countXorMores++;

					newDescriptor = As ? As(env, descriptor) : (pushTo ? {} : descriptor);
					exec(rule, newDescriptor, env);

					if (env.error)
						break;

					count++;

					if (!newDescriptor.skip && pushTo)
						if (pushToString) {
							descriptor[pushTo] = descriptor[pushTo] || [];
							descriptor[pushTo].push(newDescriptor);
						} else
							pushTo(env, descriptor, newDescriptor);

					if (separator && env.string.length)
						exec(separator, newDescriptor, env);
				}
				env.error = (count < options.minimum);
				if (!count)
					env.string = string;
			});
		},
		zeroOrMore: function(rule) {
			return this.xOrMore(rule);
		},
		oneOrMore: function(rule) {
			if (typeof rule === 'string' || rule.__elenpi__)
				rule = {
					rule: rule,
					minimum: 1
				}
			return this.xOrMore(rule);
		},
		zeroOrOne: function(rule) {
			var options = (typeof rule === 'string' ||  rule.__elenpi__) ? { rule: rule } : rule;
			return this.done(function(env, descriptor) {
				if (!env.string.length)
					return;
				// Parser.counts.countZeroOrOne++;
				var newDescriptor = options.as ? options.as(env, descriptor) : (options.set ? {} : descriptor);
				var string = env.string;
				exec(options.rule, newDescriptor, env);
				if (!env.error) {
					if (!newDescriptor.skip && options.set) {
						if (typeof options.set === 'string')
							descriptor[options.set] = newDescriptor;
						else
							options.set(env, descriptor, newDescriptor);
					}
					return;
				}
				env.string = string;
				env.error = false;
			});
		},
		oneOf: function(rules) {
			var opt = (typeof rules === 'string' || rules.__elenpi__) ? { rules: [].slice.call(arguments) } : rules;
			return this.done(function(env, descriptor) {
				if (!env.string.length) {
					env.error = true;
					return;
				}

				var options = opt,
					count = 0,
					len = options.rules.length,
					rule,
					newDescriptor,
					string = env.string;
				// Parser.counts.countOneOf++;
				while (count < len) {
					rule = options.rules[count];
					count++;
					// Parser.counts.countOneOfs++;
					newDescriptor = options.as ? options.as(env, descriptor) : (options.set ? {} : descriptor);
					exec(rule, newDescriptor, env);
					if (!env.error) {
						if (!newDescriptor.skip && options.set) {
							if (typeof options.set === 'string')
								descriptor[options.set] = newDescriptor;
							else
								options.set(env, descriptor, newDescriptor);
						}
						return;
					}
					env.error = false;
					env.string = string;
				}
				env.error = true;
			});
		},
		one: function(rule) {
			var opt = (typeof rule === 'string' ||  (rule && rule.__elenpi__)) ? { rule: rule } : rule;
			return this.done(function(env, descriptor) {
				if (!env.string.length) {
					env.error = true;
					return;
				}
				// Parser.counts.countOne++;
				var options = opt,
					newDescriptor = options.as ? options.as(env, descriptor) : (options.set ? {} : descriptor);
				exec(options.rule, newDescriptor, env);
				if (!env.error && !newDescriptor.skip && options.set) {
					if (typeof options.set === 'string')
						descriptor[options.set] = newDescriptor;
					else
						options.set(env, descriptor, newDescriptor);
				}
			});
		},
		skip: function() {
			return this.done(function(env, descriptor) {
				descriptor.skip = true;
			});
		},
		space: function(needed) {
			return this.done(function(env, descriptor) {
				if (!env.string.length) {
					if (needed)
						env.error = true;
					return;
				}
				var cap = (env.parser.rules.space || defaultSpaceRegExp).exec(env.string);
				if (cap)
					env.string = env.string.substring(cap[0].length);
				else if (needed)
					env.error = true;
			});
		},
		end: function(needed) {
			return this.done(function(env, descriptor) {
				if (!env.string.length)
					env.stop = true;
				else if (needed)
					env.error = true;
			});
		}
	};

	var Parser = function(rules, defaultRule) {
		this.rules = rules;
		this.defaultRule = defaultRule;
	};
	Parser.prototype = {
		exec: function(rule, descriptor, env) {
			exec(rule, descriptor, env);
		},
		parse: function(string, rule, descriptor, env) {
			env = env || {};
			descriptor = descriptor || {};
			env.parser = this;
			env.soFar = Infinity;
			env.string = string;
			if (!rule)
				rule = this.rules[this.defaultRule];
			exec(rule, descriptor, env);
			if (env.error || env.string.length) {
				var pos = string.length - env.soFar;
				// todo : catch line number
				console.error('elenpi parsing failed : (pos:' + pos + ') near :\n', string.substring(Math.max(pos - 1, 0), pos + 50));
				return false;
			}
			return descriptor;
		}
	};

	// 	Parser.counts = {
	// 	countTerminalTest: 0,
	// 	countTerminalMatched: 0,
	// 	countOneOf: 0,
	// 	countOneOfs: 0,
	// 	countExec: 0,
	// 	countXorMore: 0,
	// 	countXorMores: 0,
	// 	countZeroOrOne: 0,
	// 	countOne: 0
	// };

	var elenpi = {
		r: function() {
			return new Rule();
		},
		Rule: Rule,
		Parser: Parser
	};

	if (typeof module !== 'undefined' && module.exports)
		module.exports = elenpi; // use common js if avaiable
	else this.elenpi = elenpi; // assign to global window
})();
//___________________________________________________
<|endoftext|>