<commit_before>/*
 * Geddy JavaScript Web development framework
 * Copyright 2112 Matthew Eernisse (mde@fleegix.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/

var domain = require('domain')
  , fs = require('fs')
  , path = require('path')
  , model = require('model')
  , controller = require('../controller')
  , format = require('../response/format')
  , utils = require('../utils')
  , cwd = process.cwd()
  , errors = require('../response/errors')
  , init = require('../init')
  , helpers = require('../template/helpers')
  , actionHelpers = require('../template/helpers/action')
  , ErrorController = require('../controller/error_controller').ErrorController
  , StaticFileController =
        require('../controller/static_file_controller').StaticFileController
  , controllerInit = require('../controller/init')
  , InFlight = require('../in_flight').InFlight
  , usingCoffee // Global variable for CoffeeScript
  , logging = require('./logging')
  , templateAdapters = require('../template/adapters')
  , requestHelpers = require('./request_helpers');

// Use Geddy logger as the utility logger
utils.log.registerLogger(geddy.log);

var App = function () {

  this.config = null;
  this.router = null;
  this.modelRegistry = {};
  this.templateRegistry = {};
  this.controllerRegistry = {};

  this.init = function (config, callback) {
    var self = this;

    // Local copy of the config obj for all the init shits
    this.config = config;

    init.init(this, function () {

      // Set up a bunch of aliases
      geddy.inFlight = new InFlight();
      geddy.inflection = utils.inflection;
      geddy.model = model;
      geddy.controller = controller;
      geddy.utils = utils;
      geddy.errors = errors;
      geddy.viewHelpers = {};
      geddy.template = templateAdapters;
      geddy.addFormat = format.addFormat.bind(format);
      geddy.mailer = self.mailer;

      self.start(callback);
    });
  };

  this.handleControllerAction = function (controllerInst, reqUrl, method,
          params, accessTime, reqObj, respObj) {
    var initKeys
      , initializer
      , cb;

    // Async setup steps to allow the controller to handle
    // the request
    initKeys = [
      'cookies'
    , 'i18n'
    , 'inFlight'
    , 'parseBody'
    , 'session'
    ];

    // Mix all the shits onto the controller instance
    utils.mixin(controllerInst, {
      app: this
    , url: reqUrl  // Can we dispense with this? It's in the params
    , method: method // And this?
    , params: params
    , accessTime: accessTime
    , request: reqObj
    , response: respObj
    , name: params.controller
    });

    reqObj.controller = controllerInst;
    respObj.controller = controllerInst;

    cb = function () {
      controllerInst._handleAction.call(controllerInst, params.action);
      // TODO Replace this with readable-stream module for 0.8 support
      if (reqObj.req && typeof reqObj.req.read != 'function') {
        reqObj.sync(); // Flush buffered events and begin emitting
      }
    };
    initializer = new utils.async.Initializer(initKeys, cb);

    // Run all the async setup steps
    initKeys.forEach(function (key) {
      controllerInit[key].call(controllerInst, function () {
        initializer.complete(key);
      });
    });
  };

  this.handleStaticFile = function (staticPath, params, reqUrl, reqObj, respObj) {
    var controllerInst;
    // May be a path to a directory, with or without a trailing
    // slash -- any trailing slash has already been stripped by
    // this point
    if (fs.statSync(staticPath).isDirectory()) {
      // TODO: Make the name of any index file configurable
      staticPath = path.join(staticPath, 'index.html');
      if (utils.file.existsSync(staticPath) && fs.statSync(staticPath).isFile()) {
        controllerInst = new StaticFileController(reqObj, respObj, params);
        controllerInst.respond({
          path: staticPath
        });
      }
      // Directory with no index file
      else {
        this.handleNotFound(reqUrl, params, reqObj, respObj);
      }
    }
    // Path to an actual file. Just serve it up
    else if (fs.statSync(staticPath).isFile()) {
      controllerInst = new StaticFileController(reqObj, respObj, params);
      controllerInst.respond({
        path: staticPath
      });
    }
  };

  this.handleMethodNotAllowed = function (method, reqUrl, params, reqObj, respObj,
      nonMethodRoutes) {
    // build a unique list of acceptable methods for this resource
    var acceptableMethods = {}
      , err
      , controllerInst;

    nonMethodRoutes.map(function (params) {
      acceptableMethods[params.method] = true;
    });
    acceptableMethods = Object.keys(acceptableMethods);

    // send a friendly error response
    throw new errors.MethodNotAllowedError(
      method + ' method not allowed. Please consider ' +
      acceptableMethods.join(', ').replace(/,\s(\w+)$/," or $1") +
      ' instead.');
  };

  this.handleNotFound = function (reqUrl, params, reqObj, respObj) {
    throw new errors.NotFoundError(reqUrl + ' not found.');
  };

  this.handleNoMatchedRoute = function (method, reqUrl, params, reqObj, respObj) {
    var staticPath
      , controllerInst
      , nonMethodRoutes;

    // Get the path to the file, decoding the request URI
    staticPath = this.config.staticFilePath + decodeURIComponent(reqUrl);
    // Ignore querystring
    staticPath = staticPath.split('?')[0];

    // Static?
    if (utils.file.existsSync(staticPath)) {
      this.handleStaticFile(staticPath, params, reqUrl, reqObj, respObj, params);
    }
    else {
      nonMethodRoutes = this.router.all(reqUrl);

      // Good route, wrong verb -- 405?
      if (nonMethodRoutes.length) {
        this.handleMethodNotAllowed(method, reqUrl, params, reqObj, respObj,
          nonMethodRoutes);
      }
      // Nada, 404
      else {
        this.handleNotFound(reqUrl, params, reqObj, respObj);
      }
    }
  };
  this.handleNoAction = function (params, reqObj, respObj) {
    throw new errors.InternalServerError('No ' + params.action +
        ' action on ' + params.controller + ' controller.');
  };

  this.handleNoController = function (params, reqObj, respObj) {
    throw new errors.InternalServerError('controller ' +
        params.controller + ' not found.');
  };

  this.start = function (callback) {
    var self = this
      , ctors = this.controllerRegistry
      , controllerActionTimers = {};

    // Handle the requests
    // ==================
    geddy.server.addListener('request', function (req, resp) {
      var dmn = domain.create()
        , caught = false
        , badRequestErr
        , controllerInst
        , reqObj
        , respObj;

      // Attempt a nice, high-fi customizable error
      // Only try this once -- if something fails during the
      // rendering process for the error, fall back to a low-fi
      // fool-proof error to display that
      dmn.on('error', function (err) {
        var serverErr
          , controllerInst;

        if (caught) {
          return errors.respond(err, respObj);
        }

        caught = true;

        if (err.statusCode) {
          serverErr = err;
        }
        else {
          serverErr = new errors.InternalServerError(err.message, err.stack);
        }

        try {
          controllerInst = new ErrorController(reqObj, respObj);
          controllerInst.respondWith(serverErr);
        }
        // Catch sync errors in the error-rendering process
        // Respond with a low-fi fool-proof err
        // Async ones will be handled by re-entering this domain
        // on-error handler
        catch(e) {
          errors.respond(e, respObj);
        }
      });

      dmn.add(req);
      dmn.add(resp);

      // Parsing URLs may result in a bad request -- if this happens,
      // throw the error inside the domain code, so we can get a nice,
      // customizable error message
      try {
        reqObj = requestHelpers.enhanceRequest(req);
        respObj = requestHelpers.enhanceResponse(resp);
      }
      catch (err) {
        req.url = '/';
        reqObj = requestHelpers.enhanceRequest(req);
        respObj = requestHelpers.enhanceResponse(resp);
        badRequestErr = new errors.BadRequestError(err.message, err.stack);
        controllerInst = new ErrorController(reqObj, respObj);
        return controllerInst.respondWith(badRequestErr);
      }

      dmn.add(reqObj);
      dmn.add(respObj);

      dmn.run(function () {
        var reqUrl
          , urlParams
          , method
          , accessTime
          , params
          , controllerInst;

        // Parse out some needed request properties
        reqUrl = requestHelpers.normalizeUrl(req);
        urlParams = requestHelpers.getUrlParams(reqUrl);
        method = requestHelpers.getMethod(reqUrl, urlParams, req);
        accessTime = requestHelpers.getAccessTime();

        // Now only for timeout, domains are handling errors
        requestHelpers.initInFlight(reqObj, respObj, method, accessTime);

        // TODO: Allow custom formats
        logging.initRequestLogger(reqUrl, reqObj, respObj, method, accessTime);

        params = requestHelpers.getParams(self.router, reqUrl, method);
        // Route/method combo give us something valid?
        if (params) {

          controllerInst = controller.create(params.controller);
          // Valid controller?
          if (controllerInst) {
            // Enhance the parsed params with URL params
            geddy.mixin(params, urlParams);

            // FIXME: Backward-compat shim for old action-name 'destroy'
            if (params.action == 'destroy' &&
                typeof controllerInst.destroy != 'function') {
              params.action = 'remove';
            }

            if (typeof controllerInst[params.action] == 'function') {
              self.handleControllerAction(controllerInst, reqUrl, method,
                      params, accessTime, reqObj, respObj);
            }
            // No action, 500 error
            else {
              self.handleNoAction(params, reqObj, respObj);
            }
          }
          // No controller, 500 error
          else {
            self.handleNoController(params, reqObj, respObj);
          }
        }
        // Either 405, static, or 404
        else {
          self.handleNoMatchedRoute(method, reqUrl, params, reqObj, respObj);
        }

      });
    });

    callback();
  };

};

module.exports.App = App;

<commit_msg>Move shortcut aliases back outside app init<commit_after>/*
 * Geddy JavaScript Web development framework
 * Copyright 2112 Matthew Eernisse (mde@fleegix.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/

var domain = require('domain')
  , fs = require('fs')
  , path = require('path')
  , model = require('model')
  , controller = require('../controller')
  , format = require('../response/format')
  , utils = require('../utils')
  , cwd = process.cwd()
  , errors = require('../response/errors')
  , init = require('../init')
  , helpers = require('../template/helpers')
  , actionHelpers = require('../template/helpers/action')
  , ErrorController = require('../controller/error_controller').ErrorController
  , StaticFileController =
        require('../controller/static_file_controller').StaticFileController
  , controllerInit = require('../controller/init')
  , InFlight = require('../in_flight').InFlight
  , usingCoffee // Global variable for CoffeeScript
  , logging = require('./logging')
  , templateAdapters = require('../template/adapters')
  , requestHelpers = require('./request_helpers');

// Use Geddy logger as the utility logger
utils.log.registerLogger(geddy.log);

// Set up a bunch of aliases
geddy.model = model;
geddy.controller = controller;
geddy.inFlight = new InFlight();
geddy.inflection = utils.inflection;
geddy.utils = utils;
geddy.errors = errors;
geddy.viewHelpers = {};
geddy.template = templateAdapters;
geddy.addFormat = format.addFormat.bind(format);

var App = function () {

  this.config = null;
  this.router = null;
  this.modelRegistry = {};
  this.templateRegistry = {};
  this.controllerRegistry = {};

  this.init = function (config, callback) {
    var self = this;

    // Local copy of the config obj for all the init shits
    this.config = config;

    init.init(this, function () {
      self.start(callback);
    });
  };

  this.handleControllerAction = function (controllerInst, reqUrl, method,
          params, accessTime, reqObj, respObj) {
    var initKeys
      , initializer
      , cb;

    // Async setup steps to allow the controller to handle
    // the request
    initKeys = [
      'cookies'
    , 'i18n'
    , 'inFlight'
    , 'parseBody'
    , 'session'
    ];

    // Mix all the shits onto the controller instance
    utils.mixin(controllerInst, {
      app: this
    , url: reqUrl  // Can we dispense with this? It's in the params
    , method: method // And this?
    , params: params
    , accessTime: accessTime
    , request: reqObj
    , response: respObj
    , name: params.controller
    });

    reqObj.controller = controllerInst;
    respObj.controller = controllerInst;

    cb = function () {
      controllerInst._handleAction.call(controllerInst, params.action);
      // TODO Replace this with readable-stream module for 0.8 support
      if (reqObj.req && typeof reqObj.req.read != 'function') {
        reqObj.sync(); // Flush buffered events and begin emitting
      }
    };
    initializer = new utils.async.Initializer(initKeys, cb);

    // Run all the async setup steps
    initKeys.forEach(function (key) {
      controllerInit[key].call(controllerInst, function () {
        initializer.complete(key);
      });
    });
  };

  this.handleStaticFile = function (staticPath, params, reqUrl, reqObj, respObj) {
    var controllerInst;
    // May be a path to a directory, with or without a trailing
    // slash -- any trailing slash has already been stripped by
    // this point
    if (fs.statSync(staticPath).isDirectory()) {
      // TODO: Make the name of any index file configurable
      staticPath = path.join(staticPath, 'index.html');
      if (utils.file.existsSync(staticPath) && fs.statSync(staticPath).isFile()) {
        controllerInst = new StaticFileController(reqObj, respObj, params);
        controllerInst.respond({
          path: staticPath
        });
      }
      // Directory with no index file
      else {
        this.handleNotFound(reqUrl, params, reqObj, respObj);
      }
    }
    // Path to an actual file. Just serve it up
    else if (fs.statSync(staticPath).isFile()) {
      controllerInst = new StaticFileController(reqObj, respObj, params);
      controllerInst.respond({
        path: staticPath
      });
    }
  };

  this.handleMethodNotAllowed = function (method, reqUrl, params, reqObj, respObj,
      nonMethodRoutes) {
    // build a unique list of acceptable methods for this resource
    var acceptableMethods = {}
      , err
      , controllerInst;

    nonMethodRoutes.map(function (params) {
      acceptableMethods[params.method] = true;
    });
    acceptableMethods = Object.keys(acceptableMethods);

    // send a friendly error response
    throw new errors.MethodNotAllowedError(
      method + ' method not allowed. Please consider ' +
      acceptableMethods.join(', ').replace(/,\s(\w+)$/," or $1") +
      ' instead.');
  };

  this.handleNotFound = function (reqUrl, params, reqObj, respObj) {
    throw new errors.NotFoundError(reqUrl + ' not found.');
  };

  this.handleNoMatchedRoute = function (method, reqUrl, params, reqObj, respObj) {
    var staticPath
      , controllerInst
      , nonMethodRoutes;

    // Get the path to the file, decoding the request URI
    staticPath = this.config.staticFilePath + decodeURIComponent(reqUrl);
    // Ignore querystring
    staticPath = staticPath.split('?')[0];

    // Static?
    if (utils.file.existsSync(staticPath)) {
      this.handleStaticFile(staticPath, params, reqUrl, reqObj, respObj, params);
    }
    else {
      nonMethodRoutes = this.router.all(reqUrl);

      // Good route, wrong verb -- 405?
      if (nonMethodRoutes.length) {
        this.handleMethodNotAllowed(method, reqUrl, params, reqObj, respObj,
          nonMethodRoutes);
      }
      // Nada, 404
      else {
        this.handleNotFound(reqUrl, params, reqObj, respObj);
      }
    }
  };
  this.handleNoAction = function (params, reqObj, respObj) {
    throw new errors.InternalServerError('No ' + params.action +
        ' action on ' + params.controller + ' controller.');
  };

  this.handleNoController = function (params, reqObj, respObj) {
    throw new errors.InternalServerError('controller ' +
        params.controller + ' not found.');
  };

  this.start = function (callback) {
    var self = this
      , ctors = this.controllerRegistry
      , controllerActionTimers = {};

    // Handle the requests
    // ==================
    geddy.server.addListener('request', function (req, resp) {
      var dmn = domain.create()
        , caught = false
        , badRequestErr
        , controllerInst
        , reqObj
        , respObj;

      // Attempt a nice, high-fi customizable error
      // Only try this once -- if something fails during the
      // rendering process for the error, fall back to a low-fi
      // fool-proof error to display that
      dmn.on('error', function (err) {
        var serverErr
          , controllerInst;

        if (caught) {
          return errors.respond(err, respObj);
        }

        caught = true;

        if (err.statusCode) {
          serverErr = err;
        }
        else {
          serverErr = new errors.InternalServerError(err.message, err.stack);
        }

        try {
          controllerInst = new ErrorController(reqObj, respObj);
          controllerInst.respondWith(serverErr);
        }
        // Catch sync errors in the error-rendering process
        // Respond with a low-fi fool-proof err
        // Async ones will be handled by re-entering this domain
        // on-error handler
        catch(e) {
          errors.respond(e, respObj);
        }
      });

      dmn.add(req);
      dmn.add(resp);

      // Parsing URLs may result in a bad request -- if this happens,
      // throw the error inside the domain code, so we can get a nice,
      // customizable error message
      try {
        reqObj = requestHelpers.enhanceRequest(req);
        respObj = requestHelpers.enhanceResponse(resp);
      }
      catch (err) {
        req.url = '/';
        reqObj = requestHelpers.enhanceRequest(req);
        respObj = requestHelpers.enhanceResponse(resp);
        badRequestErr = new errors.BadRequestError(err.message, err.stack);
        controllerInst = new ErrorController(reqObj, respObj);
        return controllerInst.respondWith(badRequestErr);
      }

      dmn.add(reqObj);
      dmn.add(respObj);

      dmn.run(function () {
        var reqUrl
          , urlParams
          , method
          , accessTime
          , params
          , controllerInst;

        // Parse out some needed request properties
        reqUrl = requestHelpers.normalizeUrl(req);
        urlParams = requestHelpers.getUrlParams(reqUrl);
        method = requestHelpers.getMethod(reqUrl, urlParams, req);
        accessTime = requestHelpers.getAccessTime();

        // Now only for timeout, domains are handling errors
        requestHelpers.initInFlight(reqObj, respObj, method, accessTime);

        // TODO: Allow custom formats
        logging.initRequestLogger(reqUrl, reqObj, respObj, method, accessTime);

        params = requestHelpers.getParams(self.router, reqUrl, method);
        // Route/method combo give us something valid?
        if (params) {

          controllerInst = controller.create(params.controller);
          // Valid controller?
          if (controllerInst) {
            // Enhance the parsed params with URL params
            geddy.mixin(params, urlParams);

            // FIXME: Backward-compat shim for old action-name 'destroy'
            if (params.action == 'destroy' &&
                typeof controllerInst.destroy != 'function') {
              params.action = 'remove';
            }

            if (typeof controllerInst[params.action] == 'function') {
              self.handleControllerAction(controllerInst, reqUrl, method,
                      params, accessTime, reqObj, respObj);
            }
            // No action, 500 error
            else {
              self.handleNoAction(params, reqObj, respObj);
            }
          }
          // No controller, 500 error
          else {
            self.handleNoController(params, reqObj, respObj);
          }
        }
        // Either 405, static, or 404
        else {
          self.handleNoMatchedRoute(method, reqUrl, params, reqObj, respObj);
        }

      });
    });

    callback();
  };

};

module.exports.App = App;

<|endoftext|>


<commit_before>module.exports = asyncMap
function asyncMap (list, fn, cb_) {
  var n = list.length
    , results = []
    , errState = null
  function cb (er, data) {
    if (errState) return
    if (er) return cb_(errState = er)
    results.push(data)
    if (-- n === 0)
      return cb_(null, results)
  }
  if (list.length === 0) return cb_(null, [])
  list.forEach(function (l) {
    fn(l, cb)
  })
}
<commit_msg>Allow for more than one step, like npm does<commit_after>
/*
usage:

// do something to a list of things
asyncMap(myListOfStuff, function (thing, cb) { doSomething(thing.foo, cb) }, cb)
// do more than one thing to each item
asyncMap(list, fooFn, barFn, cb)

*/

