<commit_before>var util = require('util')
  , fs = require('fs')
  , spawn = require('./spawn')
  , proxy = require('./proxy')
  , errors = require('./errors')
  , cwd = process.cwd();

/**
 * Make some curl opts friendlier.
 */

var curl_map = {
    timeout: 'max-time'
  , redirects: 'max-redirs'
  , method: 'request'
  , useragent: 'user-agent'
};

/**
 * Default user-agents.
 */

var ua_file = __dirname + '/useragents.txt'
  , user_agents = fs.readFileSync(ua_file).toString().split('\n').slice(0, -1)
  , user_agents_len = user_agents.length;

/**
 * Default request headers.
 */

var default_headers = {
    'Accept': '*/*'
  , 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3'
  , 'Accept-Language': 'en-US,en;q=0.8'
};

/**
 * Make a request with cURL.
 *
 * @param {Object|String} options (optional) - see `man curl`
 * @param {Function} callback (optional)
 * @api public
 */

exports.request = function (options, callback) {
    if (arguments.length === 1) {
        var defaults = options;
        return function (options, callback) {
            if (typeof options === 'function') {
                callback = options;
                options = {};
            } else if (typeof options === 'string') {
                options = { url: options };
            }
            for (var key in defaults) {
                if (typeof options[key] === 'undefined') {
                    options[key] = defaults[key];
                }
            }
            exports.request.call(this, options, callback);
        };
    }

    if (options.retries) {
        var remaining = options.retries;
        delete options.retries;
        return (function curl() {
            exports.request(options, function (err) {
                if (!err || !--remaining) {
                    return callback.apply(this, arguments);
                }
                process.nextTick(curl);
            });
        })();
    }

    if (typeof options === 'string') {
        options = { url: options };
    } else {
        options = exports.copy(options);
    }

    for (var key in curl_map) {
        if (typeof options[key] !== 'undefined') {
            options[curl_map[key]] = options[key];
            delete options[key];
        }
    }

    if (options.proxies) {
        if (!proxy.transform) {
            proxy.transform = proxy.unpack(options.key).transform;
        }
        options = proxy.transform(options);
        delete options.key;
    }

    var curl
      , curl_path = 'curl'
      , args = ['--silent', '--show-error', '--no-buffer']
      , start = new Date
      , err
      , stdoutlen
      , stdout = new Buffer(stdoutlen = 0)
      , encoding
      , complete
      , cleanup
      , postprocess
      , require_str
      , require_not_str
      , scope = {}
      , cmd = 'curl'
      , timeout;

    function finish() {
        if (err in errors) {
            err = errors[err];
        }
        callback.call(scope, err, stdout, {
            cmd: cmd
          , args: args
          , time: (new Date().getTime() - start.getTime())
        });
        complete = true;
    }

    //Allow for a custom curl path
    if (options.curl_path) {
        curl_path = options.curl_path;
        delete options.curl_path;
    }

    //Follow location by default
    if ('max-redirs' in options) {
        options.location = !!options['max-redirs'];
    } else {
        options.location = true;
        options['max-redirs'] = 3;
    }

    //Add an additional setTimeout for max-time
    if (options['max-time']) {
        timeout = setTimeout(function () {
            if (complete) return;
            stderr = 'timeout', stdout = null;
            finish();
            if (curl && curl.kill) curl.kill('SIGKILL');
        }, 1000 * options['max-time']);
    }

    //Default encoding is utf8. Set encoding = null to get a buffer
    if (!options.encoding && options.encoding !== null) {
        options.encoding = 'utf8';
    }
    if (options.encoding) {
        encoding = options.encoding;
        if (encoding === 'ascii') {
            options['use-ascii'] = true;
        }
        delete options.encoding;
    }

    //Parse POST data
    if (options.data && typeof options.data === 'object') {
        var data = [];
        for (var key in options.data) {
            data.push(encodeURIComponent(key) + '=' + encodeURIComponent(options.data[key]));
        }
        options.data = data.join('&');
    }

    //Check for the occurrence of a string and fail if not found
    if (options.require) {
        require_str = options.require;
        if (!Array.isArray(require_str)) {
            require_str = [require_str];
        }
        delete options.require;
    }

    //Check for the occurrence of a string and fail if found
    if (options.require_not) {
        require_not_str = options.require_not;
        if (!Array.isArray(require_not_str)) {
            require_not_str = [require_not_str];
        }
        delete options.require_not;
    }

    //Call the callback in a custom scope
    if (options.scope) {
        scope = options.scope;
        delete options.scope;
    }

    //Apply a post-processing function?
    if (options.process) {
        postprocess = options.process;
        delete options.process;
    }

    //Setup default headers
    var key, headers = {};
    for (key in default_headers) {
        headers[key] = default_headers[key];
    }
    if (options.headers) {
        for (key in options.headers) {
            key = key.replace(/[_-]/g, ' ').split(' ').map(function (str) {
                if (str.length) {
                    str = str[0].toUpperCase() + str.substr(1);
                }
                return str;
            }).join('-');
            headers[key] = options.headers[key];
        }
        delete options.headers;
    }
    options.header = options.header || [];
    for (key in headers) {
        options.header.push(key + ': ' + headers[key]);
    }

    //Select a random user agent if one wasn't provided
    if (!headers['User-Agent'] && !options['user-agent']) {
        options['user-agent'] = user_agents[Math.random() * user_agents_len | 0];
    }

    //Prepare curl args
    var key, values;
    for (key in options) {
        values = Array.isArray(options[key]) ? options[key] : [options[key]];
        values.forEach(function (value) {
            args.push('--' + key);
            if (true !== value) {
                args.push(value);
            }
        });
    }

    if (options.file) {
        cmd = 'cat';
        args = [options.file];
    }

    //Simulate the spawn?
    if (options.pretend) {
        return finish();
    }

    //Spawn the process
    var child = spawn(cmd, args, { cwd: options.cwd || cwd }, function (curl) {

        //Collect stdout
        curl.stdout.on('data', function (data) {
            if (complete) return;
            var len = data.length, prev = stdout;
            stdout = new Buffer(len + stdoutlen);
            prev.copy(stdout, 0, 0, stdoutlen);
            data.copy(stdout, stdoutlen, 0, len);
            stdoutlen += len;
        });

        //Pipe stderr to the current process?
        if (options.stderr) {
            if (options.stderr === true) {
                curl.stderr.pipe(process.stderr);
                delete options.stderr
            }
        }

        //Handle curl exit
        curl.on('exit', function (code) {
            try {
                err = code;
                if (complete) return;
                if (encoding) {
                    stdout = stdout.toString(encoding);
                }
                if (postprocess && stdout) {
                    stdout = postprocess(stdout);
                }
                if (require_str) {
                    var valid = false;
                    if (!encoding) {
                        stdout = stdout.toString();
                    }
                    for (var i = 0, l = require_str.length; i < l; i++) {
                        if ((util.isRegExp(require_str[i]) && require_str[i].test(stdout))
                                || stdout.indexOf(require_str[i]) !== -1) {
                            valid = true;
                            break;
                        }
                    }
                    if (!valid) {
                        err = 'response does not contain required string(s)';
                        stdout = null
                    } else if (!encoding) {
                        stdout = new Buffer(stdout);
                    }
                }
                if (require_not_str) {
                    var valid = true;
                    if (!encoding) {
                        stdout = stdout.toString();
                    }
                    for (var i = 0, l = require_not_str.length; i < l; i++) {
                        if ((util.isRegExp(require_not_str[i]) && require_not_str[i].test(stdout))
                                || stdout.indexOf(require_not_str[i]) !== -1) {
                            valid = false;
                            break;
                        }
                    }
                    if (!valid) {
                        err = 'response contains bad string(s)';
                        stdout = null
                    } else if (!encoding) {
                        stdout = new Buffer(stdout);
                    }
                }
            } catch (e) {
                err = typeof e === 'object' ? e.message || '' : e;
            }
            finish();
            if (timeout) clearTimeout(timeout);
        });
    });
};

/**
 * Expose a helper for scraping urls from a page.
 */