module.exports = asyncMap

function asyncMap () {
  var steps = Array.prototype.slice.call(arguments)
    , list = steps.shift() || []
    , cb_ = steps.pop()
  if (typeof cb_ !== "function") throw new Error(
    "No callback provided to asyncMap")
  if (!list) return cb_(null, [])
  if (!Array.isArray(list)) list = [list]
  var n = steps.length
    , data = [] // 2d array
    , errState = null
    , l = list.length
    , a = l * n
  if (!a) return cb_(null, [])
  function cb (er) {
    if (errState) return
    var argLen = arguments.length
    for (var i = 1; i < argLen; i ++) if (arguments[i] !== undefined) {
      data[i - 1] = (data[i - 1] || []).concat(arguments[i])
    }
    // see if any new things have been added.
    if (list.length > l) {
      var newList = list.slice(l)
      a += (list.length - l) * n
      l = list.length
      process.nextTick(function () {
        newList.forEach(function (ar) {
          steps.forEach(function (fn) { fn(ar, cb) })
        })
      })
    }

    if (er || --a === 0) {
      errState = er
      cb_.apply(null, [errState].concat(data))
    }
  }
  // expect the supplied cb function to be called
  // "n" times for each thing in the array.
  list.forEach(function (ar) {
    steps.forEach(function (fn) { fn(ar, cb) })
  })
}
<|endoftext|>


<commit_before>var crypto = require('crypto');
var cluster = require('cluster');
var ms = require('ms');
var config = require('./config');
var errors = require('./errors');
var log = require('./log');
var clients = config.clients || { };

// Get the set of valid host names.
var validHostnameSet =
	config.validHostnames &&
	Array.isArray(config.validHostnames) &&
	config.validHostnames.reduce(function (set, value, index) {
		set[value] = true;
		return set;
	}, { });

// Get the IP address header.
var ipHeader = null;
if (config.ipHeader) {
	ipHeader = config.ipHeader;
	if (cluster.isMaster) {
		log.info('Using rate limiting IP header "' + ipHeader + '"');
	}
}

// Get the rate limiter instance.
var rateLimiter = null;
if (config.rateLimiter) {
	var limiterType = Object.keys(config.rateLimiter);
	if (limiterType.length !== 1) {
		throw new Error('Exactly one "rateLimiter" must be defined in config.');
	}
	limiterType = limiterType[0];
	var RateLimiter = require('./rate-limiter/' + limiterType + '.js');
	rateLimiter = new RateLimiter(config.rateLimiter[limiterType]);
}

// Rate limit unique ID builders.
var rateLimitUniqueBuilder = {
	ip: function rateLimitUniqueBuilderIp(req) {
		return (ipHeader && req.header(ipHeader)) || req.ip;
	}
};

function verifySignature(signature, path, key) {
	var hash = crypto.createHmac('sha1', key).update(path).digest('hex');
	return hash === signature;
}

function buildClient(clientName, client) {

	// Read secret key or unsafe options.

	if (client.secret && client.unsafe) {
		throw new Error('Client "' + clientName + '" cannot have both "secret" and "unsafe" options.');
	}
	if (!client.secret && !client.unsafe) {
		throw new Error('Client "' + clientName + '" must either "secret" or "unsafe" option.');
	}
	if (client.secret) {
		client.signatureRequired = true;
		client.verifySignature = function clientVerifySignature(signature, path) {
			return verifySignature(signature, path, client.secret);
		};
	} else {
		client.signatureRequired = false;
	}

	// Read rate limit options.

	if (!client.rateLimit) {
		throw new Error('Client "' + clientName + '" "rateLimit" must be an object or "none".');
	}
	if (client.rateLimit === 'none') {

		client.performRateLimit = false;

	} else {

		if (!rateLimiter) {
			throw new Error('No "rateLimiter" defined in config.');
		}
		if (!client.rateLimit.by) {
			throw new Error('Missing "by" option for "rateLimit" config in client "' + clientName + '".');
		}
		var rateLimitBuilder = rateLimitUniqueBuilder[client.rateLimit.by];
		if (!rateLimitBuilder) {
			throw new Error('Invalid "by" option for "rateLimit" config in client "' + clientName + '".');
		}
		if (!client.rateLimit.max || 'number' !== typeof client.rateLimit.max) {
			throw new Error('Missing/invalid "max" option for "rateLimit" config in client "' + clientName + '".');
		}
		var perMillis = -1;
		var perMillisUnits = {
			perMillis: 1,
			perSeconds: 1000,
			perMinutes: 60 * 1000,
			perHours: 60 * 60 * 1000,
			perDays: 24 * 60 * 60 * 1000
		};
		Object.keys(client.rateLimit).forEach(function (rateLimitKey) {
			if (['by', 'max'].indexOf(rateLimitKey) >= 0) {
				return;
			}
			if (!perMillisUnits[rateLimitKey]) {
				throw new Error('Unknown "' + rateLimitKey + '" option for "rateLimit" config in client "' + clientName + '".');
			}
			if (perMillis >= 0) {
				throw new Error('Cannot define more than one "per" time for "rateLimit" config in client "' + clientName + '".');
			}
			perMillis = Math.round(client.rateLimit[rateLimitKey] * perMillisUnits[rateLimitKey]);
		});
		if (perMillis < 0) {
			throw new Error(
				'Must have at least one time ("perMinutes", "perHours", etc.) ' +
				'defined for "rateLimit" config in client "' + clientName + '".'
			);
		}
		client.performRateLimit = true;
		client.getRateLimitId = function () {
			return clientName + ':' + rateLimitBuilder.apply(this, arguments);
		};
		client.rateLimitMax = client.rateLimit.max;
		client.rateLimitMillis = perMillis;
		client.rateLimit = rateLimiter.create(client.rateLimitMax, client.rateLimitMillis);
	}
}

Object.keys(clients).forEach(function (clientName) {
	buildClient(clientName, clients[clientName]);
});

function authorize(req, res, next) {

	// Verify the hostname.

	if (validHostnameSet && !validHostnameSet[req.hostname]) {
		return next(new errors.InvalidHost('Unknown host'));
	}

	// Verify the arguments.

	if (!req.params || !req.params.client) {
		return next(new errors.InvalidClient('Client not provided'));
	}
	var client = clients[req.params.client];
	if (!client) {
		return next(new errors.InvalidClient('Invalid client'));
	}
	if (!req.params.operations) {
		return next(new errors.MissingOperations('Operations not provided'));
	}
	if (!req.params.imageUrl) {
		return next(new errors.MissingImageUrl('Image URL not provided'));
	}

	// Verify the signature.

	if (client.signatureRequired) {
		if (!req.params.signature) {
			return next(new errors.MissingSignature('Signature not provided'));
		}
		if (!client.verifySignature(req.params.signature, req.params.operations + '/' + req.params.imageUrl)) {
			return next(new errors.InvalidSignature('Bad signature'));
		}
	} else if (req.params.signature) {
		return next(new errors.UnneededSignature('Signature not needed'));
	}

	// Check the rate limit.

	if (client.performRateLimit) {
		var rateLimitId = client.getRateLimitId(req);
		client.rateLimit(rateLimitId, function (e, limit) {

			if (e) {
				return next(e);
			}

			res.set('x-ratelimit-limit', limit.allowMax);
			res.set('x-ratelimit-remaining', Math.max(0, limit.allowRemaining - 1));
			res.set('x-ratelimit-reset', limit.resetMillis);

			// We still have requests left for this rate limit.
			if (limit.allowRemaining) {
				return next();
			}

			// Rate limit reached.
			var delta = (limit.resetMillis * 1000) - Date.now() | 0;
			var after = limit.resetMillis - (Date.now() / 1000) | 0;
			res.set('retry-after', after);
			return next(new errors.RateLimitReached(
				'Rate limit exceeded, retry in ' + ms(delta, { long: true })
			));
		});

		return;
	}

	return next();
}

module.exports = authorize;<commit_msg>Generate signature based on original URL.<commit_after>var crypto = require('crypto');
var cluster = require('cluster');
var ms = require('ms');
var config = require('./config');
var errors = require('./errors');
var log = require('./log');
var clients = config.clients || { };

// Get the set of valid host names.
var validHostnameSet =
	config.validHostnames &&
	Array.isArray(config.validHostnames) &&
	config.validHostnames.reduce(function (set, value, index) {
		set[value] = true;
		return set;
	}, { });

// Get the IP address header.
var ipHeader = null;
if (config.ipHeader) {
	ipHeader = config.ipHeader;
	if (cluster.isMaster) {
		log.info('Using rate limiting IP header "' + ipHeader + '"');
	}
}

// Get the rate limiter instance.
var rateLimiter = null;
if (config.rateLimiter) {
	var limiterType = Object.keys(config.rateLimiter);
	if (limiterType.length !== 1) {
		throw new Error('Exactly one "rateLimiter" must be defined in config.');
	}
	limiterType = limiterType[0];
	var RateLimiter = require('./rate-limiter/' + limiterType + '.js');
	rateLimiter = new RateLimiter(config.rateLimiter[limiterType]);
}

// Rate limit unique ID builders.
var rateLimitUniqueBuilder = {
	ip: function rateLimitUniqueBuilderIp(req) {
		return (ipHeader && req.header(ipHeader)) || req.ip;
	}
};

function verifySignature(signature, path, key) {
	var hash = crypto.createHmac('sha1', key).update(path).digest('hex');
	return hash === signature;
}

function buildClient(clientName, client) {

	// Read secret key or unsafe options.

	if (client.secret && client.unsafe) {
		throw new Error('Client "' + clientName + '" cannot have both "secret" and "unsafe" options.');
	}
	if (!client.secret && !client.unsafe) {
		throw new Error('Client "' + clientName + '" must either "secret" or "unsafe" option.');
	}
	if (client.secret) {
		client.signatureRequired = true;
		client.verifySignature = function clientVerifySignature(signature, path) {
			return verifySignature(signature, path, client.secret);
		};
	} else {
		client.signatureRequired = false;
	}

	// Read rate limit options.

	if (!client.rateLimit) {
		throw new Error('Client "' + clientName + '" "rateLimit" must be an object or "none".');
	}
	if (client.rateLimit === 'none') {

		client.performRateLimit = false;

	} else {

		if (!rateLimiter) {
			throw new Error('No "rateLimiter" defined in config.');
		}
		if (!client.rateLimit.by) {
			throw new Error('Missing "by" option for "rateLimit" config in client "' + clientName + '".');
		}
		var rateLimitBuilder = rateLimitUniqueBuilder[client.rateLimit.by];
		if (!rateLimitBuilder) {
			throw new Error('Invalid "by" option for "rateLimit" config in client "' + clientName + '".');
		}
		if (!client.rateLimit.max || 'number' !== typeof client.rateLimit.max) {
			throw new Error('Missing/invalid "max" option for "rateLimit" config in client "' + clientName + '".');
		}
		var perMillis = -1;
		var perMillisUnits = {
			perMillis: 1,
			perSeconds: 1000,
			perMinutes: 60 * 1000,
			perHours: 60 * 60 * 1000,
			perDays: 24 * 60 * 60 * 1000
		};
		Object.keys(client.rateLimit).forEach(function (rateLimitKey) {
			if (['by', 'max'].indexOf(rateLimitKey) >= 0) {
				return;
			}
			if (!perMillisUnits[rateLimitKey]) {
				throw new Error('Unknown "' + rateLimitKey + '" option for "rateLimit" config in client "' + clientName + '".');
			}
			if (perMillis >= 0) {
				throw new Error('Cannot define more than one "per" time for "rateLimit" config in client "' + clientName + '".');
			}
			perMillis = Math.round(client.rateLimit[rateLimitKey] * perMillisUnits[rateLimitKey]);
		});
		if (perMillis < 0) {
			throw new Error(
				'Must have at least one time ("perMinutes", "perHours", etc.) ' +
				'defined for "rateLimit" config in client "' + clientName + '".'
			);
		}
		client.performRateLimit = true;
		client.getRateLimitId = function () {
			return clientName + ':' + rateLimitBuilder.apply(this, arguments);
		};
		client.rateLimitMax = client.rateLimit.max;
		client.rateLimitMillis = perMillis;
		client.rateLimit = rateLimiter.create(client.rateLimitMax, client.rateLimitMillis);
	}
}

Object.keys(clients).forEach(function (clientName) {
	buildClient(clientName, clients[clientName]);
});

var firstPathChunkRegex = new RegExp('^/[^/]+/');

function authorize(req, res, next) {

	// Verify the hostname.

	if (validHostnameSet && !validHostnameSet[req.hostname]) {
		return next(new errors.InvalidHost('Unknown host'));
	}

	// Verify the arguments.

	if (!req.params || !req.params.client) {
		return next(new errors.InvalidClient('Client not provided'));
	}
	var client = clients[req.params.client];
	if (!client) {
		return next(new errors.InvalidClient('Invalid client'));
	}
	if (!req.params.operations) {
		return next(new errors.MissingOperations('Operations not provided'));
	}
	if (!req.params.imageUrl) {
		return next(new errors.MissingImageUrl('Image URL not provided'));
	}

	// Verify the signature.

	if (client.signatureRequired) {
		if (!req.params.signature) {
			return next(new errors.MissingSignature('Signature not provided'));
		}
		var path = req.originalUrl.replace(firstPathChunkRegex, '');
		if (!client.verifySignature(req.params.signature, path)) {
			return next(new errors.InvalidSignature('Bad signature'));
		}
	} else if (req.params.signature) {
		return next(new errors.UnneededSignature('Signature not needed'));
	}

	// Check the rate limit.

	if (client.performRateLimit) {
		var rateLimitId = client.getRateLimitId(req);
		client.rateLimit(rateLimitId, function (e, limit) {

			if (e) {
				return next(e);
			}

			res.set('x-ratelimit-limit', limit.allowMax);
			res.set('x-ratelimit-remaining', Math.max(0, limit.allowRemaining - 1));
			res.set('x-ratelimit-reset', limit.resetMillis);

			// We still have requests left for this rate limit.
			if (limit.allowRemaining) {
				return next();
			}

			// Rate limit reached.
			var delta = (limit.resetMillis * 1000) - Date.now() | 0;
			var after = limit.resetMillis - (Date.now() / 1000) | 0;
			res.set('retry-after', after);
			return next(new errors.RateLimitReached(
				'Rate limit exceeded, retry in ' + ms(delta, { long: true })
			));
		});

		return;
	}

	return next();
}

module.exports = authorize;<|endoftext|>


<commit_before>/**
 *  class BabelFish
 *
 *  Internalization and localization library that makes i18n and l10n fun again.
 *
 *  ##### Example
 *
 *      var BabelFish = require('babelfish'),
 *          i18n = new BabelFish();
 **/

'use strict';

var Common = require('./babelfish/common');

// helpers
////////////////////////////////////////////////////////////////////////////////

// RegExp for testing string if it contains macros (plurals, variables, etc)
var MACROS_REGEXP = new RegExp("(?:^|[^\\\\])[#%]{"); // } <-- vim hack

function trim(str) {
  return (str || '').toString().trimLeft().trimRight();
}

function parseScope(scope) {
  var chunks, parts = {key: null, chunks: []};

  chunks = scope.split('.');

  // get key and make sure we are not dealing with empty key, e.g. `foo.`
  while (!parts.key && !!chunks.length) {
    parts.key = trim(chunks.pop());
  }

  // empty scope given `` or `.`, or `..` etc.
  if (!parts.key) {
    throw new TypeError('Invalid scope');
  }

  // filter chunks that left
  chunks.forEach(function (chunk) {
    chunk = trim(chunk);
    if (!!chunk) {
      parts.chunks.push(chunk);
    }
  });

  return parts;
}