var urls = /(?:href|src|HREF|SRC)=["']?([^"' >]+)/g;

exports.urls = function (data, regex) {
    var match, matches = [];
    while (match = urls.exec(data)) {
        if (regex && !regex.test(match[1])) {
            continue;
        }
        matches.push(match[1].replace(/[\r\n\t\s]/g, ''));
    }
    return matches;
};

/**
 * A helper for handling async concurrency.
 */

exports.concurrent = function (input, concurrency, fn) {
    if (arguments.length === 3) {
        var len = input.length, pos = 0, remaining = concurrency;
        for (var i = 0; i < concurrency; i++) {
            (function exec() {
                if (pos >= len) {
                    if (!--remaining) {
                        fn(null, function () {});
                    }
                } else {
                    fn(input[pos++], function () {
                        process.nextTick(exec);
                    });
                }
            })();
        }
    } else {
        fn = concurrency;
        concurrency = input;
        for (var i = 0; i < concurrency; i++) {
            (function exec() {
                fn(function () {
                    process.nextTick(exec);
                });
            })();
        }
    }
};

/**
 * A helper for copying an object.
 */

exports.copy = function (obj) {
    var copy = {};
    for (var i in obj) {
        if (typeof obj[i] === 'object') {
            copy[i] = exports.copy(obj[i]);
        } else {
            copy[i] = obj[i];
        }
    }
    return copy;
};

<commit_msg>Don't include --pretend when dumping the curl command<commit_after>var util = require('util')
  , fs = require('fs')
  , spawn = require('./spawn')
  , proxy = require('./proxy')
  , errors = require('./errors')
  , cwd = process.cwd();

/**
 * Make some curl opts friendlier.
 */

var curl_map = {
    timeout: 'max-time'
  , redirects: 'max-redirs'
  , method: 'request'
  , useragent: 'user-agent'
};

/**
 * Default user-agents.
 */

var ua_file = __dirname + '/useragents.txt'
  , user_agents = fs.readFileSync(ua_file).toString().split('\n').slice(0, -1)
  , user_agents_len = user_agents.length;

/**
 * Default request headers.
 */

var default_headers = {
    'Accept': '*/*'
  , 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3'
  , 'Accept-Language': 'en-US,en;q=0.8'
};

/**
 * Make a request with cURL.
 *
 * @param {Object|String} options (optional) - see `man curl`
 * @param {Function} callback (optional)
 * @api public
 */

exports.request = function (options, callback) {
    if (arguments.length === 1) {
        var defaults = options;
        return function (options, callback) {
            if (typeof options === 'function') {
                callback = options;
                options = {};
            } else if (typeof options === 'string') {
                options = { url: options };
            }
            for (var key in defaults) {
                if (typeof options[key] === 'undefined') {
                    options[key] = defaults[key];
                }
            }
            exports.request.call(this, options, callback);
        };
    }

    if (options.retries) {
        var remaining = options.retries;
        delete options.retries;
        return (function curl() {
            exports.request(options, function (err) {
                if (!err || !--remaining) {
                    return callback.apply(this, arguments);
                }
                process.nextTick(curl);
            });
        })();
    }

    if (typeof options === 'string') {
        options = { url: options };
    } else {
        options = exports.copy(options);
    }

    for (var key in curl_map) {
        if (typeof options[key] !== 'undefined') {
            options[curl_map[key]] = options[key];
            delete options[key];
        }
    }

    if (options.proxies) {
        if (!proxy.transform) {
            proxy.transform = proxy.unpack(options.key).transform;
        }
        options = proxy.transform(options);
        delete options.key;
    }

    var curl
      , curl_path = 'curl'
      , args = ['--silent', '--show-error', '--no-buffer']
      , start = new Date
      , err
      , stdoutlen
      , stdout = new Buffer(stdoutlen = 0)
      , encoding
      , complete
      , cleanup
      , postprocess
      , require_str
      , require_not_str
      , scope = {}
      , cmd = 'curl'
      , timeout;

    function finish() {
        if (err in errors) {
            err = errors[err];
        }
        callback.call(scope, err, stdout, {
            cmd: cmd
          , args: args
          , time: (new Date().getTime() - start.getTime())
        });
        complete = true;
    }

    //Allow for a custom curl path
    if (options.curl_path) {
        curl_path = options.curl_path;
        delete options.curl_path;
    }

    //Follow location by default
    if ('max-redirs' in options) {
        options.location = !!options['max-redirs'];
    } else {
        options.location = true;
        options['max-redirs'] = 3;
    }

    //Add an additional setTimeout for max-time
    if (options['max-time']) {
        timeout = setTimeout(function () {
            if (complete) return;
            stderr = 'timeout', stdout = null;
            finish();
            if (curl && curl.kill) curl.kill('SIGKILL');
        }, 1000 * options['max-time']);
    }

    //Default encoding is utf8. Set encoding = null to get a buffer
    if (!options.encoding && options.encoding !== null) {
        options.encoding = 'utf8';
    }
    if (options.encoding) {
        encoding = options.encoding;
        if (encoding === 'ascii') {
            options['use-ascii'] = true;
        }
        delete options.encoding;
    }

    //Parse POST data
    if (options.data && typeof options.data === 'object') {
        var data = [];
        for (var key in options.data) {
            data.push(encodeURIComponent(key) + '=' + encodeURIComponent(options.data[key]));
        }
        options.data = data.join('&');
    }

    //Check for the occurrence of a string and fail if not found
    if (options.require) {
        require_str = options.require;
        if (!Array.isArray(require_str)) {
            require_str = [require_str];
        }
        delete options.require;
    }

    //Check for the occurrence of a string and fail if found
    if (options.require_not) {
        require_not_str = options.require_not;
        if (!Array.isArray(require_not_str)) {
            require_not_str = [require_not_str];
        }
        delete options.require_not;
    }

    //Call the callback in a custom scope
    if (options.scope) {
        scope = options.scope;
        delete options.scope;
    }

    //Apply a post-processing function?
    if (options.process) {
        postprocess = options.process;
        delete options.process;
    }

    //Setup default headers
    var key, headers = {};
    for (key in default_headers) {
        headers[key] = default_headers[key];
    }
    if (options.headers) {
        for (key in options.headers) {
            key = key.replace(/[_-]/g, ' ').split(' ').map(function (str) {
                if (str.length) {
                    str = str[0].toUpperCase() + str.substr(1);
                }
                return str;
            }).join('-');
            headers[key] = options.headers[key];
        }
        delete options.headers;
    }
    options.header = options.header || [];
    for (key in headers) {
        options.header.push(key + ': ' + headers[key]);
    }

    //Select a random user agent if one wasn't provided
    if (!headers['User-Agent'] && !options['user-agent']) {
        options['user-agent'] = user_agents[Math.random() * user_agents_len | 0];
    }

    //Prepare curl args
    var key, values;
    for (key in options) {
        if (key === 'pretend') {
            continue;
        }
        values = Array.isArray(options[key]) ? options[key] : [options[key]];
        values.forEach(function (value) {
            args.push('--' + key);
            if (true !== value) {
                args.push(value);
            }
        });
    }

    if (options.file) {
        cmd = 'cat';
        args = [options.file];
    }

    //Simulate the spawn?
    if (options.pretend) {
        return finish();
    }

    //Spawn the process
    var child = spawn(cmd, args, { cwd: options.cwd || cwd }, function (curl) {

        //Collect stdout
        curl.stdout.on('data', function (data) {
            if (complete) return;
            var len = data.length, prev = stdout;
            stdout = new Buffer(len + stdoutlen);
            prev.copy(stdout, 0, 0, stdoutlen);
            data.copy(stdout, stdoutlen, 0, len);
            stdoutlen += len;
        });

        //Pipe stderr to the current process?
        if (options.stderr) {
            if (options.stderr === true) {
                curl.stderr.pipe(process.stderr);
                delete options.stderr
            }
        }

        //Handle curl exit
        curl.on('exit', function (code) {
            try {
                err = code;
                if (complete) return;
                if (encoding) {
                    stdout = stdout.toString(encoding);
                }
                if (postprocess && stdout) {
                    stdout = postprocess(stdout);
                }
                if (require_str) {
                    var valid = false;
                    if (!encoding) {
                        stdout = stdout.toString();
                    }
                    for (var i = 0, l = require_str.length; i < l; i++) {
                        if ((util.isRegExp(require_str[i]) && require_str[i].test(stdout))
                                || stdout.indexOf(require_str[i]) !== -1) {
                            valid = true;
                            break;
                        }
                    }
                    if (!valid) {
                        err = 'response does not contain required string(s)';
                        stdout = null
                    } else if (!encoding) {
                        stdout = new Buffer(stdout);
                    }
                }
                if (require_not_str) {
                    var valid = true;
                    if (!encoding) {
                        stdout = stdout.toString();
                    }
                    for (var i = 0, l = require_not_str.length; i < l; i++) {
                        if ((util.isRegExp(require_not_str[i]) && require_not_str[i].test(stdout))
                                || stdout.indexOf(require_not_str[i]) !== -1) {
                            valid = false;
                            break;
                        }
                    }
                    if (!valid) {
                        err = 'response contains bad string(s)';
                        stdout = null
                    } else if (!encoding) {
                        stdout = new Buffer(stdout);
                    }
                }
            } catch (e) {
                err = typeof e === 'object' ? e.message || '' : e;
            }
            finish();
            if (timeout) clearTimeout(timeout);
        });
    });
};

/**
 * Expose a helper for scraping urls from a page.
 */

var urls = /(?:href|src|HREF|SRC)=["']?([^"' >]+)/g;

exports.urls = function (data, regex) {
    var match, matches = [];
    while (match = urls.exec(data)) {
        if (regex && !regex.test(match[1])) {
            continue;
        }
        matches.push(match[1].replace(/[\r\n\t\s]/g, ''));
    }
    return matches;
};

/**
 * A helper for handling async concurrency.
 */

exports.concurrent = function (input, concurrency, fn) {
    if (arguments.length === 3) {
        var len = input.length, pos = 0, remaining = concurrency;
        for (var i = 0; i < concurrency; i++) {
            (function exec() {
                if (pos >= len) {
                    if (!--remaining) {
                        fn(null, function () {});
                    }
                } else {
                    fn(input[pos++], function () {
                        process.nextTick(exec);
                    });
                }
            })();
        }
    } else {
        fn = concurrency;
        concurrency = input;
        for (var i = 0; i < concurrency; i++) {
            (function exec() {
                fn(function () {
                    process.nextTick(exec);
                });
            })();
        }
    }
};

/**
 * A helper for copying an object.
 */

exports.copy = function (obj) {
    var copy = {};
    for (var i in obj) {
        if (typeof obj[i] === 'object') {
            copy[i] = exports.copy(obj[i]);
        } else {
            copy[i] = obj[i];
        }
    }
    return copy;
};

<|endoftext|>


<commit_before>'use strict';

var methodNames = [
  'arc',
  'arcTo',
  'beginPath',
  'bezierCurveTo',
  'clearRect',
  'clip',
  'closePath',
  'createLinearGradient',
  'createRadialGradient',
  'drawImage',
  'ellipse',
  'fill',
  'fillRect',
  'fillText',
  'lineTo',
  'moveTo',
  'quadraticCurveTo',
  'rect',
  'resetClip',
  'restore',
  'rotate',
  'save',
  'scale',
  'setTransform',
  'stroke',
  'strokeRect',
  'strokeText',
  'transform',
  'translate'
];

var setterNames = [
  'fillStyle',
  'font',
  'globalAlpha',
  'globalCompositeOperation',
  'lineCap',
  'lineJoin',
  'lineWidth',
  'strokeStyle',
  'textAlign',
  'textBaseline'
];

var Leinwand = function Leinwand(canvas) {
  if (!(this instanceof Leinwand)) {
    return new Leinwand(canvas);
  }

  this._canvas = canvas;
  this._ctx = canvas.getContext('2d');
};

methodNames.forEach(function(el) {
  Leinwand.prototype[el] = function() {
    this._ctx[el].apply(this._ctx, arguments);
    return this;
  };
});

setterNames.forEach(function(el) {
  Leinwand.prototype[el] = function(s) {
    this._ctx[el] = s;
    return this;
  };
});

//Custom methods
Leinwand.prototype.clear = function clear() {
  return this.clearRect(0, 0, this._canvas.width, this._canvas.height);
};

Leinwand.prototype.circle = function cirlce(x, y, r) {
  return this.arc(x, y, r, 0, 2 * Math.PI, false);
};

Leinwand.prototype.rotateContextAt = function rotateContextAt(x, y, r) {
  return this
    .translate(x, y)
    .rotate(r)
    .translate(-1 * x, -1 * y);
};

Leinwand.prototype.resetCanvas = function resetCanvas() {
  this._canvas.width = this._canvas.width;
  return this;
};

Leinwand.prototype.resetTransforms = function resetTransforms() {
  return this.setTransform(1, 0, 0, 1, 0, 0);
};

Leinwand.prototype.clearWithTransforms = function clearWithTransforms() {
  return this
    .save()
    .resetTransforms()
    .clear()
    .restore();
};

Leinwand.prototype.fillRectCenteredAt = function fillRectCenteredAt(x, y, w, h) {
  return this
    .fillRect(x - w / 2, y - h / 2, w, h);
};

Leinwand.prototype.strokeRectCenteredAt = function strokeRectCenteredAt(x, y, w, h) {
  return this
    .strokeRect(x - w / 2, y - h / 2, w, h);
};

Leinwand.prototype.fillTextCenteredAt = function fillTextCenteredAt(text, x, y) {
  return this
    .save()
    .textBaseline('middle')
    .textAlign('center')
    .fillText(text, x, y)
    .restore();
};

Leinwand.prototype.strokeTextCenteredAt = function strokeTextCenteredAt(text, x, y) {
  return this
    .save()
    .textBaseline('middle')
    .textAlign('center')
    .strokeText(text, x, y)
    .resore();
};

Leinwand.prototype.drawImageCenteredAt = function drawImageCenteredAt(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) {
  var args = arguments.length;
  if (args === 3) {
    this.drawImage(image, sx - image.width / 2, sy - image.height / 2);
  } else if (args === 5) {
    this.drawImage(image, sx - sWidth / 2, sy - sHeight / 2, sWidth, sHeight);
  } else if (args === 9) {
    this.drawImage(image, sx, sy, sWidth, sHeight, dx - dWidth / 2, dy - dHeight / 2, dWidth, dHeight);
  } else {
    throw new TypeError(arguments.length + ' is not a valid number of arguments to Leinwand.drawImageCenteredAt');
  }

  return this;
};

//Getters
Leinwand.prototype.getContext = function getContext() {
  return this._ctx;
};

Leinwand.prototype.getCanvas = function getCanvas() {
  return this._canvas;
};

Leinwand.prototype.getWidth = function getWidth() {
  return this._canvas.width;
};

Leinwand.prototype.getHeight = function getHeight() {
  return this._canvas.height;
};

//Aliases
Leinwand.prototype.lt = Leinwand.prototype.lineTo;
Leinwand.prototype.mt = Leinwand.prototype.moveTo;
Leinwand.prototype.opacity = Leinwand.prototype.globalAlpha;

module.exports = Leinwand;
<commit_msg>Add getImageData and measureText<commit_after>'use strict';

var methodNames = [
  'arc',
  'arcTo',
  'beginPath',
  'bezierCurveTo',
  'clearRect',
  'clip',
  'closePath',
  'createLinearGradient',
  'createRadialGradient',
  'drawImage',
  'ellipse',
  'fill',
  'fillRect',
  'fillText',
  'lineTo',
  'moveTo',
  'quadraticCurveTo',
  'rect',
  'resetClip',
  'restore',
  'rotate',
  'save',
  'scale',
  'setTransform',
  'stroke',
  'strokeRect',
  'strokeText',
  'transform',
  'translate'
];

var setterNames = [
  'fillStyle',
  'font',
  'globalAlpha',
  'globalCompositeOperation',
  'lineCap',
  'lineJoin',
  'lineWidth',
  'strokeStyle',
  'textAlign',
  'textBaseline'
];

var passthroughNames = [
  'getImageData',
  'measureText'
];

var Leinwand = function Leinwand(canvas) {
  if (!(this instanceof Leinwand)) {
    return new Leinwand(canvas);
  }

  this._canvas = canvas;
  this._ctx = canvas.getContext('2d');
};

methodNames.forEach(function(el) {
  Leinwand.prototype[el] = function() {
    this._ctx[el].apply(this._ctx, arguments);
    return this;
  };
});

setterNames.forEach(function(el) {
  Leinwand.prototype[el] = function(s) {
    this._ctx[el] = s;
    return this;
  };
});

passthroughNames.forEach(function(el) {
  Leinwand.prototype[el] = function() {
    return this._ctx[el].apply(this._ctx, arguments);
  };
});

//Custom methods
Leinwand.prototype.clear = function clear() {
  return this.clearRect(0, 0, this._canvas.width, this._canvas.height);
};

Leinwand.prototype.circle = function cirlce(x, y, r) {
  return this.arc(x, y, r, 0, 2 * Math.PI, false);
};

Leinwand.prototype.rotateContextAt = function rotateContextAt(x, y, r) {
  return this
    .translate(x, y)
    .rotate(r)
    .translate(-1 * x, -1 * y);
};

Leinwand.prototype.resetCanvas = function resetCanvas() {
  this._canvas.width = this._canvas.width;
  return this;
};

Leinwand.prototype.resetTransforms = function resetTransforms() {
  return this.setTransform(1, 0, 0, 1, 0, 0);
};

Leinwand.prototype.clearWithTransforms = function clearWithTransforms() {
  return this
    .save()
    .resetTransforms()
    .clear()
    .restore();
};

Leinwand.prototype.fillRectCenteredAt = function fillRectCenteredAt(x, y, w, h) {
  return this
    .fillRect(x - w / 2, y - h / 2, w, h);
};

Leinwand.prototype.strokeRectCenteredAt = function strokeRectCenteredAt(x, y, w, h) {
  return this
    .strokeRect(x - w / 2, y - h / 2, w, h);
};

Leinwand.prototype.fillTextCenteredAt = function fillTextCenteredAt(text, x, y) {
  return this
    .save()
    .textBaseline('middle')
    .textAlign('center')
    .fillText(text, x, y)
    .restore();
};

Leinwand.prototype.strokeTextCenteredAt = function strokeTextCenteredAt(text, x, y) {
  return this
    .save()
    .textBaseline('middle')
    .textAlign('center')
    .strokeText(text, x, y)
    .resore();
};

Leinwand.prototype.drawImageCenteredAt = function drawImageCenteredAt(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) {
  var args = arguments.length;
  if (args === 3) {
    this.drawImage(image, sx - image.width / 2, sy - image.height / 2);
  } else if (args === 5) {
    this.drawImage(image, sx - sWidth / 2, sy - sHeight / 2, sWidth, sHeight);
  } else if (args === 9) {
    this.drawImage(image, sx, sy, sWidth, sHeight, dx - dWidth / 2, dy - dHeight / 2, dWidth, dHeight);
  } else {
    throw new TypeError(arguments.length + ' is not a valid number of arguments to Leinwand.drawImageCenteredAt');
  }

  return this;
};

//Getters
Leinwand.prototype.getContext = function getContext() {
  return this._ctx;
};

Leinwand.prototype.getCanvas = function getCanvas() {
  return this._canvas;
};

Leinwand.prototype.getWidth = function getWidth() {
  return this._canvas.width;
};

Leinwand.prototype.getHeight = function getHeight() {
  return this._canvas.height;
};

//Aliases
Leinwand.prototype.lt = Leinwand.prototype.lineTo;
Leinwand.prototype.mt = Leinwand.prototype.moveTo;
Leinwand.prototype.opacity = Leinwand.prototype.globalAlpha;

module.exports = Leinwand;<|endoftext|>


<commit_before>var postcss = require('postcss');

module.exports = postcss.plugin('postcss-interpolate', () => {
    return css => {

        // Html font-size value
        var rootString;
        var startFrom;
        var directionViewport;

        var defaultRem = true;
        var defaultRemValue = '16px'; // default value of 1rem

        css.walkRules(rule => {
            if (rule.selector.indexOf('html') > -1) {
                rule.walkDecls(decl => {
                    if (decl.prop.indexOf('font-size') > -1) {
                        rootString = decl.value;
                        defaultRem = false;
                    }
                });
            }
            if (defaultRem) {
                rootString = defaultRemValue;
            }
        });

        function isString(array) {
            return isNaN(parseFloat(array));
        }

        function isOdd(array) {
            return (array.length & 1);
        }

        function isPx(i) {
            return i.indexOf('px') > -1;
        }

        function isRem(i) {
            return i.indexOf('rem') > -1;
        }

        // Get html font-size value
        function getHtmlValue(actualViewport, mediaArray, valueArray, decl) {
            actualViewport = parseFloat(actualViewport);

            if (mediaArray.every(isPx) && valueArray.every(isPx)) {
                return 1;
            } else if (mediaArray.every(isPx) && valueArray.every(isRem)) {
                var rootMediaArray = [];
                var rootValueArray = [];
                var rootReadyArray;
                var rootStartFrom;

                if (rootString.indexOf('interpolate(') > -1) {
                    rootReadyArray = rootString.match(/\(([^)]+)\)/)[1].split(',');

                    if (!isOdd(rootReadyArray) && !isString(rootReadyArray[0])) {
                        rootStartFrom = 0
                    } else if (isOdd(rootReadyArray) && isString(rootReadyArray[0])) {
                        rootStartFrom = 1;
                    } else {
                        throw decl.error('Deprecated value syntax', {
                            word: decl.value
                        });
                    }

                    // Get array of mediaqueries and array of values
                    for (var i = rootStartFrom; i < rootReadyArray.length; i += 2) {
                        rootMediaArray.push(rootReadyArray[i]);
                        rootValueArray.push(rootReadyArray[i + 1]);
                    }

                    var minrootMediaArray,
                        maxrootMediaArray,
                        minrootValueArray,
                        maxrootValueArray;

                    for (var i = 0; i < rootMediaArray.length; i++) {
                        if ((actualViewport <= parseFloat(rootMediaArray[i])) && (actualViewport >= parseFloat(rootMediaArray[i - 1]))) {
                            maxrootMediaArray = parseFloat(rootMediaArray[i]);
                            minrootMediaArray = parseFloat(rootMediaArray[i - 1]);
                            maxrootValueArray = parseFloat(rootValueArray[i]);
                            minrootValueArray = parseFloat(rootValueArray[i - 1]);
                        }
                    }

                    var formula;

                    if (minrootMediaArray === maxrootMediaArray) {
                        formula = parseFloat(minrootValueArray);
                    } else {
                        formula = minrootValueArray + (maxrootValueArray - minrootValueArray) * (actualViewport - minrootMediaArray) / (maxrootMediaArray - minrootMediaArray);
                    }

                    return formula;

                } else if (rootString.indexOf('px') > -1) {
                    return parseFloat(rootString);
                }
            } else {
                throw decl.error(
                    'This combination of units is not supported', {
                        word: decl.value
                    });
            }
        }

        // Generate media and values arrays
        function interpolate(interpolateArray, decl) {
            const rule = decl.parent;
            const root = rule.parent;
            var mediaArray = [];
            var valueArray = [];

            if (!isOdd(interpolateArray) && !isString(interpolateArray[0])) {
                directionViewport = 'vw';
                startFrom = 0;
            } else if (isOdd(interpolateArray) && isString(interpolateArray[0])) {
                var direction = interpolateArray[0];

                if ((direction.indexOf('horizontally') > -1) || (direction.indexOf('vw') > -1)) {
                    directionViewport = 'vw';
                } else if ((direction.indexOf('vertically') > -1) || (direction.indexOf('vh') > -1)) {
                    directionViewport = 'vh';
                } else {
                    throw decl.error('Deprecated direction property', {
                        word: interpolateArray[0]
                    });
                }
                startFrom = 1;
            } else {
                // Syntax error
                throw decl.error('Deprecated value syntax', {
                    word: decl.value
                });
            }

            for (var i = startFrom; i < interpolateArray.length; i += 2) {
                mediaArray.push(interpolateArray[i]);
                valueArray.push(interpolateArray[i + 1]);
            }

            // First @media rule
            decl.replaceWith({
                prop: decl.prop,
                value: interpolateArray[startFrom + 1]
            });

            // Middle @media rule
            for (var i = 0; i < mediaArray.length - 1; i++) {
                for (var i = 0; i < valueArray.length - 1; i++) {

                    // Generate @media atrule
                    var mediaAtrule = postcss.atRule({
                        name: 'media',
                        params: 'screen and (min-width: ' + mediaArray[i] + ')'
                    });

                    var maxMedia = mediaArray[i + 1];
                    var minMedia = mediaArray[i];
                    var maxValue = valueArray[i + 1];
                    var minValue = valueArray[i];

                    // Append current selector with properties and values
                    mediaAtrule.append({
                        selector: rule.selector
                    }).walkRules( function (selector) {
                        selector.append({
                            prop: decl.prop,
                            value: 'calc(' + minValue + ' + ' + (parseFloat(maxValue) - parseFloat(minValue)) + ' * (100' + directionViewport + ' - ' + minMedia + ') / ' + ((parseFloat(maxMedia) - parseFloat(minMedia)) / getHtmlValue(maxMedia, mediaArray, valueArray, decl)) + ')'
                        });
                    });

                    // Insert middle mediaquery
                    root.insertAfter(root.last, mediaAtrule);
                }
            }

            // Last @media rule
            var lastMedia = postcss.atRule({
                name: 'media',
                params: 'screen and (min-width: ' + mediaArray[mediaArray.length - 1] + ')'
            });

            // Append current selector with properties and values
            lastMedia.append({
                selector: rule.selector
            }).walkRules( function (selector) {
                selector.append({
                    prop: decl.prop,
                    value: valueArray[valueArray.length - 1]
                });
            });

            // Insert last media rule
            root.insertAfter(root.last, lastMedia);

            return;
        }

        css.walkDecls(decl => {

            // Get property line
            if (decl.value.indexOf('interpolate(') !== -1) {

                // Get array of interolate() values
                const getInterpolateArray = decl.value.match(/\(([^)]+)\)/)[1];
                const interpolateArray = getInterpolateArray.split(',');

                if (interpolateArray.length > 3) {

                    // Get array of mediaqueries and array of values
                    interpolate(interpolateArray,  decl);

                } else {
                    throw decl.error('Deprecated property amount', {
                        word: decl.value
                    });
                }
            }
        });
    };
});
<commit_msg>Fix 'min-height' bug<commit_after>var postcss = require('postcss');