// Compiles given string into translator function. Used to compile phrases,
// which contains `plurals`, `variables`, etc.
function compile(str) {
  if (!MACROS_REGEXP.test(str)) {
    return str;
  }

  return function (params) {
    params = ('object' === typeof params) ? Common.flattenParams(params) : {};
    throw {message: 'Not implemented yet'};
  };
}

// Returns fallbacks of given locale
function getFallbacks(self, locale) {
  var fallbacks = self._fallbacks[locale];

  if (!fallbacks) {
    return [self.defaultLocale];
  }

  return fallbacks.slice().concat([self.defaultLocale]);
}

// Returns locale storage. Creates one if needed
function getLocaleStorage(self, locale) {
  if (undefined === self._storage[locale]) {
    self._storage[locale] = {};
  }

  return self._storage[locale];
}

// propose given data to locales which hase given locale as fallback
function proposeTranslationFallback(self, fallback, scope, data) {
  // not implemented yet
}

function mergeTranslations(receiver, transmitter, locale) {
  Common.each(transmitter, function (data, key) {
    // go deeper
    if ('object' === data.type) {
      if (!receiver[key] || 'object' !== receiver[key].type) {
        receiver[key] = {
          type:   'object',
          locale: null,
          orig:   null,
          value:  {}
        };
      }
      mergeTranslations(receiver[key].value, data.value, locale);
      return;
    }

    // propose translation. make a copy
    if (data.locale === locale) {
      receiver[key] = Common.merge({}, data);
    }
  });

  return receiver;
}

// recompiles phrases for locale
function recompile(self, locale) {
  var fallbacks, fb_locale, old_storage, new_storage, len;

  fallbacks = getFallbacks(self, locale).slice(); // get copy of fallbacks
  old_storage = getLocaleStorage(self, locale);
  new_storage = Common.merge({}, getLocaleStorage(self, fallbacks.pop()));

  // mix-in fallbacks
  while (fallbacks.length) {
    fb_locale = fallbacks.pop();
    mergeTranslations(new_storage, getLocaleStorage(self, fb_locale), fb_locale);
  }

  // mix-in locale overrides
  self._storage[locale] = mergeTranslations(new_storage, old_storage, locale);
}

// public api (module)
////////////////////////////////////////////////////////////////////////////////

/**
 *  new BabelFish([defaultLocale = 'en'])
 *
 *  Initiates new instance of BabelFish. It can't be used as function (without
 *  `new` keyword. Use [[BabelFish.create]] for this purpose.
 **/
function BabelFish(defaultLocale) {
  /** read-only
   *  BabelFish#defaultLocale -> String
   *
   *  Default locale, tht will be used if requested locale has no translation,
   *  and have no fallacks or none of its fallbacks have translation as well.
   **/
  Object.defineProperty(this, 'defaultLocale', {
    value: trim(defaultLocale) || 'en'
  });

  // hash of locale => [ fallback1, fallback2, ... ] pairs
  this._fallbacks = {};

  // hash of fallback => [ locale1, locale2, ... ] pairs
  this._fallbacksReverse = {};

  // storage of compiled translations
  this._storage = {};
}

/** chainable
 *  BabelFish.create([defaultLocale = 'en']) -> BabelFish
 *
 *  Syntax sugar for constructor:
 *
 *      new BabelFish('ru')
 *      // equals to:
 *      BabelFish.create('ru');
 **/
BabelFish.create = function create(defaultLocale) {
  return new BabelFish(defaultLocale);
};

// public api (instance)
////////////////////////////////////////////////////////////////////////////////

/** chainable
 *  BabelFish#addPhrase(locale, scope, value) -> BabelFish
 *  - locale (String): Locale of translation
 *  - scope (String|Null): Scope of value, e.g. `apps.forum`
 *  - value (String|Object): Value or nested scopes with values.
 *
 *  ##### Example
 *
 *      i18n.addPhrase('ru-RU',
 *        'apps.forums.replies_count',
 *        '#{count} %{ответ|ответа|ответов}:count в теме');
 *
 *      // equals to:
 *      i18n.addPhrase('ru-RU',
 *        'apps.forums',
 *        { replies_count: '#{count} %{ответ|ответа|ответов}:count в теме' });
 **/
BabelFish.prototype.addPhrase = function addPhrase(locale, scope, value) {
  var parts, storage, data;

  storage = getLocaleStorage(this, locale);
  parts = parseScope(scope);

  // process inner scope
  parts.chunks.forEach(function (scope_key) {
    if (!storage[scope_key] || 'object' !== storage[scope_key].type) {
      storage[scope_key] = {
        type:   'object',
        locale: null,
        orig:   null,
        value:  {}
      };
    }

    storage = storage[scope_key].value;
  });

  // prepare data object
  data = storage[parts.key] = {
    type:   null, // will set it below
    locale: locale,
    orig:   value,
    value:  compile(value)
  };

  // set data type
  data.type = ('function' === typeof data.value) ? 'function' : 'string';

  // propose new value to fallbacks
  proposeTranslationFallback(this, locale, scope, data);

  return this;
};

/** chainable
 *  BabelFish#setFallback(locale, fallbacks) -> BabelFish
 *  - locale (String): Target locale
 *  - fallbacks (Array): List of fallback locales
 *
 *  Set fallbacks for given locale.
 *
 *  When `locale` has no translation for the phrase, `fallbacks[0]` will be
 *  tried, if translation still not found, then `fallbacks[1]` will be tried
 *  and so on. If none of fallbacks have translation,
 *  [[BabelFish#defaultLocale]] will be tried as last resort.
 *
 *  **WARNING** calling this method force recompilation of translations for
 *  given `locale` in order to respect new fallbacks. Consider calling this
 *  method before you'll populate BabelFish with phrases.
 *
 *  ##### Errors
 *
 *  - throws `Error`, when `locale` equals [[BabelFish#defaultLocale]]
 *
 *  ##### Example
 *
 *      i18n.setFallback('ua-UK', ['ua', 'ru']);
 **/
BabelFish.prototype.setFallback = function setFallback(locale, fallbacks) {
  var self = this;

  if (self.defaultLocale === locale) {
    throw new Error("Default locale can't have fallbacks");
  }

  // clear out current fallbacks
  if (!!self._fallbacks[locale]) {
    self._fallbacks[locale].forEach(function (fallback) {
      var idx;

      if (!!self._fallbacksReverse[fallback]) {
        idx = self._fallbacksReverse[fallback].indexOf(locale);
        if (-1 !== idx) {
          delete self._fallbacksReverse[fallback][idx];
        }
      }
    });
  }

  // set new empty stack of fallbacks
  self._fallbacks[locale] = [];

  // fill in new fallbacks. defaultLocale is appended as last fallback
  fallbacks.concat([self.defaultLocale]).forEach(function (fallback) {
    if (!self._fallbacksReverse[fallback]) {
      self._fallbacksReverse[fallback] = [];
    }

    self._fallbacksReverse[fallback].push(locale);
    self._fallbacks[locale].push(fallback);
  });

  // recompile locale to respect new fallbacks
  recompile(self, locale);

  return this;
};

/**
 *  BabelFish#translate(locale, scope[, params]) -> String
 *  - locale (String): Locale of translation
 *  - scope (String): Full phrase path, e.g. `app.forums.replies_count`
 *  - params (Object): Params for translation
 *
 *  ##### Example
 *
 *      i18n.addPhrase('ru-RU',
 *        'apps.forums.replies_count',
 *        '#{count} %{ответ|ответа|ответов}:count в теме');
 *
 *      // ...
 *
 *      i18n.translate('ru-RU', 'app.forums.replies_count', {count: 1});
 *      // -> '1 ответ'
 *
 *      i18n.translate('ru-RU', 'app.forums.replies_count', {count: 2});
 *      // -> '2 ответa'
 **/
BabelFish.prototype.translate = function translate(locale, scope, params) {
  var translator = this.getTranslation(locale, scope);

  if (!translator) {
    return locale + ': No translation for <' + scope + '>';
  }

  switch (translator.type) {
  case 'string':    return translator.value;
  case 'function':  return translator.value(params);
  default:          return 'Invalid scope usage: ' + scope;
  }
};

/** alias of: BabelFish#translate
 *  BabelFish#t(locale, scope[, params]) -> String
 **/
BabelFish.prototype.t = BabelFish.prototype.translate;

/**
 *  BabelFish#getTranslation(locale, scope[, options]) -> Object
 *  - locale (String): Locale of translation
 *  - scope (String): Full phrase path, e.g. `app.forums.replies_count`
 *  - options (Object): Params for translation
 *
 *  Returns compiled "translator" (if `scope` points phrase) or nested scope
 *  of translators. Each value of hash is an object with fields:
 *
 *  - **type** _(String)_
 *    - _string_:     Simple translation (contains no substitutions)
 *    - _function_:   Translation with macroses
 *    - _object_:     Nested scope of translations
 *
 *  - **locale** _(String|Null)_
 *    Locale of translation. It can differ from requested locale in case when
 *    translation was taken from fallback locale.
 *    `Null`, when `type` is `object`.
 *
 *  - **orig** _(String|Null)_
 *    Represents original string of translation as of [[BabelFish#addPhrase]].
 *    `Null`, when `type` is `object`.
 *
 *  - **value** _(String|Function|Object)_
 *
 *
 *  ##### Options
 *
 *  - **deep** _(Boolean, Default: true)_
 *    Whenever return nested translations.
 **/
BabelFish.prototype.getTranslation = function getTranslation(locale, scope, options) {
  var storage, parts;

  options = options || {};
  storage = getLocaleStorage(this, locale);
  parts = parseScope(scope);

  parts.chunks.forEach(function (scope_key) {
    if (storage && storage[scope_key] && 'object' === storage[scope_key].type) {
      storage = storage[scope_key].value;
      return;
    }

    // skip any further searchings
    storage = null;
  });

  // unknown translation
  if (!storage || !storage[parts.key]) {
    return null;
  }

  // if we got translation, or scope and deep copy requested,
  // return copy of the object from storage
  if ('object' !== storage[parts.key].type || false !== options.deep) {
    return Common.merge({}, storage[parts.key]);
  }

  // we got scope and return flat copy of storage is requested
  return Common.filter(storage[parts.key], function (val) {
    return 'object' !== val.type;
  });
};

// export module
module.exports = BabelFish;
<commit_msg>Finisehed fallback handing<commit_after>/**
 *  class BabelFish
 *
 *  Internalization and localization library that makes i18n and l10n fun again.
 *
 *  ##### Example
 *
 *      var BabelFish = require('babelfish'),
 *          i18n = new BabelFish();
 **/

'use strict';

var Common = require('./babelfish/common');

// helpers
////////////////////////////////////////////////////////////////////////////////

// RegExp for testing string if it contains macros (plurals, variables, etc)
var MACROS_REGEXP = new RegExp("(?:^|[^\\\\])[#%]{"); // } <-- vim hack

function trim(str) {
  return (str || '').toString().trimLeft().trimRight();
}

function parseScope(scope) {
  var chunks, parts = {key: null, chunks: []};

  chunks = scope.split('.');

  // get key and make sure we are not dealing with empty key, e.g. `foo.`
  while (!parts.key && !!chunks.length) {
    parts.key = trim(chunks.pop());
  }

  // empty scope given `` or `.`, or `..` etc.
  if (!parts.key) {
    throw new TypeError('Invalid scope');
  }

  // filter chunks that left
  chunks.forEach(function (chunk) {
    chunk = trim(chunk);
    if (!!chunk) {
      parts.chunks.push(chunk);
    }
  });

  return parts;
}

// Compiles given string into translator function. Used to compile phrases,
// which contains `plurals`, `variables`, etc.
function compile(str) {
  if (!MACROS_REGEXP.test(str)) {
    return str;
  }

  return function (params) {
    params = ('object' === typeof params) ? Common.flattenParams(params) : {};
    throw {message: 'Not implemented yet'};
  };
}

// Returns locale storage. Creates one if needed
function getLocaleStorage(self, locale) {
  if (undefined === self._storage[locale]) {
    self._storage[locale] = {};
  }

  return self._storage[locale];
}

function mergeTranslations(receiver, transmitter, locale) {
  Common.each(transmitter, function (data, key) {
    // go deeper
    if ('object' === data.type) {
      if (!receiver[key] || 'object' !== receiver[key].type) {
        receiver[key] = {
          type:   'object',
          locale: null,
          orig:   null,
          value:  {}
        };
      }
      mergeTranslations(receiver[key].value, data.value, locale);
      return;
    }

    // propose translation. make a copy
    if (data.locale === locale) {
      receiver[key] = Common.merge({}, data);
    }
  });

  return receiver;
}

// recompiles phrases for locale
function recompile(self, locale) {
  var fallbacks, fb_locale, old_storage, new_storage, len;

  fallbacks   = (self._fallbacks[locale] || []).slice();
  old_storage = getLocaleStorage(self, locale);
  new_storage = Common.merge({}, getLocaleStorage(self, self.defaultLocale));

  // mix-in fallbacks
  while (fallbacks.length) {
    fb_locale = fallbacks.pop();
    mergeTranslations(new_storage, getLocaleStorage(self, fb_locale), fb_locale);
  }

  // mix-in locale overrides
  self._storage[locale] = mergeTranslations(new_storage, old_storage, locale);

  // mark locale as compiled
  self._compiled[locale] = true;
}

// public api (module)
////////////////////////////////////////////////////////////////////////////////

/**
 *  new BabelFish([defaultLocale = 'en'])
 *
 *  Initiates new instance of BabelFish. It can't be used as function (without
 *  `new` keyword. Use [[BabelFish.create]] for this purpose.
 **/
function BabelFish(defaultLocale) {
  /** read-only
   *  BabelFish#defaultLocale -> String
   *
   *  Default locale, tht will be used if requested locale has no translation,
   *  and have no fallacks or none of its fallbacks have translation as well.
   **/
  Object.defineProperty(this, 'defaultLocale', {
    value: trim(defaultLocale) || 'en'
  });

  // hash of locale => [ fallback1, fallback2, ... ] pairs
  this._fallbacks = {};

  // hash of fallback => [ locale1, locale2, ... ] pairs
  this._fallbacksReverse = {};

  // states of compilation per each locale locale => bool pairs
  this._compiled = {};

  // storage of compiled translations
  this._storage = {};
}

/** chainable
 *  BabelFish.create([defaultLocale = 'en']) -> BabelFish
 *
 *  Syntax sugar for constructor:
 *
 *      new BabelFish('ru')
 *      // equals to:
 *      BabelFish.create('ru');
 **/
BabelFish.create = function create(defaultLocale) {
  return new BabelFish(defaultLocale);
};

// public api (instance)
////////////////////////////////////////////////////////////////////////////////

/** chainable
 *  BabelFish#addPhrase(locale, scope, value) -> BabelFish
 *  - locale (String): Locale of translation
 *  - scope (String|Null): Scope of value, e.g. `apps.forum`
 *  - value (String|Object): Value or nested scopes with values.
 *
 *  ##### Example
 *
 *      i18n.addPhrase('ru-RU',
 *        'apps.forums.replies_count',
 *        '#{count} %{ответ|ответа|ответов}:count в теме');
 *
 *      // equals to:
 *      i18n.addPhrase('ru-RU',
 *        'apps.forums',
 *        { replies_count: '#{count} %{ответ|ответа|ответов}:count в теме' });
 **/
BabelFish.prototype.addPhrase = function addPhrase(locale, scope, value) {
  var self = this, parts, storage, data;

  storage = getLocaleStorage(this, locale);
  parts = parseScope(scope);

  // process inner scope
  parts.chunks.forEach(function (scope_key) {
    if (!storage[scope_key] || 'object' !== storage[scope_key].type) {
      storage[scope_key] = {
        type:   'object',
        locale: null,
        orig:   null,
        value:  {}
      };
    }

    storage = storage[scope_key].value;
  });

  // prepare data object
  data = storage[parts.key] = {
    type:   null, // will set it below
    locale: locale,
    orig:   value,
    value:  compile(value)
  };

  // set data type
  data.type = ('function' === typeof data.value) ? 'function' : 'string';

  // mark all "dependant" locales for recompilation
  (self._fallbacksReverse[locale] || []).forEach(function (locale) {
    // we need to recompile non-default locales only
    if (locale !== self.defaultLocale) {
      self._compiled[locale] = false;
    }
  });

  return this;
};

/** chainable
 *  BabelFish#setFallback(locale, fallbacks) -> BabelFish
 *  - locale (String): Target locale
 *  - fallbacks (Array): List of fallback locales
 *
 *  Set fallbacks for given locale.
 *
 *  When `locale` has no translation for the phrase, `fallbacks[0]` will be
 *  tried, if translation still not found, then `fallbacks[1]` will be tried
 *  and so on. If none of fallbacks have translation,
 *  [[BabelFish#defaultLocale]] will be tried as last resort.
 *
 *  ##### Errors
 *
 *  - throws `Error`, when `locale` equals [[BabelFish#defaultLocale]]
 *
 *  ##### Example
 *
 *      i18n.setFallback('ua-UK', ['ua', 'ru']);
 **/
BabelFish.prototype.setFallback = function setFallback(locale, fallbacks) {
  var self = this;

  if (self.defaultLocale === locale) {
    throw new Error("Default locale can't have fallbacks");
  }

  // clear out current fallbacks
  if (!!self._fallbacks[locale]) {
    self._fallbacks[locale].forEach(function (fallback) {
      var idx;

      if (!!self._fallbacksReverse[fallback]) {
        idx = self._fallbacksReverse[fallback].indexOf(locale);
        if (-1 !== idx) {
          delete self._fallbacksReverse[fallback][idx];
        }
      }
    });
  }

  // set new empty stack of fallbacks
  self._fallbacks[locale] = [];

  // fill in new fallbacks. defaultLocale is appended as last fallback
  fallbacks.concat([self.defaultLocale]).forEach(function (fallback) {
    if (!self._fallbacksReverse[fallback]) {
      self._fallbacksReverse[fallback] = [];
    }

    self._fallbacksReverse[fallback].push(locale);
    self._fallbacks[locale].push(fallback);
  });

  // recompile locale to respect new fallbacks
  recompile(self, locale);

  return this;
};

/**
 *  BabelFish#translate(locale, scope[, params]) -> String
 *  - locale (String): Locale of translation
 *  - scope (String): Full phrase path, e.g. `app.forums.replies_count`
 *  - params (Object): Params for translation
 *
 *  ##### Example
 *
 *      i18n.addPhrase('ru-RU',
 *        'apps.forums.replies_count',
 *        '#{count} %{ответ|ответа|ответов}:count в теме');
 *
 *      // ...
 *
 *      i18n.translate('ru-RU', 'app.forums.replies_count', {count: 1});
 *      // -> '1 ответ'
 *
 *      i18n.translate('ru-RU', 'app.forums.replies_count', {count: 2});
 *      // -> '2 ответa'
 **/
BabelFish.prototype.translate = function translate(locale, scope, params) {
  var translator = this.getTranslation(locale, scope);

  if (!translator) {
    return locale + ': No translation for <' + scope + '>';
  }

  switch (translator.type) {
  case 'string':    return translator.value;
  case 'function':  return translator.value(params);
  default:          return 'Invalid scope usage: ' + scope;
  }
};

/** alias of: BabelFish#translate
 *  BabelFish#t(locale, scope[, params]) -> String
 **/
BabelFish.prototype.t = BabelFish.prototype.translate;

/**
 *  BabelFish#getTranslation(locale, scope[, options]) -> Object
 *  - locale (String): Locale of translation
 *  - scope (String): Full phrase path, e.g. `app.forums.replies_count`
 *  - options (Object): Params for translation
 *
 *  Returns compiled "translator" (if `scope` points phrase) or nested scope
 *  of translators. Each value of hash is an object with fields:
 *
 *  - **type** _(String)_
 *    - _string_:     Simple translation (contains no substitutions)
 *    - _function_:   Translation with macroses
 *    - _object_:     Nested scope of translations
 *
 *  - **locale** _(String|Null)_
 *    Locale of translation. It can differ from requested locale in case when
 *    translation was taken from fallback locale.
 *    `Null`, when `type` is `object`.
 *
 *  - **orig** _(String|Null)_
 *    Represents original string of translation as of [[BabelFish#addPhrase]].
 *    `Null`, when `type` is `object`.
 *
 *  - **value** _(String|Function|Object)_
 *
 *
 *  ##### Options
 *
 *  - **deep** _(Boolean, Default: true)_
 *    Whenever return nested translations.
 **/
BabelFish.prototype.getTranslation = function getTranslation(locale, scope, options) {
  var storage, parts;

  // force recompilation if needed
  if (!this._compiled[locale]) {
    recompile(this, locale);
  }

  options = options || {};
  storage = getLocaleStorage(this, locale);
  parts = parseScope(scope);

  parts.chunks.forEach(function (scope_key) {
    if (storage && storage[scope_key] && 'object' === storage[scope_key].type) {
      storage = storage[scope_key].value;
      return;
    }

    // skip any further searchings
    storage = null;
  });

  // unknown translation
  if (!storage || !storage[parts.key]) {
    return null;
  }

  // if we got translation, or scope and deep copy requested,
  // return copy of the object from storage
  if ('object' !== storage[parts.key].type || false !== options.deep) {
    return Common.merge({}, storage[parts.key]);
  }

  // we got scope and return flat copy of storage is requested
  return Common.filter(storage[parts.key], function (val) {
    return 'object' !== val.type;
  });
};

// export module
module.exports = BabelFish;
<|endoftext|>


<commit_before>'use babel';

import BookmarksView from './bookmarks-view';
import { CompositeDisposable, Emitter } from 'atom';