module.exports = postcss.plugin('postcss-interpolate', () => {
    return css => {

        // Html font-size value
        var rootString;
        var startFrom;
        var directionViewport;
        var minDirection = 'width';
        var defaultRem = true;
        var defaultRemValue = '16px'; // default value of 1rem

        css.walkRules(rule => {
            if (rule.selector.indexOf('html') > -1) {
                rule.walkDecls(decl => {
                    if (decl.prop.indexOf('font-size') > -1) {
                        rootString = decl.value;
                        defaultRem = false;
                    }
                });
            }
            if (defaultRem) {
                rootString = defaultRemValue;
            }
        });

        function isString(array) {
            return isNaN(parseFloat(array));
        }

        function isOdd(array) {
            return (array.length & 1);
        }

        function isPx(i) {
            return i.indexOf('px') > -1;
        }

        function isRem(i) {
            return i.indexOf('rem') > -1;
        }

        // Get html font-size value
        function getHtmlValue(actualViewport, mediaArray, valueArray, decl) {
            actualViewport = parseFloat(actualViewport);

            if (mediaArray.every(isPx) && valueArray.every(isPx)) {
                return 1;
            } else if (mediaArray.every(isPx) && valueArray.every(isRem)) {
                var rootMediaArray = [];
                var rootValueArray = [];
                var rootReadyArray;
                var rootStartFrom;

                if (rootString.indexOf('interpolate(') > -1) {
                    rootReadyArray = rootString.match(/\(([^)]+)\)/)[1].split(',');

                    if (!isOdd(rootReadyArray) && !isString(rootReadyArray[0])) {
                        rootStartFrom = 0
                    } else if (isOdd(rootReadyArray) && isString(rootReadyArray[0])) {
                        rootStartFrom = 1;
                    } else {
                        throw decl.error('Deprecated value syntax', {
                            word: decl.value
                        });
                    }

                    // Get array of mediaqueries and array of values
                    for (var i = rootStartFrom; i < rootReadyArray.length; i += 2) {
                        rootMediaArray.push(rootReadyArray[i]);
                        rootValueArray.push(rootReadyArray[i + 1]);
                    }

                    var minrootMediaArray,
                        maxrootMediaArray,
                        minrootValueArray,
                        maxrootValueArray;

                    for (var i = 0; i < rootMediaArray.length; i++) {
                        if ((actualViewport <= parseFloat(rootMediaArray[i])) && (actualViewport >= parseFloat(rootMediaArray[i - 1]))) {
                            maxrootMediaArray = parseFloat(rootMediaArray[i]);
                            minrootMediaArray = parseFloat(rootMediaArray[i - 1]);
                            maxrootValueArray = parseFloat(rootValueArray[i]);
                            minrootValueArray = parseFloat(rootValueArray[i - 1]);
                        }
                    }

                    var formula;

                    if (minrootMediaArray === maxrootMediaArray) {
                        formula = parseFloat(minrootValueArray);
                    } else {
                        formula = minrootValueArray + (maxrootValueArray - minrootValueArray) * (actualViewport - minrootMediaArray) / (maxrootMediaArray - minrootMediaArray);
                    }

                    return formula;

                } else if (rootString.indexOf('px') > -1) {
                    return parseFloat(rootString);
                }
            } else {
                throw decl.error(
                    'This combination of units is not supported', {
                        word: decl.value
                    });
            }
        }

        // Generate media and values arrays
        function interpolate(interpolateArray, decl) {
            const rule = decl.parent;
            const root = rule.parent;
            var mediaArray = [];
            var valueArray = [];

            if (!isOdd(interpolateArray) && !isString(interpolateArray[0])) {
                directionViewport = 'vw';
                startFrom = 0;
            } else if (isOdd(interpolateArray) && isString(interpolateArray[0])) {
                var direction = interpolateArray[0];

                if ((direction.indexOf('horizontally') > -1) || (direction.indexOf('vw') > -1)) {
                    directionViewport = 'vw';
                } else if ((direction.indexOf('vertically') > -1) || (direction.indexOf('vh') > -1)) {
                    directionViewport = 'vh';
                    minDirection = 'height';
                } else {
                    throw decl.error('Deprecated direction property', {
                        word: interpolateArray[0]
                    });
                }
                startFrom = 1;
            } else {
                // Syntax error
                throw decl.error('Deprecated value syntax', {
                    word: decl.value
                });
            }

            for (var i = startFrom; i < interpolateArray.length; i += 2) {
                mediaArray.push(interpolateArray[i]);
                valueArray.push(interpolateArray[i + 1]);
            }

            // First @media rule
            decl.replaceWith({
                prop: decl.prop,
                value: interpolateArray[startFrom + 1]
            });

            // Middle @media rule
            for (var i = 0; i < mediaArray.length - 1; i++) {
                for (var i = 0; i < valueArray.length - 1; i++) {

                    // Generate @media atrule
                    var mediaAtrule = postcss.atRule({
                        name: 'media',
                        params: 'screen and (min-' + minDirection + ': ' + mediaArray[i] + ')'
                    });

                    var maxMedia = mediaArray[i + 1];
                    var minMedia = mediaArray[i];
                    var maxValue = valueArray[i + 1];
                    var minValue = valueArray[i];

                    // Append current selector with properties and values
                    mediaAtrule.append({
                        selector: rule.selector
                    }).walkRules( function (selector) {
                        selector.append({
                            prop: decl.prop,
                            value: 'calc(' + minValue + ' + ' + (parseFloat(maxValue) - parseFloat(minValue)) + ' * (100' + directionViewport + ' - ' + minMedia + ') / ' + ((parseFloat(maxMedia) - parseFloat(minMedia)) / getHtmlValue(maxMedia, mediaArray, valueArray, decl)) + ')'
                        });
                    });

                    // Insert middle mediaquery
                    root.insertAfter(root.last, mediaAtrule);
                }
            }

            // Last @media rule
            var lastMedia = postcss.atRule({
                name: 'media',
                params: 'screen and (min-' + minDirection + ': ' + mediaArray[mediaArray.length - 1] + ')'
            });

            // Append current selector with properties and values
            lastMedia.append({
                selector: rule.selector
            }).walkRules( function (selector) {
                selector.append({
                    prop: decl.prop,
                    value: valueArray[valueArray.length - 1]
                });
            });

            // Insert last media rule
            root.insertAfter(root.last, lastMedia);

            return;
        }

        css.walkDecls(decl => {

            // Get property line
            if (decl.value.indexOf('interpolate(') !== -1) {

                // Get array of interolate() values
                const getInterpolateArray = decl.value.match(/\(([^)]+)\)/)[1];
                const interpolateArray = getInterpolateArray.split(',');

                if (interpolateArray.length > 3) {

                    // Get array of mediaqueries and array of values
                    interpolate(interpolateArray,  decl);

                } else {
                    throw decl.error('Deprecated property amount', {
                        word: decl.value
                    });
                }
            }
        });
    };
});
<|endoftext|>