export default {

    bookmarksView: null,
    panel: null,
    subscriptions: null,

    activate(state) {
        this.bookmarksView = new BookmarksView(state.bookmarksViewState);
        this.panel = atom.workspace.addLeftPanel({
            item: this.bookmarksView.getElement(),
            visible: false
        });

        // Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
        this.subscriptions = new CompositeDisposable();

        // Register command that toggles this view
        this.subscriptions.add(atom.commands.add('atom-workspace', {
            'pinned-bookmarks:toggle': () => this.toggle(),
            'pinned-bookmarks:add-current': () => this.add(),
            'pinned-bookmarks:add': () => this.add(),
            'pinned-bookmarks:open': (path) => this.open(path),
            'pinned-bookmarks:remove': (query) => this.remove(query),
            'pinned-bookmarks:clear': () => this.clear(),
        }));
        this.listen();
        var self = this;
        this.setupAction(function() { self.add(); }, {
            text: 'Add current',
            icon: 'plus',
        })
        this.setupAction(function() { self.clear(); }, {
            text: 'Clear',
            icon: 'x',
        })
    },

    deactivate() {
        this.panel.destroy();
        this.subscriptions.dispose();
        this.bookmarksView.destroy();
    },

    notify(message, details, icon) {
        atom.notifications.addInfo(message, {
            detail: typeof details === 'undefined' ? null : details,
            dismissable: false,
            icon: typeof icon === 'undefined' ? 'circle-slash' : icon
        });
        return false;
    },

    setupAction(action, options) {
        if(typeof action === 'undefined') return false;
        var btn = this.bookmarksView.addActionButton(options);
        btn.addEventListener('click', function(evt) {
            action();
        });
    },

    add(path) {
        console.log(this);
        console.log(book);
        var book = this;
        if(typeof path === 'undefined') {
            var editor = atom.workspace.getActiveTextEditor();
            if(typeof editor === 'undefined')
                return book.notify("Current tab is not an editor.");
            var name = typeof editor.getLongTitle() !== 'undefined' ? editor.getLongTitle() : '';
            path = typeof editor.getPath() !== 'undefined' ? editor.getPath() : '';
        }
        else if(typeof path.name !== 'undefined') {
            var name = path.name;
            path = path.path;
        }
        else {
            var name = "lörs";
        }

        var item = {
            name: name,
            path: path
        };
        added_element = book.bookmarksView.addItem(item);
        if(added_element===false) return false;

        added_element.elements.open.addEventListener('click', function(evt) {
            var name = evt.target.textContent;
            var path = evt.target.parentNode.getAttribute('data-path');
            book.open(path);
        });
        added_element.elements.close.addEventListener('click', function(evt) {
            var path = evt.target.parentNode.getAttribute('data-path');
            book.remove(path);
        });
        return added_element;
    },

    //TypeError: pane.items[index].getPath is not a function
    //    at Object.open (D:\Programming\Atom\bookmarks\lib\bookmarks.js:101:35)
    //    at HTMLSpanElement.<anonymous>
    // (D:\Programming\Atom\bookmarks\lib\bookmarks.js:87:18)
    open(path) {
        pane = atom.workspace.getActivePane()
        for(index in pane.items) {

            if(pane.items[index].getPath()==path) {
                pane.activateItemAtIndex(index);
                return index;
            }
        }
        atom.workspace.open(path);
    },

    remove(item) {
        return typeof item === 'undefined' ? this.clear() :
            this.bookmarksView.removeItem(item);
    },

    clear() {
        this.bookmarksView.items = [];
        this.bookmarksView.updateList();
    },

    listen() {
        var self = this;
        var editor = atom.workspace.getActiveTextEditor();
        this.subscriptions.add(editor.onDidSave(function(arg) {
            var name = typeof editor.getLongTitle() !== 'undefined' ? editor.getLongTitle() : '';
            self.add({
                name: name,
                path: typeof editor.getPath() !== 'undefined' ? editor.getPath() : ''
            });
        }));
    },

    serialize() {
        return {
            bookmarksViewState: this.bookmarksView.serialize()
        };
    },

    toggle() {
        return (
            this.panel.isVisible() ?
            this.panel.hide() :
            this.panel.show()
        );
    }

};
<commit_msg>Fixed issue while opening bookmark when non-text editors were present<commit_after>'use babel';

import BookmarksView from './bookmarks-view';
import { CompositeDisposable, Emitter } from 'atom';

export default {

    bookmarksView: null,
    panel: null,
    subscriptions: null,

    activate(state) {
        this.bookmarksView = new BookmarksView(state.bookmarksViewState);
        this.panel = atom.workspace.addLeftPanel({
            item: this.bookmarksView.getElement(),
            visible: false
        });

        // Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
        this.subscriptions = new CompositeDisposable();

        // Register command that toggles this view
        this.subscriptions.add(atom.commands.add('atom-workspace', {
            'pinned-bookmarks:toggle': () => this.toggle(),
            'pinned-bookmarks:add-current': () => this.add(),
            'pinned-bookmarks:add': () => this.add(),
            'pinned-bookmarks:open': (path) => this.open(path),
            'pinned-bookmarks:remove': (query) => this.remove(query),
            'pinned-bookmarks:clear': () => this.clear(),
        }));
        this.listen();
        var self = this;
        this.setupAction(function() { self.add(); }, {
            text: 'Add current',
            icon: 'plus',
        })
        this.setupAction(function() { self.clear(); }, {
            text: 'Clear',
            icon: 'x',
        })
    },

    deactivate() {
        this.panel.destroy();
        this.subscriptions.dispose();
        this.bookmarksView.destroy();
    },

    notify(message, details, icon) {
        atom.notifications.addInfo(message, {
            detail: typeof details === 'undefined' ? null : details,
            dismissable: false,
            icon: typeof icon === 'undefined' ? 'circle-slash' : icon
        });
        return false;
    },

    setupAction(action, options) {
        if(typeof action === 'undefined') return false;
        var btn = this.bookmarksView.addActionButton(options);
        btn.addEventListener('click', function(evt) {
            action();
        });
    },

    add(path) {
        console.log(this);
        console.log(book);
        var book = this;
        if(typeof path === 'undefined') {
            var editor = atom.workspace.getActiveTextEditor();
            if(typeof editor === 'undefined')
                return book.notify("Current tab is not an editor.");
            var name = typeof editor.getLongTitle !== 'undefined' ? editor.getLongTitle() : '';
            path = typeof editor.getPath !== 'undefined' ? editor.getPath() : '';
        }
        else if(typeof path.name !== 'undefined') {
            var name = path.name;
            path = path.path;
        }
        else {
            var name = "lörs";
        }

        var item = {
            name: name,
            path: path
        };
        added_element = book.bookmarksView.addItem(item);
        if(added_element===false) return false;

        added_element.elements.open.addEventListener('click', function(evt) {
            var name = evt.target.textContent;
            var path = evt.target.parentNode.getAttribute('data-path');
            book.open(path);
        });
        added_element.elements.close.addEventListener('click', function(evt) {
            var path = evt.target.parentNode.getAttribute('data-path');
            book.remove(path);
        });
        return added_element;
    },

    open(path) {
        pane = atom.workspace.getActivePane()
        for(index in pane.items) {
            if(typeof pane.items[index] === 'undefined' ||
               typeof pane.items[index].getPath === 'undefined') continue;
            if(pane.items[index].getPath()==path) {
                pane.activateItemAtIndex(index);
                return index;
            }
        }
        atom.workspace.open(path);
    },

    remove(item) {
        return typeof item === 'undefined' ? this.clear() :
            this.bookmarksView.removeItem(item);
    },

    clear() {
        this.bookmarksView.items = [];
        this.bookmarksView.updateList();
    },

    listen() {
        var self = this;
        var editor = atom.workspace.getActiveTextEditor();
        this.subscriptions.add(editor.onDidSave(function(arg) {
            var name = typeof editor.getLongTitle !== 'undefined' ? editor.getLongTitle() : '';
            self.add({
                name: name,
                path: typeof editor.getPath !== 'undefined' ? editor.getPath() : ''
            });
        }));
    },

    serialize() {
        return {
            bookmarksViewState: this.bookmarksView.serialize()
        };
    },

    toggle() {
        return (
            this.panel.isVisible() ?
            this.panel.hide() :
            this.panel.show()
        );
    }

};
<|endoftext|>


<commit_before>import {loadProjects, setUser, updateData} from './actions'
import {lock} from './auth0'
import ProjectStore from './project-store'
import getDataForModifications from './get-data-for-modifications'

export default function bootstrap (store) {
  refreshToken(store, lock.getClient())

  store.projectStore = new ProjectStore(store)

  store.projectStore
    .getProjects()
    .then((projects) => store.dispatch(loadProjects(projects)))

  getDataForModifications({ modifications: [], bundleId: null })
    .then((data) => store.dispatch(updateData(data)))

  // TODO: Split this up and move it
  // debug: log state changes
  store.subscribe(() => {
    const state = store.getState()
    console.log(store.getState())
    window.requestAnimationFrame(function () {
      window.localStorage.setItem('state', JSON.stringify(state))
    })
  })
}

function refreshToken (store, auth0) {
  const {user} = store.getState()
  if (user && user.refresh_token) {
    auth0.refreshToken(user.refresh_token, function (err, delegationResult) {
      if (err) {
        store.dispatch(setUser(null))
      } else {
        user.id_token = delegationResult.id_token
        store.dispatch(setUser(user))
      }
    })
  }
}
<commit_msg>Only save user state in localStorage for now<commit_after>import {loadProjects, setUser, updateData} from './actions'
import {lock} from './auth0'
import ProjectStore from './project-store'
import getDataForModifications from './get-data-for-modifications'

export default function bootstrap (store) {
  refreshToken(store, lock.getClient())

  store.projectStore = new ProjectStore(store)

  store.projectStore
    .getProjects()
    .then((projects) => store.dispatch(loadProjects(projects)))

  getDataForModifications({ modifications: [], bundleId: null })
    .then((data) => store.dispatch(updateData(data)))

  // TODO: Split this up and move it
  // debug: log state changes
  store.subscribe(() => {
    const state = store.getState()
    console.log(store.getState())
    window.requestAnimationFrame(function () {
      window.localStorage.setItem('state', JSON.stringify({ user: state.user }))
    })
  })
}

function refreshToken (store, auth0) {
  const {user} = store.getState()
  if (user && user.refresh_token) {
    auth0.refreshToken(user.refresh_token, function (err, delegationResult) {
      if (err) {
        store.dispatch(setUser(null))
      } else {
        user.id_token = delegationResult.id_token
        store.dispatch(setUser(user))
      }
    })
  }
}
<|endoftext|>


<commit_before>var GRID_SIZE = 4;
var PIECE_SIZE = 100;
var COLORS = [ "#212121", "#8D6E63", "#6D4C41", "#EF6C00", "#E65100", "#B71C1C", "#880E4F", "#6A1B9A", "#4A148C", "#3F51B5", "#1A237E" ];
//var BOARD_COLOR = "#9E9E9E";
var BOARD_COLOR = "#BDBDBD";

var FONT_SIZE = 30;
var OFFSET = 2;

var canvas;
var ctx;

var over = false;
var score = 0;
var grid = [];
for (var i = 0; i < Math.pow(GRID_SIZE, 2); i++) {
    grid.push(0);
}

window.onload = function() {
    document.addEventListener('keydown', function(event) {
        switch (event.keyCode) {
            case 37:
                moveLeft();
                break;
            case 38:
                moveUp();
                break;
            case 39:
                moveRight();
                break;
            case 40:
                moveDown();
                break;
        }
        document.getElementById("score").innerHTML = "Score: "+score;
    });

    canvas = document.getElementById("canvas");
    ctx = canvas.getContext('2d');

    canvas.width = canvas.height = GRID_SIZE * PIECE_SIZE;

    newTile();
    newTile();
    draw();
}

function draw() {
	ctx.fillStyle = BOARD_COLOR;
	ctx.fillRect(0, 0, canvas.width, canvas.height);

    for (var i = 0; i < grid.length; i++) {
        drawPiece(i, grid[i]);
    }
}

function drawPiece(pos, val) {
    var x = pos % GRID_SIZE;
    var y = (pos - pos % GRID_SIZE) / GRID_SIZE;

    var piece_x = x * PIECE_SIZE;
    var piece_y = y * PIECE_SIZE;

    var text_x = piece_x + (PIECE_SIZE / 2);
    var text_y = piece_y + (PIECE_SIZE / 2);

    if (val > 0) {
        ctx.shadowBlur = 4;
        ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
        ctx.fillStyle = COLORS[val % COLORS.length];
        ctx.fillRect(piece_x + OFFSET, piece_y + OFFSET, PIECE_SIZE - (2 * OFFSET), PIECE_SIZE - (2 * OFFSET));
        ctx.shadowBlur = 0;

        ctx.font = FONT_SIZE + "px Sans";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        ctx.fillStyle = "white";
        ctx.fillText(Math.pow(2, val), text_x, text_y);

    }

}

function moveUp() {
	direction = -GRID_SIZE;
	edge = [];
    for (var i = 0; i < GRID_SIZE; i++) {
        edge.push(i);
    }
    moveTiles(direction, edge);
}
function moveLeft() {
	direction = -1;
	edge = [];
    for (var i = 0; i < GRID_SIZE; i++) {
        edge.push(i * GRID_SIZE);
    }
    moveTiles(direction, edge);
}
function moveDown() {
	direction = GRID_SIZE;
	edge = [];
    for (var i = 0; i < GRID_SIZE; i++) {
        edge.push(i + GRID_SIZE * (GRID_SIZE - 1));
    }
    moveTiles(direction, edge);
}
function moveRight() {
	direction = 1;
	edge = [];
    for (var i = 0; i < GRID_SIZE; i++) {
        edge.push((i + 1) * GRID_SIZE - 1);
    }
    moveTiles(direction, edge);
}