<commit_before>// @flow
const assert = require('assert')
const Immutable = require('immutable')

// Flow types
type FSMDescription = [Node]
type FSMState = Immutable.Record<{
  stateName: string,
  transitioning: boolean,
  inputBuffer: ?[string],
}>
type FSMOptions = {
  cancelOnInput?: Boolean
}
type Node = {
  _fullName?: ?string,
  name: string,
  action: (state: ?any, input: ?string) => ?Promise<any>,
  accepts?: string | RegExp | (x: string) => Boolean,
  children?: [Node],
  next?: string
}

// States
const States = {
  START: 'START',
  END: 'END'
}

// Actions
const Actions = {
  _TRANSITIONED: 'TRANSITIONED',
  _TRANSITIONING: 'TRANSITIONING',
  _UPDATE_BUFFER: 'UPDATE_BUFFER',
  _PROCESS_BUFFER: 'PROCESS_INPUT',
  HANDLE_INPUT: 'HANDLE_INPUT'
}

/**
 * Constructs an FSM based on a description object.
 * (see the README for more details)
 *
 * @param {object} description - a tree of states that define the FSM
 * @param {object} options - options object
 */
function FSM (key: string, description: FSMDescription, options?: FSMOptions) {
  if (!(this instanceof FSM)) return new FSM(key, description, options)
  this.key = key
  this.options = options || {}
  this.description = description

  this._buildIndex()
}

/**
 * Build an index so that node lookups are fast.
 *
 * This method will also mutate the description object so that all nodes contain
 * an expanded name property with their complete prefixes.
 */
FSM.prototype._buildIndex = function () {
  this.index = {}
  this.index[FSM.States.START] = {
    _fullName: FSM.States.START,
    name: FSM.States.START,
    children: this.description
  }
  this.index[FSM.States.END] = {
    _fullName: FSM.States.END,
    name: FSM.States.END
  }

  const self = this
  function _indexNode (prefix: ?string, node: Node) {
    const newPrefix = (prefix) ? (prefix + '.' + node.name) : node.name
    node._fullName = newPrefix
    self.index[newPrefix] = node
    const children: ?[Node] = node.children
    if (children && children.length !== 0) {
      children.forEach(function (child) {
        _indexNode(newPrefix, child)
      })
    }
  }
  // All top-level states are successors to START.
  this.description.forEach(function (node) {
    _indexNode(null, node)
  })
}