function newTile() {
	var whichTile = Math.random();
	if (whichTile > .9) {
		var n = 2;
	}
	else {
		var n = 1;
	}
	var empty = [];
	for (i = 0; i < grid.length; i++) {
		if (grid[i] == 0) {
			empty.push(i);
		}
	}
	if (empty == []) {
		return 0;
	}
	var newPos = Math.floor(Math.random() * empty.length);
	grid[empty[newPos]] = n;

}
function moveTiles(direction, edge) {
	for (var i = 0; i < grid.length; i++) {
		moveTile(i, direction, edge);
	}
	for (var i = 0; i < grid.length; i++) {
		combine(i, direction, edge);
	}
	for (var i = 0; i < grid.length; i++) {
		moveTile(i, direction, edge);
	}
	if (changed) {
		newTile();
		draw();
		changed = false;
	}

}
function isEdge(piece, edge) {
    for (var i = 0; i < edge.length; i++) {
        if (edge[i] == piece) {
            return true;
        }
    }
    return false;
}
function combine(piece, direction, edge) {
	if (grid[piece] != 0 && !isEdge(piece, edge)) {
		if (grid[piece + direction] == grid[piece]) {
			grid[piece + direction] = grid[piece] + 1;
			grid[piece] = 0;
			score += Math.pow(2, grid[piece + direction]);
			changed = true;
		}
	}
}
function moveTile(piece, direction, edge) {
	if (grid[piece] != 0 && !isEdge(piece, edge)) {
		if (grid[piece + direction] == 0) {
			grid[piece + direction] = grid[piece];
			grid[piece] = 0;
			moveTile(piece + direction, direction, edge);
			changed = true;
		}
	}
}
<commit_msg>Fixed error in game logic<commit_after>var GRID_SIZE = 4;
var PIECE_SIZE = 100;
var COLORS = [ "#212121", "#8D6E63", "#6D4C41", "#EF6C00", "#E65100", "#B71C1C", "#880E4F", "#6A1B9A", "#4A148C", "#3F51B5", "#1A237E" ];
//var BOARD_COLOR = "#9E9E9E";
var BOARD_COLOR = "#BDBDBD";

var FONT_SIZE = 30;
var OFFSET = 2;

var canvas;
var ctx;

var over = false;
var score = 0;
var grid = [];
for (var i = 0; i < Math.pow(GRID_SIZE, 2); i++) {
    grid.push(0);
}

window.onload = function() {
    document.addEventListener('keydown', function(event) {
        switch (event.keyCode) {
            case 37:
                moveLeft();
                break;
            case 38:
                moveUp();
                break;
            case 39:
                moveRight();
                break;
            case 40:
                moveDown();
                break;
        }
        document.getElementById("score").innerHTML = "Score: "+score;
    });

    canvas = document.getElementById("canvas");
    ctx = canvas.getContext('2d');

    canvas.width = canvas.height = GRID_SIZE * PIECE_SIZE;

    newTile();
    newTile();
    draw();
}

function draw() {
	ctx.fillStyle = BOARD_COLOR;
	ctx.fillRect(0, 0, canvas.width, canvas.height);

    for (var i = 0; i < grid.length; i++) {
        drawPiece(i, grid[i]);
    }
}

function drawPiece(pos, val) {
    var x = pos % GRID_SIZE;
    var y = (pos - pos % GRID_SIZE) / GRID_SIZE;

    var piece_x = x * PIECE_SIZE;
    var piece_y = y * PIECE_SIZE;

    var text_x = piece_x + (PIECE_SIZE / 2);
    var text_y = piece_y + (PIECE_SIZE / 2);

    if (val > 0) {
        ctx.shadowBlur = 4;
        ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
        ctx.fillStyle = COLORS[val % COLORS.length];
        ctx.fillRect(piece_x + OFFSET, piece_y + OFFSET, PIECE_SIZE - (2 * OFFSET), PIECE_SIZE - (2 * OFFSET));
        ctx.shadowBlur = 0;

        ctx.font = FONT_SIZE + "px Sans";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        ctx.fillStyle = "white";
        ctx.fillText(Math.pow(2, val), text_x, text_y);

    }

}

function moveUp() {
	var direction = -GRID_SIZE;
	var edge = [];
    for (var i = 0; i < GRID_SIZE; i++) {
        edge.push(i);
    }
    moveTiles(direction, edge);
}
function moveLeft() {
	var direction = -1;
	var edge = [];
    for (var i = 0; i < GRID_SIZE; i++) {
        edge.push(i * GRID_SIZE);
    }
    moveTiles(direction, edge);
}
function moveDown() {
	var direction = GRID_SIZE;
	var edge = [];
    for (var i = 0; i < GRID_SIZE; i++) {
        edge.push(i + GRID_SIZE * (GRID_SIZE - 1));
    }
    moveTiles(direction, edge);
}
function moveRight() {
	var direction = 1;
	var edge = [];
    for (var i = 0; i < GRID_SIZE; i++) {
        edge.push((i + 1) * GRID_SIZE - 1);
    }
    moveTiles(direction, edge);
}

function newTile() {
	var whichTile = Math.random();
	if (whichTile > .9) {
		var n = 2;
	}
	else {
		var n = 1;
	}
	var empty = [];
	for (i = 0; i < grid.length; i++) {
		if (grid[i] == 0) {
			empty.push(i);
		}
	}
	if (empty == []) {
		return 0;
	}
	var newPos = Math.floor(Math.random() * empty.length);
	grid[empty[newPos]] = n;

}
function moveTiles(direction, edge) {
    if (direction < 0) {
        for (var i = 0; i < grid.length; i++) {
            moveTile(i, direction, edge);
        }
        for (var i = 0; i < grid.length; i++) {
            combine(i, direction, edge);
        }
        for (var i = 0; i < grid.length; i++) {
            moveTile(i, direction, edge);
        }
    } else {
        for (var i = grid.length - 1; i >= 0; i--) {
            moveTile(i, direction, edge);
        }
        for (var i = grid.length - 1; i >= 0; i--) {
            combine(i, direction, edge);
        }
        for (var i = grid.length - 1; i >= 0; i--) {
            moveTile(i, direction, edge);
        }
    }
	if (changed) {
		newTile();
		draw();
		changed = false;
	}

}
function isEdge(piece, edge) {
    for (var i = 0; i < edge.length; i++) {
        if (edge[i] == piece) {
            return true;
        }
    }
    return false;
}
function combine(piece, direction, edge) {
	if (grid[piece] != 0 && !isEdge(piece, edge)) {
		if (grid[piece + direction] == grid[piece]) {
			grid[piece + direction] = grid[piece] + 1;
			grid[piece] = 0;
			score += Math.pow(2, grid[piece + direction]);
			changed = true;
		}
	}
}
function moveTile(piece, direction, edge) {
	if (grid[piece] != 0 && !isEdge(piece, edge)) {
		if (grid[piece + direction] == 0) {
			grid[piece + direction] = grid[piece];
			grid[piece] = 0;
			moveTile(piece + direction, direction, edge);
			changed = true;
		}
	}
}
<|endoftext|>


<commit_before>// Define
module.exports = function(cache,type,index,element) {
  if (cache[type]) {
    cache[type].items[index] = {item: element, time: Date.now()/1000};
  } else
    return 'Invalid type';
};
<commit_msg>Remove unnecessary braces<commit_after>// Define
module.exports = function(cache,type,index,element) {
  if (cache[type])
    cache[type].items[index] = {item: element, time: Date.now()/1000};
  else
    return 'Invalid type';
};
<|endoftext|>


<commit_before>Minionette.CollectionView = Minionette.View.extend({

    // The View class to render the collection as.
    ModelView: Backbone.View,

    // Listen to the default events.
    collectionEvents: {
        'add': 'addOne',
        'remove': 'removeOne',
        'reset': 'render',
        'sort': 'render'
    },

    // A default useful render function.
    render: function() {
        this.trigger('render:before');

        // Dump all our modelViews.
        // They will be removed by the jQuery#remove
        // listener when we clear $el
        this._modelViews = {};
        // Collect the ModelView class.
        var ModelView = this._getModelView();

        var $el = this.$el.html(this.template(this.serializeData()));
        // Use a DocumentFragment to speed up #render()
        this.$el = $(document.createDocumentFragment());

        // Loop through all our models, and build their view.
        this.collection.each(function(model) {
            this._addModelView(model, ModelView);
        }, this);

        // Append the DocumentFragment to the rendered template,
        // and set that as this.$el
        this.$el = $el.append(this.$el);

        this.trigger('render');
        return this;
    },

    // Add an individual model's view to this.$el.
    addOne: function(model) {
        this.trigger('addOne:before');

        // Collect the ModelView class.
        var ModelView = this._getModelView(),
            view = this._addModelView(model, ModelView);

        this.trigger('addOne');
        return view;
    },

    // Remove an individual model's view from this.$el.
    removeOne: function(model) {
        this.trigger('removeOne:before');

        var view = this._findModelViewByModel(model);
        if (view) { view.remove(); }

        this.trigger('removeOne');
        return view;
    },

    // Add an individual model's view to this.$el.
    _addModelView: function(model, ModelView) {
        var modelView = new ModelView({model: model});

        // Add the modelView, and keep track of it.
        this._modelViews || (this._modelViews = {});
        this._modelViews[modelView.cid] = modelView;
        modelView._parent = this;

        this.$el.append(modelView.render().el);
        return modelView;
    },

    // Find the correct ModelView to use.
    // Prioritizes the one passed at initialization.
    _getModelView: function() {
        var ModelView = this.ModelView;
        if (this.options && this.options.ModelView) {
            ModelView = this.options.ModelView;
        }
        return ModelView;
    },

    // Find the view associated with a model from our modelViews.
    _findModelViewByModel: function(model) {
        return _.findWhere(this._modelViews, {model: model});
    }
});
<commit_msg>Append the #$el, not the #el<commit_after>Minionette.CollectionView = Minionette.View.extend({

    // The View class to render the collection as.
    ModelView: Backbone.View,

    // Listen to the default events.
    collectionEvents: {
        'add': 'addOne',
        'remove': 'removeOne',
        'reset': 'render',
        'sort': 'render'
    },

    // A default useful render function.
    render: function() {
        this.trigger('render:before');

        // Dump all our modelViews.
        // They will be removed by the jQuery#remove
        // listener when we clear $el
        this._modelViews = {};
        // Collect the ModelView class.
        var ModelView = this._getModelView();

        var $el = this.$el.html(this.template(this.serializeData()));
        // Use a DocumentFragment to speed up #render()
        this.$el = $(document.createDocumentFragment());

        // Loop through all our models, and build their view.
        this.collection.each(function(model) {
            this._addModelView(model, ModelView);
        }, this);

        // Append the DocumentFragment to the rendered template,
        // and set that as this.$el
        this.$el = $el.append(this.$el);

        this.trigger('render');
        return this;
    },

    // Add an individual model's view to this.$el.
    addOne: function(model) {
        this.trigger('addOne:before');

        // Collect the ModelView class.
        var ModelView = this._getModelView(),
            view = this._addModelView(model, ModelView);

        this.trigger('addOne');
        return view;
    },

    // Remove an individual model's view from this.$el.
    removeOne: function(model) {
        this.trigger('removeOne:before');

        var view = this._findModelViewByModel(model);
        if (view) { view.remove(); }

        this.trigger('removeOne');
        return view;
    },

    // Add an individual model's view to this.$el.
    _addModelView: function(model, ModelView) {
        var modelView = new ModelView({model: model});

        // Add the modelView, and keep track of it.
        this._modelViews || (this._modelViews = {});
        this._modelViews[modelView.cid] = modelView;
        modelView._parent = this;

        this.$el.append(modelView.render().$el);
        return modelView;
    },

    // Find the correct ModelView to use.
    // Prioritizes the one passed at initialization.
    _getModelView: function() {
        var ModelView = this.ModelView;
        if (this.options && this.options.ModelView) {
            ModelView = this.options.ModelView;
        }
        return ModelView;
    },

    // Find the view associated with a model from our modelViews.
    _findModelViewByModel: function(model) {
        return _.findWhere(this._modelViews, {model: model});
    }
});
<|endoftext|>