/**
 * Create a transitioned action signifying that a transition has completed.
 */
FSM.prototype._transitioned = function (stateName) {
  return {
    type: FSM.Actions._TRANSITIONED,
    key: this.key,
    nextState: stateName
  }
}

/**
 * Create a transitioning action signifying that an async action is in progress.
 */
FSM.prototype._transitioning = function () {
  return {
    type: FSM.Actions._TRANSITIONING,
    key: this.key
  }
}

/**
 * Buffer input for processing once the in-progress async action is complete.
 */
FSM.prototype._updateBuffer = function (buffer) {
  return {
    type: FSM.Actions._UPDATE_BUFFER,
    key: this.key,
    buffer: buffer
  }
}

FSM.prototype._handleInput = function (getState, dispatch, key, input) {
  function _findMatchingChild (children: [Node], input: string): ?Node {
    if (!children || children.length === 0) {
      return null
    }
    for (var i = 0; i < children.length; i++) {
      const child = children[i]
      const accepts = child.accepts
      if (!accepts) return child
      if ((typeof accepts === 'string' && accepts === input) ||
          (typeof accepts === 'function' && accepts(input)) ||
          (accepts instanceof RegExp && accepts.test(input))) {
        return child
      }
    }
    return null
  }

  const nodePath = getState()[key].stateName
  const node= this.index[nodePath]
  if (!nodePath) {
    throw new Error('Attempting to read the value of nonexistent state machine: ' + key)
  }
  if (node.name === FSM.States.END) {
    const warning = 'State machine is in the END state and is not processing inputs.'
    return console.warn(warning)
  }

  // If a state doesn't specify any successors, immediately transition to END.
  if (!node.children || node.children.length === 0) {
    return this._transitioned(FSM.States.END)
  }
  // If a state specifies an invalid successor, immediately transition to END.
  if (node.next && !this.index[node.next]) {
    console.warn('Attempting to transition into an invalid state:', node.next)
    return this._transitioned(FSM.States.END)
  }

  const successor = _findMatchingChild(node.children, input)
  if (!successor) {
    throw new Error('Could not find a valid successor state for input: ' + input)
  }

  var nextState = successor._fullName
  if (successor.next) {
    if (!this.index[successor.next]) {
      console.warn('Attempting to transition into an invalid state:', successor.next)
      nextState = FSM.States.END
    } else {
      nextState = this.index[successor.next]._fullName
    }
  }

  const promise = successor.action(getState, dispatch)
  if (promise) {
    if (!(promise instanceof Promise)) {
      throw new Error('Action must return null or a Promise.')
    }
    const self = this
    promise.then(function (res) {
      dispatch(self._transitioned(nextState))
    })
    promise.catch(function (err) {
      // TODO: handle error?
      console.error(err)
      dispatch(self._transitioned(nextState))
    })
    return this._transitioning()
  }
  dispatch(this._transitioned(nextState))
}

/**
 * Creates an action to send input to the FSM specified by `key`.
 *
 * @param {string} key - the FSM key
 * @param {string} input - an input string
 */
FSM.handleInput = function (key, input) {
  return {
    type: FSM.Actions.HANDLE_INPUT,
    key: key,
    input: input
  }
}

/**
 * Create a Redux reducer for updating a state machine's internal state.
 */
FSM.prototype.reducer = function () {
  const self = this
  return function (state, action) {
    if (typeof state === 'undefined') {
      return self.createEmpty()
    }
    if (action.key !== self.key) {
      return state
    }
    switch (action.type) {
      case FSM.Actions._TRANSITIONED:
        return state.set('stateName', action.nextState)
                    .set('transitioning', false)
      case FSM.Actions._TRANSITIONING:
        return state.set('transitioning', true)
      case FSM.Actions._UPDATE_BUFFER:
        return state.set('inputBuffer', action.buffer)
      default:
        return state
    }
  }
}

/**
 * Creates an empty Redux state.
 */
FSM.prototype.createEmpty = function (): FSMState {
  return new Immutable.Record({
    stateName: FSM.States.START,
    transitioning: false,
    inputBuffer: new Immutable.List()
  })()
}

/**
 * Creates Redux middleware that intercepts actions for processing by a state
 * machine.
 */
FSM.prototype.middleware = function () {
  const self = this
  return store => next => action => {
    if (action.key !== self.key) {
      return next(action)
    }
    const fsmState = store.getState()[self.key]
    assert(fsmState)
    var nextAction = null
    switch (action.type) {

      case FSM.Actions.HANDLE_INPUT:
        assert(action.input)
        if (fsmState.transitioning) {
          return next(self._updateBuffer(
                      fsmState.inputBuffer.unshift(action.input)))
        }
        nextAction = self._handleInput(
                    store.getState, store.dispatch, action.key, action.input)
        if (nextAction) return next(nextAction)
        break

      case FSM.Actions._TRANSITIONED:
        next(action)
        const nextInput = fsmState.inputBuffer.last()
        if (nextInput) {
          next(self._updateBuffer(fsmState.inputBuffer.pop()))
          nextAction = self._handleInput(
                      store.getState, store.dispatch, action.key, nextInput)
          if (nextAction) return next(nextAction)
        }
        break

      default:
        return next(action)
    }
  }
}

FSM.States = States
FSM.Actions = Actions

module.exports = FSM
<commit_msg>Fixed missing input<commit_after>// @flow
const assert = require('assert')
const Immutable = require('immutable')

// Flow types
type FSMDescription = [Node]
type FSMState = Immutable.Record<{
  stateName: string,
  transitioning: boolean,
  inputBuffer: ?[string],
}>
type FSMOptions = {
  cancelOnInput?: Boolean
}
type Node = {
  _fullName?: ?string,
  name: string,
  action: (state: ?any, input: ?string) => ?Promise<any>,
  accepts?: string | RegExp | (x: string) => Boolean,
  children?: [Node],
  next?: string
}

// States
const States = {
  START: 'START',
  END: 'END'
}

// Actions
const Actions = {
  _TRANSITIONED: 'TRANSITIONED',
  _TRANSITIONING: 'TRANSITIONING',
  _UPDATE_BUFFER: 'UPDATE_BUFFER',
  _PROCESS_BUFFER: 'PROCESS_INPUT',
  HANDLE_INPUT: 'HANDLE_INPUT'
}

/**
 * Constructs an FSM based on a description object.
 * (see the README for more details)
 *
 * @param {object} description - a tree of states that define the FSM
 * @param {object} options - options object
 */
function FSM (key: string, description: FSMDescription, options?: FSMOptions) {
  if (!(this instanceof FSM)) return new FSM(key, description, options)
  this.key = key
  this.options = options || {}
  this.description = description

  this._buildIndex()
}

/**
 * Build an index so that node lookups are fast.
 *
 * This method will also mutate the description object so that all nodes contain
 * an expanded name property with their complete prefixes.
 */
FSM.prototype._buildIndex = function () {
  this.index = {}
  this.index[FSM.States.START] = {
    _fullName: FSM.States.START,
    name: FSM.States.START,
    children: this.description
  }
  this.index[FSM.States.END] = {
    _fullName: FSM.States.END,
    name: FSM.States.END
  }

  const self = this
  function _indexNode (prefix: ?string, node: Node) {
    const newPrefix = (prefix) ? (prefix + '.' + node.name) : node.name
    node._fullName = newPrefix
    self.index[newPrefix] = node
    const children: ?[Node] = node.children
    if (children && children.length !== 0) {
      children.forEach(function (child) {
        _indexNode(newPrefix, child)
      })
    }
  }
  // All top-level states are successors to START.
  this.description.forEach(function (node) {
    _indexNode(null, node)
  })
}

/**
 * Create a transitioned action signifying that a transition has completed.
 */
FSM.prototype._transitioned = function (stateName) {
  return {
    type: FSM.Actions._TRANSITIONED,
    key: this.key,
    nextState: stateName
  }
}

/**
 * Create a transitioning action signifying that an async action is in progress.
 */
FSM.prototype._transitioning = function () {
  return {
    type: FSM.Actions._TRANSITIONING,
    key: this.key
  }
}

/**
 * Buffer input for processing once the in-progress async action is complete.
 */
FSM.prototype._updateBuffer = function (buffer) {
  return {
    type: FSM.Actions._UPDATE_BUFFER,
    key: this.key,
    buffer: buffer
  }
}

FSM.prototype._handleInput = function (getState, dispatch, key, input) {
  function _findMatchingChild (children: [Node], input: string): ?Node {
    if (!children || children.length === 0) {
      return null
    }
    for (var i = 0; i < children.length; i++) {
      const child = children[i]
      const accepts = child.accepts
      if (!accepts) return child
      if ((typeof accepts === 'string' && accepts === input) ||
          (typeof accepts === 'function' && accepts(input)) ||
          (accepts instanceof RegExp && accepts.test(input))) {
        return child
      }
    }
    return null
  }

  const nodePath = getState()[key].stateName
  const node= this.index[nodePath]
  if (!nodePath) {
    throw new Error('Attempting to read the value of nonexistent state machine: ' + key)
  }
  if (node.name === FSM.States.END) {
    const warning = 'State machine is in the END state and is not processing inputs.'
    return console.warn(warning)
  }

  // If a state doesn't specify any successors, immediately transition to END.
  if (!node.children || node.children.length === 0) {
    return this._transitioned(FSM.States.END)
  }
  // If a state specifies an invalid successor, immediately transition to END.
  if (node.next && !this.index[node.next]) {
    console.warn('Attempting to transition into an invalid state:', node.next)
    return this._transitioned(FSM.States.END)
  }

  const successor = _findMatchingChild(node.children, input)
  if (!successor) {
    throw new Error('Could not find a valid successor state for input: ' + input)
  }

  var nextState = successor._fullName
  if (successor.next) {
    if (!this.index[successor.next]) {
      console.warn('Attempting to transition into an invalid state:', successor.next)
      nextState = FSM.States.END
    } else {
      nextState = this.index[successor.next]._fullName
    }
  }

  const promise = successor.action(getState, dispatch, input)
  if (promise) {
    if (!(promise instanceof Promise)) {
      throw new Error('Action must return null or a Promise.')
    }
    const self = this
    promise.then(function (res) {
      dispatch(self._transitioned(nextState))
    })
    promise.catch(function (err) {
      // TODO: handle error?
      console.error(err)
      dispatch(self._transitioned(nextState))
    })
    return this._transitioning()
  }
  dispatch(this._transitioned(nextState))
}

/**
 * Creates an action to send input to the FSM specified by `key`.
 *
 * @param {string} key - the FSM key
 * @param {string} input - an input string
 */