<commit_before>/*jshint strict:false, asi:true, laxcomma:true, laxbreak:true, boss:true, curly:true, node:true, browser:true, devel:true */

function VCompiler(ast, originalMarkup, options){
	this.ast = ast;
	this.originalMarkup = originalMarkup || '';
	this.options = options || {};

	this.reQuote = /(["'])/gi
	this.reEscapedQuote = /\\+(["'])/gi
	this.reLineBreak = /[\n\r]/gi
	this.reHelpersName = /HELPERSNAME/g
	this.reModelName = /MODELNAME/g
	this.reOriginalMarkup = /ORIGINALMARKUP/g

	this.buffer = [];
}

var VCP = VCompiler.prototype;

VCP.insertDebugVars = function(tok){

	if(this.options.debug){
		this.buffer.push(
			this.options.helpersName + '.vl = ' + tok.line + ', '
			,this.options.helpersName + '.vc = ' + tok.chr + '; \n'
		);
	}
}

VCP.visitMarkupTok = function(tok, parentNode, index){

	this.insertDebugVars(tok);
	this.buffer.push(
		"MKP('" + tok.val
			.replace(this.reQuote, '\\$1')
			.replace(this.reLineBreak, '\\n')
		+ "')MKP" );
}

VCP.visitBlockTok = function(tok, parentNode, index){

	this.buffer.push( tok.val );
}

VCP.visitExpressionTok = function(tok, parentNode, index, isHomogenous){

	var  start = ''
		,end = ''
		,parentParentIsNotEXP = parentNode.parent && parentNode.parent.mode !== EXP;

	if(this.options.htmlEscape !== false){

		if( parentParentIsNotEXP && index === 0 && isHomogenous ){
			start += this.options.helpersName + '.escape(';
		}

		if( parentParentIsNotEXP && index === parentNode.length - 1 && isHomogenous){
			end += ").toHtmlString()";
		}
	}

	if(parentParentIsNotEXP && (index === 0 ) ){
		this.insertDebugVars(tok);
		start = "__vbuffer.push(" + start;
	}

	if( parentParentIsNotEXP && index === parentNode.length - 1 ){
		end += "); \n";
	}

	this.buffer.push( start + tok.val + end );

	if(parentParentIsNotEXP && index === parentNode.length - 1){
		this.insertDebugVars(tok);
	}
}

VCP.visitNode = function(node){

	var n, children = node.slice(0), nonExp, i, child;

	if(node.mode === EXP && (node.parent && node.parent.mode !== EXP)){
		// see if this node's children are all EXP
		nonExp = node.filter(VCompiler.findNonExp).length;
	}

	for(i = 0; i < children.length; i++){
		child = children[i];

		// if saveAT is true, or if AT_COLON is used, these should not be compiled
		if( child.type && child.type === AT || child.type === AT_COLON ) continue;

		if(child.vquery){

			this.visitNode(child);

		} else if(node.mode === MKP){

			this.visitMarkupTok(child, node, i);

		} else if(node.mode === BLK){

			this.visitBlockTok(child, node, i);

		} else if(node.mode === EXP){

			this.visitExpressionTok(child, node, i, (nonExp > 0 ? false : true));

		}
	}

}

VCP.escapeForDebug = function( str ){
	return str
		.replace(this.reLineBreak, '!LB!')
		.replace(this.reQuote, '\\$1')
		.replace(this.reEscapedQuote, '\\$1')
}

VCP.replaceDevTokens = function( str ){
	return str
		.replace( this.reHelpersName, this.options.helpersName )
		.replace( this.reModelName, this.options.modelName );
}

VCP.wrapBody = function(body){

	var options = this.options;

	var head = ''
		+ (options.debug ? 'try { \n' : '')
		+ 'var __vbuffer = HELPERSNAME.buffer; \n'
		+ 'MODELNAME = MODELNAME || {}; \n'
		+ (options.useWith ? 'with( MODELNAME ){ \n' : '');

	var foot = ''
		+ (options.simple
			? 'return HELPERSNAME.buffer.join(""); \n'
			: '(__vopts && __vopts.onRenderEnd && __vopts.onRenderEnd(null, HELPERSNAME)); \n'
				+ 'return (__vopts && __vopts.asContext) \n'
				+ '  ? HELPERSNAME \n'
				+ '  : HELPERSNAME.toString(); \n' )
		+ (options.useWith ? '} \n' : '')
		+ (options.debug ? '} catch( e ){ \n'
			+ '  HELPERSNAME.reportError( e, HELPERSNAME.vl, HELPERSNAME.vc, "ORIGINALMARKUP" ); \n'
			+ '} \n' : '');

	head = this.replaceDevTokens( head );
	foot = this.replaceDevTokens( foot )
		.replace( this.reOriginalMarkup, this.escapeForDebug( this.originalMarkup ) );

	return head + body + foot;
}

VCP.generate = function(){
	var options = this.options;

	// clear whatever's in the current buffer
	this.buffer.length = 0;

	this.visitNode(this.ast);

	// coalesce markup
	var joined = this.buffer
		.join("")
		.split("')MKPMKP('").join('')
		.split("MKP(").join( "__vbuffer.push(")
		.split(")MKP").join("); \n");

	joined = this.wrapBody( joined );

	if(options.debugCompiler){
		console.log(joined);
	}

	this.cmpFunc = vash.link( joined, options );
	return this.cmpFunc;
}

VCompiler.noop = function(){}

VCompiler.findNonExp = function(node){

	if(node.vquery && node.mode === EXP){
		return node.filter(VCompiler.findNonExp).length > 0;
	}

	if(node.vquery && node.mode !== EXP){
		return true;
	} else {
		return false;
	}
}
<commit_msg>addBody split into addHead, addFoot<commit_after>/*jshint strict:false, asi:true, laxcomma:true, laxbreak:true, boss:true, curly:true, node:true, browser:true, devel:true */

function VCompiler(ast, originalMarkup, options){
	this.ast = ast;
	this.originalMarkup = originalMarkup || '';
	this.options = options || {};

	this.reQuote = /(["'])/gi
	this.reEscapedQuote = /\\+(["'])/gi
	this.reLineBreak = /[\n\r]/gi
	this.reHelpersName = /HELPERSNAME/g
	this.reModelName = /MODELNAME/g
	this.reOriginalMarkup = /ORIGINALMARKUP/g

	this.buffer = [];
}

var VCP = VCompiler.prototype;

VCP.insertDebugVars = function(tok){

	if(this.options.debug){
		this.buffer.push(
			this.options.helpersName + '.vl = ' + tok.line + ', '
			,this.options.helpersName + '.vc = ' + tok.chr + '; \n'
		);
	}
}

VCP.visitMarkupTok = function(tok, parentNode, index){

	this.insertDebugVars(tok);
	this.buffer.push(
		"MKP('" + tok.val
			.replace(this.reQuote, '\\$1')
			.replace(this.reLineBreak, '\\n')
		+ "')MKP" );
}

VCP.visitBlockTok = function(tok, parentNode, index){

	this.buffer.push( tok.val );
}

VCP.visitExpressionTok = function(tok, parentNode, index, isHomogenous){

	var  start = ''
		,end = ''
		,parentParentIsNotEXP = parentNode.parent && parentNode.parent.mode !== EXP;

	if(this.options.htmlEscape !== false){

		if( parentParentIsNotEXP && index === 0 && isHomogenous ){
			start += this.options.helpersName + '.escape(';
		}

		if( parentParentIsNotEXP && index === parentNode.length - 1 && isHomogenous){
			end += ").toHtmlString()";
		}
	}

	if(parentParentIsNotEXP && (index === 0 ) ){
		this.insertDebugVars(tok);
		start = "__vbuffer.push(" + start;
	}

	if( parentParentIsNotEXP && index === parentNode.length - 1 ){
		end += "); \n";
	}

	this.buffer.push( start + tok.val + end );

	if(parentParentIsNotEXP && index === parentNode.length - 1){
		this.insertDebugVars(tok);
	}
}

VCP.visitNode = function(node){

	var n, children = node.slice(0), nonExp, i, child;

	if(node.mode === EXP && (node.parent && node.parent.mode !== EXP)){
		// see if this node's children are all EXP
		nonExp = node.filter(VCompiler.findNonExp).length;
	}

	for(i = 0; i < children.length; i++){
		child = children[i];

		// if saveAT is true, or if AT_COLON is used, these should not be compiled
		if( child.type && child.type === AT || child.type === AT_COLON ) continue;

		if(child.vquery){

			this.visitNode(child);

		} else if(node.mode === MKP){

			this.visitMarkupTok(child, node, i);

		} else if(node.mode === BLK){

			this.visitBlockTok(child, node, i);

		} else if(node.mode === EXP){

			this.visitExpressionTok(child, node, i, (nonExp > 0 ? false : true));

		}
	}

}

VCP.escapeForDebug = function( str ){
	return str
		.replace(this.reLineBreak, '!LB!')
		.replace(this.reQuote, '\\$1')
		.replace(this.reEscapedQuote, '\\$1')
}

VCP.replaceDevTokens = function( str ){
	return str
		.replace( this.reHelpersName, this.options.helpersName )
		.replace( this.reModelName, this.options.modelName );
}

VCP.addHead = function(body){

	var options = this.options;

	var head = ''
		+ (options.debug ? 'try { \n' : '')
		+ 'var __vbuffer = HELPERSNAME.buffer; \n'
		+ 'MODELNAME = MODELNAME || {}; \n'
		+ (options.useWith ? 'with( MODELNAME ){ \n' : '');

	head = this.replaceDevTokens( head );
	return head + body;
}

VCP.addFoot = function(body){

	var options = this.options;

	var foot = ''
		+ (options.simple
			? 'return HELPERSNAME.buffer.join(""); \n'
			: '(__vopts && __vopts.onRenderEnd && __vopts.onRenderEnd(null, HELPERSNAME)); \n'
				+ 'return (__vopts && __vopts.asContext) \n'
				+ '  ? HELPERSNAME \n'
				+ '  : HELPERSNAME.toString(); \n' )
		+ (options.useWith ? '} \n' : '')
		+ (options.debug ? '} catch( e ){ \n'
			+ '  HELPERSNAME.reportError( e, HELPERSNAME.vl, HELPERSNAME.vc, "ORIGINALMARKUP" ); \n'
			+ '} \n' : '');

	foot = this.replaceDevTokens( foot )
		.replace( this.reOriginalMarkup, this.escapeForDebug( this.originalMarkup ) );

	return body + foot;
}

VCP.generate = function(){
	var options = this.options;

	// clear whatever's in the current buffer
	this.buffer.length = 0;

	this.visitNode(this.ast);

	// coalesce markup
	var joined = this.buffer
		.join("")
		.split("')MKPMKP('").join('')
		.split("MKP(").join( "__vbuffer.push(")
		.split(")MKP").join("); \n");

	joined = this.addHead( joined );
	joined = this.addFoot( joined );

	if(options.debugCompiler){
		console.log(joined);
	}

	this.cmpFunc = vash.link( joined, options );
	return this.cmpFunc;
}

VCompiler.noop = function(){}

VCompiler.findNonExp = function(node){

	if(node.vquery && node.mode === EXP){
		return node.filter(VCompiler.findNonExp).length > 0;
	}

	if(node.vquery && node.mode !== EXP){
		return true;
	} else {
		return false;
	}
}
<|endoftext|>