FSM.handleInput = function (key, input) {
  return {
    type: FSM.Actions.HANDLE_INPUT,
    key: key,
    input: input
  }
}

/**
 * Create a Redux reducer for updating a state machine's internal state.
 */
FSM.prototype.reducer = function () {
  const self = this
  return function (state, action) {
    if (typeof state === 'undefined') {
      return self.createEmpty()
    }
    if (action.key !== self.key) {
      return state
    }
    switch (action.type) {
      case FSM.Actions._TRANSITIONED:
        return state.set('stateName', action.nextState)
                    .set('transitioning', false)
      case FSM.Actions._TRANSITIONING:
        return state.set('transitioning', true)
      case FSM.Actions._UPDATE_BUFFER:
        return state.set('inputBuffer', action.buffer)
      default:
        return state
    }
  }
}

/**
 * Creates an empty Redux state.
 */
FSM.prototype.createEmpty = function (): FSMState {
  return new Immutable.Record({
    stateName: FSM.States.START,
    transitioning: false,
    inputBuffer: new Immutable.List()
  })()
}

/**
 * Creates Redux middleware that intercepts actions for processing by a state
 * machine.
 */
FSM.prototype.middleware = function () {
  const self = this
  return store => next => action => {
    if (action.key !== self.key) {
      return next(action)
    }
    const fsmState = store.getState()[self.key]
    assert(fsmState)
    var nextAction = null
    switch (action.type) {

      case FSM.Actions.HANDLE_INPUT:
        assert(action.input)
        if (fsmState.transitioning) {
          return next(self._updateBuffer(
                      fsmState.inputBuffer.unshift(action.input)))
        }
        nextAction = self._handleInput(
                    store.getState, store.dispatch, action.key, action.input)
        if (nextAction) return next(nextAction)
        break

      case FSM.Actions._TRANSITIONED:
        next(action)
        const nextInput = fsmState.inputBuffer.last()
        if (nextInput) {
          next(self._updateBuffer(fsmState.inputBuffer.pop()))
          nextAction = self._handleInput(
                      store.getState, store.dispatch, action.key, nextInput)
          if (nextAction) return next(nextAction)
        }
        break

      default:
        return next(action)
    }
  }
}

FSM.States = States
FSM.Actions = Actions

module.exports = FSM
<|endoftext|>


<commit_before>'use strict'

const Trailpack = require('trailpack')
const lib = require('./lib')

/**
 * Core Trailpack
 *
 * Validate api, config, and pkg definitions; merge configuration setings; bind
 * event handlers/logging.
 */
module.exports = class Core extends Trailpack {

  validate () {
    this.app.log.info(this.app.config.motd.info.start)

    return Promise.all([
      lib.Validator.validatePackage(this.app.pkg),
      lib.Validator.validateConfig(this.app.config),
      lib.Validator.validateApi(this.app.api)
    ])
  }

  configure () {
    this.app.log = this.config.log.logger
  }

  /**
   * Listen for key app events, and bind context for API resources
   */
  initialize () {
    lib.Motd.bindMotdInitialize(this.app)

    this.app.controllers = lib.Context.bindMethods(this.app, 'controllers')
    this.app.policies = lib.Context.bindMethods(this.app, 'policies')
    this.app.services = lib.Context.bindMethods(this.app, 'services')
    this.app.models = lib.Context.bindMethods(this.app, 'models')

    return lib.i18n.init(this.app).then(i18n => {
      this.i18n = i18n
      this.app.__ = i18n.t
      this.app.emit('i18n:ready')
    })
  }

  constructor (app) {
    super(app, {
      config: require('./config'),
      pkg: require('./package')
    })

    if (!this.app.config.i18n) {
      this.app.config.i18n = { }
    }
    if (!this.app.config.log) {
      this.app.config.log = { }
    }
  }
}

<commit_msg>[config] do not set log property manually on app<commit_after>'use strict'

const Trailpack = require('trailpack')
const lib = require('./lib')

/**
 * Core Trailpack
 *
 * Validate api, config, and pkg definitions; merge configuration setings; bind
 * event handlers/logging.
 */
module.exports = class Core extends Trailpack {

  validate () {
    this.app.log.info(this.app.config.motd.info.start)

    return Promise.all([
      lib.Validator.validatePackage(this.app.pkg),
      lib.Validator.validateConfig(this.app.config),
      lib.Validator.validateApi(this.app.api)
    ])
  }

  configure () {
  }

  /**
   * Listen for key app events, and bind context for API resources
   */
  initialize () {
    lib.Motd.bindMotdInitialize(this.app)

    this.app.controllers = lib.Context.bindMethods(this.app, 'controllers')
    this.app.policies = lib.Context.bindMethods(this.app, 'policies')
    this.app.services = lib.Context.bindMethods(this.app, 'services')
    this.app.models = lib.Context.bindMethods(this.app, 'models')

    return lib.i18n.init(this.app).then(i18n => {
      this.i18n = i18n
      this.app.__ = i18n.t
      this.app.emit('i18n:ready')
    })
  }

  constructor (app) {
    super(app, {
      config: require('./config'),
      pkg: require('./package')
    })

    if (!this.app.config.i18n) {
      this.app.config.i18n = { }
    }
    if (!this.app.config.log) {
      this.app.config.log = { }
    }
  }
}

<|endoftext|>


<commit_before>var http = require('http'),
    httpget = require('http-get'),
    express = require('express'),
    fs = require('fs'),
    exec = require('exec'),
    firebase = require('firebase'),
    dx = require('./dx')

app = express()

var tileserver = process.env.TILESERVER || 'http://localhost:8888/v2/'
console.log('tileserver: ' + tileserver)

app.get('/:image', function(req, res) {
  var image = req.params.image,
      imageName = image.replace(/\.\w+$/, '')

  if(imageName == undefined || imageName == 'favicon') return

  res.header('Access-Control-Allow-Origin', '*')
  res.header('Access-Control-Allow-Methods', 'GET')

  tileJson = tileserver+imageName+'.json'
  console.log(' --- http.get ' + tileJson)
  http.get(tileJson, function(tile_res) {
    console.log(req.params, tile_res.statusCode)
    var progressRef = new firebase("https://tilesaw.firebaseio.com/"+imageName)
    if(tile_res.statusCode == '200') {
      tile_res.pipe(res) // pipe through the JSON from tilestream
      progressRef.set({status: 'tiled'})
    } else {
      res.send(404) // return 404 then start the tiling process [TODO: use raw websockets instead of firebase?]
      var size = dx.maxDimension(imageName)
      console.log('image size :' + size)

      if(image != undefined) {
        var imageUrl = "http://api.artsmia.org/images/1/tdx/"+size+"/"+image
        console.log(imageUrl)
        var imageFile = imageName + '.jpg'
        var tilesawPath = process.env.TILESAW_PATH || '/Users/kolsen/tmp/tilesaw',
            tilesaw = tilesawPath + '/tilesaw.sh',
            tileDirectory = process.env.TILE_DIRECTORY || '/Users/kolsen/Documents/Mapbox/tiles'

        progressRef.set({status: 'downloading original image'})
        console.log('getting image: ' + imageUrl)
        httpget.get({url: imageUrl}, tilesawPath + '/' + imageFile, function(error, result) {
          progressRef.set({status: 'processing image'})
          if(result == undefined) { console.log(error); return }
          var saw = exec([tilesaw, tilesawPath + '/' + imageFile], function(err, out, code) {
            console.log('tilesaw', err, out, code)
            if(code == 0) {
              mv = exec(['mv', imageName + '.mbtiles', tileDirectory], function(err, out, code) {
                exec(['rm', imageName + '.jpg'], function() {})
                progressRef.set({status: 'tiled'})
              })
            } else {
              console.log('error')
            }
          })
        })
      }
    }
  })
})

console.log('starting tilesaw on ' + (process.env.PORT || 8887))
http.createServer(app).listen(process.env.PORT || 8887);
<commit_msg>Sentry<commit_after>var http = require('http'),
    httpget = require('http-get'),
    express = require('express'),
    fs = require('fs'),
    exec = require('exec'),
    firebase = require('firebase'),
    dx = require('./dx')
    raven = require('raven')

app = express()

raven = new raven.Client(process.env.NODE_ENV === 'production' && process.env.SENTRY_DSN)
raven.patchGlobal()

var tileserver = process.env.TILESERVER || 'http://localhost:8888/v2/'

app.get('/:image', function(req, res) {
  var image = req.params.image,
      imageName = image.replace(/\.\w+$/, '')

  if(imageName == undefined || imageName == 'favicon') return

  res.header('Access-Control-Allow-Origin', '*')
  res.header('Access-Control-Allow-Methods', 'GET')

  tileJson = tileserver+imageName+'.json'
  http.get(tileJson, function(tile_res) {
    client.captureMessage(req.params, tile_res.statusCode)
    var progressRef = new firebase("https://tilesaw.firebaseio.com/"+imageName)
    if(tile_res.statusCode == '200') {
      tile_res.pipe(res) // pipe through the JSON from tilestream
      progressRef.set({status: 'tiled'})
      client.captureMessage('already tiled, piping tilestream')
    } else {
      res.send(404) // return 404 then start the tiling process [TODO: use raw websockets instead of firebase?]
      var size = dx.maxDimension(imageName)
      console.log('image size :' + size)
      client.captureMessage(imageName + ' not yet tiled, commence sawing')

      if(image != undefined) {
        var imageUrl = "http://api.artsmia.org/images/1/tdx/"+size+"/"+image
        console.log(imageUrl)
        var imageFile = imageName + '.jpg'
        var tilesawPath = process.env.TILESAW_PATH || '/Users/kolsen/tmp/tilesaw',
            tilesaw = tilesawPath + '/tilesaw.sh',
            tileDirectory = process.env.TILE_DIRECTORY || '/Users/kolsen/Documents/Mapbox/tiles'

        progressRef.set({status: 'downloading original image'})
        httpget.get({url: imageUrl}, tilesawPath + '/' + imageFile, function(error, result) {
          client.captureMessage(imageName + ' downloaded')
          progressRef.set({status: 'processing image'})
          if(result == undefined) { client.captureError(error); return }
          var saw = exec([tilesaw, tilesawPath + '/' + imageFile], function(err, out, code) {
            if(code == 0) {
              mv = exec(['mv', imageName + '.mbtiles', tileDirectory], function(err, out, code) {
                exec(['rm', imageName + '.jpg'], function() {})
                progressRef.set({status: 'tiled'})
              })
            } else {
              client.captureError(error)
            }
          })
        })
      }
    }
  })
})

http.createServer(app).listen(process.env.PORT || 8887);
<|endoftext|>


<commit_before>'use strict';

require('dotenv').config({silent: true});

const irc = require('irc');
const moment = require('moment');
const crypto = require('crypto');

const euro2016commands = require('./commands/euro2016');
const alert = require('./util/alert');
const ircUtil = require('./util/irc');
const track = require('./util/track');

const server = process.env.IRC_SERVER || 'irc.rizon.net';
const nick = process.env.IRC_NICK || 'Euro2016';
const channels = process.env.IRC_CHANNELS.split(',').map(channel => `#${channel }`) || [];
const port = process.env.IRC_PORT || 6667;
const realName = process.env.IRC_REALNAME || null;
const userName = process.env.IRC_NAME || null;

const client = new irc.Client(server, nick, { port, channels, realName, userName });

const ALERT_MESSAGES_PER_MINUTE = process.env.IRC_ALERT_MESSAGES_PER_MINUTE || 100;
const commandMonitor = {};

const helpMapping = {
    group: {
        desc: 'Show standings for a group',
        usage: '[query]',
        example: ['a', 'france']
    },
    country: {
        desc: 'Show matches for a country',
        usage: '[country]',
        example: ['england', 'rep ireland', 'ger'],
        aliases: ['team']
    },
    matches: {
        desc: 'Show matches on a given day',
        usage: '[day]',
        example: ['tomorrow', 'this friday', 'yesterday']
    },
    player: {
        desc: 'Query player information (beta)',
        usage: '[player name]',
        example: ['rooney', 'rashford', 'ibra', 'cech']
    },
    next: {
        desc: 'Show the next match',
        usage: ''
    }
};

const commandMapping = {
    hello: require('./commands/hello'),
    help: (command, client) => require('./commands/help')(command, client, helpMapping, Object.keys(commandMapping)),
    group: euro2016commands.group,
    country: euro2016commands.country,
    team: euro2016commands.country,
    matches: euro2016commands.matches,
    player: euro2016commands.player,
    next: euro2016commands.next
};

client.addListener('registered', () => {
    if (process.env.IRC_PASSWORD) {
        client.say('nickserv', `IDENTIFY ${process.env.IRC_PASSWORD}`);
    }
});

client.addListener('message', (from, to, message) => {
    const command = ircUtil.parseCommand(message, from, to);
    if (!command) {
        track('invalid_command', crypto.createHash('sha256').update(from).digest('hex'), { server, nick });
        return;
    }
    const min = moment(new Date).format('YYYY-MM-DD HH:mm');
    if (!commandMonitor[min]) {
        commandMonitor[min] = 0;
    }
    commandMonitor[min]++;
    if (commandMonitor[min] === ALERT_MESSAGES_PER_MINUTE) {
        alert('Euro 2016 IRC Bot High usage!', `Over ${ALERT_MESSAGES_PER_MINUTE} commands per minute detected.`);
    }
    track('command', crypto.createHash('sha256').update(from).digest('hex'), { command, server, nick });
    ircUtil.mapCommand(command, commandMapping, client);
});

/* eslint-disable no-console */
client.addListener('error', message => {
    alert('ERROR with Euro2016 IRC Bot', message);
    track('error', 0, { server, nick, message });
    console.log('error: ', message);
});
<commit_msg>hotfix: tracking of invalid commands<commit_after>'use strict';

require('dotenv').config({silent: true});

const irc = require('irc');
const moment = require('moment');
const crypto = require('crypto');

const euro2016commands = require('./commands/euro2016');
const alert = require('./util/alert');
const ircUtil = require('./util/irc');
const track = require('./util/track');

const server = process.env.IRC_SERVER || 'irc.rizon.net';
const nick = process.env.IRC_NICK || 'Euro2016';
const channels = process.env.IRC_CHANNELS.split(',').map(channel => `#${channel }`) || [];
const port = process.env.IRC_PORT || 6667;
const realName = process.env.IRC_REALNAME || null;
const userName = process.env.IRC_NAME || null;

const client = new irc.Client(server, nick, { port, channels, realName, userName });

const ALERT_MESSAGES_PER_MINUTE = process.env.IRC_ALERT_MESSAGES_PER_MINUTE || 100;
const commandMonitor = {};

const helpMapping = {
    group: {
        desc: 'Show standings for a group',
        usage: '[query]',
        example: ['a', 'france']
    },
    country: {
        desc: 'Show matches for a country',
        usage: '[country]',
        example: ['england', 'rep ireland', 'ger'],
        aliases: ['team']
    },
    matches: {
        desc: 'Show matches on a given day',
        usage: '[day]',
        example: ['tomorrow', 'this friday', 'yesterday']
    },
    player: {
        desc: 'Query player information (beta)',
        usage: '[player name]',
        example: ['rooney', 'rashford', 'ibra', 'cech']
    },
    next: {
        desc: 'Show the next match',
        usage: ''
    }
};

const commandMapping = {
    hello: require('./commands/hello'),
    help: (command, client) => require('./commands/help')(command, client, helpMapping, Object.keys(commandMapping)),
    group: euro2016commands.group,
    country: euro2016commands.country,
    team: euro2016commands.country,
    matches: euro2016commands.matches,
    player: euro2016commands.player,
    next: euro2016commands.next
};

client.addListener('registered', () => {
    if (process.env.IRC_PASSWORD) {
        client.say('nickserv', `IDENTIFY ${process.env.IRC_PASSWORD}`);
    }
});

client.addListener('message', (from, to, message) => {
    const command = ircUtil.parseCommand(message, from, to);
    if (!command) {
        return;
    }
    const min = moment(new Date).format('YYYY-MM-DD HH:mm');
    if (!commandMonitor[min]) {
        commandMonitor[min] = 0;
    }
    commandMonitor[min]++;
    if (commandMonitor[min] === ALERT_MESSAGES_PER_MINUTE) {
        alert('Euro 2016 IRC Bot High usage!', `Over ${ALERT_MESSAGES_PER_MINUTE} commands per minute detected.`);
    }

    if (ircUtil.mapCommand(command, commandMapping, client)) {
        track('command', crypto.createHash('sha256').update(from).digest('hex'), { command, server, nick });
    } else {
        track('invalid_command', crypto.createHash('sha256').update(from).digest('hex'), { command, server, nick });
    }
});

/* eslint-disable no-console */
client.addListener('error', message => {
    alert('ERROR with Euro2016 IRC Bot', message);
    track('error', 0, { server, nick, message });
    console.log('error: ', message);
});
<|endoftext|>


<commit_before>var url = require("url")

var routeTree = {
	path:"/",
	type:"explicit",
	children:[],
	callback:undefined
}

module.exports = Crisscut

function Crisscut(routes){
	if (routes!==undefined){
		routes = correctRoutes(routes)
		addRoutesToRouteTree(routes)
	}
}
Crisscut.prototype.route = function(req,res,errCallback){
	var parsedUrl = url.parse(req.url)
	var rawUrl = parsedUrl.pathname
	var rawArguments = parsedUrl.query
	var routeResult = findRouteFromUrl(rawUrl);
	if (routeResult!=null){
		var methods = routeResult.methods;
		var args = routeResult.args;
		var method = req.method.toLowerCase();
		var parsedUrlArgs = parseArguments(rawArguments);
		if (methods.hasOwnProperty(method)){
			methods[method].apply({},[req,res].concat(args,parsedUrlArgs))
		}
		else if (methods.hasOwnProperty("on")){
			methods.on.apply({},[req,res].concat(args,parsedUrlArgs))
		}
		else{
			errCallback(methodNotAllowed(rawUrl,method));
		}
	}
	else{
		errCallback(pathNotFound(rawUrl))
	}
}

function correctRoutes(routes){
	Object.keys(routes).forEach(function(route){
		var original = route
		var value = routes[route]
		if (route[0]==="/" && route.length>1){ //Routes must not begin with a /, unless they are /
			route = route.substring(1)
		}
		if (route[route.length-1]==="/" && route.length>1){ //Routes must not end with a /, unless of course, they are to the homepage.
			route = route.substring(0,route.length-1)
		}
		if (route!=original){
			delete routes[original]
			routes[route] = value
		}
		if (typeof routes[route] === "object"){
			Object.keys(routes[route]).forEach(function(item){
				if (item!=item.toLowerCase()){
					var value = routes[route][item]
					delete routes[route][item]
					routes[route][item.toLowerCase()] = value
				}
			})
		}
	})
	return routes
}

function addRouteToRouteTree(route,functions,parentNode){
	if (route.length===0){
		return; //No point in adding a route that literally is nothing.
	}
	if (route==="/"){ // We already have a root element pre-made, we can just add a function in
		routeTree.functions = functions
		return;
	}
	var routeSplit = route.split("/")
	if (parentNode===undefined || parentNode===null){
		parentNode = routeTree
	}
	var index = findObjectWithPropertyValueInArray(parentNode.children, "path",routeSplit[0])
	if (index>-1){
		routeSplit.shift()
		addRouteToRouteTree(routeSplit.join("/"), functions, parentNode.children[index])
	}
	else{
		var type = routeSplit[0][0]===":" ? "variable":"explicit"
		if (type==="variable"){
			type = routeSplit[0][1]==="(" && routeSplit[0][routeSplit[0].length-1]===")" ? "regex":"variable"
		}
		if (type==="variable"){
			index = findObjectWithPropertyValueInArray(parentNode.children, "type", "variable")
			if (index>-1){
				routeSplit.shift()
				addRouteToRouteTree(routeSplit.join("/"), functions, routeSplit.children[index])
			}
			else{
				var leaf = createLeaf(routeSplit[0], type)
				parentNode.children.push(leaf)
				routeSplit.shift()
				if (routeSplit.length>0){
					addRouteToRouteTree(routeSplit.join("/"), functions, leaf)
				}
				else{
					leaf.functions = functions
				}
			}
		}
		else{
			var leaf = createLeaf(routeSplit[0], type)
			parentNode.children.push(leaf)
			routeSplit.shift()
			if (routeSplit.length>0){
				addRouteToRouteTree(routeSplit.join("/"), functions, leaf)
			}
			else{
				leaf.functions = functions
			}
		}
	}
}

function addRoutesToRouteTree(routes){
	Object.keys(routes).forEach(function(route){
		addRouteToRouteTree(route,routes[route])
	})
}

function findRouteFromUrl(url,parentNode,args){
	if (parentNode===null || parentNode===undefined){
		parentNode = routeTree
	}
	if (args===null || args===undefined){
		args = []
	}
	if (url==="/"){
		if (parentNode.functions==null){
			return null;
		}
		else{
			return {
				methods:parentNode.functions,
				args:args
			}
		}
	}
	else{
		if (url[0]==="/"){
			url = url.substring(1);
		}
		if (url[url.length-1]==="/"){
			url = url.substring(0,url.length-1)
		}

	}
	var urlSplit = url.split("/")
	var index = findObjectWithPropertyValueInArray(parentNode.children, "path", urlSplit[0])
	//If we have an explicit route, use it, otherwise, we need to do some searching.
	if (index>-1){
		urlSplit.shift()
		if (urlSplit.length>0){
			return findRouteFromUrl(urlSplit.join("/"), parentNode.children[index],args)
		}
		else{
			var methods = parentNode.children[index].functions
			if (methods===null || methods===undefined){
				return null
			}

			return {
				methods:methods,
				args:args
			}
		}
	}
	else{
		var regexIndexes = findObjectsWithPropertyValueInArray(parentNode.children, "type", "regex")
		var variableIndex = findObjectWithPropertyValueInArray(parentNode.children, "type", "variable") //There should only ever be one variable in the tree.
		for (var i=0; i<regexIndexes.length; i++){
			var index = regexIndexes[i];
			var match = urlSplit[0].match(parentNode.children[index].path.substring(1));
			if (match && match[0]===urlSplit[0]){
				args.push(urlSplit[0])
				urlSplit.shift()
				if (urlSplit.length>0){
					return findRouteFromUrl(urlSplit.join("/"), parentNode.children[index],args)
				}
				else{
					var methods = parentNode.children[index].functions
					if (methods===null || methods===undefined){
						return null
					}

					return {
						methods:methods,
						args:args
					}
				}
			}
		}
		//If we haven't matched regex, we can see if we have a variable. otherwise, we return null.
		if (variableIndex>-1){
			args.push(urlSplit[0])
			urlSplit.shift()
			if (urlSplit.length>0){
				return findRouteFromUrl(urlSplit.join("/"),parentNode.chldren[variableIndex],args)
			}
			else{
				var methods = parentNode.children[index].functions
				if (methods===null || methods===undefined){
					return null
				}

				return {
					methods:methods,
					args:args
				}
			}
		}
	}
}

function createLeaf(name,type,func){
	return {
		path:name,
		type:type,
		children:[],
		func:func,
	}
}

function parseArguments(queryString){
	if (queryString[0]==="?"){
		queryString = queryString.substring(1)
	}
	var andSplit = queryString.split("&")
	var parsedArgs = {}
	andSplit.forEach(function(item){
		var split = item.split("=")
		parsedArgs[split[0]]=split[1]
	});
	return parsedArgs
}

function pathNotFound(path){
	return {
		errorCode:404,
		error:path+" is not a known route."
	}
}
function methodNotAllowed(path,method){
	return {
		errorCode:405,
		error: method+" is not allowed on "+path
	}

}
function arrayHasObjectWithProperty(array,property){
	for (var i = 0; i<array.length; i++){
		if (array[i].hasOwnProperty(property)){
			return true
		}
	}
	return false
}

function arrayHasObjectWithPropertyValue(array,property,value){
	for (var i = 0; i<array.length; i++){
		if (array[i].hasOwnProperty(property) && array[i][property]===value){
			return false
		}
	}
	return true
}

function findObjectWithPropertyValueInArray(array,property,value){
	for (var i = 0; i<array.length; i++){
		if (array[i].hasOwnProperty(property) && array[i][property]===value){
			return i
		}
	}
	return -1
}

function findObjectsWithPropertyValueInArray(array,property,value){
	var results = []
	for (var i = 0; i<array.length; i++){
		if (array[i].hasOwnProperty(property) && array[i][property]===value){
			results.push(i)
		}
	}
	return results
}

function clone(object){
	return JSON.parse(JSON.stringify(object))
}
<commit_msg>fix bug where not having a querystring would throw an exception<commit_after>var url = require("url")

var routeTree = {
	path:"/",
	type:"explicit",
	children:[],
	callback:undefined
}

module.exports = Crisscut

function Crisscut(routes){
	if (routes!==undefined){
		routes = correctRoutes(routes)
		addRoutesToRouteTree(routes)
	}
}
Crisscut.prototype.route = function(req,res,errCallback){
	var parsedUrl = url.parse(req.url)
	var rawUrl = parsedUrl.pathname
	var rawArguments = parsedUrl.query
	var routeResult = findRouteFromUrl(rawUrl);
	if (routeResult!=null){
		var methods = routeResult.methods;
		var args = routeResult.args;
		var method = req.method.toLowerCase();
		var parsedUrlArgs = rawArguments!=null ? parseArguments(rawArguments):null;
		if (methods.hasOwnProperty(method)){
			methods[method].apply({},[req,res].concat(args,parsedUrlArgs))
		}
		else if (methods.hasOwnProperty("on")){
			methods.on.apply({},[req,res].concat(args,parsedUrlArgs))
		}
		else{
			errCallback(methodNotAllowed(rawUrl,method));
		}
	}
	else{
		errCallback(pathNotFound(rawUrl))
	}
}

function correctRoutes(routes){
	Object.keys(routes).forEach(function(route){
		var original = route
		var value = routes[route]
		if (route[0]==="/" && route.length>1){ //Routes must not begin with a /, unless they are /
			route = route.substring(1)
		}
		if (route[route.length-1]==="/" && route.length>1){ //Routes must not end with a /, unless of course, they are to the homepage.
			route = route.substring(0,route.length-1)
		}
		if (route!=original){
			delete routes[original]
			routes[route] = value
		}
		if (typeof routes[route] === "object"){
			Object.keys(routes[route]).forEach(function(item){
				if (item!=item.toLowerCase()){
					var value = routes[route][item]
					delete routes[route][item]
					routes[route][item.toLowerCase()] = value
				}
			})
		}
	})
	return routes
}

function addRouteToRouteTree(route,functions,parentNode){
	if (route.length===0){
		return; //No point in adding a route that literally is nothing.
	}
	if (route==="/"){ // We already have a root element pre-made, we can just add a function in
		routeTree.functions = functions
		return;
	}
	var routeSplit = route.split("/")
	if (parentNode===undefined || parentNode===null){
		parentNode = routeTree
	}
	var index = findObjectWithPropertyValueInArray(parentNode.children, "path",routeSplit[0])
	if (index>-1){
		routeSplit.shift()
		addRouteToRouteTree(routeSplit.join("/"), functions, parentNode.children[index])
	}
	else{
		var type = routeSplit[0][0]===":" ? "variable":"explicit"
		if (type==="variable"){
			type = routeSplit[0][1]==="(" && routeSplit[0][routeSplit[0].length-1]===")" ? "regex":"variable"
		}
		if (type==="variable"){
			index = findObjectWithPropertyValueInArray(parentNode.children, "type", "variable")
			if (index>-1){
				routeSplit.shift()
				addRouteToRouteTree(routeSplit.join("/"), functions, routeSplit.children[index])
			}
			else{
				var leaf = createLeaf(routeSplit[0], type)
				parentNode.children.push(leaf)
				routeSplit.shift()
				if (routeSplit.length>0){
					addRouteToRouteTree(routeSplit.join("/"), functions, leaf)
				}
				else{
					leaf.functions = functions
				}
			}
		}
		else{
			var leaf = createLeaf(routeSplit[0], type)
			parentNode.children.push(leaf)
			routeSplit.shift()
			if (routeSplit.length>0){
				addRouteToRouteTree(routeSplit.join("/"), functions, leaf)
			}
			else{
				leaf.functions = functions
			}
		}
	}
}

function addRoutesToRouteTree(routes){
	Object.keys(routes).forEach(function(route){
		addRouteToRouteTree(route,routes[route])
	})
}

function findRouteFromUrl(url,parentNode,args){
	if (parentNode===null || parentNode===undefined){
		parentNode = routeTree
	}
	if (args===null || args===undefined){
		args = []
	}
	if (url==="/"){
		if (parentNode.functions==null){
			return null;
		}
		else{
			return {
				methods:parentNode.functions,
				args:args
			}
		}
	}
	else{
		if (url[0]==="/"){
			url = url.substring(1);
		}
		if (url[url.length-1]==="/"){
			url = url.substring(0,url.length-1)
		}

	}
	var urlSplit = url.split("/")
	var index = findObjectWithPropertyValueInArray(parentNode.children, "path", urlSplit[0])
	//If we have an explicit route, use it, otherwise, we need to do some searching.
	if (index>-1){
		urlSplit.shift()
		if (urlSplit.length>0){
			return findRouteFromUrl(urlSplit.join("/"), parentNode.children[index],args)
		}
		else{
			var methods = parentNode.children[index].functions
			if (methods===null || methods===undefined){
				return null
			}

			return {
				methods:methods,
				args:args
			}
		}
	}
	else{
		var regexIndexes = findObjectsWithPropertyValueInArray(parentNode.children, "type", "regex")
		var variableIndex = findObjectWithPropertyValueInArray(parentNode.children, "type", "variable") //There should only ever be one variable in the tree.
		for (var i=0; i<regexIndexes.length; i++){
			var index = regexIndexes[i];
			var match = urlSplit[0].match(parentNode.children[index].path.substring(1));
			if (match && match[0]===urlSplit[0]){
				args.push(urlSplit[0])
				urlSplit.shift()
				if (urlSplit.length>0){
					return findRouteFromUrl(urlSplit.join("/"), parentNode.children[index],args)
				}
				else{
					var methods = parentNode.children[index].functions
					if (methods===null || methods===undefined){
						return null
					}

					return {
						methods:methods,
						args:args
					}
				}
			}
		}
		//If we haven't matched regex, we can see if we have a variable. otherwise, we return null.
		if (variableIndex>-1){
			args.push(urlSplit[0])
			urlSplit.shift()
			if (urlSplit.length>0){
				return findRouteFromUrl(urlSplit.join("/"),parentNode.chldren[variableIndex],args)
			}
			else{
				var methods = parentNode.children[index].functions
				if (methods===null || methods===undefined){
					return null
				}

				return {
					methods:methods,
					args:args
				}
			}
		}
	}
}

function createLeaf(name,type,func){
	return {
		path:name,
		type:type,
		children:[],
		func:func,
	}
}

function parseArguments(queryString){
	if (queryString.length===0){
		return null
	}
	if (queryString[0]==="?"){
		queryString = queryString.substring(1)
		if (queryString.length===0){
			return null
		}
	}
	var andSplit = queryString.split("&")
	var parsedArgs = {}
	andSplit.forEach(function(item){
		var split = item.split("=")
		parsedArgs[split[0]]=split[1]
	});
	return parsedArgs
}

function pathNotFound(path){
	return {
		errorCode:404,
		error:path+" is not a known route."
	}
}
function methodNotAllowed(path,method){
	return {
		errorCode:405,
		error: method+" is not allowed on "+path
	}

}
function arrayHasObjectWithProperty(array,property){
	for (var i = 0; i<array.length; i++){
		if (array[i].hasOwnProperty(property)){
			return true
		}
	}
	return false
}

function arrayHasObjectWithPropertyValue(array,property,value){
	for (var i = 0; i<array.length; i++){
		if (array[i].hasOwnProperty(property) && array[i][property]===value){
			return false
		}
	}
	return true
}

function findObjectWithPropertyValueInArray(array,property,value){
	for (var i = 0; i<array.length; i++){
		if (array[i].hasOwnProperty(property) && array[i][property]===value){
			return i
		}
	}
	return -1
}

function findObjectsWithPropertyValueInArray(array,property,value){
	var results = []
	for (var i = 0; i<array.length; i++){
		if (array[i].hasOwnProperty(property) && array[i][property]===value){
			results.push(i)
		}
	}
	return results
}

function clone(object){
	return JSON.parse(JSON.stringify(object))
}
<|endoftext|>