Commit 97371a8

bryfry <bryon.fryer@gmail.com>
2014-01-28 01:15:13
add level2
1 parent 26df2e1
Changed files (157)
level2
network_simulation
node_modules
test
level2/network_simulation/lib/check_server.js
@@ -0,0 +1,94 @@
+"use strict";
+
+var http = require('http');
+var underscore = require('underscore');
+
+var checkServers = exports.checkServers = function (array, success, error) {
+  var upCount = 0,
+      errored = false,
+      target,
+      i;
+  for (i = 0; i < array.length; i++) {
+    check(
+      array[i],
+      function () {
+        upCount += 1;
+        if (upCount === array.length) {
+          success();
+        }
+      },
+      function () {
+        if (errored === false) {
+          errored = true;
+          error();
+        }
+      }
+    );
+  }
+}
+
+var check = exports.check = function (connectionOptions, success, error) {
+  var baseOptions = {
+    path: "/",
+    method: 'HEAD'
+  };
+  var options = underscore.extend(baseOptions, connectionOptions);
+  var request = http.request(options, function (response) {
+    response.on('data', function () {
+      return;
+    });
+    response.on('end', function () {
+      if (response.statusCode === 200) {
+        console.log("Upstream server is up.");
+        success();
+      } else if (response.statusCode === 400) {
+        console.log("400 response code. We may be using the wrong secret.");
+        error();
+      } else if (response.statusCode === 500) {
+        console.log("500 response code. Shield may be failing to come up.");
+        error();
+      } else {
+        console.log("Unexpected response code %s.", response.statusCode);
+        error();
+      }
+    });
+  });
+
+  request.setTimeout(1000, function (socket) {
+      console.log("Timeout checking backend. The backend is probably down.");
+      request.abort();
+  });
+
+  request.on("error", function (err) {
+    console.log("Error checking backend. The immediate upstream is probably down.");
+    error();
+  });
+
+  request.end();
+};
+
+var checkWithBackoff = exports.checkWithBackoff = function (connectionOptions, success, failure, attempts, backoff) {
+  var attempts,
+      backoff = backoff || 500; // ms
+  if (attempts === undefined) {
+    attempts = 4;
+  }
+  console.log("Checking if backends are up.");
+  var failedTry = function () {
+    if (attempts >= 1) {
+      // Try again, doubling the backoff time
+      setTimeout(
+        checkWithBackoff,
+        backoff,
+        connectionOptions,
+        success,
+        failure,
+        attempts - 1,
+        backoff * 2
+      );
+    } else {
+      failure();
+    }
+  };
+  check(connectionOptions, success, failedTry);
+};
\ No newline at end of file
level2/network_simulation/lib/proxy.js
@@ -0,0 +1,408 @@
+// This file is a port of [node-http-proxy](https://github.com/nodejitsu/node-http-proxy)
+// The purpose of the port is to enable connections to the backend
+// server over a Unix socket file.
+
+// node-http-proxy includes the following message:
+
+// Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, & Marak Squires
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+"use strict";
+
+var http = require('http');
+var url = require('url')
+var events = require('events');
+var util = require('util');
+
+var HttpProxy = exports.HttpProxy = function(options) {
+  events.EventEmitter.call(this);
+  var self  = this;
+  this.target = options.target;
+  this.enable  = {};
+  this.timeout = options.timeout;
+}
+
+util.inherits(HttpProxy, events.EventEmitter);
+
+exports.createServer = function (options, callback) {
+  var handlers = [callback];
+  var proxy = new HttpProxy(options);
+  function handler(req, res) {
+    return callback(req, res, proxy);
+  }
+  var server = http.createServer(handler);
+  server.on('close', function () {
+    proxy.close();
+  });
+  server.proxy = proxy;
+  return server;
+}
+
+//
+// ### function proxyRequest (req, res, buffer)
+// #### @req {ServerRequest} Incoming HTTP Request to proxy.
+// #### @res {ServerResponse} Outgoing HTTP Request to write proxied data to.
+// #### @buffer {Object} Result from `httpProxy.buffer(req)`
+//
+HttpProxy.prototype.proxyRequest = function (req, res, buffer) {
+  var self = this,
+      errState = false,
+      outgoing = {},
+      reverseProxy,
+      location;
+
+  // If this is a DELETE request then set the "content-length"
+  // header (if it is not already set)
+  if (req.method === 'DELETE') {
+    req.headers['content-length'] = req.headers['content-length'] || '0';
+  }
+
+  //
+  // Add common proxy headers to the request so that they can
+  // be availible to the proxy target server. If the proxy is
+  // part of proxy chain it will append the address:
+  //
+  // * `x-forwarded-for`: IP Address of the original request
+  // * `x-forwarded-proto`: Protocol of the original request
+  // * `x-forwarded-port`: Port of the original request.
+  //
+  if (this.enable.xforward && req.connection && req.socket) {
+    if (req.headers['x-forwarded-for']) {
+      var addressToAppend = "," + req.connection.remoteAddress || req.socket.remoteAddress;
+      req.headers['x-forwarded-for'] += addressToAppend;
+    }
+    else {
+      req.headers['x-forwarded-for'] = req.connection.remoteAddress || req.socket.remoteAddress;
+    }
+
+    if (req.headers['x-forwarded-port']) {
+      var portToAppend = "," + getPortFromHostHeader(req);
+      req.headers['x-forwarded-port'] += portToAppend;
+    }
+    else {
+      req.headers['x-forwarded-port'] = getPortFromHostHeader(req);
+    }
+
+    if (req.headers['x-forwarded-proto']) {
+      var protoToAppend = "," + getProto(req);
+      req.headers['x-forwarded-proto'] += protoToAppend;
+    }
+    else {
+      req.headers['x-forwarded-proto'] = getProto(req);
+    }
+  }
+
+  if (this.timeout) {
+    req.socket.setTimeout(this.timeout);
+  }
+
+  //
+  // Emit the `start` event indicating that we have begun the proxy operation.
+  //
+  this.emit('start', req, res, this.target);
+
+  //
+  // #### function proxyError (err)
+  // #### @err {Error} Error contacting the proxy target
+  // Short-circuits `res` in the event of any error when
+  // contacting the proxy target at `host` / `port`.
+  //
+  function proxyError(err) {
+    errState = true;
+
+    //
+    // Emit an `error` event, allowing the application to use custom
+    // error handling. The error handler should end the response.
+    //
+    if (self.emit('proxyError', err, req, res)) {
+      return;
+    }
+
+    res.writeHead(500, { 'Content-Type': 'application/json' });
+
+    if (req.method !== 'HEAD') {
+      var contents = {
+        'error': err
+      }
+      res.write(JSON.stringify(contents));
+    }
+
+    try { res.end() }
+    catch (ex) { console.error("res.end error: %s", ex.message) }
+  }
+
+  //
+  // Setup outgoing proxy with relevant properties.
+  //
+  outgoing.host       = this.target.host;
+  outgoing.hostname   = this.target.hostname;
+  outgoing.port       = this.target.port;
+  outgoing.socketPath = this.target.socketPath;
+  outgoing.agent      = this.target.agent;
+  outgoing.method     = req.method;
+  outgoing.path       = url.parse(req.url).path;
+  outgoing.headers    = req.headers;
+
+  //
+  // If the changeOrigin option is specified, change the
+  // origin of the host header to the target URL! Please
+  // don't revert this without documenting it!
+  //
+  if (this.changeOrigin) {
+    outgoing.headers.host = this.target.host;
+    // Only add port information to the header if not default port
+    // for this protocol.
+    // See https://github.com/nodejitsu/node-http-proxy/issues/458
+    if (this.target.port !== 443 && this.target.https ||
+      this.target.port !== 80 && !this.target.https) {
+      outgoing.headers.host += ':' + this.target.port;
+    }
+  }
+
+  //
+  // Open new HTTP request to internal resource with will act
+  // as a reverse proxy pass
+  //
+  reverseProxy = http.request(outgoing, function (response) {
+    //
+    // Process the `reverseProxy` `response` when it's received.
+    //
+    if (req.httpVersion === '1.0') {
+      if (req.headers.connection) {
+        response.headers.connection = req.headers.connection
+      } else {
+        response.headers.connection = 'close'
+      }
+    } else if (!response.headers.connection) {
+      if (req.headers.connection) { response.headers.connection = req.headers.connection }
+      else {
+        response.headers.connection = 'keep-alive'
+      }
+    }
+
+    // Remove `Transfer-Encoding` header if client's protocol is HTTP/1.0
+    // or if this is a DELETE request with no content-length header.
+    // See: https://github.com/nodejitsu/node-http-proxy/pull/373
+    if (req.httpVersion === '1.0' || (req.method === 'DELETE'
+      && !req.headers['content-length'])) {
+      delete response.headers['transfer-encoding'];
+    }
+
+    if ((response.statusCode === 301 || response.statusCode === 302)
+      && typeof response.headers.location !== 'undefined') {
+      location = url.parse(response.headers.location);
+      if (location.host === req.headers.host) {
+        if (self.source.https && !self.target.https) {
+          response.headers.location = response.headers.location.replace(/^http\:/, 'https:');
+        }
+        if (self.target.https && !self.source.https) {
+          response.headers.location = response.headers.location.replace(/^https\:/, 'http:');
+        }
+      }
+    }
+
+    //
+    // When the `reverseProxy` `response` ends, end the
+    // corresponding outgoing `res` unless we have entered
+    // an error state. In which case, assume `res.end()` has
+    // already been called and the 'error' event listener
+    // removed.
+    //
+    var ended = false;
+    response.on('close', function () {
+      if (!ended) { response.emit('end') }
+    });
+
+    //
+    // After reading a chunked response, the underlying socket
+    // will hit EOF and emit a 'end' event, which will abort
+    // the request. If the socket was paused at that time,
+    // pending data gets discarded, truncating the response.
+    // This code makes sure that we flush pending data.
+    //
+    response.connection.on('end', function () {
+      if (response.readable && response.resume) {
+        response.resume();
+      }
+    });
+
+    response.on('end', function () {
+      ended = true;
+      if (!errState) {
+        try { res.end() }
+        catch (ex) { console.error("res.end error: %s", ex.message) }
+
+        // Emit the `end` event now that we have completed proxying
+        self.emit('end', req, res, response);
+      }
+    });
+
+    // Allow observer to modify headers or abort response
+    try { self.emit('proxyResponse', req, res, response) }
+    catch (ex) {
+      errState = true;
+      return;
+    }
+
+    // Set the headers of the client response
+    if (res.sentHeaders !== true) {
+      Object.keys(response.headers).forEach(function (key) {
+        res.setHeader(key, response.headers[key]);
+      });
+      res.writeHead(response.statusCode);
+    }
+
+    function ondata(chunk) {
+      if (res.writable) {
+        // Only pause if the underlying buffers are full,
+        // *and* the connection is not in 'closing' state.
+        // Otherwise, the pause will cause pending data to
+        // be discarded and silently lost.
+        if (false === res.write(chunk) && response.pause
+            && response.connection.readable) {
+          response.pause();
+        }
+      }
+    }
+
+    response.on('data', ondata);
+
+    function ondrain() {
+      if (response.readable && response.resume) {
+        response.resume();
+      }
+    }
+
+    res.on('drain', ondrain);
+  });
+
+  // allow unlimited listeners
+  reverseProxy.setMaxListeners(0);
+
+  //
+  // Handle 'error' events from the `reverseProxy`. Setup timeout override if needed
+  //
+  reverseProxy.once('error', proxyError);
+
+  // Set a timeout on the socket if `this.timeout` is specified.
+  reverseProxy.once('socket', function (socket) {
+    if (self.timeout) {
+      socket.setTimeout(self.timeout);
+    }
+  });
+
+  //
+  // Handle 'error' events from the `req` (e.g. `Parse Error`).
+  //
+  req.on('error', proxyError);
+
+  //
+  // If `req` is aborted, we abort our `reverseProxy` request as well.
+  //
+  req.on('aborted', function () {
+    reverseProxy.abort();
+  });
+
+  //
+  // For each data `chunk` received from the incoming
+  // `req` write it to the `reverseProxy` request.
+  //
+  req.on('data', function (chunk) {
+    if (!errState) {
+      var flushed = reverseProxy.write(chunk);
+      if (!flushed) {
+        req.pause();
+        reverseProxy.once('drain', function () {
+          try { req.resume() }
+          catch (er) { console.error("req.resume error: %s", er.message) }
+        });
+
+        //
+        // Force the `drain` event in 100ms if it hasn't
+        // happened on its own.
+        //
+        setTimeout(function () {
+          reverseProxy.emit('drain');
+        }, 100);
+      }
+    }
+  });
+
+  //
+  // When the incoming `req` ends, end the corresponding `reverseProxy`
+  // request unless we have entered an error state.
+  //
+  req.on('end', function () {
+    if (!errState) {
+      reverseProxy.end();
+    }
+  });
+
+  // Aborts reverseProxy if client aborts the connection.
+  req.on('close', function () {
+    if (!errState) {
+      reverseProxy.abort();
+    }
+  });
+
+  //
+  // If we have been passed buffered data, resume it.
+  //
+  if (buffer) {
+    return !errState
+      ? buffer.resume()
+      : buffer.destroy();
+  }
+};
+
+exports.buffer = function (obj) {
+  var events = [],
+      onData,
+      onEnd;
+
+  obj.on('data', onData = function (data, encoding) {
+    events.push(['data', data, encoding]);
+  });
+
+  obj.on('end', onEnd = function (data, encoding) {
+    events.push(['end', data, encoding]);
+  });
+
+  return {
+    end: function () {
+      obj.removeListener('data', onData);
+      obj.removeListener('end', onEnd);
+    },
+    destroy: function () {
+      this.end();
+        this.resume = function () {
+          console.error("Cannot resume buffer after destroying it.");
+        };
+
+        onData = onEnd = events = obj = null;
+    },
+    resume: function () {
+      this.end();
+      for (var i = 0, len = events.length; i < len; ++i) {
+        obj.emit.apply(obj, events[i]);
+      }
+    }
+  };
+};
\ No newline at end of file
level2/network_simulation/lib/util.js
@@ -0,0 +1,41 @@
+"use strict";
+
+var crypto = require('crypto');
+
+var sum = exports.sum = function (array) {
+  return array.reduce(function (a, b) { return a + b; });
+}
+
+exports.average = function (array) {
+  return sum(array) / array.length;
+}
+
+exports.sign = function (nonce, secret) {
+  var hmacCreator = crypto.createHmac('sha256', secret);
+  hmacCreator.update(nonce);
+  return hmacCreator.digest('hex');
+}
+
+exports.hash = function (value) {
+  var hashCreator = crypto.createHash('sha256');
+  hashCreator.update(value);
+  return hashCreator.digest('hex');
+}
+
+exports.generateRandom = function (byteCount) {
+  var buf = crypto.randomBytes(byteCount);
+  return buf.toString('hex');
+}
+
+exports.randString = function (randGenerator, length) {
+  var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZ';
+  length = length || 32;
+  var string = '';
+  var charIndex;
+  var randomNumber;
+  for (charIndex = 0; charIndex < length; charIndex++) {
+    randomNumber = Math.floor(randGenerator() * chars.length);
+    string += chars.substring(randomNumber, randomNumber + 1);
+  }
+  return string;
+}
\ No newline at end of file
level2/network_simulation/backend.js
@@ -0,0 +1,172 @@
+#!/usr/bin/env node
+
+"use strict";
+
+var crypto = require('crypto');
+var fs = require('fs');
+var http = require("http");
+var nopt = require('nopt');
+var url = require("url");
+
+var util = require('./lib/util');
+
+function respond(response, statusCode, contents) {
+  response.writeHead(statusCode, {"Content-Type": "application/json"});
+  if (contents) {
+    response.write(JSON.stringify(contents));
+  }
+  response.end();
+}
+
+function respondError(response, responseCode, errorMessage) {
+  var contents = {
+    "error": errorMessage
+  };
+  respond(response, responseCode, contents);
+}
+
+var DownTime = function () {
+  this.startTime = Date.now();
+};
+DownTime.prototype.end = function () {
+  this.endTime = Date.now();
+};
+DownTime.prototype.duration = function () {
+  var endTime = this.endTime || Date.now()
+  return (endTime - this.startTime);
+};
+
+var QueueServer = function (responseDelay, allowedInFlight, maxQueueLength) {
+  this.responseDelay = responseDelay;
+  this.allowedInFlight = allowedInFlight;
+  this.maxQueueLength = maxQueueLength;
+  this.inFlight = 0;
+  this.queued = [];
+  this.downtimes = [];
+};
+QueueServer.prototype.pushRequest = function (reqData) {
+  if (this.queued.length >= this.maxQueueLength) {
+    respondError(reqData.response, "500", "Server is falling over from the load.");
+  } else {
+    this.queued.push(reqData);
+  }
+};
+QueueServer.prototype.popRequest = function () {
+  if (this.queued.length === 0) {
+    this.inFlight -= 1;
+    this.downtimes.push(new DownTime());
+    return;
+  }
+  var value = this.queued[0];
+  this.queued.splice(0, 1);
+  this.handleRequest(value);
+};
+QueueServer.prototype.handleRequest = function (reqData) {
+  if (reqData.closed) {
+    process.nextTick(this.popRequest.bind(this));
+    return;
+  }
+  var parsed = url.parse(reqData.request.url, true),
+      nonce = parsed.query.nonce,
+      contents,
+      hmac;
+  if (nonce === undefined) {
+    respondError(reqData.response, "400", "No nonce was provided.");
+    process.nextTick(this.popRequest.bind(this));
+    return;
+  }
+  hmac = util.sign(nonce, this.secret);
+  contents = {
+    "hmac": hmac
+  };
+  setTimeout(this.waitAndRespond.bind(this), this.responseDelay, reqData, contents);
+};
+QueueServer.prototype.waitAndRespond = function (reqData, contents) {
+  if (reqData.closed) {
+    console.log("Client timed out while waiting for us.");
+  } else {
+    respond(reqData.response, 200, contents);
+  }
+  this.popRequest();
+};
+
+var RequestData = function (request, response) {
+  this.request = request;
+  this.response = response;
+  this.closed = false;
+};
+
+function handleGET(queue, request, response) {
+  var reqData = new RequestData(request, response);
+  response.on('close', function () {
+    reqData.closed = true;
+  });
+  if (queue.inFlight >= queue.allowedInFlight) {
+    // Save this request for later
+    queue.pushRequest(reqData);
+  } else {
+    // Handle now
+    if (queue.downtimes.length != 0) {
+      queue.downtimes[queue.downtimes.length - 1].end();
+    }
+    queue.inFlight += 1;
+    queue.handleRequest(reqData);
+  }
+}
+
+function handleHEAD(validPath, request, response) {
+  var parsed = url.parse(request.url);
+  if (parsed['pathname'] == "/" + validPath) {
+    respond(response, 200);
+  } else {
+    respond(response, 400);
+  }
+}
+
+function main() {
+  var opts = {
+    "secret": String,
+    "in-socket": String,
+    "in-port": String,
+  };
+  var parsed = nopt(opts),
+      secret = parsed.secret || "defaultsecret",
+      secretHash = util.hash(secret),
+      listenTarget;
+
+  if (parsed['in-socket'] !== undefined && parsed['in-port'] !== undefined) {
+    console.log("Cannot specify both an in-port and an in-socket. Exiting.");
+    process.exit(1);
+  } else if (parsed['in-socket']) {
+    listenTarget = parsed['in-socket'];
+  } else {
+    // Default: listen on port 3001
+    listenTarget = parsed['in-port'] || '3001';
+  }
+  // Response delay in ms, Allowed in flight connections, Allowed queue length
+  var queue = new QueueServer(75, 2, 4);
+  queue.secret = secret;
+  var server = http.createServer(function (request, response) {
+    switch (request.method) {
+    case "GET":
+      handleGET(queue, request, response);
+      break;
+    case "HEAD":
+      handleHEAD(secretHash, request, response);
+      break;
+    default:
+      respondUserError(response, "Unsupported HTTP method " + request.method);
+    }
+  });
+
+  server.on("listening", function () {
+    if (parsed['in-socket'] !== undefined) {
+      fs.chmodSync(parsed['in-socket'], "0666");
+    }
+  })
+
+  console.log("The backend is up and listening.");
+  server.listen(listenTarget);
+}
+
+main();
level2/network_simulation/sword.js
@@ -0,0 +1,359 @@
+#!/usr/bin/env node
+
+"use strict";
+
+var assert = require('assert');
+var events = require('events');
+var fs = require('fs');
+var http = require('http');
+var nopt = require('nopt');
+var path = require('path');
+var seedModule = require('seed-random');
+var underscore = require('underscore');
+
+var checkServer = require('./lib/check_server');
+var util = require('./lib/util');
+
+var Request = function (client, roundIndex, nonce) {
+  this.client = client;
+  this.simulation = client.simulation;
+  this.roundIndex = roundIndex;
+  this.nonce = nonce;
+};
+
+Request.prototype.log = function (message, force) {
+  if (this.client.simulation.debugOutput || force) {
+    console.log('Request[%s,%s]: %s',
+      this.client.ip.slice(0, 8),
+      this.nonce.slice(0, 8),
+      message);
+  }
+};
+
+Request.prototype.handleResponse = function (res, pageData) {
+  var jsonResponse,
+      hmac;
+  if (res.statusCode !== 200) {
+    this.log("Non-200 return code.");
+    return;
+  }
+  try {
+    jsonResponse = JSON.parse(pageData);
+  } catch (err) {
+    this.log("Could not parse response body.", true);
+    return;
+  }
+  if (!jsonResponse.hmac) {
+    this.log("Responded with no HMAC.", true);
+    return;
+  }
+  hmac = util.sign(this.nonce, this.client.simulation.secret);
+  if (hmac === jsonResponse.hmac) {
+    this.simulation.registerSuccess(this.client, this.roundIndex, this.latency);
+  } else {
+    this.log("Responded with invalid HMAC", true);
+  }
+};
+
+Request.prototype.run = function () {
+  var self = this;
+  var baseOptions = {
+    path: '/?nonce=' + self.nonce,
+    headers: {
+      'X-Forwarded-For': self.client.ip
+    }
+  };
+  var options = underscore.extend(self.client.simulation.connection, baseOptions);
+  var startTime = Date.now();
+  var req = http.get(options, function (res) {
+    var pageData = "";
+    res.on('data', function (chunk) {
+      pageData += chunk;
+    });
+
+    res.on('end', function () {
+      self.latency = Date.now() - startTime;
+      self.handleResponse(res, pageData);
+    });
+  });
+
+  req.on('socket', function (socket) {
+    socket.setMaxListeners(0)
+    socket.setTimeout(1000); // In ms
+    socket.on('timeout', function () {
+      self.log("Timeout. Aborting.");
+      req.abort();
+    });
+  });
+
+  req.on("error", function (e) {
+    // Socket errors are expected when we our resources are small compared
+    // to the traffic that we try to push through.
+    self.log("Sword HTTP error: " + e.message);
+  });
+};
+
+var Client = function (simulation, birthRound) {
+  this.simulation = simulation;
+  this.ip = simulation.randString(48);
+  simulation.clients[this.ip] = this;
+  this.birthRound = birthRound;
+  this.lifetime = simulation.simulationParameters.clientLifetime;
+  if (simulation.random() > simulation.simulationParameters.pElephant) {
+    this.type = "mouse";
+    this.delayUntilStart = simulation.random() * simulation.simulationParameters.roundLength;
+    this.requestsPerRound = simulation.simulationParameters.mouseRequestsPerRound;
+  } else {
+    this.type = "elephant";
+    this.delayUntilStart = (simulation.random() / 5) * simulation.simulationParameters.roundLength;
+    this.requestsPerRound = simulation.simulationParameters.elephantRequestsPerRound;
+  }
+};
+
+Client.prototype.expired = function (roundIndex) {
+  return (this.lifetime <= roundIndex - this.birthRound);
+};
+
+Client.prototype.waitTime = function () {
+  return this.simulation.simulationParameters.roundLength / this.requestsPerRound;
+};
+
+Client.prototype.runMouse = function (roundIndex) {
+  var roundLength = this.simulation.simulationParameters.roundLength,
+      waitBetweenRequests = (roundLength - this.delayUntilStart) / this.requestsPerRound;
+  setTimeout(
+    this.sendRequest.bind(this),
+    this.delayUntilStart,
+    0,
+    roundIndex,
+    waitBetweenRequests
+  );
+};
+
+Client.prototype.runElephant = function (roundIndex) {
+  var waitBetweenRequests = this.simulation.simulationParameters.roundLength / this.requestsPerRound;
+  this.sendRequest(0, roundIndex, waitBetweenRequests);
+};
+
+Client.prototype.run = function (roundIndex) {
+  switch (this.type) {
+  case "mouse":
+    this.runMouse(roundIndex);
+    break;
+  case "elephant":
+    this.runElephant(roundIndex);
+    break;
+  default:
+    assert(false, "Fell through cases.");
+  }
+};
+
+Client.prototype.sendRequest = function (requestIndex, roundIndex, waitBetweenRequests) {
+  var requestNonce = util.generateRandom(64);
+  var request = new Request(this, roundIndex, requestNonce);
+  request.run();
+  var nextRequestIndex = requestIndex + 1;
+  if (nextRequestIndex < this.requestsPerRound) {
+    setTimeout(
+      this.sendRequest.bind(this),
+      waitBetweenRequests,
+      nextRequestIndex,
+      roundIndex,
+      waitBetweenRequests
+    );
+  }
+};
+
+var Simulation = function (seed, simulationParameters) {
+  this.simulationParameters = simulationParameters;
+  this.random = seedModule(seed);
+  this.simulationResults = [];
+  this.responseTotal = 0;
+  this.clients = {};
+  this.currentRound = -1;
+  this.roundStartTimes = [];
+};
+
+Simulation.prototype.randString = function (length) {
+  return util.randString(this.random, length);
+};
+
+Simulation.prototype.duration = function () {
+  return this.simulationParameters['roundLength'] * this.simulationParameters['roundCount'];
+};
+
+Simulation.prototype.runSimulation = function (clients) {
+  if (clients === undefined) {
+    clients = [];
+  }
+  var roundIndex = ++this.currentRound;
+  var i;
+  var client;
+
+  this.roundStartTimes[roundIndex] = Date.now();
+  this.simulationResults[roundIndex] = [];
+
+  var runningClients = [];
+  // Create clients to replace any that have expired
+  for (i = 0; i < clients.length; i++) {
+    client = clients[i];
+    if (!client.expired(roundIndex)) {
+      runningClients.push(client);
+    }
+  }
+  var clientsToCreate = this.simulationParameters.clientsPerRound - runningClients.length;
+  for (i = 0; i < clientsToCreate; i++) {
+    client = new Client(this, roundIndex);
+    runningClients.push(client);
+  }
+  // Run the clients
+  for (i = 0; i < runningClients.length; i++) {
+    runningClients[i].run(roundIndex);
+  }
+  if (roundIndex + 1 >= this.simulationParameters.roundCount) {
+    setTimeout(
+      this.scoreSimulation.bind(this),
+      this.simulationParameters.roundLength
+    );
+  } else {
+    setTimeout(
+      this.runSimulation.bind(this),
+      this.simulationParameters.roundLength,
+      runningClients
+    );
+  }
+};
+
+Simulation.prototype.registerSuccess = function (client, roundIndex, latency) {
+  this.responseTotal += 1;
+  if (client.type === "elephant") {
+    // No points for elephants!
+    return;
+  }
+  var mouseCount = this.simulationResults[roundIndex][client.ip] || 0;
+  this.simulationResults[roundIndex][client.ip] = mouseCount + 1;
+  this.maxLatency = this.maxLatency ? Math.max(this.maxLatency, latency) : latency;
+};
+
+Simulation.prototype.scoreRound = function (roundResults) {
+  console.log("Round");
+  var score = 0;
+  var self = this;
+  Object.keys(roundResults).forEach(function (ip) {
+    var successes = roundResults[ip];
+    var client = self.clients[ip];
+    console.log("IP %s (%s): %d", ip.slice(0, 8), client.type, successes);
+    score += successes;
+  });
+  if (score == 0) {
+    console.log("No mice got through this round.");
+  }
+  return score;
+};
+
+Simulation.prototype.calculateDowntime = function (responseCount) {
+  // Calculate the number of additional requests that could have been handled
+  var totalTime = this.simulationParameters['backendCount'] * this.simulationParameters['backendInFlight'] * this.duration(),
+      potentialResponses = totalTime / this.simulationParameters['backendProcessingTime'];
+  return potentialResponses - responseCount;
+}
+
+Simulation.prototype.scoreSimulation = function () {
+  console.log("Scoring now");
+  var resultsPath = this.resultsPath,
+      roundScores = this.simulationResults.map(this.scoreRound.bind(this)),
+      goodCount = util.sum(roundScores),
+      backendDeficit = this.calculateDowntime(this.responseTotal);
+
+  var output = {
+    "good_responses": goodCount,
+    "backend_deficit": backendDeficit,
+    "correct": true
+  };
+  console.log("Number of total responses %s", this.responseTotal)
+  console.log("Number of good responses: %s", goodCount);
+  console.log("Number of responses less than ideal: %s", backendDeficit);
+  fs.writeFileSync(resultsPath, JSON.stringify(output));
+  process.exit();
+};
+
+function main() {
+  var opts = {
+    "secret": String,
+    "out-socket": String,
+    "out-port": String,
+    "check-server": Boolean,
+    "debug": Boolean,
+    "results-path": String,
+    "run-time": Number
+  };
+  var parsed = nopt(opts),
+      resultsPath = parsed['results-path'] || path.resolve(__dirname, "results.json"),
+      secret = parsed.secret || "defaultsecret",
+      secretHash = util.hash(secret),
+      connectionOptions,
+      seed,
+      simulationParameters;
+
+  if (parsed['out-socket'] !== undefined && parsed['out-port'] !== undefined) {
+    console.log("Cannot specify both an out-port and an out-socket. Exiting.");
+    process.exit(1);
+  } else if (parsed['out-socket']) {
+    connectionOptions = {'socketPath': parsed['out-socket']};
+  } else {
+    connectionOptions = {
+      'host': 'localhost',
+      'port':  parsed['out-port'] || '3000'
+    };
+  }
+  connectionOptions['path'] = "/" + secretHash;
+
+  if (parsed.argv.remain.length > 1) {
+    console.log("Expected at most one extra arg and received more. Exiting.");
+  }
+  seed = parsed.argv.remain[0] || util.generateRandom(32);
+  console.log("Using seed %s", seed);
+
+  simulationParameters = {
+    'clientLifetime': 2, // In rounds
+    'roundLength': 500, // In ms
+    'roundCount': 40,
+    'clientsPerRound': 5,
+    'pElephant': 0.4,
+    'mouseRequestsPerRound': 2,
+    'elephantRequestsPerRound': 50,
+    'backendCount': 2,
+    'backendInFlight': 2,
+    'backendProcessingTime': 75
+  };
+
+  if (parsed['run-time']) {
+    simulationParameters.roundCount = Math.floor(
+      parsed['run-time'] * 1000 / simulationParameters.roundLength);
+  }
+
+  var simulation = new Simulation(seed, simulationParameters);
+  simulation.connection = connectionOptions;
+  simulation.resultsPath = resultsPath;
+  simulation.debugOutput = parsed['debug'];
+  simulation.secret = secret;
+
+  checkServer.checkWithBackoff(
+    connectionOptions,
+    function () {
+      if (parsed['check-server']) {
+        // Just check that the server is up.
+        console.log("Server is up.");
+        process.exit(0);
+      } else {
+        simulation.runSimulation();
+      }
+    },
+    function () {
+      console.log("Server is not up. Exiting.");
+      process.exit(1);
+    }
+  );
+}
+
+main();
level2/node_modules/.bin/nopt
@@ -0,0 +1,1 @@
+../nopt/bin/nopt.js
\ No newline at end of file
level2/node_modules/nopt/bin/nopt.js
@@ -0,0 +1,51 @@
+#!/usr/bin/env node
+var nopt = require("../lib/nopt")
+  , types = { num: Number
+            , bool: Boolean
+            , help: Boolean
+            , list: Array
+            , "num-list": [Number, Array]
+            , "str-list": [String, Array]
+            , "bool-list": [Boolean, Array]
+            , str: String
+            , clear: Boolean
+            , config: Boolean
+            , length: Number
+            }
+  , shorthands = { s: [ "--str", "astring" ]
+                 , b: [ "--bool" ]
+                 , nb: [ "--no-bool" ]
+                 , tft: [ "--bool-list", "--no-bool-list", "--bool-list", "true" ]
+                 , "?": ["--help"]
+                 , h: ["--help"]
+                 , H: ["--help"]
+                 , n: [ "--num", "125" ]
+                 , c: ["--config"]
+                 , l: ["--length"]
+                 }
+  , parsed = nopt( types
+                 , shorthands
+                 , process.argv
+                 , 2 )
+
+console.log("parsed", parsed)
+
+if (parsed.help) {
+  console.log("")
+  console.log("nopt cli tester")
+  console.log("")
+  console.log("types")
+  console.log(Object.keys(types).map(function M (t) {
+    var type = types[t]
+    if (Array.isArray(type)) {
+      return [t, type.map(function (type) { return type.name })]
+    }
+    return [t, type && type.name]
+  }).reduce(function (s, i) {
+    s[i[0]] = i[1]
+    return s
+  }, {}))
+  console.log("")
+  console.log("shorthands")
+  console.log(shorthands)
+}
level2/node_modules/nopt/examples/my-program.js
@@ -0,0 +1,30 @@
+#!/usr/bin/env node
+
+//process.env.DEBUG_NOPT = 1
+
+// my-program.js
+var nopt = require("../lib/nopt")
+  , Stream = require("stream").Stream
+  , path = require("path")
+  , knownOpts = { "foo" : [String, null]
+                , "bar" : [Stream, Number]
+                , "baz" : path
+                , "bloo" : [ "big", "medium", "small" ]
+                , "flag" : Boolean
+                , "pick" : Boolean
+                }
+  , shortHands = { "foofoo" : ["--foo", "Mr. Foo"]
+                 , "b7" : ["--bar", "7"]
+                 , "m" : ["--bloo", "medium"]
+                 , "p" : ["--pick"]
+                 , "f" : ["--flag", "true"]
+                 , "g" : ["--flag"]
+                 , "s" : "--flag"
+                 }
+             // everything is optional.
+             // knownOpts and shorthands default to {}
+             // arg list defaults to process.argv
+             // slice defaults to 2
+  , parsed = nopt(knownOpts, shortHands, process.argv, 2)
+
+console.log("parsed =\n"+ require("util").inspect(parsed))
level2/node_modules/nopt/lib/nopt.js
@@ -0,0 +1,612 @@
+// info about each config option.
+
+var debug = process.env.DEBUG_NOPT || process.env.NOPT_DEBUG
+  ? function () { console.error.apply(console, arguments) }
+  : function () {}
+
+var url = require("url")
+  , path = require("path")
+  , Stream = require("stream").Stream
+  , abbrev = require("abbrev")
+
+module.exports = exports = nopt
+exports.clean = clean
+
+exports.typeDefs =
+  { String  : { type: String,  validate: validateString  }
+  , Boolean : { type: Boolean, validate: validateBoolean }
+  , url     : { type: url,     validate: validateUrl     }
+  , Number  : { type: Number,  validate: validateNumber  }
+  , path    : { type: path,    validate: validatePath    }
+  , Stream  : { type: Stream,  validate: validateStream  }
+  , Date    : { type: Date,    validate: validateDate    }
+  }
+
+function nopt (types, shorthands, args, slice) {
+  args = args || process.argv
+  types = types || {}
+  shorthands = shorthands || {}
+  if (typeof slice !== "number") slice = 2
+
+  debug(types, shorthands, args, slice)
+
+  args = args.slice(slice)
+  var data = {}
+    , key
+    , remain = []
+    , cooked = args
+    , original = args.slice(0)
+
+  parse(args, data, remain, types, shorthands)
+  // now data is full
+  clean(data, types, exports.typeDefs)
+  data.argv = {remain:remain,cooked:cooked,original:original}
+  Object.defineProperty(data.argv, 'toString', { value: function () {
+    return this.original.map(JSON.stringify).join(" ")
+  }, enumerable: false })
+  return data
+}
+
+function clean (data, types, typeDefs) {
+  typeDefs = typeDefs || exports.typeDefs
+  var remove = {}
+    , typeDefault = [false, true, null, String, Number, Array]
+
+  Object.keys(data).forEach(function (k) {
+    if (k === "argv") return
+    var val = data[k]
+      , isArray = Array.isArray(val)
+      , type = types[k]
+    if (!isArray) val = [val]
+    if (!type) type = typeDefault
+    if (type === Array) type = typeDefault.concat(Array)
+    if (!Array.isArray(type)) type = [type]
+
+    debug("val=%j", val)
+    debug("types=", type)
+    val = val.map(function (val) {
+      // if it's an unknown value, then parse false/true/null/numbers/dates
+      if (typeof val === "string") {
+        debug("string %j", val)
+        val = val.trim()
+        if ((val === "null" && ~type.indexOf(null))
+            || (val === "true" &&
+               (~type.indexOf(true) || ~type.indexOf(Boolean)))
+            || (val === "false" &&
+               (~type.indexOf(false) || ~type.indexOf(Boolean)))) {
+          val = JSON.parse(val)
+          debug("jsonable %j", val)
+        } else if (~type.indexOf(Number) && !isNaN(val)) {
+          debug("convert to number", val)
+          val = +val
+        } else if (~type.indexOf(Date) && !isNaN(Date.parse(val))) {
+          debug("convert to date", val)
+          val = new Date(val)
+        }
+      }
+
+      if (!types.hasOwnProperty(k)) {
+        return val
+      }
+
+      // allow `--no-blah` to set 'blah' to null if null is allowed
+      if (val === false && ~type.indexOf(null) &&
+          !(~type.indexOf(false) || ~type.indexOf(Boolean))) {
+        val = null
+      }
+
+      var d = {}
+      d[k] = val
+      debug("prevalidated val", d, val, types[k])
+      if (!validate(d, k, val, types[k], typeDefs)) {
+        if (exports.invalidHandler) {
+          exports.invalidHandler(k, val, types[k], data)
+        } else if (exports.invalidHandler !== false) {
+          debug("invalid: "+k+"="+val, types[k])
+        }
+        return remove
+      }
+      debug("validated val", d, val, types[k])
+      return d[k]
+    }).filter(function (val) { return val !== remove })
+
+    if (!val.length) delete data[k]
+    else if (isArray) {
+      debug(isArray, data[k], val)
+      data[k] = val
+    } else data[k] = val[0]
+
+    debug("k=%s val=%j", k, val, data[k])
+  })
+}
+
+function validateString (data, k, val) {
+  data[k] = String(val)
+}
+
+function validatePath (data, k, val) {
+  data[k] = path.resolve(String(val))
+  return true
+}
+
+function validateNumber (data, k, val) {
+  debug("validate Number %j %j %j", k, val, isNaN(val))
+  if (isNaN(val)) return false
+  data[k] = +val
+}
+
+function validateDate (data, k, val) {
+  debug("validate Date %j %j %j", k, val, Date.parse(val))
+  var s = Date.parse(val)
+  if (isNaN(s)) return false
+  data[k] = new Date(val)
+}
+
+function validateBoolean (data, k, val) {
+  if (val instanceof Boolean) val = val.valueOf()
+  else if (typeof val === "string") {
+    if (!isNaN(val)) val = !!(+val)
+    else if (val === "null" || val === "false") val = false
+    else val = true
+  } else val = !!val
+  data[k] = val
+}
+
+function validateUrl (data, k, val) {
+  val = url.parse(String(val))
+  if (!val.host) return false
+  data[k] = val.href
+}
+
+function validateStream (data, k, val) {
+  if (!(val instanceof Stream)) return false
+  data[k] = val
+}
+
+function validate (data, k, val, type, typeDefs) {
+  // arrays are lists of types.
+  if (Array.isArray(type)) {
+    for (var i = 0, l = type.length; i < l; i ++) {
+      if (type[i] === Array) continue
+      if (validate(data, k, val, type[i], typeDefs)) return true
+    }
+    delete data[k]
+    return false
+  }
+
+  // an array of anything?
+  if (type === Array) return true
+
+  // NaN is poisonous.  Means that something is not allowed.
+  if (type !== type) {
+    debug("Poison NaN", k, val, type)
+    delete data[k]
+    return false
+  }
+
+  // explicit list of values
+  if (val === type) {
+    debug("Explicitly allowed %j", val)
+    // if (isArray) (data[k] = data[k] || []).push(val)
+    // else data[k] = val
+    data[k] = val
+    return true
+  }
+
+  // now go through the list of typeDefs, validate against each one.
+  var ok = false
+    , types = Object.keys(typeDefs)
+  for (var i = 0, l = types.length; i < l; i ++) {
+    debug("test type %j %j %j", k, val, types[i])
+    var t = typeDefs[types[i]]
+    if (t && type === t.type) {
+      var d = {}
+      ok = false !== t.validate(d, k, val)
+      val = d[k]
+      if (ok) {
+        // if (isArray) (data[k] = data[k] || []).push(val)
+        // else data[k] = val
+        data[k] = val
+        break
+      }
+    }
+  }
+  debug("OK? %j (%j %j %j)", ok, k, val, types[i])
+
+  if (!ok) delete data[k]
+  return ok
+}
+
+function parse (args, data, remain, types, shorthands) {
+  debug("parse", args, data, remain)
+
+  var key = null
+    , abbrevs = abbrev(Object.keys(types))
+    , shortAbbr = abbrev(Object.keys(shorthands))
+
+  for (var i = 0; i < args.length; i ++) {
+    var arg = args[i]
+    debug("arg", arg)
+
+    if (arg.match(/^-{2,}$/)) {
+      // done with keys.
+      // the rest are args.
+      remain.push.apply(remain, args.slice(i + 1))
+      args[i] = "--"
+      break
+    }
+    var hadEq = false
+    if (arg.charAt(0) === "-" && arg.length > 1) {
+      if (arg.indexOf("=") !== -1) {
+        hadEq = true
+        var v = arg.split("=")
+        arg = v.shift()
+        v = v.join("=")
+        args.splice.apply(args, [i, 1].concat([arg, v]))
+      }
+
+      // see if it's a shorthand
+      // if so, splice and back up to re-parse it.
+      var shRes = resolveShort(arg, shorthands, shortAbbr, abbrevs)
+      debug("arg=%j shRes=%j", arg, shRes)
+      if (shRes) {
+        debug(arg, shRes)
+        args.splice.apply(args, [i, 1].concat(shRes))
+        if (arg !== shRes[0]) {
+          i --
+          continue
+        }
+      }
+      arg = arg.replace(/^-+/, "")
+      var no = null
+      while (arg.toLowerCase().indexOf("no-") === 0) {
+        no = !no
+        arg = arg.substr(3)
+      }
+
+      if (abbrevs[arg]) arg = abbrevs[arg]
+
+      var isArray = types[arg] === Array ||
+        Array.isArray(types[arg]) && types[arg].indexOf(Array) !== -1
+
+      // allow unknown things to be arrays if specified multiple times.
+      if (!types.hasOwnProperty(arg) && data.hasOwnProperty(arg)) {
+        if (!Array.isArray(data[arg]))
+          data[arg] = [data[arg]]
+        isArray = true
+      }
+
+      var val
+        , la = args[i + 1]
+
+      var isBool = typeof no === 'boolean' ||
+        types[arg] === Boolean ||
+        Array.isArray(types[arg]) && types[arg].indexOf(Boolean) !== -1 ||
+        (typeof types[arg] === 'undefined' && !hadEq) ||
+        (la === "false" &&
+         (types[arg] === null ||
+          Array.isArray(types[arg]) && ~types[arg].indexOf(null)))
+
+      if (isBool) {
+        // just set and move along
+        val = !no
+        // however, also support --bool true or --bool false
+        if (la === "true" || la === "false") {
+          val = JSON.parse(la)
+          la = null
+          if (no) val = !val
+          i ++
+        }
+
+        // also support "foo":[Boolean, "bar"] and "--foo bar"
+        if (Array.isArray(types[arg]) && la) {
+          if (~types[arg].indexOf(la)) {
+            // an explicit type
+            val = la
+            i ++
+          } else if ( la === "null" && ~types[arg].indexOf(null) ) {
+            // null allowed
+            val = null
+            i ++
+          } else if ( !la.match(/^-{2,}[^-]/) &&
+                      !isNaN(la) &&
+                      ~types[arg].indexOf(Number) ) {
+            // number
+            val = +la
+            i ++
+          } else if ( !la.match(/^-[^-]/) && ~types[arg].indexOf(String) ) {
+            // string
+            val = la
+            i ++
+          }
+        }
+
+        if (isArray) (data[arg] = data[arg] || []).push(val)
+        else data[arg] = val
+
+        continue
+      }
+
+      if (la && la.match(/^-{2,}$/)) {
+        la = undefined
+        i --
+      }
+
+      val = la === undefined ? true : la
+      if (isArray) (data[arg] = data[arg] || []).push(val)
+      else data[arg] = val
+
+      i ++
+      continue
+    }
+    remain.push(arg)
+  }
+}
+
+function resolveShort (arg, shorthands, shortAbbr, abbrevs) {
+  // handle single-char shorthands glommed together, like
+  // npm ls -glp, but only if there is one dash, and only if
+  // all of the chars are single-char shorthands, and it's
+  // not a match to some other abbrev.
+  arg = arg.replace(/^-+/, '')
+
+  // if it's an exact known option, then don't go any further
+  if (abbrevs[arg] === arg)
+    return null
+
+  // if it's an exact known shortopt, same deal
+  if (shorthands[arg]) {
+    // make it an array, if it's a list of words
+    if (shorthands[arg] && !Array.isArray(shorthands[arg]))
+      shorthands[arg] = shorthands[arg].split(/\s+/)
+
+    return shorthands[arg]
+  }
+
+  // first check to see if this arg is a set of single-char shorthands
+  var singles = shorthands.___singles
+  if (!singles) {
+    singles = Object.keys(shorthands).filter(function (s) {
+      return s.length === 1
+    }).reduce(function (l,r) {
+      l[r] = true
+      return l
+    }, {})
+    shorthands.___singles = singles
+    debug('shorthand singles', singles)
+  }
+
+  var chrs = arg.split("").filter(function (c) {
+    return singles[c]
+  })
+
+  if (chrs.join("") === arg) return chrs.map(function (c) {
+    return shorthands[c]
+  }).reduce(function (l, r) {
+    return l.concat(r)
+  }, [])
+
+
+  // if it's an arg abbrev, and not a literal shorthand, then prefer the arg
+  if (abbrevs[arg] && !shorthands[arg])
+    return null
+
+  // if it's an abbr for a shorthand, then use that
+  if (shortAbbr[arg])
+    arg = shortAbbr[arg]
+
+  // make it an array, if it's a list of words
+  if (shorthands[arg] && !Array.isArray(shorthands[arg]))
+    shorthands[arg] = shorthands[arg].split(/\s+/)
+
+  return shorthands[arg]
+}
+
+if (module === require.main) {
+var assert = require("assert")
+  , util = require("util")
+
+  , shorthands =
+    { s : ["--loglevel", "silent"]
+    , d : ["--loglevel", "info"]
+    , dd : ["--loglevel", "verbose"]
+    , ddd : ["--loglevel", "silly"]
+    , noreg : ["--no-registry"]
+    , reg : ["--registry"]
+    , "no-reg" : ["--no-registry"]
+    , silent : ["--loglevel", "silent"]
+    , verbose : ["--loglevel", "verbose"]
+    , h : ["--usage"]
+    , H : ["--usage"]
+    , "?" : ["--usage"]
+    , help : ["--usage"]
+    , v : ["--version"]
+    , f : ["--force"]
+    , desc : ["--description"]
+    , "no-desc" : ["--no-description"]
+    , "local" : ["--no-global"]
+    , l : ["--long"]
+    , p : ["--parseable"]
+    , porcelain : ["--parseable"]
+    , g : ["--global"]
+    }
+
+  , types =
+    { aoa: Array
+    , nullstream: [null, Stream]
+    , date: Date
+    , str: String
+    , browser : String
+    , cache : path
+    , color : ["always", Boolean]
+    , depth : Number
+    , description : Boolean
+    , dev : Boolean
+    , editor : path
+    , force : Boolean
+    , global : Boolean
+    , globalconfig : path
+    , group : [String, Number]
+    , gzipbin : String
+    , logfd : [Number, Stream]
+    , loglevel : ["silent","win","error","warn","info","verbose","silly"]
+    , long : Boolean
+    , "node-version" : [false, String]
+    , npaturl : url
+    , npat : Boolean
+    , "onload-script" : [false, String]
+    , outfd : [Number, Stream]
+    , parseable : Boolean
+    , pre: Boolean
+    , prefix: path
+    , proxy : url
+    , "rebuild-bundle" : Boolean
+    , registry : url
+    , searchopts : String
+    , searchexclude: [null, String]
+    , shell : path
+    , t: [Array, String]
+    , tag : String
+    , tar : String
+    , tmp : path
+    , "unsafe-perm" : Boolean
+    , usage : Boolean
+    , user : String
+    , username : String
+    , userconfig : path
+    , version : Boolean
+    , viewer: path
+    , _exit : Boolean
+    }
+
+; [["-v", {version:true}, []]
+  ,["---v", {version:true}, []]
+  ,["ls -s --no-reg connect -d",
+    {loglevel:"info",registry:null},["ls","connect"]]
+  ,["ls ---s foo",{loglevel:"silent"},["ls","foo"]]
+  ,["ls --registry blargle", {}, ["ls"]]
+  ,["--no-registry", {registry:null}, []]
+  ,["--no-color true", {color:false}, []]
+  ,["--no-color false", {color:true}, []]
+  ,["--no-color", {color:false}, []]
+  ,["--color false", {color:false}, []]
+  ,["--color --logfd 7", {logfd:7,color:true}, []]
+  ,["--color=true", {color:true}, []]
+  ,["--logfd=10", {logfd:10}, []]
+  ,["--tmp=/tmp -tar=gtar",{tmp:"/tmp",tar:"gtar"},[]]
+  ,["--tmp=tmp -tar=gtar",
+    {tmp:path.resolve(process.cwd(), "tmp"),tar:"gtar"},[]]
+  ,["--logfd x", {}, []]
+  ,["a -true -- -no-false", {true:true},["a","-no-false"]]
+  ,["a -no-false", {false:false},["a"]]
+  ,["a -no-no-true", {true:true}, ["a"]]
+  ,["a -no-no-no-false", {false:false}, ["a"]]
+  ,["---NO-no-No-no-no-no-nO-no-no"+
+    "-No-no-no-no-no-no-no-no-no"+
+    "-no-no-no-no-NO-NO-no-no-no-no-no-no"+
+    "-no-body-can-do-the-boogaloo-like-I-do"
+   ,{"body-can-do-the-boogaloo-like-I-do":false}, []]
+  ,["we are -no-strangers-to-love "+
+    "--you-know=the-rules --and=so-do-i "+
+    "---im-thinking-of=a-full-commitment "+
+    "--no-you-would-get-this-from-any-other-guy "+
+    "--no-gonna-give-you-up "+
+    "-no-gonna-let-you-down=true "+
+    "--no-no-gonna-run-around false "+
+    "--desert-you=false "+
+    "--make-you-cry false "+
+    "--no-tell-a-lie "+
+    "--no-no-and-hurt-you false"
+   ,{"strangers-to-love":false
+    ,"you-know":"the-rules"
+    ,"and":"so-do-i"
+    ,"you-would-get-this-from-any-other-guy":false
+    ,"gonna-give-you-up":false
+    ,"gonna-let-you-down":false
+    ,"gonna-run-around":false
+    ,"desert-you":false
+    ,"make-you-cry":false
+    ,"tell-a-lie":false
+    ,"and-hurt-you":false
+    },["we", "are"]]
+  ,["-t one -t two -t three"
+   ,{t: ["one", "two", "three"]}
+   ,[]]
+  ,["-t one -t null -t three four five null"
+   ,{t: ["one", "null", "three"]}
+   ,["four", "five", "null"]]
+  ,["-t foo"
+   ,{t:["foo"]}
+   ,[]]
+  ,["--no-t"
+   ,{t:["false"]}
+   ,[]]
+  ,["-no-no-t"
+   ,{t:["true"]}
+   ,[]]
+  ,["-aoa one -aoa null -aoa 100"
+   ,{aoa:["one", null, 100]}
+   ,[]]
+  ,["-str 100"
+   ,{str:"100"}
+   ,[]]
+  ,["--color always"
+   ,{color:"always"}
+   ,[]]
+  ,["--no-nullstream"
+   ,{nullstream:null}
+   ,[]]
+  ,["--nullstream false"
+   ,{nullstream:null}
+   ,[]]
+  ,["--notadate=2011-01-25"
+   ,{notadate: "2011-01-25"}
+   ,[]]
+  ,["--date 2011-01-25"
+   ,{date: new Date("2011-01-25")}
+   ,[]]
+  ,["-cl 1"
+   ,{config: true, length: 1}
+   ,[]
+   ,{config: Boolean, length: Number, clear: Boolean}
+   ,{c: "--config", l: "--length"}]
+  ,["--acount bla"
+   ,{"acount":true}
+   ,["bla"]
+   ,{account: Boolean, credentials: Boolean, options: String}
+   ,{a:"--account", c:"--credentials",o:"--options"}]
+  ,["--clear"
+   ,{clear:true}
+   ,[]
+   ,{clear:Boolean,con:Boolean,len:Boolean,exp:Boolean,add:Boolean,rep:Boolean}
+   ,{c:"--con",l:"--len",e:"--exp",a:"--add",r:"--rep"}]
+  ,["--file -"
+   ,{"file":"-"}
+   ,[]
+   ,{file:String}
+   ,{}]
+  ,["--file -"
+   ,{"file":true}
+   ,["-"]
+   ,{file:Boolean}
+   ,{}]
+  ].forEach(function (test) {
+    var argv = test[0].split(/\s+/)
+      , opts = test[1]
+      , rem = test[2]
+      , actual = nopt(test[3] || types, test[4] || shorthands, argv, 0)
+      , parsed = actual.argv
+    delete actual.argv
+    console.log(util.inspect(actual, false, 2, true), parsed.remain)
+    for (var i in opts) {
+      var e = JSON.stringify(opts[i])
+        , a = JSON.stringify(actual[i] === undefined ? null : actual[i])
+      if (e && typeof e === "object") {
+        assert.deepEqual(e, a)
+      } else {
+        assert.equal(e, a)
+      }
+    }
+    assert.deepEqual(rem, parsed.remain)
+  })
+}
level2/node_modules/nopt/node_modules/abbrev/lib/abbrev.js
@@ -0,0 +1,111 @@
+
+module.exports = exports = abbrev.abbrev = abbrev
+
+abbrev.monkeyPatch = monkeyPatch
+
+function monkeyPatch () {
+  Object.defineProperty(Array.prototype, 'abbrev', {
+    value: function () { return abbrev(this) },
+    enumerable: false, configurable: true, writable: true
+  })
+
+  Object.defineProperty(Object.prototype, 'abbrev', {
+    value: function () { return abbrev(Object.keys(this)) },
+    enumerable: false, configurable: true, writable: true
+  })
+}
+
+function abbrev (list) {
+  if (arguments.length !== 1 || !Array.isArray(list)) {
+    list = Array.prototype.slice.call(arguments, 0)
+  }
+  for (var i = 0, l = list.length, args = [] ; i < l ; i ++) {
+    args[i] = typeof list[i] === "string" ? list[i] : String(list[i])
+  }
+
+  // sort them lexicographically, so that they're next to their nearest kin
+  args = args.sort(lexSort)
+
+  // walk through each, seeing how much it has in common with the next and previous
+  var abbrevs = {}
+    , prev = ""
+  for (var i = 0, l = args.length ; i < l ; i ++) {
+    var current = args[i]
+      , next = args[i + 1] || ""
+      , nextMatches = true
+      , prevMatches = true
+    if (current === next) continue
+    for (var j = 0, cl = current.length ; j < cl ; j ++) {
+      var curChar = current.charAt(j)
+      nextMatches = nextMatches && curChar === next.charAt(j)
+      prevMatches = prevMatches && curChar === prev.charAt(j)
+      if (!nextMatches && !prevMatches) {
+        j ++
+        break
+      }
+    }
+    prev = current
+    if (j === cl) {
+      abbrevs[current] = current
+      continue
+    }
+    for (var a = current.substr(0, j) ; j <= cl ; j ++) {
+      abbrevs[a] = current
+      a += current.charAt(j)
+    }
+  }
+  return abbrevs
+}
+
+function lexSort (a, b) {
+  return a === b ? 0 : a > b ? 1 : -1
+}
+
+
+// tests
+if (module === require.main) {
+
+var assert = require("assert")
+var util = require("util")
+
+console.log("running tests")
+function test (list, expect) {
+  var actual = abbrev(list)
+  assert.deepEqual(actual, expect,
+    "abbrev("+util.inspect(list)+") === " + util.inspect(expect) + "\n"+
+    "actual: "+util.inspect(actual))
+  actual = abbrev.apply(exports, list)
+  assert.deepEqual(abbrev.apply(exports, list), expect,
+    "abbrev("+list.map(JSON.stringify).join(",")+") === " + util.inspect(expect) + "\n"+
+    "actual: "+util.inspect(actual))
+}
+
+test([ "ruby", "ruby", "rules", "rules", "rules" ],
+{ rub: 'ruby'
+, ruby: 'ruby'
+, rul: 'rules'
+, rule: 'rules'
+, rules: 'rules'
+})
+test(["fool", "foom", "pool", "pope"],
+{ fool: 'fool'
+, foom: 'foom'
+, poo: 'pool'
+, pool: 'pool'
+, pop: 'pope'
+, pope: 'pope'
+})
+test(["a", "ab", "abc", "abcd", "abcde", "acde"],
+{ a: 'a'
+, ab: 'ab'
+, abc: 'abc'
+, abcd: 'abcd'
+, abcde: 'abcde'
+, ac: 'acde'
+, acd: 'acde'
+, acde: 'acde'
+})
+
+console.log("pass")
+
+}
level2/node_modules/nopt/node_modules/abbrev/LICENSE
@@ -0,0 +1,23 @@
+Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
level2/node_modules/nopt/node_modules/abbrev/package.json
@@ -0,0 +1,29 @@
+{
+  "name": "abbrev",
+  "version": "1.0.4",
+  "description": "Like ruby's abbrev module, but in js",
+  "author": {
+    "name": "Isaac Z. Schlueter",
+    "email": "i@izs.me"
+  },
+  "main": "./lib/abbrev.js",
+  "scripts": {
+    "test": "node lib/abbrev.js"
+  },
+  "repository": {
+    "type": "git",
+    "url": "http://github.com/isaacs/abbrev-js"
+  },
+  "license": {
+    "type": "MIT",
+    "url": "https://github.com/isaacs/abbrev-js/raw/master/LICENSE"
+  },
+  "readme": "# abbrev-js\n\nJust like [ruby's Abbrev](http://apidock.com/ruby/Abbrev).\n\nUsage:\n\n    var abbrev = require(\"abbrev\");\n    abbrev(\"foo\", \"fool\", \"folding\", \"flop\");\n    \n    // returns:\n    { fl: 'flop'\n    , flo: 'flop'\n    , flop: 'flop'\n    , fol: 'folding'\n    , fold: 'folding'\n    , foldi: 'folding'\n    , foldin: 'folding'\n    , folding: 'folding'\n    , foo: 'foo'\n    , fool: 'fool'\n    }\n\nThis is handy for command-line scripts, or other cases where you want to be able to accept shorthands.\n",
+  "readmeFilename": "README.md",
+  "bugs": {
+    "url": "https://github.com/isaacs/abbrev-js/issues"
+  },
+  "homepage": "https://github.com/isaacs/abbrev-js",
+  "_id": "abbrev@1.0.4",
+  "_from": "abbrev@1"
+}
level2/node_modules/nopt/node_modules/abbrev/README.md
@@ -0,0 +1,23 @@
+# abbrev-js
+
+Just like [ruby's Abbrev](http://apidock.com/ruby/Abbrev).
+
+Usage:
+
+    var abbrev = require("abbrev");
+    abbrev("foo", "fool", "folding", "flop");
+    
+    // returns:
+    { fl: 'flop'
+    , flo: 'flop'
+    , flop: 'flop'
+    , fol: 'folding'
+    , fold: 'folding'
+    , foldi: 'folding'
+    , foldin: 'folding'
+    , folding: 'folding'
+    , foo: 'foo'
+    , fool: 'fool'
+    }
+
+This is handy for command-line scripts, or other cases where you want to be able to accept shorthands.
level2/node_modules/nopt/.npmignore
@@ -0,0 +1,1 @@
+node_modules
level2/node_modules/nopt/LICENSE
@@ -0,0 +1,23 @@
+Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
level2/node_modules/nopt/package.json
@@ -0,0 +1,36 @@
+{
+  "name": "nopt",
+  "version": "2.1.2",
+  "description": "Option parsing for Node, supporting types, shorthands, etc. Used by npm.",
+  "author": {
+    "name": "Isaac Z. Schlueter",
+    "email": "i@izs.me",
+    "url": "http://blog.izs.me/"
+  },
+  "main": "lib/nopt.js",
+  "scripts": {
+    "test": "node lib/nopt.js"
+  },
+  "repository": {
+    "type": "git",
+    "url": "http://github.com/isaacs/nopt"
+  },
+  "bin": {
+    "nopt": "./bin/nopt.js"
+  },
+  "license": {
+    "type": "MIT",
+    "url": "https://github.com/isaacs/nopt/raw/master/LICENSE"
+  },
+  "dependencies": {
+    "abbrev": "1"
+  },
+  "readme": "If you want to write an option parser, and have it be good, there are\ntwo ways to do it.  The Right Way, and the Wrong Way.\n\nThe Wrong Way is to sit down and write an option parser.  We've all done\nthat.\n\nThe Right Way is to write some complex configurable program with so many\noptions that you go half-insane just trying to manage them all, and put\nit off with duct-tape solutions until you see exactly to the core of the\nproblem, and finally snap and write an awesome option parser.\n\nIf you want to write an option parser, don't write an option parser.\nWrite a package manager, or a source control system, or a service\nrestarter, or an operating system.  You probably won't end up with a\ngood one of those, but if you don't give up, and you are relentless and\ndiligent enough in your procrastination, you may just end up with a very\nnice option parser.\n\n## USAGE\n\n    // my-program.js\n    var nopt = require(\"nopt\")\n      , Stream = require(\"stream\").Stream\n      , path = require(\"path\")\n      , knownOpts = { \"foo\" : [String, null]\n                    , \"bar\" : [Stream, Number]\n                    , \"baz\" : path\n                    , \"bloo\" : [ \"big\", \"medium\", \"small\" ]\n                    , \"flag\" : Boolean\n                    , \"pick\" : Boolean\n                    , \"many\" : [String, Array]\n                    }\n      , shortHands = { \"foofoo\" : [\"--foo\", \"Mr. Foo\"]\n                     , \"b7\" : [\"--bar\", \"7\"]\n                     , \"m\" : [\"--bloo\", \"medium\"]\n                     , \"p\" : [\"--pick\"]\n                     , \"f\" : [\"--flag\"]\n                     }\n                 // everything is optional.\n                 // knownOpts and shorthands default to {}\n                 // arg list defaults to process.argv\n                 // slice defaults to 2\n      , parsed = nopt(knownOpts, shortHands, process.argv, 2)\n    console.log(parsed)\n\nThis would give you support for any of the following:\n\n```bash\n$ node my-program.js --foo \"blerp\" --no-flag\n{ \"foo\" : \"blerp\", \"flag\" : false }\n\n$ node my-program.js ---bar 7 --foo \"Mr. Hand\" --flag\n{ bar: 7, foo: \"Mr. Hand\", flag: true }\n\n$ node my-program.js --foo \"blerp\" -f -----p\n{ foo: \"blerp\", flag: true, pick: true }\n\n$ node my-program.js -fp --foofoo\n{ foo: \"Mr. Foo\", flag: true, pick: true }\n\n$ node my-program.js --foofoo -- -fp  # -- stops the flag parsing.\n{ foo: \"Mr. Foo\", argv: { remain: [\"-fp\"] } }\n\n$ node my-program.js --blatzk 1000 -fp # unknown opts are ok.\n{ blatzk: 1000, flag: true, pick: true }\n\n$ node my-program.js --blatzk true -fp # but they need a value\n{ blatzk: true, flag: true, pick: true }\n\n$ node my-program.js --no-blatzk -fp # unless they start with \"no-\"\n{ blatzk: false, flag: true, pick: true }\n\n$ node my-program.js --baz b/a/z # known paths are resolved.\n{ baz: \"/Users/isaacs/b/a/z\" }\n\n# if Array is one of the types, then it can take many\n# values, and will always be an array.  The other types provided\n# specify what types are allowed in the list.\n\n$ node my-program.js --many 1 --many null --many foo\n{ many: [\"1\", \"null\", \"foo\"] }\n\n$ node my-program.js --many foo\n{ many: [\"foo\"] }\n```\n\nRead the tests at the bottom of `lib/nopt.js` for more examples of\nwhat this puppy can do.\n\n## Types\n\nThe following types are supported, and defined on `nopt.typeDefs`\n\n* String: A normal string.  No parsing is done.\n* path: A file system path.  Gets resolved against cwd if not absolute.\n* url: A url.  If it doesn't parse, it isn't accepted.\n* Number: Must be numeric.\n* Date: Must parse as a date. If it does, and `Date` is one of the options,\n  then it will return a Date object, not a string.\n* Boolean: Must be either `true` or `false`.  If an option is a boolean,\n  then it does not need a value, and its presence will imply `true` as\n  the value.  To negate boolean flags, do `--no-whatever` or `--whatever\n  false`\n* NaN: Means that the option is strictly not allowed.  Any value will\n  fail.\n* Stream: An object matching the \"Stream\" class in node.  Valuable\n  for use when validating programmatically.  (npm uses this to let you\n  supply any WriteStream on the `outfd` and `logfd` config options.)\n* Array: If `Array` is specified as one of the types, then the value\n  will be parsed as a list of options.  This means that multiple values\n  can be specified, and that the value will always be an array.\n\nIf a type is an array of values not on this list, then those are\nconsidered valid values.  For instance, in the example above, the\n`--bloo` option can only be one of `\"big\"`, `\"medium\"`, or `\"small\"`,\nand any other value will be rejected.\n\nWhen parsing unknown fields, `\"true\"`, `\"false\"`, and `\"null\"` will be\ninterpreted as their JavaScript equivalents, and numeric values will be\ninterpreted as a number.\n\nYou can also mix types and values, or multiple types, in a list.  For\ninstance `{ blah: [Number, null] }` would allow a value to be set to\neither a Number or null.  When types are ordered, this implies a\npreference, and the first type that can be used to properly interpret\nthe value will be used.\n\nTo define a new type, add it to `nopt.typeDefs`.  Each item in that\nhash is an object with a `type` member and a `validate` method.  The\n`type` member is an object that matches what goes in the type list.  The\n`validate` method is a function that gets called with `validate(data,\nkey, val)`.  Validate methods should assign `data[key]` to the valid\nvalue of `val` if it can be handled properly, or return boolean\n`false` if it cannot.\n\nYou can also call `nopt.clean(data, types, typeDefs)` to clean up a\nconfig object and remove its invalid properties.\n\n## Error Handling\n\nBy default, nopt outputs a warning to standard error when invalid\noptions are found.  You can change this behavior by assigning a method\nto `nopt.invalidHandler`.  This method will be called with\nthe offending `nopt.invalidHandler(key, val, types)`.\n\nIf no `nopt.invalidHandler` is assigned, then it will console.error\nits whining.  If it is assigned to boolean `false` then the warning is\nsuppressed.\n\n## Abbreviations\n\nYes, they are supported.  If you define options like this:\n\n```javascript\n{ \"foolhardyelephants\" : Boolean\n, \"pileofmonkeys\" : Boolean }\n```\n\nThen this will work:\n\n```bash\nnode program.js --foolhar --pil\nnode program.js --no-f --pileofmon\n# etc.\n```\n\n## Shorthands\n\nShorthands are a hash of shorter option names to a snippet of args that\nthey expand to.\n\nIf multiple one-character shorthands are all combined, and the\ncombination does not unambiguously match any other option or shorthand,\nthen they will be broken up into their constituent parts.  For example:\n\n```json\n{ \"s\" : [\"--loglevel\", \"silent\"]\n, \"g\" : \"--global\"\n, \"f\" : \"--force\"\n, \"p\" : \"--parseable\"\n, \"l\" : \"--long\"\n}\n```\n\n```bash\nnpm ls -sgflp\n# just like doing this:\nnpm ls --loglevel silent --global --force --long --parseable\n```\n\n## The Rest of the args\n\nThe config object returned by nopt is given a special member called\n`argv`, which is an object with the following fields:\n\n* `remain`: The remaining args after all the parsing has occurred.\n* `original`: The args as they originally appeared.\n* `cooked`: The args after flags and shorthands are expanded.\n\n## Slicing\n\nNode programs are called with more or less the exact argv as it appears\nin C land, after the v8 and node-specific options have been plucked off.\nAs such, `argv[0]` is always `node` and `argv[1]` is always the\nJavaScript program being run.\n\nThat's usually not very useful to you.  So they're sliced off by\ndefault.  If you want them, then you can pass in `0` as the last\nargument, or any other number that you'd like to slice off the start of\nthe list.\n",
+  "readmeFilename": "README.md",
+  "bugs": {
+    "url": "https://github.com/isaacs/nopt/issues"
+  },
+  "homepage": "https://github.com/isaacs/nopt",
+  "_id": "nopt@2.1.2",
+  "_from": "nopt@*"
+}
level2/node_modules/nopt/README.md
@@ -0,0 +1,210 @@
+If you want to write an option parser, and have it be good, there are
+two ways to do it.  The Right Way, and the Wrong Way.
+
+The Wrong Way is to sit down and write an option parser.  We've all done
+that.
+
+The Right Way is to write some complex configurable program with so many
+options that you go half-insane just trying to manage them all, and put
+it off with duct-tape solutions until you see exactly to the core of the
+problem, and finally snap and write an awesome option parser.
+
+If you want to write an option parser, don't write an option parser.
+Write a package manager, or a source control system, or a service
+restarter, or an operating system.  You probably won't end up with a
+good one of those, but if you don't give up, and you are relentless and
+diligent enough in your procrastination, you may just end up with a very
+nice option parser.
+
+## USAGE
+
+    // my-program.js
+    var nopt = require("nopt")
+      , Stream = require("stream").Stream
+      , path = require("path")
+      , knownOpts = { "foo" : [String, null]
+                    , "bar" : [Stream, Number]
+                    , "baz" : path
+                    , "bloo" : [ "big", "medium", "small" ]
+                    , "flag" : Boolean
+                    , "pick" : Boolean
+                    , "many" : [String, Array]
+                    }
+      , shortHands = { "foofoo" : ["--foo", "Mr. Foo"]
+                     , "b7" : ["--bar", "7"]
+                     , "m" : ["--bloo", "medium"]
+                     , "p" : ["--pick"]
+                     , "f" : ["--flag"]
+                     }
+                 // everything is optional.
+                 // knownOpts and shorthands default to {}
+                 // arg list defaults to process.argv
+                 // slice defaults to 2
+      , parsed = nopt(knownOpts, shortHands, process.argv, 2)
+    console.log(parsed)
+
+This would give you support for any of the following:
+
+```bash
+$ node my-program.js --foo "blerp" --no-flag
+{ "foo" : "blerp", "flag" : false }
+
+$ node my-program.js ---bar 7 --foo "Mr. Hand" --flag
+{ bar: 7, foo: "Mr. Hand", flag: true }
+
+$ node my-program.js --foo "blerp" -f -----p
+{ foo: "blerp", flag: true, pick: true }
+
+$ node my-program.js -fp --foofoo
+{ foo: "Mr. Foo", flag: true, pick: true }
+
+$ node my-program.js --foofoo -- -fp  # -- stops the flag parsing.
+{ foo: "Mr. Foo", argv: { remain: ["-fp"] } }
+
+$ node my-program.js --blatzk 1000 -fp # unknown opts are ok.
+{ blatzk: 1000, flag: true, pick: true }
+
+$ node my-program.js --blatzk true -fp # but they need a value
+{ blatzk: true, flag: true, pick: true }
+
+$ node my-program.js --no-blatzk -fp # unless they start with "no-"
+{ blatzk: false, flag: true, pick: true }
+
+$ node my-program.js --baz b/a/z # known paths are resolved.
+{ baz: "/Users/isaacs/b/a/z" }
+
+# if Array is one of the types, then it can take many
+# values, and will always be an array.  The other types provided
+# specify what types are allowed in the list.
+
+$ node my-program.js --many 1 --many null --many foo
+{ many: ["1", "null", "foo"] }
+
+$ node my-program.js --many foo
+{ many: ["foo"] }
+```
+
+Read the tests at the bottom of `lib/nopt.js` for more examples of
+what this puppy can do.
+
+## Types
+
+The following types are supported, and defined on `nopt.typeDefs`
+
+* String: A normal string.  No parsing is done.
+* path: A file system path.  Gets resolved against cwd if not absolute.
+* url: A url.  If it doesn't parse, it isn't accepted.
+* Number: Must be numeric.
+* Date: Must parse as a date. If it does, and `Date` is one of the options,
+  then it will return a Date object, not a string.
+* Boolean: Must be either `true` or `false`.  If an option is a boolean,
+  then it does not need a value, and its presence will imply `true` as
+  the value.  To negate boolean flags, do `--no-whatever` or `--whatever
+  false`
+* NaN: Means that the option is strictly not allowed.  Any value will
+  fail.
+* Stream: An object matching the "Stream" class in node.  Valuable
+  for use when validating programmatically.  (npm uses this to let you
+  supply any WriteStream on the `outfd` and `logfd` config options.)
+* Array: If `Array` is specified as one of the types, then the value
+  will be parsed as a list of options.  This means that multiple values
+  can be specified, and that the value will always be an array.
+
+If a type is an array of values not on this list, then those are
+considered valid values.  For instance, in the example above, the
+`--bloo` option can only be one of `"big"`, `"medium"`, or `"small"`,
+and any other value will be rejected.
+
+When parsing unknown fields, `"true"`, `"false"`, and `"null"` will be
+interpreted as their JavaScript equivalents, and numeric values will be
+interpreted as a number.
+
+You can also mix types and values, or multiple types, in a list.  For
+instance `{ blah: [Number, null] }` would allow a value to be set to
+either a Number or null.  When types are ordered, this implies a
+preference, and the first type that can be used to properly interpret
+the value will be used.
+
+To define a new type, add it to `nopt.typeDefs`.  Each item in that
+hash is an object with a `type` member and a `validate` method.  The
+`type` member is an object that matches what goes in the type list.  The
+`validate` method is a function that gets called with `validate(data,
+key, val)`.  Validate methods should assign `data[key]` to the valid
+value of `val` if it can be handled properly, or return boolean
+`false` if it cannot.
+
+You can also call `nopt.clean(data, types, typeDefs)` to clean up a
+config object and remove its invalid properties.
+
+## Error Handling
+
+By default, nopt outputs a warning to standard error when invalid
+options are found.  You can change this behavior by assigning a method
+to `nopt.invalidHandler`.  This method will be called with
+the offending `nopt.invalidHandler(key, val, types)`.
+
+If no `nopt.invalidHandler` is assigned, then it will console.error
+its whining.  If it is assigned to boolean `false` then the warning is
+suppressed.
+
+## Abbreviations
+
+Yes, they are supported.  If you define options like this:
+
+```javascript
+{ "foolhardyelephants" : Boolean
+, "pileofmonkeys" : Boolean }
+```
+
+Then this will work:
+
+```bash
+node program.js --foolhar --pil
+node program.js --no-f --pileofmon
+# etc.
+```
+
+## Shorthands
+
+Shorthands are a hash of shorter option names to a snippet of args that
+they expand to.
+
+If multiple one-character shorthands are all combined, and the
+combination does not unambiguously match any other option or shorthand,
+then they will be broken up into their constituent parts.  For example:
+
+```json
+{ "s" : ["--loglevel", "silent"]
+, "g" : "--global"
+, "f" : "--force"
+, "p" : "--parseable"
+, "l" : "--long"
+}
+```
+
+```bash
+npm ls -sgflp
+# just like doing this:
+npm ls --loglevel silent --global --force --long --parseable
+```
+
+## The Rest of the args
+
+The config object returned by nopt is given a special member called
+`argv`, which is an object with the following fields:
+
+* `remain`: The remaining args after all the parsing has occurred.
+* `original`: The args as they originally appeared.
+* `cooked`: The args after flags and shorthands are expanded.
+
+## Slicing
+
+Node programs are called with more or less the exact argv as it appears
+in C land, after the v8 and node-specific options have been plucked off.
+As such, `argv[0]` is always `node` and `argv[1]` is always the
+JavaScript program being run.
+
+That's usually not very useful to you.  So they're sliced off by
+default.  If you want them, then you can pass in `0` as the last
+argument, or any other number that you'd like to slice off the start of
+the list.
level2/node_modules/seed-random/test/index.js
@@ -0,0 +1,27 @@
+'use strict';
+
+var assert = require('assert');
+var seed = require('../');
+
+var trueRandomA = seed();
+var trueRandomB = seed();
+assert(trueRandomA() != trueRandomB());
+
+var fakeRandomA = seed('foo');
+var fakeRandomB = seed('foo');
+assert(fakeRandomA() == fakeRandomB());
+
+var fakeRandomC = seed('foo', {entropy: true});
+var fakeRandomD = seed('foo', {entropy: true});
+assert(fakeRandomC() != fakeRandomD());
+
+
+seed('foo', {global: true});//over-ride global Math.random
+var numA = Math.random();
+seed('foo', {global: true});
+var numB = Math.random();
+assert(numA == numB);//always true
+
+seed.resetGlobal();//reset to default Math.random
+
+console.log('All Tests Passed');
\ No newline at end of file
level2/node_modules/seed-random/.npmignore
@@ -0,0 +1,2 @@
+components
+build
level2/node_modules/seed-random/.travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+  - "0.10"
\ No newline at end of file
level2/node_modules/seed-random/component.json
@@ -0,0 +1,13 @@
+{
+  "name": "seed-random",
+  "repo": "ForbesLindesay/seed-random",
+  "description": "Generate random numbers with a seed, useful for reproducible tests",
+  "version": "2.2.0",
+  "keywords": [],
+  "dependencies": {},
+  "development": {},
+  "license": "MIT",
+  "scripts": [
+    "index.js"
+  ]
+}
\ No newline at end of file
level2/node_modules/seed-random/History.md
level2/node_modules/seed-random/index.js
@@ -0,0 +1,173 @@
+'use strict';
+
+var width = 256;// each RC4 output is 0 <= x < 256
+var chunks = 6;// at least six RC4 outputs for each double
+var digits = 52;// there are 52 significant digits in a double
+var pool = [];// pool: entropy pool starts empty
+var GLOBAL = typeof global === 'undefined' ? window : global;
+
+//
+// The following constants are related to IEEE 754 limits.
+//
+var startdenom = Math.pow(width, chunks),
+    significance = Math.pow(2, digits),
+    overflow = significance * 2,
+    mask = width - 1;
+
+
+var oldRandom = Math.random;
+
+//
+// seedrandom()
+// This is the seedrandom function described above.
+//
+module.exports = function(seed, options) {
+  if (options && options.global === true) {
+    options.global = false;
+    Math.random = module.exports(seed, options);
+    options.global = true;
+    return Math.random;
+  }
+  var use_entropy = (options && options.entropy) || false;
+  var key = [];
+
+  // Flatten the seed string or build one from local entropy if needed.
+  var shortseed = mixkey(flatten(
+    use_entropy ? [seed, tostring(pool)] :
+    0 in arguments ? seed : autoseed(), 3), key);
+
+  // Use the seed to initialize an ARC4 generator.
+  var arc4 = new ARC4(key);
+
+  // Mix the randomness into accumulated entropy.
+  mixkey(tostring(arc4.S), pool);
+
+  // Override Math.random
+
+  // This function returns a random double in [0, 1) that contains
+  // randomness in every bit of the mantissa of the IEEE 754 value.
+
+  return function() {         // Closure to return a random double:
+    var n = arc4.g(chunks),             // Start with a numerator n < 2 ^ 48
+        d = startdenom,                 //   and denominator d = 2 ^ 48.
+        x = 0;                          //   and no 'extra last byte'.
+    while (n < significance) {          // Fill up all significant digits by
+      n = (n + x) * width;              //   shifting numerator and
+      d *= width;                       //   denominator and generating a
+      x = arc4.g(1);                    //   new least-significant-byte.
+    }
+    while (n >= overflow) {             // To avoid rounding up, before adding
+      n /= 2;                           //   last byte, shift everything
+      d /= 2;                           //   right using integer Math until
+      x >>>= 1;                         //   we have exactly the desired bits.
+    }
+    return (n + x) / d;                 // Form the number within [0, 1).
+  };
+};
+
+module.exports.resetGlobal = function () {
+  Math.random = oldRandom;
+};
+
+//
+// ARC4
+//
+// An ARC4 implementation.  The constructor takes a key in the form of
+// an array of at most (width) integers that should be 0 <= x < (width).
+//
+// The g(count) method returns a pseudorandom integer that concatenates
+// the next (count) outputs from ARC4.  Its return value is a number x
+// that is in the range 0 <= x < (width ^ count).
+//
+/** @constructor */
+function ARC4(key) {
+  var t, keylen = key.length,
+      me = this, i = 0, j = me.i = me.j = 0, s = me.S = [];
+
+  // The empty key [] is treated as [0].
+  if (!keylen) { key = [keylen++]; }
+
+  // Set up S using the standard key scheduling algorithm.
+  while (i < width) {
+    s[i] = i++;
+  }
+  for (i = 0; i < width; i++) {
+    s[i] = s[j = mask & (j + key[i % keylen] + (t = s[i]))];
+    s[j] = t;
+  }
+
+  // The "g" method returns the next (count) outputs as one number.
+  (me.g = function(count) {
+    // Using instance members instead of closure state nearly doubles speed.
+    var t, r = 0,
+        i = me.i, j = me.j, s = me.S;
+    while (count--) {
+      t = s[i = mask & (i + 1)];
+      r = r * width + s[mask & ((s[i] = s[j = mask & (j + t)]) + (s[j] = t))];
+    }
+    me.i = i; me.j = j;
+    return r;
+    // For robust unpredictability discard an initial batch of values.
+    // See http://www.rsa.com/rsalabs/node.asp?id=2009
+  })(width);
+}
+
+//
+// flatten()
+// Converts an object tree to nested arrays of strings.
+//
+function flatten(obj, depth) {
+  var result = [], typ = (typeof obj)[0], prop;
+  if (depth && typ == 'o') {
+    for (prop in obj) {
+      try { result.push(flatten(obj[prop], depth - 1)); } catch (e) {}
+    }
+  }
+  return (result.length ? result : typ == 's' ? obj : obj + '\0');
+}
+
+//
+// mixkey()
+// Mixes a string seed into a key that is an array of integers, and
+// returns a shortened string seed that is equivalent to the result key.
+//
+function mixkey(seed, key) {
+  var stringseed = seed + '', smear, j = 0;
+  while (j < stringseed.length) {
+    key[mask & j] =
+      mask & ((smear ^= key[mask & j] * 19) + stringseed.charCodeAt(j++));
+  }
+  return tostring(key);
+}
+
+//
+// autoseed()
+// Returns an object for autoseeding, using window.crypto if available.
+//
+/** @param {Uint8Array=} seed */
+function autoseed(seed) {
+  try {
+    GLOBAL.crypto.getRandomValues(seed = new Uint8Array(width));
+    return tostring(seed);
+  } catch (e) {
+    return [+new Date, GLOBAL, GLOBAL.navigator && GLOBAL.navigator.plugins,
+            GLOBAL.screen, tostring(pool)];
+  }
+}
+
+//
+// tostring()
+// Converts an array of charcodes to a string
+//
+function tostring(a) {
+  return String.fromCharCode.apply(0, a);
+}
+
+//
+// When seedrandom.js is loaded, we immediately mix a few bits
+// from the built-in RNG into the entropy pool.  Because we do
+// not want to intefere with determinstic PRNG state later,
+// seedrandom will not call Math.random on its own again after
+// initialization.
+//
+mixkey(Math.random(), pool);
level2/node_modules/seed-random/LICENSE
@@ -0,0 +1,50 @@
+LICENSE for modifications of JavaScript Library by Forbes Lindesay (MIT):
+
+Copyright (c) 2013 Forbes Lindesay
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+LICENSE for original seedrandom javascript file (BSD):
+
+Copyright 2013 David Bau, all rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+  1. Redistributions of source code must retain the above copyright
+     notice, this list of conditions and the following disclaimer.
+
+  2. Redistributions in binary form must reproduce the above copyright
+     notice, this list of conditions and the following disclaimer in the
+     documentation and/or other materials provided with the distribution.
+
+  3. Neither the name of this module nor the names of its contributors may
+     be used to endorse or promote products derived from this software
+     without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
\ No newline at end of file
level2/node_modules/seed-random/package.json
@@ -0,0 +1,25 @@
+{
+  "name": "seed-random",
+  "version": "2.2.0",
+  "description": "Generate random numbers with a seed, useful for reproducible tests",
+  "main": "index.js",
+  "scripts": {
+    "test": "node test/index.js"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/ForbesLindesay/seed-random.git"
+  },
+  "author": {
+    "name": "ForbesLindesay"
+  },
+  "license": "MIT",
+  "readme": "# seed-random\r\n\r\nGenerate random numbers with a seed, useful for reproducible tests\r\n\r\n\r\n[![build status](https://secure.travis-ci.org/ForbesLindesay/seed-random.png?branch=master)](http://travis-ci.org/ForbesLindesay/seed-random)\r\n[![Dependency Status](https://gemnasium.com/ForbesLindesay/seed-random.png)](https://gemnasium.com/ForbesLindesay/seed-random)\r\n[![NPM version](https://badge.fury.io/js/seed-random.png)](http://badge.fury.io/js/seed-random)\r\n\r\n## Installation\r\n\r\n    $ npm install seed-random\r\n\r\n## API\r\n\r\n```javascript\r\nvar assert = require('assert');\r\nvar seed = require('../');\r\n\r\nvar trueRandomA = seed();\r\nvar trueRandomB = seed();\r\nassert(trueRandomA() != trueRandomB());\r\n\r\nvar fakeRandomA = seed('foo');\r\nvar fakeRandomB = seed('foo');\r\nassert(fakeRandomA() == fakeRandomB());\r\n\r\nvar fakeRandomC = seed('foo', {entropy: true});\r\nvar fakeRandomD = seed('foo', {entropy: true});\r\nassert(fakeRandomC() != fakeRandomD());\r\n\r\n\r\nseed('foo', {global: true});//over-ride global Math.random\r\nvar numA = Math.random();\r\nseed('foo', {global: true});\r\nvar numB = Math.random();\r\nassert(numA == numB);//always true\r\n\r\nseed.resetGlobal();//reset to default Math.random\r\n```\r\n\r\n## License\r\n\r\nMIT\r\n",
+  "readmeFilename": "Readme.md",
+  "bugs": {
+    "url": "https://github.com/ForbesLindesay/seed-random/issues"
+  },
+  "homepage": "https://github.com/ForbesLindesay/seed-random",
+  "_id": "seed-random@2.2.0",
+  "_from": "seed-random@*"
+}
level2/node_modules/seed-random/Readme.md
@@ -0,0 +1,44 @@
+# seed-random
+
+Generate random numbers with a seed, useful for reproducible tests
+
+
+[![build status](https://secure.travis-ci.org/ForbesLindesay/seed-random.png?branch=master)](http://travis-ci.org/ForbesLindesay/seed-random)
+[![Dependency Status](https://gemnasium.com/ForbesLindesay/seed-random.png)](https://gemnasium.com/ForbesLindesay/seed-random)
+[![NPM version](https://badge.fury.io/js/seed-random.png)](http://badge.fury.io/js/seed-random)
+
+## Installation
+
+    $ npm install seed-random
+
+## API
+
+```javascript
+var assert = require('assert');
+var seed = require('../');
+
+var trueRandomA = seed();
+var trueRandomB = seed();
+assert(trueRandomA() != trueRandomB());
+
+var fakeRandomA = seed('foo');
+var fakeRandomB = seed('foo');
+assert(fakeRandomA() == fakeRandomB());
+
+var fakeRandomC = seed('foo', {entropy: true});
+var fakeRandomD = seed('foo', {entropy: true});
+assert(fakeRandomC() != fakeRandomD());
+
+
+seed('foo', {global: true});//over-ride global Math.random
+var numA = Math.random();
+seed('foo', {global: true});
+var numB = Math.random();
+assert(numA == numB);//always true
+
+seed.resetGlobal();//reset to default Math.random
+```
+
+## License
+
+MIT
level2/node_modules/sugar/lib/extras/timezonejs-shim.js
@@ -0,0 +1,47 @@
+
+/*
+ * TimezoneJS Shim for Sugar
+ *
+ * This shim creates a class DateWithTimezone that extends timezoneJS.Date
+ * and mixes in Sugar methods to allow Sugar's internal constructor to use
+ * a timezoneJS object instead of a native date. This means that Sugar's
+ * Date.create, internal comparison methods etc will all use timezoneJS
+ * objects instead.
+ *
+ * Note that the better way to do this would be to subclass the native Date
+ * class itself, however Javascript does not allow this.
+ *
+ *
+ *
+ * Usage with Sugar (example):
+ *
+ * Date.SugarNewDate = function () {
+ *   return new DateWithTimezone('America/New_York');
+ * }
+ *
+ * Also note that Olson timezone files etc need to all be loaded before using!
+ *
+ */
+(function() {
+
+  function DateWithTimezone () {
+    timezoneJS.Date.apply(this, arguments);
+  };
+
+  function mixInSugar(target) {
+    var key, m, methods = Date.SugarMethods;
+    for (key in methods) {
+      if(!methods.hasOwnProperty(key)) continue;
+      m = methods[key];
+      if(m.instance && !target[key]) {
+        target[key] = m.method;
+      }
+    };
+  }
+
+  DateWithTimezone.prototype = new timezoneJS.Date();
+  mixInSugar(DateWithTimezone.prototype);
+
+  this.DateWithTimezone = DateWithTimezone;
+
+})();
level2/node_modules/sugar/lib/locales/da.js
@@ -0,0 +1,82 @@
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('da');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('da', {
+  'plural': true,
+  'months': 'januar,februar,marts,april,maj,juni,juli,august,september,oktober,november,december',
+  'weekdays': 'sรธndag|sondag,mandag,tirsdag,onsdag,torsdag,fredag,lรธrdag|lordag',
+  'units': 'millisekund:|er,sekund:|er,minut:|ter,tim:e|er,dag:|e,ug:e|er|en,mรฅned:|er|en+maaned:|er|en,รฅr:||et+aar:||et',
+  'numbers': 'en|et,to,tre,fire,fem,seks,syv,otte,ni,ti',
+  'tokens': 'den,for',
+  'articles': 'den',
+  'short':'d. {d}. {month} {yyyy}',
+  'long': 'den {d}. {month} {yyyy} {H}:{mm}',
+  'full': '{Weekday} den {d}. {month} {yyyy} {H}:{mm}:{ss}',
+  'past': '{num} {unit} {sign}',
+  'future': '{sign} {num} {unit}',
+  'duration': '{num} {unit}',
+  'ampm': 'am,pm',
+  'modifiers': [
+    { 'name': 'day', 'src': 'forgรฅrs|i forgรฅrs|forgaars|i forgaars', 'value': -2 },
+    { 'name': 'day', 'src': 'i gรฅr|igรฅr|i gaar|igaar', 'value': -1 },
+    { 'name': 'day', 'src': 'i dag|idag', 'value': 0 },
+    { 'name': 'day', 'src': 'i morgen|imorgen', 'value': 1 },
+    { 'name': 'day', 'src': 'over morgon|overmorgen|i over morgen|i overmorgen|iovermorgen', 'value': 2 },
+    { 'name': 'sign', 'src': 'siden', 'value': -1 },
+    { 'name': 'sign', 'src': 'om', 'value':  1 },
+    { 'name': 'shift', 'src': 'i sidste|sidste', 'value': -1 },
+    { 'name': 'shift', 'src': 'denne', 'value': 0 },
+    { 'name': 'shift', 'src': 'nรฆste|naeste', 'value': 1 }
+  ],
+  'dateParse': [
+    '{num} {unit} {sign}',
+    '{sign} {num} {unit}',
+    '{1?} {num} {unit} {sign}',
+    '{shift} {unit=5-7}'
+  ],
+  'timeParse': [
+    '{0?} {weekday?} {date?} {month} {year}',
+    '{date} {month}',
+    '{shift} {weekday}'
+  ]
+});
+
level2/node_modules/sugar/lib/locales/de.js
@@ -0,0 +1,80 @@
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('de');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('de', {
+  'plural': true,
+   'capitalizeUnit': true,
+  'months': 'Januar,Februar,Mรคrz|Marz,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember',
+  'weekdays': 'Sonntag,Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag',
+  'units': 'Millisekunde:|n,Sekunde:|n,Minute:|n,Stunde:|n,Tag:|en,Woche:|n,Monat:|en,Jahr:|en',
+  'numbers': 'ein:|e|er|en|em,zwei,drei,vier,fuenf,sechs,sieben,acht,neun,zehn',
+  'tokens': 'der',
+  'short':'{d}. {Month} {yyyy}',
+  'long': '{d}. {Month} {yyyy} {H}:{mm}',
+  'full': '{Weekday} {d}. {Month} {yyyy} {H}:{mm}:{ss}',
+  'past': '{sign} {num} {unit}',
+  'future': '{sign} {num} {unit}',
+  'duration': '{num} {unit}',
+  'timeMarker': 'um',
+  'ampm': 'am,pm',
+  'modifiers': [
+    { 'name': 'day', 'src': 'vorgestern', 'value': -2 },
+    { 'name': 'day', 'src': 'gestern', 'value': -1 },
+    { 'name': 'day', 'src': 'heute', 'value': 0 },
+    { 'name': 'day', 'src': 'morgen', 'value': 1 },
+    { 'name': 'day', 'src': 'รผbermorgen|ubermorgen|uebermorgen', 'value': 2 },
+    { 'name': 'sign', 'src': 'vor:|her', 'value': -1 },
+    { 'name': 'sign', 'src': 'in', 'value': 1 },
+    { 'name': 'shift', 'src': 'letzte:|r|n|s', 'value': -1 },
+    { 'name': 'shift', 'src': 'nรคchste:|r|n|s+nachste:|r|n|s+naechste:|r|n|s+kommende:n|r', 'value': 1 }
+  ],
+  'dateParse': [
+    '{sign} {num} {unit}',
+    '{num} {unit} {sign}',
+    '{shift} {unit=5-7}'
+  ],
+  'timeParse': [
+    '{weekday?} {date?} {month} {year?}',
+    '{shift} {weekday}'
+  ]
+});
+
level2/node_modules/sugar/lib/locales/es.js
@@ -0,0 +1,79 @@
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('es');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('es', {
+  'plural': true,
+  'months': 'enero,febrero,marzo,abril,mayo,junio,julio,agosto,septiembre,octubre,noviembre,diciembre',
+  'weekdays': 'domingo,lunes,martes,miรฉrcoles|miercoles,jueves,viernes,sรกbado|sabado',
+  'units': 'milisegundo:|s,segundo:|s,minuto:|s,hora:|s,dรญa|dรญas|dia|dias,semana:|s,mes:|es,aรฑo|aรฑos|ano|anos',
+  'numbers': 'uno,dos,tres,cuatro,cinco,seis,siete,ocho,nueve,diez',
+  'tokens': 'el,la,de',
+  'short':'{d} {month} {yyyy}',
+  'long': '{d} {month} {yyyy} {H}:{mm}',
+  'full': '{Weekday} {d} {month} {yyyy} {H}:{mm}:{ss}',
+  'past': '{sign} {num} {unit}',
+  'future': '{sign} {num} {unit}',
+  'duration': '{num} {unit}',
+  'timeMarker': 'a las',
+  'ampm': 'am,pm',
+  'modifiers': [
+    { 'name': 'day', 'src': 'anteayer', 'value': -2 },
+    { 'name': 'day', 'src': 'ayer', 'value': -1 },
+    { 'name': 'day', 'src': 'hoy', 'value': 0 },
+    { 'name': 'day', 'src': 'maรฑana|manana', 'value': 1 },
+    { 'name': 'sign', 'src': 'hace', 'value': -1 },
+    { 'name': 'sign', 'src': 'dentro de', 'value': 1 },
+    { 'name': 'shift', 'src': 'pasad:o|a', 'value': -1 },
+    { 'name': 'shift', 'src': 'prรณximo|prรณxima|proximo|proxima', 'value': 1 }
+  ],
+  'dateParse': [
+    '{sign} {num} {unit}',
+    '{num} {unit} {sign}',
+    '{0?}{1?} {unit=5-7} {shift}',
+    '{0?}{1?} {shift} {unit=5-7}'
+  ],
+  'timeParse': [
+    '{shift} {weekday}',
+    '{weekday} {shift}',
+    '{date?} {2?} {month} {2?} {year?}'
+  ]
+});
level2/node_modules/sugar/lib/locales/fi.js
@@ -0,0 +1,57 @@
+Date.addLocale('fi', {
+    'plural':     true,
+    'timeMarker': 'kello',
+    'ampm':       ',',
+    'months':     'tammikuu,helmikuu,maaliskuu,huhtikuu,toukokuu,kesรคkuu,heinรคkuu,elokuu,syyskuu,lokakuu,marraskuu,joulukuu',
+    'weekdays':   'sunnuntai,maanantai,tiistai,keskiviikko,torstai,perjantai,lauantai',
+    'units':      'millisekun:ti|tia|teja|tina|nin,sekun:ti|tia|teja|tina|nin,minuut:ti|tia|teja|tina|in,tun:ti|tia|teja|tina|nin,pรคiv:รค|รครค|iรค|รคnรค|รคn,viik:ko|koa|koja|on|kona,kuukau:si|sia|tta|den|tena,vuo:si|sia|tta|den|tena',
+    'numbers':    'yksi|ensimmรคinen,kaksi|toinen,kolm:e|as,neljรค:s,vii:si|des,kuu:si|des,seitsemรค:n|s,kahdeksa:n|s,yhdeksรค:n|s,kymmene:n|s',
+    'articles':   '',
+    'optionals':  '',
+    'short':      '{d}. {month}ta {yyyy}',
+    'long':       '{d}. {month}ta {yyyy} kello {H}.{mm}',
+    'full':       '{Weekday}na {d}. {month}ta {yyyy} kello {H}.{mm}',
+    'relative':       function(num, unit, ms, format) {
+      var units = this['units'];
+      function numberWithUnit(mult) {
+        return (num === 1 ? '' : num + ' ') + units[(8 * mult) + unit];
+      }
+      switch(format) {
+        case 'duration':  return numberWithUnit(0);
+        case 'past':      return numberWithUnit(num > 1 ? 1 : 0) + ' sitten';
+        case 'future':    return numberWithUnit(4) + ' pรครคstรค';
+      }
+    },
+    'modifiers': [
+        { 'name': 'day',   'src': 'toissa pรคivรคnรค|toissa pรคivรคistรค', 'value': -2 },
+        { 'name': 'day',   'src': 'eilen|eilistรค', 'value': -1 },
+        { 'name': 'day',   'src': 'tรคnรครคn', 'value': 0 },
+        { 'name': 'day',   'src': 'huomenna|huomista', 'value': 1 },
+        { 'name': 'day',   'src': 'ylihuomenna|ylihuomista', 'value': 2 },
+        { 'name': 'sign',  'src': 'sitten|aiemmin', 'value': -1 },
+        { 'name': 'sign',  'src': 'pรครคstรค|kuluttua|myรถhemmin', 'value': 1 },
+        { 'name': 'edge',  'src': 'viimeinen|viimeisenรค', 'value': -2 },
+        { 'name': 'edge',  'src': 'lopussa', 'value': -1 },
+        { 'name': 'edge',  'src': 'ensimmรคinen|ensimmรคisenรค', 'value': 1 },
+        { 'name': 'shift', 'src': 'edellinen|edellisenรค|edeltรคvรค|edeltรคvรคnรค|viime|toissa', 'value': -1 },
+        { 'name': 'shift', 'src': 'tรคnรค|tรคmรคn', 'value': 0 },
+        { 'name': 'shift', 'src': 'seuraava|seuraavana|tuleva|tulevana|ensi', 'value': 1 }
+    ],
+    'dateParse': [
+        '{num} {unit} {sign}',
+        '{sign} {num} {unit}',
+        '{num} {unit=4-5} {sign} {day}',
+        '{month} {year}',
+        '{shift} {unit=5-7}'
+    ],
+    'timeParse': [
+        '{0} {num}{1} {day} of {month} {year?}',
+        '{weekday?} {month} {date}{1} {year?}',
+        '{date} {month} {year}',
+        '{shift} {weekday}',
+        '{shift} week {weekday}',
+        '{weekday} {2} {shift} week',
+        '{0} {date}{1} of {month}',
+        '{0}{month?} {date?}{1} of {shift} {unit=6-7}'
+    ]
+});
level2/node_modules/sugar/lib/locales/fr.js
@@ -0,0 +1,77 @@
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('fr');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('fr', {
+  'plural': true,
+  'months': 'janvier,fรฉvrier|fevrier,mars,avril,mai,juin,juillet,aoรปt,septembre,octobre,novembre,dรฉcembre|decembre',
+  'weekdays': 'dimanche,lundi,mardi,mercredi,jeudi,vendredi,samedi',
+  'units': 'milliseconde:|s,seconde:|s,minute:|s,heure:|s,jour:|s,semaine:|s,mois,an:|s|nรฉe|nee',
+  'numbers': 'un:|e,deux,trois,quatre,cinq,six,sept,huit,neuf,dix',
+  'tokens': "l'|la|le",
+  'short':'{d} {month} {yyyy}',
+  'long': '{d} {month} {yyyy} {H}:{mm}',
+  'full': '{Weekday} {d} {month} {yyyy} {H}:{mm}:{ss}',
+  'past': '{sign} {num} {unit}',
+  'future': '{sign} {num} {unit}',
+  'duration': '{num} {unit}',
+  'timeMarker': 'ร ',
+  'ampm': 'am,pm',
+  'modifiers': [
+    { 'name': 'day', 'src': 'hier', 'value': -1 },
+    { 'name': 'day', 'src': "aujourd'hui", 'value': 0 },
+    { 'name': 'day', 'src': 'demain', 'value': 1 },
+    { 'name': 'sign', 'src': 'il y a', 'value': -1 },
+    { 'name': 'sign', 'src': "dans|d'ici", 'value': 1 },
+    { 'name': 'shift', 'src': 'derni:รจr|er|รจre|ere', 'value': -1 },
+    { 'name': 'shift', 'src': 'prochain:|e', 'value': 1 }
+  ],
+  'dateParse': [
+    '{sign} {num} {unit}',
+    '{sign} {num} {unit}',
+    '{0?} {unit=5-7} {shift}'
+  ],
+  'timeParse': [
+    '{weekday?} {0?} {date?} {month} {year?}',
+    '{0?} {weekday} {shift}'
+  ]
+});
+
level2/node_modules/sugar/lib/locales/it.js
@@ -0,0 +1,78 @@
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('it');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('it', {
+  'plural': true,
+  'months': 'Gennaio,Febbraio,Marzo,Aprile,Maggio,Giugno,Luglio,Agosto,Settembre,Ottobre,Novembre,Dicembre',
+  'weekdays': 'Domenica,Luned:รฌ|i,Marted:รฌ|i,Mercoled:รฌ|i,Gioved:รฌ|i,Venerd:รฌ|i,Sabato',
+  'units': 'millisecond:o|i,second:o|i,minut:o|i,or:a|e,giorn:o|i,settiman:a|e,mes:e|i,ann:o|i',
+  'numbers': "un:|a|o|',due,tre,quattro,cinque,sei,sette,otto,nove,dieci",
+  'tokens': "l'|la|il",
+  'short':'{d} {Month} {yyyy}',
+  'long': '{d} {Month} {yyyy} {H}:{mm}',
+  'full': '{Weekday} {d} {Month} {yyyy} {H}:{mm}:{ss}',
+  'past': '{num} {unit} {sign}',
+  'future': '{num} {unit} {sign}',
+  'duration': '{num} {unit}',
+  'timeMarker': 'alle',
+  'ampm': 'am,pm',
+  'modifiers': [
+    { 'name': 'day', 'src': 'ieri', 'value': -1 },
+    { 'name': 'day', 'src': 'oggi', 'value': 0 },
+    { 'name': 'day', 'src': 'domani', 'value': 1 },
+    { 'name': 'day', 'src': 'dopodomani', 'value': 2 },
+    { 'name': 'sign', 'src': 'fa', 'value': -1 },
+    { 'name': 'sign', 'src': 'da adesso', 'value': 1 },
+    { 'name': 'shift', 'src': 'scors:o|a', 'value': -1 },
+    { 'name': 'shift', 'src': 'prossim:o|a', 'value': 1 }
+  ],
+  'dateParse': [
+    '{num} {unit} {sign}',
+    '{0?} {unit=5-7} {shift}',
+    '{0?} {shift} {unit=5-7}'
+  ],
+  'timeParse': [
+    '{weekday?} {date?} {month} {year?}',
+    '{shift} {weekday}'
+  ]
+});
+
level2/node_modules/sugar/lib/locales/ja.js
@@ -0,0 +1,76 @@
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('ja');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('ja', {
+  'monthSuffix': 'ๆœˆ',
+  'weekdays': 'ๆ—ฅๆ›œๆ—ฅ,ๆœˆๆ›œๆ—ฅ,็ซๆ›œๆ—ฅ,ๆฐดๆ›œๆ—ฅ,ๆœจๆ›œๆ—ฅ,้‡‘ๆ›œๆ—ฅ,ๅœŸๆ›œๆ—ฅ',
+  'units': 'ใƒŸใƒช็ง’,็ง’,ๅˆ†,ๆ™‚้–“,ๆ—ฅ,้€ฑ้–“|้€ฑ,ใƒถๆœˆ|ใƒตๆœˆ|ๆœˆ,ๅนด',
+  'short': '{yyyy}ๅนด{M}ๆœˆ{d}ๆ—ฅ',
+  'long': '{yyyy}ๅนด{M}ๆœˆ{d}ๆ—ฅ {H}ๆ™‚{mm}ๅˆ†',
+  'full': '{yyyy}ๅนด{M}ๆœˆ{d}ๆ—ฅ {Weekday} {H}ๆ™‚{mm}ๅˆ†{ss}็ง’',
+  'past': '{num}{unit}{sign}',
+  'future': '{num}{unit}{sign}',
+  'duration': '{num}{unit}',
+  'timeSuffixes': 'ๆ™‚,ๅˆ†,็ง’',
+  'ampm': 'ๅˆๅ‰,ๅˆๅพŒ',
+  'modifiers': [
+    { 'name': 'day', 'src': 'ไธ€ๆ˜จๆ—ฅ', 'value': -2 },
+    { 'name': 'day', 'src': 'ๆ˜จๆ—ฅ', 'value': -1 },
+    { 'name': 'day', 'src': 'ไปŠๆ—ฅ', 'value': 0 },
+    { 'name': 'day', 'src': 'ๆ˜Žๆ—ฅ', 'value': 1 },
+    { 'name': 'day', 'src': 'ๆ˜ŽๅพŒๆ—ฅ', 'value': 2 },
+    { 'name': 'sign', 'src': 'ๅ‰', 'value': -1 },
+    { 'name': 'sign', 'src': 'ๅพŒ', 'value':  1 },
+    { 'name': 'shift', 'src': 'ๅŽป|ๅ…ˆ', 'value': -1 },
+    { 'name': 'shift', 'src': 'ๆฅ', 'value':  1 }
+  ],
+  'dateParse': [
+    '{num}{unit}{sign}'
+  ],
+  'timeParse': [
+    '{shift}{unit=5-7}{weekday?}',
+    '{year}ๅนด{month?}ๆœˆ?{date?}ๆ—ฅ?',
+    '{month}ๆœˆ{date?}ๆ—ฅ?',
+    '{date}ๆ—ฅ'
+  ]
+});
+
level2/node_modules/sugar/lib/locales/ko.js
@@ -0,0 +1,80 @@
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('ko');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('ko', {
+  'digitDate': true,
+  'monthSuffix': '์›”',
+  'weekdays': '์ผ์š”์ผ,์›”์š”์ผ,ํ™”์š”์ผ,์ˆ˜์š”์ผ,๋ชฉ์š”์ผ,๊ธˆ์š”์ผ,ํ† ์š”์ผ',
+  'units': '๋ฐ€๋ฆฌ์ดˆ,์ดˆ,๋ถ„,์‹œ๊ฐ„,์ผ,์ฃผ,๊ฐœ์›”|๋‹ฌ,๋…„',
+  'numbers': '์ผ|ํ•œ,์ด,์‚ผ,์‚ฌ,์˜ค,์œก,์น ,ํŒ”,๊ตฌ,์‹ญ',
+  'short': '{yyyy}๋…„{M}์›”{d}์ผ',
+  'long': '{yyyy}๋…„{M}์›”{d}์ผ {H}์‹œ{mm}๋ถ„',
+  'full': '{yyyy}๋…„{M}์›”{d}์ผ {Weekday} {H}์‹œ{mm}๋ถ„{ss}์ดˆ',
+  'past': '{num}{unit} {sign}',
+  'future': '{num}{unit} {sign}',
+  'duration': '{num}{unit}',
+  'timeSuffixes': '์‹œ,๋ถ„,์ดˆ',
+  'ampm': '์˜ค์ „,์˜คํ›„',
+  'modifiers': [
+    { 'name': 'day', 'src': '๊ทธ์ €๊ป˜', 'value': -2 },
+    { 'name': 'day', 'src': '์–ด์ œ', 'value': -1 },
+    { 'name': 'day', 'src': '์˜ค๋Š˜', 'value': 0 },
+    { 'name': 'day', 'src': '๋‚ด์ผ', 'value': 1 },
+    { 'name': 'day', 'src': '๋ชจ๋ ˆ', 'value': 2 },
+    { 'name': 'sign', 'src': '์ „', 'value': -1 },
+    { 'name': 'sign', 'src': 'ํ›„', 'value':  1 },
+    { 'name': 'shift', 'src': '์ง€๋‚œ|์ž‘', 'value': -1 },
+    { 'name': 'shift', 'src': '์ด๋ฒˆ', 'value': 0 },
+    { 'name': 'shift', 'src': '๋‹ค์Œ|๋‚ด', 'value': 1 }
+  ],
+  'dateParse': [
+    '{num}{unit} {sign}',
+    '{shift?} {unit=5-7}'
+  ],
+  'timeParse': [
+    '{shift} {unit=5?} {weekday}',
+    '{year}๋…„{month?}์›”?{date?}์ผ?',
+    '{month}์›”{date?}์ผ?',
+    '{date}์ผ'
+  ]
+});
+
level2/node_modules/sugar/lib/locales/nl.js
@@ -0,0 +1,76 @@
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('nl');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('nl', {
+  'plural': true,
+  'months': 'januari,februari,maart,april,mei,juni,juli,augustus,september,oktober,november,december',
+  'weekdays': 'zondag|zo,maandag|ma,dinsdag|di,woensdag|woe|wo,donderdag|do,vrijdag|vrij|vr,zaterdag|za',
+  'units': 'milliseconde:|n,seconde:|n,minu:ut|ten,uur,dag:|en,we:ek|ken,maand:|en,jaar',
+  'numbers': 'een,twee,drie,vier,vijf,zes,zeven,acht,negen',
+  'tokens': '',
+  'short':'{d} {Month} {yyyy}',
+  'long': '{d} {Month} {yyyy} {H}:{mm}',
+  'full': '{Weekday} {d} {Month} {yyyy} {H}:{mm}:{ss}',
+  'past': '{num} {unit} {sign}',
+  'future': '{num} {unit} {sign}',
+  'duration': '{num} {unit}',
+  'timeMarker': "'s|om",
+  'modifiers': [
+    { 'name': 'day', 'src': 'gisteren', 'value': -1 },
+    { 'name': 'day', 'src': 'vandaag', 'value': 0 },
+    { 'name': 'day', 'src': 'morgen', 'value': 1 },
+    { 'name': 'day', 'src': 'overmorgen', 'value': 2 },
+    { 'name': 'sign', 'src': 'geleden', 'value': -1 },
+    { 'name': 'sign', 'src': 'vanaf nu', 'value': 1 },
+    { 'name': 'shift', 'src': 'laatste|vorige|afgelopen', 'value': -1 },
+    { 'name': 'shift', 'src': 'volgend:|e', 'value': 1 }
+  ],
+  'dateParse': [
+    '{num} {unit} {sign}',
+    '{0?} {unit=5-7} {shift}',
+    '{0?} {shift} {unit=5-7}'
+  ],
+  'timeParse': [
+    '{weekday?} {date?} {month} {year?}',
+    '{shift} {weekday}'
+  ]
+});
level2/node_modules/sugar/lib/locales/pl.js
@@ -0,0 +1,81 @@
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('pl');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.optionals. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('pl', {
+  'plural':    true,
+  'months':    'Styczeล„|Stycznia,Luty|Lutego,Marzec|Marca,Kwiecieล„|Kwietnia,Maj|Maja,Czerwiec|Czerwca,Lipiec|Lipca,Sierpieล„|Sierpnia,Wrzesieล„|Wrzeล›nia,Paลบdziernik|Paลบdziernika,Listopad|Listopada,Grudzieล„|Grudnia',
+  'weekdays':  'Niedziela|Niedzielฤ™,Poniedziaล‚ek,Wtorek,ลšrod:a|ฤ™,Czwartek,Piฤ…tek,Sobota|Sobotฤ™',
+  'units':     'milisekund:a|y|,sekund:a|y|,minut:a|y|,godzin:a|y|,dzieล„|dni,tydzieล„|tygodnie|tygodni,miesiฤ…ce|miesiฤ…ce|miesiฤ™cy,rok|lata|lat',
+  'numbers':   'jeden|jednฤ…,dwa|dwie,trzy,cztery,piฤ™ฤ‡,szeล›ฤ‡,siedem,osiem,dziewiฤ™ฤ‡,dziesiฤ™ฤ‡',
+  'optionals': 'w|we,roku',
+  'short':     '{d} {Month} {yyyy}',
+  'long':      '{d} {Month} {yyyy} {H}:{mm}',
+  'full' :     '{Weekday}, {d} {Month} {yyyy} {H}:{mm}:{ss}',
+  'past':      '{num} {unit} {sign}',
+  'future':    '{sign} {num} {unit}',
+  'duration':  '{num} {unit}',
+  'timeMarker':'o',
+  'ampm':      'am,pm',
+  'modifiers': [
+    { 'name': 'day', 'src': 'przedwczoraj', 'value': -2 },
+    { 'name': 'day', 'src': 'wczoraj', 'value': -1 },
+    { 'name': 'day', 'src': 'dzisiaj|dziล›', 'value': 0 },
+    { 'name': 'day', 'src': 'jutro', 'value': 1 },
+    { 'name': 'day', 'src': 'pojutrze', 'value': 2 },
+    { 'name': 'sign', 'src': 'temu|przed', 'value': -1 },
+    { 'name': 'sign', 'src': 'za', 'value': 1 },
+    { 'name': 'shift', 'src': 'zeszล‚y|zeszล‚a|ostatni|ostatnia', 'value': -1 },
+    { 'name': 'shift', 'src': 'nastฤ™pny|nastฤ™pna|nastฤ™pnego|przyszล‚y|przyszล‚a|przyszล‚ego', 'value': 1 }
+  ],
+  'dateParse': [
+    '{num} {unit} {sign}',
+    '{sign} {num} {unit}',
+    '{month} {year}',
+    '{shift} {unit=5-7}',
+    '{0} {shift?} {weekday}'
+  ],
+  'timeParse': [
+    '{date} {month} {year?} {1}',
+    '{0} {shift?} {weekday}'
+  ]
+});
+
level2/node_modules/sugar/lib/locales/pt.js
@@ -0,0 +1,79 @@
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('pt');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('pt', {
+  'plural': true,
+  'months': 'janeiro,fevereiro,marรงo,abril,maio,junho,julho,agosto,setembro,outubro,novembro,dezembro',
+  'weekdays': 'domingo,segunda-feira,terรงa-feira,quarta-feira,quinta-feira,sexta-feira,sรกbado|sabado',
+  'units': 'milisegundo:|s,segundo:|s,minuto:|s,hora:|s,dia:|s,semana:|s,mรชs|mรชses|mes|meses,ano:|s',
+  'numbers': 'um,dois,trรชs|tres,quatro,cinco,seis,sete,oito,nove,dez,uma,duas',
+  'tokens': 'a,de',
+  'short':'{d} de {month} de {yyyy}',
+  'long': '{d} de {month} de {yyyy} {H}:{mm}',
+  'full': '{Weekday}, {d} de {month} de {yyyy} {H}:{mm}:{ss}',
+  'past': '{num} {unit} {sign}',
+  'future': '{sign} {num} {unit}',
+  'duration': '{num} {unit}',
+  'timeMarker': 'ร s',
+  'ampm': 'am,pm',
+  'modifiers': [
+    { 'name': 'day', 'src': 'anteontem', 'value': -2 },
+    { 'name': 'day', 'src': 'ontem', 'value': -1 },
+    { 'name': 'day', 'src': 'hoje', 'value': 0 },
+    { 'name': 'day', 'src': 'amanh:รฃ|a', 'value': 1 },
+    { 'name': 'sign', 'src': 'atrรกs|atras|hรก|ha', 'value': -1 },
+    { 'name': 'sign', 'src': 'daqui a', 'value': 1 },
+    { 'name': 'shift', 'src': 'passad:o|a', 'value': -1 },
+    { 'name': 'shift', 'src': 'prรณximo|prรณxima|proximo|proxima', 'value': 1 }
+  ],
+  'dateParse': [
+    '{num} {unit} {sign}',
+    '{sign} {num} {unit}',
+    '{0?} {unit=5-7} {shift}',
+    '{0?} {shift} {unit=5-7}'
+  ],
+  'timeParse': [
+    '{date?} {1?} {month} {1?} {year?}',
+    '{0?} {shift} {weekday}'
+  ]
+});
+
level2/node_modules/sugar/lib/locales/ru.js
@@ -0,0 +1,91 @@
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('ru');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('ru', {
+  'months': 'ะฏะฝะฒะฐั€:ั|ัŒ,ะคะตะฒั€ะฐะป:ั|ัŒ,ะœะฐั€ั‚:ะฐ|,ะะฟั€ะตะป:ั|ัŒ,ะœะฐ:ั|ะน,ะ˜ัŽะฝ:ั|ัŒ,ะ˜ัŽะป:ั|ัŒ,ะะฒะณัƒัั‚:ะฐ|,ะกะตะฝั‚ัะฑั€:ั|ัŒ,ะžะบั‚ัะฑั€:ั|ัŒ,ะะพัะฑั€:ั|ัŒ,ะ”ะตะบะฐะฑั€:ั|ัŒ',
+  'weekdays': 'ะ’ะพัะบั€ะตัะตะฝัŒะต,ะŸะพะฝะตะดะตะปัŒะฝะธะบ,ะ’ั‚ะพั€ะฝะธะบ,ะกั€ะตะดะฐ,ะงะตั‚ะฒะตั€ะณ,ะŸัั‚ะฝะธั†ะฐ,ะกัƒะฑะฑะพั‚ะฐ',
+  'units': 'ะผะธะปะปะธัะตะบัƒะฝะด:ะฐ|ัƒ|ั‹|,ัะตะบัƒะฝะด:ะฐ|ัƒ|ั‹|,ะผะธะฝัƒั‚:ะฐ|ัƒ|ั‹|,ั‡ะฐั:||ะฐ|ะพะฒ,ะดะตะฝัŒ|ะดะตะฝัŒ|ะดะฝั|ะดะฝะตะน,ะฝะตะดะตะป:ั|ัŽ|ะธ|ัŒ|ะต,ะผะตััั†:||ะฐ|ะตะฒ|ะต,ะณะพะด|ะณะพะด|ะณะพะดะฐ|ะปะตั‚|ะณะพะดัƒ',
+  'numbers': 'ะพะด:ะธะฝ|ะฝัƒ,ะดะฒ:ะฐ|ะต,ั‚ั€ะธ,ั‡ะตั‚ั‹ั€ะต,ะฟัั‚ัŒ,ัˆะตัั‚ัŒ,ัะตะผัŒ,ะฒะพัะตะผัŒ,ะดะตะฒัั‚ัŒ,ะดะตััั‚ัŒ',
+  'tokens': 'ะฒ|ะฝะฐ,ะณะพะดะฐ',
+  'short':'{d} {month} {yyyy} ะณะพะดะฐ',
+  'long': '{d} {month} {yyyy} ะณะพะดะฐ {H}:{mm}',
+  'full': '{Weekday} {d} {month} {yyyy} ะณะพะดะฐ {H}:{mm}:{ss}',
+  'relative': function(num, unit, ms, format) {
+    var numberWithUnit, last = num.toString().slice(-1), mult;
+    switch(true) {
+      case num >= 11 && num <= 15: mult = 3; break;
+      case last == 1: mult = 1; break;
+      case last >= 2 && last <= 4: mult = 2; break;
+      default: mult = 3;
+    }
+    numberWithUnit = num + ' ' + this['units'][(mult * 8) + unit];
+    switch(format) {
+      case 'duration':  return numberWithUnit;
+      case 'past':      return numberWithUnit + ' ะฝะฐะทะฐะด';
+      case 'future':    return 'ั‡ะตั€ะตะท ' + numberWithUnit;
+    }
+  },
+  'timeMarker': 'ะฒ',
+  'ampm': ' ัƒั‚ั€ะฐ, ะฒะตั‡ะตั€ะฐ',
+  'modifiers': [
+    { 'name': 'day', 'src': 'ะฟะพะทะฐะฒั‡ะตั€ะฐ', 'value': -2 },
+    { 'name': 'day', 'src': 'ะฒั‡ะตั€ะฐ', 'value': -1 },
+    { 'name': 'day', 'src': 'ัะตะณะพะดะฝั', 'value': 0 },
+    { 'name': 'day', 'src': 'ะทะฐะฒั‚ั€ะฐ', 'value': 1 },
+    { 'name': 'day', 'src': 'ะฟะพัะปะตะทะฐะฒั‚ั€ะฐ', 'value': 2 },
+    { 'name': 'sign', 'src': 'ะฝะฐะทะฐะด', 'value': -1 },
+    { 'name': 'sign', 'src': 'ั‡ะตั€ะตะท', 'value': 1 },
+    { 'name': 'shift', 'src': 'ะฟั€ะพัˆะป:ั‹ะน|ะพะน|ะพะผ', 'value': -1 },
+    { 'name': 'shift', 'src': 'ัะปะตะดัƒัŽั‰:ะธะน|ะตะน|ะตะผ', 'value': 1 }
+  ],
+  'dateParse': [
+    '{num} {unit} {sign}',
+    '{sign} {num} {unit}',
+    '{month} {year}',
+    '{0?} {shift} {unit=5-7}'
+  ],
+  'timeParse': [
+    '{date} {month} {year?} {1?}',
+    '{0?} {shift} {weekday}'
+  ]
+});
+
level2/node_modules/sugar/lib/locales/sv.js
@@ -0,0 +1,82 @@
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('sv');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('sv', {
+  'plural': true,
+  'months': 'januari,februari,mars,april,maj,juni,juli,augusti,september,oktober,november,december',
+  'weekdays': 'sรถndag|sondag,mรฅndag:|en+mandag:|en,tisdag,onsdag,torsdag,fredag,lรถrdag|lordag',
+  'units': 'millisekund:|er,sekund:|er,minut:|er,timm:e|ar,dag:|ar,veck:a|or|an,mรฅnad:|er|en+manad:|er|en,รฅr:||et+ar:||et',
+  'numbers': 'en|ett,tvรฅ|tva,tre,fyra,fem,sex,sju,รฅtta|atta,nio,tio',
+  'tokens': 'den,fรถr|for',
+  'articles': 'den',
+  'short':'den {d} {month} {yyyy}',
+  'long': 'den {d} {month} {yyyy} {H}:{mm}',
+  'full': '{Weekday} den {d} {month} {yyyy} {H}:{mm}:{ss}',
+  'past': '{num} {unit} {sign}',
+  'future': '{sign} {num} {unit}',
+  'duration': '{num} {unit}',
+  'ampm': 'am,pm',
+  'modifiers': [
+    { 'name': 'day', 'src': 'fรถrrgรฅr|i fรถrrgรฅr|ifรถrrgรฅr|forrgar|i forrgar|iforrgar', 'value': -2 },
+    { 'name': 'day', 'src': 'gรฅr|i gรฅr|igรฅr|gar|i gar|igar', 'value': -1 },
+    { 'name': 'day', 'src': 'dag|i dag|idag', 'value': 0 },
+    { 'name': 'day', 'src': 'morgon|i morgon|imorgon', 'value': 1 },
+    { 'name': 'day', 'src': 'รถver morgon|รถvermorgon|i รถver morgon|i รถvermorgon|iรถvermorgon|over morgon|overmorgon|i over morgon|i overmorgon|iovermorgon', 'value': 2 },
+    { 'name': 'sign', 'src': 'sedan|sen', 'value': -1 },
+    { 'name': 'sign', 'src': 'om', 'value':  1 },
+    { 'name': 'shift', 'src': 'i fรถrra|fรถrra|i forra|forra', 'value': -1 },
+    { 'name': 'shift', 'src': 'denna', 'value': 0 },
+    { 'name': 'shift', 'src': 'nรคsta|nasta', 'value': 1 }
+  ],
+  'dateParse': [
+    '{num} {unit} {sign}',
+    '{sign} {num} {unit}',
+    '{1?} {num} {unit} {sign}',
+    '{shift} {unit=5-7}'
+  ],
+  'timeParse': [
+    '{0?} {weekday?} {date?} {month} {year}',
+    '{date} {month}',
+    '{shift} {weekday}'
+  ]
+});
+
level2/node_modules/sugar/lib/locales/zh-CN.js
@@ -0,0 +1,80 @@
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('zh-CN');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('zh-CN', {
+  'variant': true,
+  'monthSuffix': 'ๆœˆ',
+  'weekdays': 'ๆ˜ŸๆœŸๆ—ฅ|ๅ‘จๆ—ฅ,ๆ˜ŸๆœŸไธ€|ๅ‘จไธ€,ๆ˜ŸๆœŸไบŒ|ๅ‘จไบŒ,ๆ˜ŸๆœŸไธ‰|ๅ‘จไธ‰,ๆ˜ŸๆœŸๅ››|ๅ‘จๅ››,ๆ˜ŸๆœŸไบ”|ๅ‘จไบ”,ๆ˜ŸๆœŸๅ…ญ|ๅ‘จๅ…ญ',
+  'units': 'ๆฏซ็ง’,็ง’้’Ÿ,ๅˆ†้’Ÿ,ๅฐๆ—ถ,ๅคฉ,ไธชๆ˜ŸๆœŸ|ๅ‘จ,ไธชๆœˆ,ๅนด',
+  'tokens': 'ๆ—ฅ|ๅท',
+  'short':'{yyyy}ๅนด{M}ๆœˆ{d}ๆ—ฅ',
+  'long': '{yyyy}ๅนด{M}ๆœˆ{d}ๆ—ฅ {tt}{h}:{mm}',
+  'full': '{yyyy}ๅนด{M}ๆœˆ{d}ๆ—ฅ {weekday} {tt}{h}:{mm}:{ss}',
+  'past': '{num}{unit}{sign}',
+  'future': '{num}{unit}{sign}',
+  'duration': '{num}{unit}',
+  'timeSuffixes': '็‚น|ๆ—ถ,ๅˆ†้’Ÿ?,็ง’',
+  'ampm': 'ไธŠๅˆ,ไธ‹ๅˆ',
+  'modifiers': [
+    { 'name': 'day', 'src': 'ๅ‰ๅคฉ', 'value': -2 },
+    { 'name': 'day', 'src': 'ๆ˜จๅคฉ', 'value': -1 },
+    { 'name': 'day', 'src': 'ไปŠๅคฉ', 'value': 0 },
+    { 'name': 'day', 'src': 'ๆ˜Žๅคฉ', 'value': 1 },
+    { 'name': 'day', 'src': 'ๅŽๅคฉ', 'value': 2 },
+    { 'name': 'sign', 'src': 'ๅ‰', 'value': -1 },
+    { 'name': 'sign', 'src': 'ๅŽ', 'value':  1 },
+    { 'name': 'shift', 'src': 'ไธŠ|ๅŽป', 'value': -1 },
+    { 'name': 'shift', 'src': '่ฟ™', 'value':  0 },
+    { 'name': 'shift', 'src': 'ไธ‹|ๆ˜Ž', 'value':  1 }
+  ],
+  'dateParse': [
+    '{num}{unit}{sign}',
+    '{shift}{unit=5-7}'
+  ],
+  'timeParse': [
+    '{shift}{weekday}',
+    '{year}ๅนด{month?}ๆœˆ?{date?}{0?}',
+    '{month}ๆœˆ{date?}{0?}',
+    '{date}[ๆ—ฅๅท]'
+  ]
+});
+
level2/node_modules/sugar/lib/locales/zh-TW.js
@@ -0,0 +1,81 @@
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('zh-TW');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+  //'zh-TW': '1;ๆœˆ;ๅนด;;ๆ˜ŸๆœŸๆ—ฅ|้€ฑๆ—ฅ,ๆ˜ŸๆœŸไธ€|้€ฑไธ€,ๆ˜ŸๆœŸไบŒ|้€ฑไบŒ,ๆ˜ŸๆœŸไธ‰|้€ฑไธ‰,ๆ˜ŸๆœŸๅ››|้€ฑๅ››,ๆ˜ŸๆœŸไบ”|้€ฑไบ”,ๆ˜ŸๆœŸๅ…ญ|้€ฑๅ…ญ;ๆฏซ็ง’,็ง’้˜,ๅˆ†้˜,ๅฐๆ™‚,ๅคฉ,ๅ€‹ๆ˜ŸๆœŸ|้€ฑ,ๅ€‹ๆœˆ,ๅนด;;;ๆ—ฅ|่™Ÿ;;ไธŠๅˆ,ไธ‹ๅˆ;้ปž|ๆ™‚,ๅˆ†้˜?,็ง’;{num}{unit}{sign},{shift}{unit=5-7};{shift}{weekday},{year}ๅนด{month?}ๆœˆ?{date?}{0},{month}ๆœˆ{date?}{0},{date}{0};{yyyy}ๅนด{M}ๆœˆ{d}ๆ—ฅ {Weekday};{tt}{h}:{mm}:{ss};ๅ‰ๅคฉ,ๆ˜จๅคฉ,ไปŠๅคฉ,ๆ˜Žๅคฉ,ๅพŒๅคฉ;,ๅ‰,,ๅพŒ;,ไธŠ|ๅŽป,้€™,ไธ‹|ๆ˜Ž',
+
+Date.addLocale('zh-TW', {
+  'monthSuffix': 'ๆœˆ',
+  'weekdays': 'ๆ˜ŸๆœŸๆ—ฅ|้€ฑๆ—ฅ,ๆ˜ŸๆœŸไธ€|้€ฑไธ€,ๆ˜ŸๆœŸไบŒ|้€ฑไบŒ,ๆ˜ŸๆœŸไธ‰|้€ฑไธ‰,ๆ˜ŸๆœŸๅ››|้€ฑๅ››,ๆ˜ŸๆœŸไบ”|้€ฑไบ”,ๆ˜ŸๆœŸๅ…ญ|้€ฑๅ…ญ',
+  'units': 'ๆฏซ็ง’,็ง’้˜,ๅˆ†้˜,ๅฐๆ™‚,ๅคฉ,ๅ€‹ๆ˜ŸๆœŸ|้€ฑ,ๅ€‹ๆœˆ,ๅนด',
+  'tokens': 'ๆ—ฅ|่™Ÿ',
+  'short':'{yyyy}ๅนด{M}ๆœˆ{d}ๆ—ฅ',
+  'long': '{yyyy}ๅนด{M}ๆœˆ{d}ๆ—ฅ {tt}{h}:{mm}',
+  'full': '{yyyy}ๅนด{M}ๆœˆ{d}ๆ—ฅ {Weekday} {tt}{h}:{mm}:{ss}',
+  'past': '{num}{unit}{sign}',
+  'future': '{num}{unit}{sign}',
+  'duration': '{num}{unit}',
+  'timeSuffixes': '้ปž|ๆ™‚,ๅˆ†้˜?,็ง’',
+  'ampm': 'ไธŠๅˆ,ไธ‹ๅˆ',
+  'modifiers': [
+    { 'name': 'day', 'src': 'ๅ‰ๅคฉ', 'value': -2 },
+    { 'name': 'day', 'src': 'ๆ˜จๅคฉ', 'value': -1 },
+    { 'name': 'day', 'src': 'ไปŠๅคฉ', 'value': 0 },
+    { 'name': 'day', 'src': 'ๆ˜Žๅคฉ', 'value': 1 },
+    { 'name': 'day', 'src': 'ๅพŒๅคฉ', 'value': 2 },
+    { 'name': 'sign', 'src': 'ๅ‰', 'value': -1 },
+    { 'name': 'sign', 'src': 'ๅพŒ', 'value': 1 },
+    { 'name': 'shift', 'src': 'ไธŠ|ๅŽป', 'value': -1 },
+    { 'name': 'shift', 'src': '้€™', 'value':  0 },
+    { 'name': 'shift', 'src': 'ไธ‹|ๆ˜Ž', 'value':  1 }
+  ],
+  'dateParse': [
+    '{num}{unit}{sign}',
+    '{shift}{unit=5-7}'
+  ],
+  'timeParse': [
+    '{shift}{weekday}',
+    '{year}ๅนด{month?}ๆœˆ?{date?}{0?}',
+    '{month}ๆœˆ{date?}{0?}',
+    '{date}[ๆ—ฅ่™Ÿ]'
+  ]
+});
+
level2/node_modules/sugar/lib/patches/sugar-es6-patch.js
@@ -0,0 +1,27 @@
+/*
+ *  Sugar ES6 Patch
+ *
+ *  This patch will restore Sugar methods that have been "underwritten"
+ *  by new ES6 method implementations. This patch is only for versions
+ *  below 1.4.0! If you are not sure if you need this, then it's likely
+ *  you don't. Consider upgrading to 1.4.0 instead.
+ *
+ */
+(function() {
+
+  var SugarMethods = 'SugarMethods';
+
+  function restoreMethods(klass, method) {
+    var sugar   = klass[SugarMethods] && klass[SugarMethods][method];
+    var current = klass.prototype[method];
+    if(sugar && sugar !== current) {
+      klass.prototype[method] = sugar['method'];
+    }
+  }
+
+  restoreMethods(Array, 'find');
+  restoreMethods(Array, 'findIndex');
+  restoreMethods(String, 'repeat');
+  restoreMethods(String, 'normalize');
+
+})();
level2/node_modules/sugar/lib/plugins/array/to_sentence.js
@@ -0,0 +1,48 @@
+/***
+ * @method toSentence([conjunction] = 'and')
+ * @author nadinengland
+ * @returns String
+ * @dependencies Array
+ * @short Builds a grammatical list from the array.
+ * @extra A custom string [conjuction] can be supplied for localization, 'and' is used if not a string.
+ * @example
+ *
+ *   ['a', 'b', 'c'].toSentence()                      -> 'a, b and c';
+ *   ['a', 2, {c:3}].toSentence()                      -> 'a, 2 and [object Object]';
+ *   ['Lundi', 'Mardi', 'Mercredi'].toSentence('et')   -> 'Lundi, Mardi et Mercredi';
+ *
+ ***/
+
+Array.extend({
+
+  'toSentence': function(conjunction) {
+    var sentence = "",
+      twoWordConjunction,
+      lastWordConjunction;
+
+    // Quick escape
+    if (this.length === 0) return sentence;
+
+    if (typeof conjunction !== 'string') {
+      conjunction = "and";
+    }
+
+    twoWordConjunction = ' ' + conjunction + ' ';
+    lastWordConjunction = ' ' + conjunction + ' ';
+
+    switch (this.length) {
+      case 1:
+        sentence = this[0];
+      break;
+      case 2:
+        sentence = this.join(twoWordConjunction);
+      break;
+      default:
+        sentence = this.first(this.length - 1).join(', ') + lastWordConjunction + this.last();
+      break;
+    };
+
+    return sentence;
+  }
+
+});
level2/node_modules/sugar/lib/plugins/string/namespace.js
@@ -0,0 +1,29 @@
+/***
+ * @method namespace([init] = global)
+ * @author andrewplummer
+ * @returns Mixed
+ * @short Finds the namespace or property indicated by the string.
+ * @extra [init] can be passed to provide a starting context, otherwise the global context will be used. If any level returns a falsy value, that will be the final result.
+ * @dependencies ES5
+ * @example
+ *
+ *   'Path.To.Namespace'.namespace() -> Path.To.Namespace
+ *   '$.fn'.namespace()              -> $.fn
+ *
+ ***/
+
+(function(global) {
+
+  String.extend({
+
+    'namespace': function(context) {
+      context = context || global;
+      this.split('.').every(function(s, i) {
+        return !!(context = context[s]);
+      });
+      return context;
+    }
+
+  });
+
+})(this);
level2/node_modules/sugar/lib/plugins/string/normalize.js
@@ -0,0 +1,130 @@
+(function() {
+
+  var NormalizeMap = {}, NormalizeReg, NormalizeSource;
+
+  function buildNormalizeMap() {
+    var normalized, str, all = '';
+    for(normalized in NormalizeSource) {
+      if(!NormalizeSource.hasOwnProperty(normalized)) continue;
+      str = NormalizeSource[normalized];
+      str.split('').forEach(function(character) {
+        NormalizeMap[character] = normalized;
+      });
+      all += str;
+    }
+    NormalizeReg = RegExp('[' + all + ']', 'g');
+  }
+
+  NormalizeSource = {
+    'A':  'Aโ’ถ๏ผกร€รร‚แบฆแบคแบชแบจรƒฤ€ฤ‚แบฐแบฎแบดแบฒศฆว ร„วžแบขร…วบวศ€ศ‚แบ แบฌแบถแธ€ฤ„ศบโฑฏ',
+    'B':  'Bโ’ท๏ผขแธ‚แธ„แธ†ษƒฦ‚ฦ',
+    'C':  'Cโ’ธ๏ผฃฤ†ฤˆฤŠฤŒร‡แธˆฦ‡ศป๊œพ',
+    'D':  'Dโ’น๏ผคแธŠฤŽแธŒแธแธ’แธŽฤฦ‹ฦŠฦ‰๊น',
+    'E':  'Eโ’บ๏ผฅรˆร‰รŠแป€แบพแป„แป‚แบผฤ’แธ”แธ–ฤ”ฤ–ร‹แบบฤšศ„ศ†แบธแป†ศจแธœฤ˜แธ˜แธšฦฦŽ',
+    'F':  'Fโ’ป๏ผฆแธžฦ‘๊ป',
+    'G':  'Gโ’ผ๏ผงวดฤœแธ ฤžฤ วฆฤขวคฦ“๊ž ๊ฝ๊พ',
+    'H':  'Hโ’ฝ๏ผจฤคแธขแธฆศžแธคแธจแธชฤฆโฑงโฑต๊ž',
+    'I':  'Iโ’พ๏ผฉรŒรรŽฤจฤชฤฌฤฐรแธฎแปˆวศˆศŠแปŠฤฎแธฌฦ—',
+    'J':  'Jโ’ฟ๏ผชฤดษˆ',
+    'K':  'Kโ“€๏ผซแธฐวจแธฒฤถแธดฦ˜โฑฉ๊€๊‚๊„๊žข',
+    'L':  'Lโ“๏ผฌฤฟฤนฤฝแธถแธธฤปแธผแธบลศฝโฑขโฑ ๊ˆ๊†๊ž€',
+    'M':  'Mโ“‚๏ผญแธพแน€แน‚โฑฎฦœ',
+    'N':  'Nโ“ƒ๏ผฎวธลƒร‘แน„ล‡แน†ล…แนŠแนˆศ ฦ๊ž๊žค',
+    'O':  'Oโ“„๏ผฏร’ร“ร”แป’แปแป–แป”ร•แนŒศฌแนŽลŒแนแน’ลŽศฎศฐร–ศชแปŽลว‘ศŒศŽฦ แปœแปšแป แปžแปขแปŒแป˜วชวฌร˜วพฦ†ฦŸ๊Š๊Œ',
+    'P':  'Pโ“…๏ผฐแน”แน–ฦคโฑฃ๊๊’๊”',
+    'Q':  'Qโ“†๏ผฑ๊–๊˜ษŠ',
+    'R':  'Rโ“‡๏ผฒล”แน˜ล˜ศศ’แนšแนœล–แนžษŒโฑค๊š๊žฆ๊ž‚',
+    'S':  'Sโ“ˆ๏ผณแบžลšแนคลœแน ล แนฆแนขแนจศ˜ลžโฑพ๊žจ๊ž„',
+    'T':  'Tโ“‰๏ผดแนชลคแนฌศšลขแนฐแนฎลฆฦฌฦฎศพ๊ž†',
+    'U':  'Uโ“Š๏ผตร™รšร›ลจแนธลชแนบลฌรœว›ว—ว•ว™แปฆลฎลฐว“ศ”ศ–ฦฏแปชแปจแปฎแปฌแปฐแปคแนฒลฒแนถแนดษ„',
+    'V':  'Vโ“‹๏ผถแนผแนพฦฒ๊žษ…',
+    'W':  'Wโ“Œ๏ผทแบ€แบ‚ลดแบ†แบ„แบˆโฑฒ',
+    'X':  'Xโ“๏ผธแบŠแบŒ',
+    'Y':  'Yโ“Ž๏ผนแปฒรลถแปธศฒแบŽลธแปถแปดฦณษŽแปพ',
+    'Z':  'Zโ“๏ผบลนแบลปลฝแบ’แบ”ฦตศคโฑฟโฑซ๊ข',
+    'a':  'aโ“๏ฝแบšร รกรขแบงแบฅแบซแบฉรฃฤฤƒแบฑแบฏแบตแบณศงวกรควŸแบฃรฅวปวŽศศƒแบกแบญแบทแธฤ…โฑฅษ',
+    'b':  'bโ“‘๏ฝ‚แธƒแธ…แธ‡ฦ€ฦƒษ“',
+    'c':  'cโ“’๏ฝƒฤ‡ฤ‰ฤ‹ฤรงแธ‰ฦˆศผ๊œฟโ†„',
+    'd':  'dโ““๏ฝ„แธ‹ฤแธแธ‘แธ“แธฤ‘ฦŒษ–ษ—๊บ',
+    'e':  'eโ“”๏ฝ…รจรฉรชแปแบฟแป…แปƒแบฝฤ“แธ•แธ—ฤ•ฤ—รซแบปฤ›ศ…ศ‡แบนแป‡ศฉแธฤ™แธ™แธ›ษ‡ษ›ว',
+    'f':  'fโ“•๏ฝ†แธŸฦ’๊ผ',
+    'g':  'gโ“–๏ฝ‡วตฤแธกฤŸฤกวงฤฃวฅษ ๊žกแตน๊ฟ',
+    'h':  'hโ“—๏ฝˆฤฅแธฃแธงศŸแธฅแธฉแธซแบ–ฤงโฑจโฑถษฅ',
+    'i':  'iโ“˜๏ฝ‰รฌรญรฎฤฉฤซฤญรฏแธฏแป‰วศ‰ศ‹แป‹ฤฏแธญษจฤฑ',
+    'j':  'jโ“™๏ฝŠฤตวฐษ‰',
+    'k':  'kโ“š๏ฝ‹แธฑวฉแธณฤทแธตฦ™โฑช๊๊ƒ๊…๊žฃ',
+    'l':  'lโ“›๏ฝŒล€ฤบฤพแธทแธนฤผแธฝแธปลฟล‚ฦšษซโฑก๊‰๊ž๊‡',
+    'm':  'mโ“œ๏ฝแธฟแนแนƒษฑษฏ',
+    'n':  'nโ“๏ฝŽวนล„รฑแน…ลˆแน‡ล†แน‹แน‰ฦžษฒล‰๊ž‘๊žฅ',
+    'o':  'oโ“ž๏ฝรฒรณรดแป“แป‘แป—แป•รตแนศญแนลแน‘แน“ลศฏศฑรถศซแปล‘ว’ศศฦกแปแป›แปกแปŸแปฃแปแป™วซวญรธวฟษ”๊‹๊ษต',
+    'p':  'pโ“Ÿ๏ฝแน•แน—ฦฅแตฝ๊‘๊“๊•',
+    'q':  'qโ“ ๏ฝ‘ษ‹๊—๊™',
+    'r':  'rโ“ก๏ฝ’ล•แน™ล™ศ‘ศ“แน›แนล—แนŸษษฝ๊›๊žง๊žƒ',
+    's':  'sโ“ข๏ฝ“ล›แนฅลแนกลกแนงแนฃแนฉศ™ลŸศฟ๊žฉ๊ž…แบ›',
+    't':  'tโ“ฃ๏ฝ”แนซแบ—ลฅแนญศ›ลฃแนฑแนฏลงฦญสˆโฑฆ๊ž‡',
+    'u':  'uโ“ค๏ฝ•รนรบรปลฉแนนลซแนปลญรผวœว˜ว–วšแปงลฏลฑว”ศ•ศ—ฦฐแปซแปฉแปฏแปญแปฑแปฅแนณลณแนทแนตส‰',
+    'v':  'vโ“ฅ๏ฝ–แนฝแนฟส‹๊ŸสŒ',
+    'w':  'wโ“ฆ๏ฝ—แบแบƒลตแบ‡แบ…แบ˜แบ‰โฑณ',
+    'x':  'xโ“ง๏ฝ˜แบ‹แบ',
+    'y':  'yโ“จ๏ฝ™แปณรฝลทแปนศณแบรฟแปทแบ™แปตฦดษแปฟ',
+    'z':  'zโ“ฉ๏ฝšลบแบ‘ลผลพแบ“แบ•ฦถศฅษ€โฑฌ๊ฃ',
+    'AA': '๊œฒ',
+    'AE': 'ร†วผวข',
+    'AO': '๊œด',
+    'AU': '๊œถ',
+    'AV': '๊œธ๊œบ',
+    'AY': '๊œผ',
+    'DZ': 'วฑว„',
+    'Dz': 'วฒว…',
+    'LJ': 'ว‡',
+    'Lj': 'วˆ',
+    'NJ': 'วŠ',
+    'Nj': 'ว‹',
+    'OI': 'ฦข',
+    'OO': '๊Ž',
+    'OU': 'ศข',
+    'TZ': '๊œจ',
+    'VY': '๊ ',
+    'aa': '๊œณ',
+    'ae': 'รฆวฝวฃ',
+    'ao': '๊œต',
+    'au': '๊œท',
+    'av': '๊œน๊œป',
+    'ay': '๊œฝ',
+    'dz': 'วณว†',
+    'hv': 'ฦ•',
+    'lj': 'ว‰',
+    'nj': 'วŒ',
+    'oi': 'ฦฃ',
+    'ou': 'ศฃ',
+    'oo': '๊',
+    'ss': 'รŸ',
+    'tz': '๊œฉ',
+    'vy': '๊ก'
+  };
+
+  String.extend({
+    /***
+     * @method normalize()
+     * @returns String
+     * @short Returns the string with accented and non-standard Latin-based characters converted into ASCII approximate equivalents.
+     * @example
+     *
+     *   'รก'.normalize()                  -> 'a'
+     *   'Mรฉnage ร  trois'.normalize()     -> 'Menage a trois'
+     *   'Volkswagen'.normalize()         -> 'Volkswagen'
+     *   '๏ผฆ๏ผต๏ผฌ๏ผฌ๏ผท๏ผฉ๏ผค๏ผด๏ผจ'.normalize() -> 'FULLWIDTH'
+     *
+     ***/
+    'normalize': function() {
+      return this.replace(NormalizeReg, function(character) {
+        return NormalizeMap[character];
+      });
+    }
+
+  });
+
+  buildNormalizeMap();
+
+})();
+
level2/node_modules/sugar/lib/plugins/string/split.js
@@ -0,0 +1,84 @@
+/***
+ * @method split([separator], [limit])
+ * @author andrewplummer
+ * @returns Array
+ * @dependencies None
+ * @short Split a string into an array. This method is native to Javascript, but this plugin patches it to provide cross-browser reliability when splitting on a regex.
+ * @example
+ *
+ *   'comma,separated,values'.split(',') -> ['comma','separated','values']
+ *   'a,b|c>d'.split(/[,|>]/)            -> ['multi','separated','values']
+ *
+ ***/
+
+// Many thanks to Steve Levithan here for a ton of inspiration and work dealing with
+// cross browser Regex splitting.  http://blog.stevenlevithan.com/archives/cross-browser-split
+
+String.extend({
+
+  'split': function(separator, limit) {
+    var output = [];
+    var lastLastIndex = 0;
+    var flags;
+    var getRegExpFlags = function(reg, add) {
+      var flags = reg.toString().match(/[^/]*$/)[0];
+      if(add) {
+        flags = (flags + add).split('').sort().join('').replace(/([gimy])\1+/g, '$1');
+      }
+      return flags;
+    }
+    var flags = getRegExpFlags(separator, 'g');
+    // make `global` and avoid `lastIndex` issues by working with a copy
+    var separator = new RegExp(separator.source, flags);
+    var separator2, match, lastIndex, lastLength;
+    if(!RegExp.NPCGSupport) {
+      // doesn't need /g or /y, but they don't hurt
+      separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
+    }
+    if(limit === undefined || limit < 0) {
+      limit = Infinity;
+    } else {
+      limit = Math.floor(limit);
+      if(!limit) return [];
+    }
+
+    while (match = separator.exec(this)) {
+      lastIndex = match.index + match[0].length; // `separator.lastIndex` is not reliable cross-browser
+      if(lastIndex > lastLastIndex) {
+        output.push(this.slice(lastLastIndex, match.index));
+        // fix browsers whose `exec` methods don't consistently return `undefined` for nonparticipating capturing groups
+        if(!RegExp.NPCGSupport && match.length > 1) {
+          match[0].replace(separator2, function () {
+            for (var i = 1; i < arguments.length - 2; i++) {
+              if(arguments[i] === undefined) {
+                match[i] = undefined;
+              }
+            }
+          });
+        }
+        if(match.length > 1 && match.index < this.length) {
+          array.prototype.push.apply(output, match.slice(1));
+        }
+        lastLength = match[0].length;
+        lastLastIndex = lastIndex;
+        if(output.length >= limit) {
+          break;
+        }
+      }
+      if(separator.lastIndex === match.index) {
+        separator.lastIndex++; // avoid an infinite loop
+      }
+    }
+    if(lastLastIndex === this.length) {
+      if(lastLength || !separator.test('')) output.push('');
+    } else {
+      output.push(this.slice(lastLastIndex));
+    }
+    return output.length > limit ? output.slice(0, limit) : output;
+  }
+
+}, function(s) {
+  return Object.prototype.toString.call(s) === '[object RegExp]';
+});
+
+
level2/node_modules/sugar/lib/analyzer.js
@@ -0,0 +1,1999 @@
+(function(context) {
+
+
+  // BEGIN LIBS
+//  Compatiblity index:
+//
+//  0 - Does not exist.
+//  1 - Exists but does not support all functionality.
+//  2 - Exists and supports all functionality.
+//  3 - Exists and supports all functionality plus more.
+
+
+var SugarPrototypeMethods = [
+  {
+  // Global namespace
+  type: 'class',
+  namespace: 'Global',
+  methods: [
+    {
+      name: 'Hash',
+      description: 'Creates a hash object with methods specific to working with hashes.',
+      sugar_compatibility: 1,
+      sugar_notes: 'The Hash class does not exist in Sugar, however hash-like objects exist through Object.extended, which will return an extended object with instance methods on it similar to hashes in Prototype. Keep in mind, however, that the instance methods available to extended objects in Sugar do not 100% match those of Prototype.',
+      original_code: "$H({ 0: 'a', 1: 'b', 2: 'c' })",
+      sugar_code: "Object.extended({ 0: 'a', 1: 'b', 2: 'c' })",
+      ref: 'Object/extended'
+    },
+    {
+      name: '$A',
+      description: 'Creates an array from an array-like collection',
+      sugar_compatibility: 1,
+      sugar_notes: '$A exists in Sugar as Array.create. Be aware, however, that when a Javascript primitive is passed, it will simply be added to the array. If, for example, you need a string to be split into the array, then use the standard String#split instead. The most common use of this method, converting an arguments object into an actual array, will work, however.',
+      original_code: '$A(arguments)',
+      sugar_code: 'Array.create(arguments)',
+      ref: 'Array/create'
+    },
+    {
+      name: '$H',
+      description: 'Creates a hash object with methods specific to working with hashes.',
+      sugar_compatibility: 1,
+      sugar_notes: '$H exists in Sugar as Object.extended. This will return an extended object with instance methods on it similar to hashes in Prototype. Keep in mind, however, that the instance methods available to extended objects in Sugar do not 100% match those of Prototype.',
+      original_code: "$H({ 0: 'a', 1: 'b', 2: 'c' })",
+      sugar_code: "Object.extended({ 0: 'a', 1: 'b', 2: 'c' })",
+      ref: 'Object/extended'
+    },
+    {
+      name: '$R',
+      description: 'Creates an ObjectRange object that represents a range of consecutive values.',
+      sugar_compatibility: 0,
+      sugar_notes: '$R exists in Sugar as ranges, which can be created via Number.range, Date.range, or String.range. Additionally Number#upto or Number#downto will return an array of numbers in that range.',
+      original_code: "$R(0, 10)",
+      sugar_code: "(0).upto(10)",
+      ref: 'Number/upto'
+    },
+    {
+      name: '$w',
+      description: 'Splits a string into an array with all whitespace treated as a delimiter',
+      sugar_compatibility: 0,
+      sugar_notes: '$w does not exist in Sugar. Use native String#split instead.',
+      original_code: "$w('apples and oranges')",
+      sugar_code: "'apples and oranges'.split(' ')"
+    }
+  ]
+},
+{
+  namespace: 'Number',
+  type: 'instance',
+  methods: [
+    {
+    name: 'succ',
+    description: 'Returns the successor (+1) of the current number.',
+    sugar_compatibility: 0,
+    sugar_notes: 'Number#succ does not exist in Sugar. This is often done with the + operator :)',
+    original_code: "num.succ()",
+    sugar_code: "num + 1"
+  },
+  {
+    name: 'times',
+    description: 'Calls the passed iterator n times, where n is the number.',
+    sugar_compatibility: 1,
+    sugar_notes: 'Number#times exists in Sugar but does not take a context argument. Use Function#bind for this purpose instead.',
+    original_code: "(8).times(function(){}, 'barf')",
+    sugar_code: "(8).times(function(){}.bind('barf'))",
+    live_notes: 'Number#times does not accept a second argument, but found {1}. If you need to bind context, use Function#bind instead.',
+    conflict: function() {
+      var type = typeof arguments[1];
+      return [arguments.length > 1 && type != 'number', arguments[1]];
+    },
+    ref: 'Function/bind'
+  },
+  {
+    name: 'toColorPart',
+    description: 'Returns a 2 digit hexadecimal representation of the number.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Number#toColorPart exists in Sugar as Number#hex (pad to 2 places).',
+    original_code: "(255).toColorPart()",
+    sugar_code: "(255).hex(2)",
+    ref: 'Number/hex'
+  },
+  {
+    name: 'toPaddedString',
+    description: 'Returns a string representation of the number padded with zeros.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Number#toPaddedString exists in Sugar as Number#pad. Note that in Sugar, the radix is the third argument, as the second is to force the sign.',
+    original_code: "(20).toPaddedString(4, 2)",
+    sugar_code: "(20).pad(4, false, 2)",
+    ref: 'Number/pad'
+  },
+  {
+    name: 'abs',
+    description: 'Returns the absolute value of the number.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Number#abs exists in Sugar and is identical.',
+    conflict: false,
+    ref: 'Number/abs'
+  },
+  {
+    name: 'ceil',
+    description: 'Rounds then number up.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Number#ceil exists in Sugar. It can additionally round to a precision, specified in the first argument.',
+    conflict: false,
+    ref: 'Number/ceil'
+  },
+  {
+    name: 'floor',
+    description: 'Rounds then number down.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Number#floor exists in Sugar. It can additionally round to a precision, specified in the first argument.',
+    conflict: false,
+    ref: 'Number/floor'
+  },
+  {
+    name: 'round',
+    description: 'Rounds then number.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Number#round exists in Sugar. It can additionally round to a precision, specified in the first argument.',
+    conflict: false,
+    ref: 'Number/round'
+  }
+  ]
+},
+{
+  namespace: 'String',
+  type: 'class',
+  methods: [
+    {
+    name: 'interpret',
+    description: 'Coerces the value into a string.',
+    sugar_compatibility: 0,
+    sugar_notes: 'String.interpret does not exist in Sugar. Use String() instead. Note, however that this will not convert null/undefined into a blank string, as is the case with String.interpret.',
+    original_code: "String.interpret(156)",
+    sugar_code: "String(156)"
+  }
+  ]
+},
+{
+  namespace: 'String',
+  type: 'instance',
+  methods: [
+    {
+    name: 'blank',
+    description: 'Checks if the string is empty or contains only whitespace.',
+    sugar_compatibility: 2,
+    sugar_notes: 'String#blank exists in Sugar as String#isBlank.',
+    original_code: "'hello'.blank()",
+    sugar_code: "'hello'.isBlank()",
+    ref: 'String/isBlank'
+  },
+  {
+    name: 'camelize',
+    description: 'Converts a string to its camelCase equivalent.',
+    sugar_compatibility: 3,
+    sugar_notes: 'String#camelize exists and will also operate on whitespace and underscores as well as dashes.',
+    live_notes: 'String#camelize found white space or underscores! Note that #camelize in Sugar will remove whitespace and operate on underscores the same as dashes.',
+    conflict: function() {
+      return /[ _]/.test(this);
+    },
+    ref: 'String/camelize'
+  },
+  {
+    name: 'dasherize',
+    description: 'Replaces underscores with dashes.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Sugar#dasherize will also remove whitespace and convert camelCase as well.',
+    conflict: function() {
+      return /[A-Z\s]/.test(this);
+    },
+    live_notes: 'String#dasherize found white space or capital letters! Note that #dasherize in Sugar will remove whitespace and operate on capital letters the same as underscores.',
+    ref: 'String/dasherize'
+  },
+  {
+    name: 'empty',
+    description: 'Checks if the string is empty.',
+    sugar_compatibility: 0,
+    sugar_notes: 'String#empty does not exist in Sugar. Use a straight comparison instead.',
+    original_code: "str.empty()",
+    sugar_code: "str === ''"
+  },
+  {
+    name: 'evalJSON',
+    description: 'Evaluates the string as JSON and returns the resulting object.',
+    sugar_compatibility: 0,
+    sugar_notes: 'String#evalJSON does not exist in Sugar. JSON.parse may work as an alternative, but it is not available in all browsers. If you absolutely require JSON support, consider adding in a separate library such as: https://github.com/douglascrockford/JSON-js',
+    original_code: "str.evalJSON()",
+    sugar_code: "JSON.parse(str)"
+  },
+  {
+    name: 'evalScripts',
+    description: 'Evaluates the content of any <script> block in the string.',
+    sugar_compatibility: 0,
+    sugar_notes: "String#evalScripts does not exist in Sugar. It's highly unlikely that you should be doing something like this anyway, (and even if you are, libraries like jQuery should perform this automatically), but if you really need to in a pinch, something like this may work:",
+    original_code: "str.evalScripts()",
+    sugar_code: "str.match(/<script.*?>.+?<\/script>/g).map(function(m){\n  return eval(m.replace(/<\\/?script.*?>/g, ''));\n})"
+  },
+  {
+    name: 'extractScripts',
+    description: 'Extracts <script> blocks in the string and returns them as an array.',
+    sugar_compatibility: 0,
+    sugar_notes: 'String#extractScripts does not exist in Sugar. If you really need to do this, then in a pinch something like this may work.',
+    original_code: "str.extractScripts()",
+    sugar_code: "str.match(/<script.*?>.+?<\/script>/g).map(function(m){\n  return m.replace(/<\\/?script.*?>/g, '');\n})"
+  },
+  {
+    name: 'gsub',
+    description: 'Returns the string with every occurrence of the passed regex replaced.',
+    sugar_compatibility: 0,
+    sugar_notes: 'String#gsub does not exist in Sugar. Just use the native .replace function instead. Note that Javascript allows functions to be passed to .replace just like gsub.',
+    original_code: "'Image: (img.png)'.gsub(/Image: \(.+\)/, function(match, src) {\n  return '<img src=\"' + src + '\" />';\n})",
+    sugar_code: "'Image: (img.png)'.replace(/Image: \(.+\)/, function(match, src) {\n  return '<img src=\"' + src + '\" />';\n})"
+  },
+  {
+    name: 'include',
+    description: 'Checks if the string contains the substring.',
+    sugar_compatibility: 2,
+    sugar_notes: 'String#include exists in Sugar as String#has.',
+    original_code: "'foobar'.include('bar')",
+    sugar_code: "'foobar'.has('bar')",
+    ref: 'String/has'
+  },
+  {
+    name: 'inspect',
+    description: 'Returns a debug oriented version of the string.',
+    sugar_compatibility: 0,
+    sugar_notes: 'String#inspect does not exist in Sugar. Consider using JSON.stringify(str) instead. The JSON global does not exist in all implementations but should be enough to get you through a debug session.',
+    original_code: "'foofa'.inspect()",
+    sugar_code: "JSON.stringify('foofa')"
+  },
+  {
+    name: 'interpolate',
+    description: 'Fills the string with properties of an object, using the syntax #{}.',
+    sugar_compatibility: 3,
+    sugar_notes: 'String#interpolate exists in Sugar as String#assign, with a slightly different syntax.',
+    original_code: "'i like #{fruit}'.interpolate({ fruit: 'peaches' })",
+    sugar_code: "'i like {fruit}'.assign({ fruit: 'peaches' })",
+    ref: 'String/assign'
+  },
+  {
+    name: 'isJSON',
+    description: 'Checks if the string is valid JSON.',
+    sugar_compatibility: 0,
+    sugar_notes: 'String#isJSON does not exist in Sugar. The simplest way of determining if a value is JSON or not is to attempt parsing and catch errors. If you absolutely require full JSON support, consider adding in a separate library such as: https://github.com/douglascrockford/JSON-js',
+    original_code: "valid = str.isJSON()",
+    sugar_code: "try { JSON.parse(str); valid = true; } catch(e){ valid = false; }"
+  },
+  {
+    name: 'scan',
+    description: 'Iterates over every occurrence of the passed regex pattern.',
+    sugar_compatibility: 3,
+    sugar_notes: 'String#scan exists in Sugar as String#each. It additionally returns an array of the matched patterns.',
+    original_code: "'apple, pear & orange'.scan(/\w+/, console.log)",
+    sugar_code: "'apple, pear & orange'.each(/\w+/, console.log)",
+    ref: 'String/each'
+  },
+  {
+    name: 'strip',
+    description: 'Removes leading and trailing whitespace from the string.',
+    sugar_compatibility: 2,
+    sugar_notes: 'String#strip exists in Sugar as String#trim. This is an ES5 standard method that Sugar provides a shim for when unavailable.',
+    original_code: "'    howdy   '.strip()",
+    sugar_code: "'    howdy   '.trim()",
+    ref: 'String/trim'
+  },
+  {
+    name: 'stripScripts',
+    description: 'Strips HTML <script> tags from the string.',
+    sugar_compatibility: 3,
+    sugar_notes: 'String#stripScripts can be achieved in Sugar with String#removeTags.',
+    original_code: "'<script>doEvilStuff();</script>'.stripScripts()",
+    sugar_code: "'<script>doEvilStuff();</script>'.removeTags('script')",
+    ref: 'String/removeTags'
+  },
+  {
+    name: 'stripTags',
+    description: 'Strips the string of any HTML tags.',
+    sugar_notes: 'String#stripTags exists in Sugar and will additionally strip tags like <xsl:template>.',
+    sugar_compatibility: 3,
+    live_notes: 'String#stripTags found namespaced tags such as <xsl:template>. Be aware that Sugar will strip these tags too!',
+    conflict: function() {
+      return arguments.length == 0 && /<.*?:.*?>/.test(this);
+    },
+    ref: 'String/stripTags'
+  },
+  {
+    name: 'sub',
+    description: 'Returns a string with the first occurrence of the regex pattern replaced.',
+    sugar_compatibility: 0,
+    sugar_notes: 'String#sub does not exist in Sugar. Standard Javascript .replace is the closest approximation. If you need to replace more than one occurrence of a pattern (but not all), your best bet is to set a counter and test against it.',
+    original_code: "'one two three four'.sub(' ', ', ', 2)",
+    sugar_code: "var c = 0; 'one two three four'.replace(/\s/g, function(m, i){ c++; return c < 3 ? ', ' : ' '; })"
+  },
+  {
+    name: 'succ',
+    description: 'Returns the next character in the Unicode table.',
+    sugar_compatibility: 3,
+    sugar_notes: 'String#succ exists in Sugar as String#shift, which can move up or down the Unicode range by a number which is the first argument passed.',
+    original_code: "'a'.succ()",
+    sugar_code: "'a'.shift(1);",
+    ref: 'String/shift'
+  },
+  {
+    name: 'times',
+    description: 'Concatenates the string n times, where n is the first argument.',
+    sugar_compatibility: 2,
+    sugar_notes: 'String#times exists in Sugar as String#repeat.',
+    original_code: "'echo '.times(3)",
+    sugar_code: "'echo '.repeat(3);",
+    ref: 'String/repeat'
+  },
+  {
+    name: 'toArray',
+    description: 'Returns the string as an array of characters.',
+    sugar_compatibility: 3,
+    sugar_notes: 'String#toArray exists in Sugar as String#chars, which can also run a function against each character.',
+    original_code: "'howdy'.toArray()",
+    sugar_code: "'howdy'.chars();",
+    ref: 'String/chars'
+  },
+  {
+    name: 'toQueryParams',
+    description: 'Parses a URI-like query string and returns an object of key/value pairs.',
+    sugar_compatibility: 3,
+    sugar_notes: 'String#toQueryParams exists in Sugar but from an inverted perspective as Object.fromQueryString. Note that by default this will also parse out nested params with the non-standard "[]" syntax, however this can be turned off.',
+    original_code: "'section=blog&id=45'.toQueryParams()",
+    sugar_code: "Object.fromQueryString('section=blog&id=45')",
+    ref: 'Object/fromQueryString'
+  },
+  {
+    name: 'truncate',
+    description: 'Truncates a string to the given length and adds a suffix to it.',
+    sugar_compatibility: 3,
+    sugar_notes: 'String#truncate exists in Sugar and additionally allows truncating from the left or middle. Also, String#truncateOnWord will do the same as truncate but not split words.',
+    original_code: "longString.truncate(10)",
+    sugar_code: "longString.truncate(10)",
+    ref: 'String/truncate'
+  },
+  {
+    name: 'underscore',
+    description: 'Converts a camelized string into words separated by an underscore.',
+    sugar_compatibility: 3,
+    sugar_notes: 'String#underscore exists in Sugar and will additionally remove all white space.',
+    conflict: function() {
+      return /\s/.test(this);
+    },
+    live_notes: 'String#underscore found white space! Note that underscore in Sugar will remove all whitespace.',
+    ref: 'String/underscore'
+  },
+  {
+    name: 'unfilterJSON',
+    description: 'Strips comment delimiters around Ajax JSON or Javascript responses.',
+    sugar_compatibility: 0,
+    sugar_notes: 'String#unfilterJSON does not exist in Sugar.'
+  },
+  {
+    name: 'capitalize',
+    description: 'Capitalizes the first letter of the string and downcases the others.',
+    sugar_compatibility: 3,
+    sugar_notes: 'String#capitalize exists in Sugar. Passing true as the first argument will additionally capitalize all words.',
+    conflict: false,
+    ref: 'String/capitalize'
+  },
+  {
+    name: 'startsWith',
+    description: 'Checks if the string starts with the passed substring.',
+    sugar_compatibility: 3,
+    sugar_notes: 'String#startsWith exists in Sugar and additionally accepts an argument for case sensitivity (default is true).',
+    conflict: false,
+    ref: 'String/startsWith'
+  },
+  {
+    name: 'endsWith',
+    description: 'Checks if the string ends with the passed substring.',
+    sugar_compatibility: 3,
+    sugar_notes: 'String#endsWith exists in Sugar and additionally accepts an argument for case sensitivity (default is true).',
+    conflict: false,
+    ref: 'String/endsWith'
+  },
+  {
+    name: 'escapeHTML',
+    description: 'Converts HTML special characters to their entity equivalents.',
+    sugar_compatibility: 2,
+    sugar_notes: 'String#escapeHTML exists in Sugar is identical.',
+    conflict: false,
+    ref: 'String/escapeHTML'
+  },
+  {
+    name: 'unescapeHTML',
+    description: 'Converts HTML entities to their normal form.',
+    sugar_compatibility: 2,
+    sugar_notes: 'String#unescapeHTML exists in Sugar is identical.',
+    ref: 'String/unescapeHTML'
+  }
+  ]
+},
+{
+  namespace: 'Hash',
+  type: 'instance',
+  methods: [
+    {
+    name: 'each',
+    description: 'Iterates over each key/value entry in the hash.',
+    sugar_notes: 'Extended objects in Sugar function like hashes and have an identical each method.',
+    sugar_compatibility: 2,
+    conflict: function() {
+      var type = typeof arguments[1];
+      return [arguments.length > 1 && type != 'number', arguments[1]];
+    },
+    live_notes: 'Second argument to .each found. If you need to bind context, use Function#bind instead.',
+    original_code: "new Hash().each(function(){}, 'context')",
+    sugar_code: "Object.extended().each(function(){}.bind('context'))",
+    ref: 'Object/each'
+  },
+  {
+    name: 'get',
+    description: 'Retrieves the hash value for the given key.',
+    sugar_notes: 'Sugar extended objects do not have a "get" method. Simply access the property as you would a normal object literal.',
+    sugar_compatibility: 0,
+    original_code: "var h = new Hash({ foo: 'bar' }); h.get('foo')",
+    sugar_code: "var h = Object.extended({ foo: 'bar' }); h['foo']",
+    ref: 'Object/extended'
+  },
+  {
+    name: 'index',
+    description: 'Returns the first key in the hash whose value matches the passed value.',
+    sugar_compatibility: 0,
+    sugar_notes: 'Sugar extended objects do not have an "index" method. Object.keys can provide help to get this.',
+    original_code: "var key = new Hash({ foo: 'bar' }).index('bar')",
+    sugar_code: "var key; Object.extended({ foo: 'bar' }).keys(function(k){ if(this[k] == 'bar') key = k; })"
+  },
+  {
+    name: 'inspect',
+    description: 'Returns a debug-oriented string representation of the hash.',
+    sugar_compatibility: 0,
+    sugar_notes: 'Sugar extended objects do not have an "inspect" method. Consider using JSON.stringify() instead. The JSON global does not exist in all implementations but should be enough to get you through a debug session.',
+    original_code: "new Hash({ foo: 'bar' }).inspect()",
+    sugar_code: "JSON.stringify(Object.extended({ foo: 'bar' }))"
+  },
+  {
+    name: 'merge',
+    description: "Returns a new hash with the passed object's key/value pairs merged in.",
+    sugar_compatibility: 3,
+    sugar_notes: 'Sugar extended objects have a "merge" method, and have additional functionality such as specifying deep/shallow merges and resolution of conflicts. Unlike prototype they will modify the object. If you need to create a clone, use the "clone" method first.',
+    original_code: "new Hash({ foo: 'bar' }).merge({ moo: 'car' })",
+    sugar_code: "Object.extended({ foo: 'bar' }).clone().merge({ moo: 'car' })",
+    ref: 'Object/merge'
+  },
+  {
+    name: 'set',
+    description: 'Sets a value in the hash for the given key.',
+    sugar_compatibility: 0,
+    sugar_notes: 'Sugar extended objects do not have a "set" method. Simply set the property as you would a normal object literal.',
+    original_code: "var h = new Hash({ foo: 'bar' }); h.set('moo', 'car')",
+    sugar_code: "var h = Object.extended({ foo: 'bar' }); h['moo'] = 'car'",
+    ref: 'Object/extended'
+  },
+  {
+    name: 'toJSON',
+    description: 'Returns a JSON representation of the hash.',
+    sugar_compatibility: 0,
+    sugar_notes: 'Sugar extended objects do not have a "toJSON" method.  JSON.stringify may work as an alternative, but it is not available in all browsers. If you absolutely require JSON support, consider adding in a separate library such as: https://github.com/douglascrockford/JSON-js',
+    original_code: "Object.toJSON(obj)",
+    sugar_code: "JSON.stringify(obj)"
+  },
+  {
+    name: 'toObject',
+    description: 'Returns a cloned, vanilla object with the same properties as the hash.',
+    sugar_compatibility: 0,
+    sugar_notes: 'Sugar extended objects do not have a "toObject" method, as they already behave like vanilla objects.',
+    ref: 'Object/extended'
+  },
+  {
+    name: 'toTemplateReplacements',
+    description: 'Returns a vanilla object, alias of Hash#toObject.',
+    sugar_compatibility: 0,
+    sugar_notes: 'Sugar extended objects do not have a "toTemplateReplacements" method. This method is not necessary as extended objects already behave like vanilla objects.',
+    ref: 'Object/extended'
+  },
+  {
+    name: 'unset',
+    description: 'Deletes the property in the hash for the given key.',
+    sugar_compatibility: 0,
+    sugar_notes: 'Sugar extended objects do not have an "unset" method. Simply delete the property as you would a normal object literal.',
+    original_code: "var h = new Hash({ foo: 'bar' }); h.unset('foo')",
+    sugar_code: "var h = Object.extended({ foo: 'bar' }); delete h.foo",
+    ref: 'Object/extended'
+  },
+  {
+    name: 'update',
+    description: 'Updates the hash in place by merging in the passed object.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Hash#update exists in Sugar as "merge" on extended objects.',
+    original_code: "new Hash({ foo: 'bar' }).merge({ moo: 'car' })",
+    sugar_code: "Object.extended({ foo: 'bar' }).merge({ moo: 'car' })",
+    ref: 'Object/merge'
+  },
+  {
+    name: 'clone',
+    description: 'Returns a clone of the hash.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Hash#clone exists on Sugar extended objects, and is identical.',
+    original_code: "new Hash({ foo: 'bar' }).clone()",
+    sugar_code: "Object.extended({ foo: 'bar' }).clone()",
+    conflict: false,
+    ref: 'Object/clone'
+  },
+  {
+    name: 'keys',
+    description: 'Returns an array of all keys defined on the hash.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Hash#keys exists on Sugar extended objects, and is identical.',
+    original_code: "new Hash({ foo: 'bar' }).keys()",
+    sugar_code: "Object.extended({ foo: 'bar' }).keys()",
+    conflict: false,
+    ref: 'Object/keys'
+  },
+  {
+    name: 'values',
+    description: 'Returns an array of all values in the hash.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Hash#values exists on Sugar extended objects, and is identical.',
+    original_code: "new Hash({ foo: 'bar' }).values()",
+    sugar_code: "Object.extended({ foo: 'bar' }).values()",
+    conflict: false,
+    ref: 'Object/values'
+  },
+  {
+    name: 'toQueryString',
+    description: 'Returns a URL-encoded string representing the contents of the hash.',
+    sugar_compatibility: 0,
+    sugar_notes: 'Hash#toQueryString exists in Sugar as Object.toQueryString.',
+    original_code: "hash.toQueryString()",
+    sugar_code: "Object.toQueryString(obj);"
+  }
+  ]
+},
+{
+  namespace: 'Object',
+  type: 'class',
+  methods: [
+    {
+    name: 'clone',
+    description: 'Returns a shallow copy of the object.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Object.clone exists in Sugar and additionally can create deep clones as well.',
+    conflict: false,
+    ref: 'Object/clone'
+  },
+  {
+    name: 'extend',
+    description: 'Copies all properties from the source to the destination object.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Object.extend exists in Sugar as Object.merge. It can optionally do deep merges as well as intelligent conflict resolution.',
+    original_code: "Object.extend({ a: 1 }, { b: 2 })",
+    sugar_code: "Object.merge({ a: 1 }, { b: 2 })",
+    ref: 'Object/merge'
+  },
+  {
+    name: 'inspect',
+    description: 'Returns a debug-oriented representation of the object.',
+    sugar_compatibility: 0,
+    sugar_notes: 'Object.inspect does not exist in Sugar. Consider using JSON.stringify(object) instead. The JSON global does not exist in all implementations but should be enough to get you through a debug session.',
+    original_code: "Object.inspect([1,2,3])",
+    sugar_code: "JSON.stringify([1,2,3])"
+  },
+  {
+    name: 'isHash',
+    description: 'Returns true if the object is a hash.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Object.isHash does not exist in Sugar. Use Object.isObject instead.',
+    original_code: "Object.isHash({ a: 1 })",
+    sugar_code: "Object.isObject({ a: 1 })",
+    ref: 'Object/isObject'
+  },
+  {
+    name: 'isUndefined',
+    description: 'Returns true if the object is undefined.',
+    sugar_compatibility: 0,
+    sugar_notes: 'Object.isUndefined does not exist in Sugar. Use straight Javascript instead.',
+    original_code: "Object.isUndefined(obj)",
+    sugar_code: "obj === undefined"
+  },
+  {
+    name: 'toJSON',
+    description: 'Returns a JSON representation of the object.',
+    sugar_compatibility: 0,
+    sugar_notes: 'Object.toJSON does not exist in Sugar. JSON.stringify may work as an alternative, but it is not available in all browsers. If you absolutely require JSON support, consider adding in a separate library such as: https://github.com/douglascrockford/JSON-js',
+    original_code: "Object.toJSON(obj)",
+    sugar_code: "JSON.stringify(obj)"
+  },
+  {
+    name: 'isArray',
+    description: 'Returns true if the object is an array.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Object.isArray and is an alias of browser native Array.isArray, which is shimmed if it does not exist.',
+    conflict: false,
+    ref: 'Object/isArray'
+  },
+  {
+    name: 'isDate',
+    description: 'Returns true if the object is a date.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Object.isDate exists in Sugar and is identical.',
+    conflict: false,
+    ref: 'Object/isDate'
+  },
+  {
+    name: 'isFunction',
+    description: 'Returns true if the object is a function.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Object.isFunction exists in Sugar and is identical.',
+    conflict: false,
+    ref: 'Object/isFunction'
+  },
+  {
+    name: 'isNumber',
+    description: 'Returns true if the object is a number.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Object.isNumber exists in Sugar and is identical.',
+    conflict: false,
+    ref: 'Object/isNumber'
+  },
+  {
+    name: 'isString',
+    description: 'Returns true if the object is a string.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Object.isString exists in Sugar and is identical.',
+    conflict: false,
+    ref: 'Object/isString'
+  },
+  {
+    name: 'keys',
+    description: 'Returns an array of the keys in the object.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Object.keys is a browser native method, which Sugar provides a shim for if it does not exist.',
+    conflict: false,
+    ref: 'Object/keys'
+  },
+  {
+    name: 'values',
+    description: 'Returns an array of the values in the object.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Object.values exists in Sugar and is identical.',
+    conflict: false,
+    ref: 'Object/values'
+  },
+  {
+    name: 'isElement',
+    description: 'Returns true if the object is a DOM element.',
+    sugar_compatibility: 0,
+    sugar_notes: "Sugar, which has no direct association with the DOM, does not provide this method. However, this functionality can be easily replicated (taken from Prototype's own implementation).",
+    original_code: 'Object.isElement(obj)',
+    sugar_code: 'Object.extend({ isElement: function(obj){ return !!(obj && obj.nodeType == 1); }}, false, false);'
+  },
+  {
+    name: 'toHTML',
+    description: 'Returns an HTML representation of the object.',
+    sugar_compatibility: 0,
+    sugar_notes: "Object.toHTML does not exist in Sugar. You'll have to define this one yourself!"
+  },
+  {
+    name: 'toQueryString',
+    description: 'Returns a URL-encoded string representing the contents of the object.',
+    sugar_compatibility: 0,
+    sugar_notes: 'Object.toQueryString exists in Sugar and additionally allows a parameter to namespace deep params.',
+    original_code: "Object.toQueryString(obj)",
+    sugar_code: "Object.toQueryString(obj);"
+  }
+  ]
+},
+{
+  namespace: 'Array',
+  type: 'class',
+  methods: [
+    {
+    name: 'from',
+    description: 'Creates an array from an array-like collection.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Array.from exists in Sugar as Array.create. Be aware, however, that when a Javascript primitive is passed, it will simply be added to the array. If, for example, you need a string to be split into the array, then use the standard String#split instead. The most common use of this method, converting an arguments object into an actual array, will work, however.',
+    original_code: "Array.from(arguments)",
+    sugar_code: "Array.create(arguments)",
+    ref: 'Array.create'
+  }
+  ]
+},
+{
+  namespace: 'Array',
+  type: 'instance',
+  methods: [
+    {
+    name: 'collect',
+    description: 'Returns the result of applying an iterator to the array.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Enumerable#collect exists Sugar as Array#map, and can additionally pass a shortcut string for a function that returns a property of the same name.',
+    original_code: "[1,2,3].collect(function(n){ return n * 2; })",
+    sugar_code: "[1,2,3].map(function(n){ return n * 2; })",
+    ref: 'Array/map'
+  },
+  {
+    name: 'detect',
+    description: 'Returns the first element for which the iterator returns a truthy value.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Enumerable#detect exists in Sugar as Array#find, and is identical.',
+    original_code: "[1,2,3].detect(function(n){ return n > 1; })",
+    sugar_code: "[1,2,3].find(function(n){ return n > 1; })",
+    ref: 'Array/find'
+  },
+  {
+    name: 'each',
+    description: 'Iterates over the key/value pairs in the hash.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Array#each exists in Sugar and can additionally pass a starting index and loop from the beginning of the array. If context needs to be bound, use Function#bind instead.',
+    conflict: function() {
+      var type = typeof arguments[1];
+      return [arguments.length > 1 && type != 'number', arguments[1]];
+    },
+    live_notes: 'Second argument to .each should be a number but instead was {1}. If you need to bind context, use Function#bind instead.',
+    original_code: "['a','b','c'].each(function(){}, 'context')",
+    sugar_code: "['a','b','c'].each(function(){}.bind('context'))",
+    ref: 'Array/each'
+  },
+  {
+    name: 'eachSlice',
+    description: 'Groups array elements into chunks of a given size and iterates over them.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Enumerable#eachSlice exists in Sugar as Array#inGroupsOf instead.',
+    original_code: "[1,2,3,4].eachSlice(2, function(){})",
+    sugar_code: "[1,2,3,4].inGroupsOf(2).each(function(){})",
+    ref: 'Array/inGroupsOf'
+  },
+  {
+    name: 'entries',
+    description: 'alias of enumerable#toarray.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Enumerable#entries is not necessary in Sugar, but its behavior of effectively cloning the array can be achieved with Array#clone.',
+    original_code: "[1,2,3].entries()",
+    sugar_code: "[1,2,3].clone()",
+    ref: 'Array/clone'
+  },
+  {
+    name: 'find',
+    description: 'Returns the first element for which the iterator returns a truthy value.',
+    sugar_notes: 'Array#find also exists in Sugar and is identical.',
+    sugar_compatibility: 3,
+    conflict: function() {
+      var type = typeof arguments[1];
+      return [arguments.length > 1 && type != 'number', arguments[1]];
+    },
+    live_notes: 'Second argument to Array#find should be a number but instead was {1}. If you need to bind context, use Function#bind instead.',
+    original_code: "['a','b','c'].find(function(){}, 'context')",
+    sugar_code: "['a','b','c'].find(function(){}.bind('context'))",
+    ref: 'Function/bind'
+  },
+  {
+    name: 'findAll',
+    description: 'Returns all elements for which the iterator returns a truthy value.',
+    sugar_notes: 'Array#findAll also exists in Sugar. Some semantic differences exist including the ability to pass a starting index in the place of in-line context binding.',
+    sugar_compatibility: 3,
+    conflict: function() {
+      var type = typeof arguments[1];
+      return [arguments.length > 1 && type != 'number', arguments[1]];
+    },
+    live_notes: 'Second argument to Array#findAll should be a number but instead was {1}. If you need to bind context, use Function#bind instead.',
+    original_code: "['a','b','c'].findAll(function(){}, 'context')",
+    sugar_code: "['a','b','c'].findAll(function(){}.bind('context'))",
+    ref: 'Function/bind'
+  },
+  {
+    name: 'grep',
+    description: 'Returns all elements for which the passed regex matches.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Enumerable#grep exists in Sugar as Array#findAll with slightly different semantics.',
+    original_code: "['a','b','c'].grep(/[ab]/)",
+    sugar_code: "['a','b','c'].findAll(/[ab]/)",
+    ref: 'Array/findAll'
+  },
+  {
+    name: 'include',
+    description: 'Returns true if the array contains the given element.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Enumerable#include exists in Javascript as native Array#some, or the Sugar alias Array#any. Array#include in Sugar instead the passed argument to the array without modifying it. Array#include is a reciprocal of Array#exclude, and a non-destructive version of Array#add.',
+    conflict: function(f) {
+      return typeof f !== 'object' && arguments.length == 1;
+    },
+    original_code: "[1,2,3].include(1)",
+    sugar_code: "[1,2,3].any(1)",
+    ref: 'Array/has'
+  },
+  {
+    name: 'inject',
+    description: 'Incrementally builds a return value by successively iterating over the array.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Enumerable#inject in Javascript as native Array#reduce. Sugar provides a shim for this method when it does not exist.',
+    original_code: '[1,2,3,4].inject(100, function(a, b){ return a + b; });',
+    sugar_code: '[1,2,3,4].reduce(function(a, b){ return a + b; }, 100);'
+  },
+  {
+    name: 'invoke',
+    description: 'Invokes the same method for each element in the array.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Sugar allows a string shortcut to be passed to Array#map, which achieves the same effect as Array#invoke.',
+    original_code: "['hello','world'].invoke('toUpperCase')",
+    sugar_code: "['hello','world'].map('toUpperCase')",
+    ref: 'Array/map'
+  },
+  {
+    name: 'max',
+    description: 'Returns the element of the array with the highest value.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Array#max exists in Sugar and additionally has the ability to return all the maximum values, as more than one may exist. Sugar also returns the actual array element instead of the return value of the iterator. Sugar also does not allow a context parameter, use Function#bind instead.',
+    live_notes: 'Use caution when using Enumerable#max:  (1) Sugar will return an array of maximum values (as there can be more than one), where Prototype only returns the first value. (2) When using iterators, Prototype will return the value compared, where Sugar will return the actual array element itself. (3) Finally, Sugar does not allow a context to be passed. Use Function#bind instead to bind context.',
+    original_code: "[{ a: 5 },{ a: 10 }].max(function(el){ return el['a']; }, 'context')",
+    sugar_code: "[{ a: 5 },{ a: 10 }].max(function(el){ return el['a']; }.bind('context')).first().a",
+    ref: 'Array/max'
+  },
+  {
+    name: 'member',
+    description: 'Returns true if the array contains the given element. Alias of Enumerable#include.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Enumerable#member exists in Javascript as Array#some or the Sugar alias Array#any.',
+    original_code: "[1,2,3].member(1)",
+    sugar_code: "[1,2,3].any(1)",
+    ref: 'Array/has'
+  },
+  {
+    name: 'min',
+    description: 'Returns the element of the array with the lowest value.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Array#min exists in Sugar and additionally has the ability to return all the minimum values, as more than one may exist. Sugar also returns the actual array element instead of the return value of the iterator. Sugar also does not allow a context parameter, use Function#bind instead.',
+    live_notes: 'Use caution when using Enumerable#min:  (1) Sugar will return an array of minimum values (as there can be more than one), where Prototype only returns the first value. (2) When using iterators, Prototype will return the value compared, where Sugar will return the actual array element itself. (3) Finally, Sugar does not allow a context to be passed.',
+    original_code: "[{ a: 5 },{ a: 10 }].min(function(el){ return el['a']; }, 'context')",
+    sugar_code: "[{ a: 5 },{ a: 10 }].min(function(el){ return el['a']; }.bind('context')).first().a",
+    ref: 'Array/min'
+  },
+  {
+    name: 'partition',
+    description: 'Partitions the array into two groups, one for which the iterator passed returns true, and one for which the iterator returns false.',
+    sugar_compatibility: 3,
+    sugar_notes: "Enumerable#partition does not exist in Sugar. Array#groupBy however has similar functionality, and may be a suitable alternative. It will create a hash with keys based on the return values of the iterator, with each grouping as the value. Instead of accessing the split array, you can access the hash by these keys. This method has the added advantage that it can also split into more than two groups.",
+    original_code: "[1,2,3,4,5,6].partition(function(n){ return n % 2 === 0; })",
+    sugar_code: "[1,2,3,4,5,6].group(function(n){ return n % 2 === 0 ? 'even' : 'odd'; })",
+    ref: 'Array/groupBy'
+  },
+  {
+    name: 'pluck',
+    description: 'Returns a mapped array of the same property of each element in the array.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Sugar allows a string shortcut to Array#map, making it effectively identical to Enumerable#pluck.',
+    original_code: "['hello','world'].pluck('length')",
+    sugar_code: "['hello','world'].map('length')",
+    ref: 'Array/map'
+  },
+  {
+    name: 'reject',
+    description: 'Returns all elements for which the iterator returns a falsy value.',
+    sugar_compatibility: 3,
+    sugar_notes: "Enumerable#reject does not exist in Sugar. Its equivalent is Array#exclude. This is a non-destructive way to remove elements from an array. If you want a destructive version, use Array#remove instead. Also note these methods' reciprocals: Array#include and Array#add.",
+    original_code: "[1,2,3].reject(function(n){ n < 3; })",
+    sugar_code: "[1,2,3].exclude(function(n){ n < 3; })",
+    ref: 'Array/exclude'
+  },
+  {
+    name: 'select',
+    description: 'Returns all elements for which the iterator returns a truthy value. Alias of Enumerable#findAll.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Enumerable#select exists in Sugar as Array#findAll.',
+    original_code: "[1,2,3].select(function(n){ n < 3; })",
+    sugar_code: "[1,2,3].findAll(function(n){ n < 3; })",
+    ref: 'Array/findAll'
+  },
+  {
+    name: 'sortBy',
+    description: 'Returns an array sorted on the value returned by the iterator.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Array#sortBy in Sugar additionally allows a flag for descending order and also has a mechanism for intelligent alphanumeric sorting of string properties. For binding of context, use Function#bind instead.',
+    conflict: function(f, scope) {
+      var type = typeof arguments[1];
+      return [arguments.length > 1 && type != 'boolean', arguments[1]];
+    },
+    live_notes: 'Second argument to .sortBy should be a boolean but instead was {1}. If you need to bind context, use Function#bind instead.',
+    original_code: "[{ a: 5 },{ a: 10 }].sortBy(function(el){ return el['a']; })",
+    sugar_code: "[{ a: 5 },{ a: 10 }].sortBy(function(el){ return el['a']; }.bind('context'))",
+    ref: 'Function/bind'
+  },
+  {
+    name: 'size',
+    description: 'Returns the length of the array.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Enumerable#size does not exist in Sugar. Just use array.length!',
+    original_code: "[1,2,3].size()",
+    sugar_code: "[1,2,3].length"
+  },
+  {
+    name: 'toArray',
+    description: 'Returns a clone of the array.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Enumerable#toArray does not exist in Sugar. Use Array#clone instead.',
+    original_code: "[1,2,3].toArray()",
+    sugar_code: "[1,2,3].clone()",
+    ref: 'Array/clone'
+  },
+  {
+    name: 'zip',
+    description: '"zips" together multiple arrays into a single multi-dimensional array.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Enumerable#zip exists in Sugar and is identical.',
+    original_code: "firstNames.zip(lastNames)",
+    sugar_code: "firstNames.zip(lastNames)",
+    ref: 'Array/zip'
+  },
+  {
+    name: 'compact',
+    description: 'Returns a copy of the array with all undefined and null values removed.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Array#compact exists and is nearly identical except that it will also remove any values which are NaN from the array as well. It additionally has a flag to remove all falsy values.',
+    conflict: function() {
+      for(var i = 0; i < this.length; i++){
+        if(isNaN(this[i])) return true;
+      }
+      return false;
+    },
+    live_notes: 'Caution: Array#compact was called on an array that contains NaN values. Sugar will remove these from the array while Prototype leaves them alone.',
+    ref: 'Array/compact'
+  },
+  {
+    name: 'clear',
+    description: 'Removes all elements from the array.',
+    sugar_compatibility: 0,
+    sugar_notes: 'Array#clear does not exist in Sugar. Use array.length = 0 or simply set array = [] instead.',
+    original_code: "f.clear()",
+    sugar_code: "f = []"
+  },
+  {
+    name: 'inspect',
+    description: 'Returns a debug-oriented string representing the array.',
+    sugar_compatibility: 0,
+    sugar_notes: 'Array#inspect does not exist in Sugar. Consider using JSON.stringify(array) instead. The JSON global does not exist in all implementations but should be enough to get you through a debug session.',
+    original_code: "[1,2,3].inspect()",
+    sugar_code: "JSON.stringify([1,2,3])"
+  },
+  {
+    name: 'reverse',
+    description: 'Reverses the arrays contents.',
+    sugar_compatibility: 2,
+    conflict: function(inline) {
+      return inline === false;
+    },
+    sugar_notes: 'Array#reverse exists in native Javascript, but is destructive. For a non-destructive version use Array#clone first.',
+    original_code: "array.reverse(false)",
+    sugar_code: "array.clone().reverse()",
+    ref: 'Array.clone'
+  },
+  {
+    name: 'uniq',
+    description: 'Returns a new array without duplicates.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Array#uniq exists in Sugar as Array#unique and can also operate on arrays of objects. Additionally it accepts a mapping function to indicate the field to uniquify on.',
+    original_code: "[1,1,1].uniq()",
+    sugar_code: "[1,1,1].unique()",
+    ref: 'Array/unique'
+  },
+  {
+    name: 'without',
+    description: 'Creates a new array that does not contain the specified values.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Array#without exists in Sugar as Array#exclude.',
+    original_code: "[1,2,3].without(3)",
+    sugar_code: "[1,2,3].exclude(3)",
+    ref: 'Array/exclude'
+  },
+  {
+    name: 'indexOf',
+    description: 'Returns the index of the first occurence of the item in the array.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Array#indexOf exists natively in modern browsing engines. Sugar provides this method when it is not supported.',
+    conflict: false,
+    ref: 'Array/indexOf'
+  },
+  {
+    name: 'lastIndexOf',
+    description: 'Returns the index of the last occurence of the item in the array.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Array#lastIndexOf exists natively in modern browsing engines. Sugar provides this method when it is not supported.',
+    conflict: false,
+    ref: 'Array/lastIndexOf'
+  },
+  {
+    name: 'all',
+    description: 'Returns true if the passed iterator returns a truthy value for all elements in the array.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Array#all exists in Sugar as an alias to native Array#every (for which it adds a shim for browsers without support), and is identical.',
+    conflict: false,
+    ref: 'Array/all'
+  },
+  {
+    name: 'any',
+    description: 'Returns true if the passed iterator returns a truthy value for any elements in the array.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Array#any exists in Sugar as an alias to native Array#some (for which it adds a shim for browsers without support).',
+    conflict: false,
+    ref: 'Array/any'
+  },
+  {
+    name: 'map',
+    description: 'Returns the result of applying an iterator to the array. Alias of Enumerable#collect.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Array#map exists natively in modern browsing engines. Sugar provides this method when it is not supported, and additionally augments it to handle string shortcuts.',
+    conflict: false,
+    ref: 'Array/map'
+  },
+  {
+    name: 'every',
+    description: 'Returns true if the passed iterator returns a truthy value for all elements in the array.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Array#every exists natively in modern browsing engines. Sugar provides this method when it is not supported, and additionally augments it to handle strings, numbers, regexes, and deep objects.',
+    conflict: false,
+    ref: 'Array/every'
+  },
+  {
+    name: 'filter',
+    description: 'Returns all elements for which the iterator returns a truthy value.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Array#filter exists natively in modern browsing engines. Sugar provides this method when it is not supported, and additionally augments it to handle strings, numbers, regexes, and deep objects.',
+    conflict: false,
+    ref: 'Array/filter'
+  },
+  {
+    name: 'some',
+    description: 'Returns true if the passed iterator returns a truthy value for any elements in the array.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Array#some exists natively in modern browsing engines. Sugar provides this method when it is not supported, and additionally augments it to handle strings, numbers, regexes, and deep objects.',
+    conflict: false,
+    ref: 'Array/some'
+  },
+  {
+    name: 'inGroupsOf',
+    description: 'Splits the array into groups of n elements, where n is the number passed.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Array#inGroupsOf exists in Sugar and is identical.',
+    conflict: false,
+    ref: 'Array/inGroupsOf'
+  },
+  {
+    name: 'clone',
+    description: 'Returns a copy of the array.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Array#clone exists in Sugar and is identical.',
+    conflict: false,
+    ref: 'Array/clone'
+  },
+  {
+    name: 'first',
+    description: 'Returns the first element in the array.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Array#first exists in Sugar, and can additionally get the first n elements.',
+    conflict: false,
+    ref: 'Array/first'
+  },
+  {
+    name: 'last',
+    description: 'Returns the last element in the array.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Array#last exists in Sugar, and can additionally get the last n elements.',
+    conflict: false,
+    ref: 'Array/last'
+  },
+  {
+    name: 'flatten',
+    description: 'Returns a one-dimensional copy of the array.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Array#flatten exists in Sugar, and can additionally flatten an array to any level.',
+    conflict: false,
+    ref: 'Array/flatten'
+  },
+  {
+    name: 'intersect',
+    description: 'Returns an array containing every item that that is shared between the passed arrays.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Array#intersect exists in Sugar, and can additionally get the intersection of any number of arrays passed in.',
+    conflict: false,
+    ref: 'Array/intersect'
+  }
+  ]
+},
+{
+  namespace: 'Function',
+  type: 'instance',
+  methods: [
+    {
+    name: 'bind',
+    description: 'Binds the function to the given context.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Function#bind exists natively in modern browsing engines. Sugar provides this method when it is not supported.',
+    conflict: false,
+    ref: 'Function/bind'
+  },
+  {
+    name: 'argumentNames',
+    description: 'Returns an array of the argument names as stated in the function definition.',
+    sugar_compatibility: 0,
+    sugar_notes: 'Function#argumentNames does not exist in Sugar.'
+
+  },
+  {
+    name: 'bindAsEventListener',
+    description: 'An event specific version Function#bind. Will allow an event object to be passed as the first argument to the bound function.',
+    sugar_compatibility: 0,
+    sugar_notes: 'Function#bindAsEventListener does not exist in Sugar, but can be easily approximated with an undefined space for the event in the arguments using Function#fill. Keep in mind, however, that this is only necessary if you are also currying arguments as well. If you are just trying to bind context, then Function#bind alone is enough.',
+    original_code: "(function(event, one) { this == \"bound\", one == 1; }).bindAsEventListener('bound', 1) ",
+    sugar_code: "(function(event, one) { this == \"bound\", one == 1; }).fill('bound', undefined, 1)",
+    ref: 'Function/bind'
+  },
+  {
+    name: 'curry',
+    description: 'Burns-in arguments to a function and returns a new function.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Function#curry exists in Sugar as Function#fill. When passing undefined to Function#fill it will additionally serve as a placeholder where arguments to the original function will be allowed in.',
+    original_code: "fn.curry('one','two')",
+    sugar_code: "fn.fill('one', 'two')",
+    ref: 'Function/fill'
+  },
+  {
+    name: 'defer',
+    description: 'Schedules the function to run as soon as the interpreter is idle.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Function#defer exists in Sugar as Function#delay. When no params are passed it will behave precisely the same as calling the function with a timeout of 1 ms (as with defer).',
+    original_code: "fn.defer()",
+    sugar_code: "fn.delay()",
+    ref: 'Function/delay'
+  },
+  {
+    name: 'delay',
+    description: 'Schedules the function to run after a specified amount of time.',
+    sugar_compatibility: 1,
+    sugar_notes: 'Function#delay exists in Sugar, but is slightly different. First, the delay is passed in milliseconds, not seconds. Second, delay will return a reference to the function instead of an integer to clear the timeout. If you need to cancel the timeout, instead use Function#cancel. Arguments passed after the timeout are still curried like Prototype.',
+    original_code: "var t = fn.delay(2) clearTimeout(t) ",
+    sugar_code: "fn.delay(2000) fn.cancel()",
+    ref: 'Function/delay'
+  },
+  {
+    name: 'methodize',
+    description: 'Wraps the function inside another function that pushes the object it is called on as the first argument.',
+    sugar_compatibility: 0,
+    sugar_notes: 'Function#methodize does not exist in Sugar. No direct equivalent exists, but in a pinch the following code will achieve the same effect.',
+    original_code: "obj.method = fn.methodize()",
+    sugar_code: "obj.method = function(){ fn.apply(null, [this].concat(Array.prototype.slice.call(arguments))); }"
+  },
+  {
+    name: 'wrap',
+    description: 'Returns a function wrapped around the original function.',
+    sugar_compatibility: 0,
+    sugar_notes: 'Function#wrap does not exist in Sugar. No direct equivalent exists, but Function#bind can be used to achieve the same effect in a pinch.',
+    original_code: "fn = fn.wrap(function(original){ return original() + 3; })",
+    sugar_code: "fn = (function(original){ return original() + 3; }).bind(null, fn);"
+  }
+  ]
+},
+{
+  namespace: 'Date',
+  type: 'instance',
+  methods: [
+    {
+    name: 'toISOString',
+    description: 'Returns an ISO8601 representation of the date.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Date#toISOString exists natively in modern browsing engines. Sugar provides this method when it is not supported.',
+    conflict: false,
+    ref: 'Date/toISOString'
+  },
+  {
+    name: 'toJSON',
+    description: 'Returns an ISO8601 representation of the date, identical to Date#toISOString.',
+    sugar_compatibility: 2,
+    sugar_notes: 'Date#toJSON exists natively in modern browsing engines. Sugar provides this method when it is not supported.',
+    conflict: false,
+    ref: 'Date/toJSON'
+  }
+  ]
+}
+];
+
+
+//  Compatiblity index:
+//
+//  0 - Does not exist.
+//  1 - Exists but does not support all functionality.
+//  2 - Exists and supports all functionality.
+//  3 - Exists and supports all functionality plus more.
+
+var SugarUnderscoreMethods = [
+  {
+  type: 'class',
+  namespace: '_',
+  methods: [
+    {
+    name: 'each',
+    description: 'Iterates over an enumerable collection.',
+    sugar_compatibility: 3,
+    sugar_notes: '_.each exists natively on arrays in modern browsing engines as Array#forEach. Sugar provides this method when it is not supported. It also provides Array#each, which can break out of the loop, start from a given index, loop from the beginning, and intelligently handle sparse arrays. Sugar also provides Object.each for iterating over objects.',
+    original_code: '_.each([1,2,3], function(element, index, array){})',
+    sugar_code: '[1,2,3].forEach(function(element, index, array){})',
+    ref: 'Array/each'
+  },
+  {
+    name: 'map',
+    description: 'Creates a new array by using the return value of a mapping function on each element.',
+    sugar_compatibility: 3,
+    sugar_notes: '_.map exists natively in modern browsing engines as Array#map. Sugar provides this method when it is not supported, and additionally augments it to allow passing a string as a shortcut.',
+    original_code: '_.map([1,2,3], function(a){ return a * 2; })',
+    sugar_code: '[1,2,3].map(function(a){ return a * 2; })',
+    ref: 'Array/map'
+  },
+  {
+    name: 'reduce',
+    description: 'Boils down a list of values to a single value, optionally with a starting value.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.reduce exists natively in modern browsing engines as Array#reduce. Sugar provides this method when it is not supported.',
+    original_code: '_.reduce([1,2,3], function(m, a){ return m + a; })',
+    sugar_code: '[1,2,3].reduce(function(m, a){ return m + a; })',
+    ref: 'Array/reduce'
+  },
+  {
+    name: 'reduceRight',
+    description: 'Boils down a list of values to a single value starting from the last entry (the right), optionally with a starting value.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.reduceRight exists natively in modern browsing engines as Array#reduceRight. Sugar provides this method when it is not supported.',
+    original_code: '_.reduceRight([1,2,3], function(m, a){ return m + a; })',
+    sugar_code: '[1,2,3].reduceRight(function(m, a){ return m + a; })',
+    ref: 'Array/reduceRight'
+  },
+  {
+    name: 'find',
+    description: 'Finds the first value in the list for which the iterator returns true.',
+    sugar_compatibility: 3,
+    sugar_notes: '_.find exists in Sugar as Array#find, and additionally allows searching on primitives, deep objects, and testing against regexes.',
+    original_code: '_.find([1,2,3], function(a){ return a == 1; })',
+    sugar_code: '[1,2,3].find(1)',
+    bind_context: true,
+    ref: 'Array/find'
+  },
+  {
+    name: 'filter',
+    description: 'Finds all values in the list for which the iterator returns true.',
+    sugar_compatibility: 3,
+    sugar_notes: '_.filter exists natively in modern browsing engines as Array#filter. Sugar provides this method when it is not supported, and additionally augments it to allow search on primitives, deep objects, or against a regex. Sugar also provides Array#findAll which can start from a given index, loop from the beginning, and intelligently handles sparse arrays.',
+    original_code: '_.filter([1,2,3], function(a){ return a % 2 == 0; })',
+    sugar_code: '[1,2,3].reduceRight(function(a){ return a % 2 == 0; })',
+    ref: 'Array/filter'
+  },
+  {
+    name: 'reject',
+    description: 'Returns all elements in the list for which the iterator does not return true. Opposite of filter.',
+    sugar_compatibility: 3,
+    sugar_notes: '_.reject exists in Sugar as Array#exclude. It additionally allows searching on primitives, deep objects, or against a regex. This method is non-destructive with a destructive reciprocal method: Array#remove.',
+    original_code: '_.reject([1,2,3,4,5,6], function(a){ return a % 2 == 0; })',
+    sugar_code: '[1,2,3,4,5,6].exclude(function(a){ return a % 2 == 0; })',
+    ref: 'Array/exclude'
+  },
+  {
+    name: 'all',
+    description: 'Returns true if the iterator returns true for all elements in the list, false otherwise.',
+    sugar_compatibility: 3,
+    sugar_notes: '_.all exists natively in modern browsing engines as Array#every. Sugar provides this method when it is not supported, and addtionally augments it to search on primitives, deep objects, or against a regex. Sugar also has its own alias Array#all.',
+    original_code: '_.all([1,2,3], function(a){ return a == 2; })',
+    sugar_code: '[1,2,3].all(function(a){ return a % 2 == 0; })',
+    ref: 'Array/all'
+  },
+  {
+    name: 'any',
+    description: 'Returns true if the iterator returns true for any elements in the list, false otherwise.',
+    sugar_compatibility: 3,
+    sugar_notes: '_.any exists natively in modern browsing engines as Array#some. Sugar provides this method when it is not supported, and addtionally augments it to search on primitives, deep objects, or against a regex. Sugar also has its own alias Array#any.',
+    original_code: '_.any([1,2,3], function(a){ return a % 2 == 0; })',
+    sugar_code: '[1,2,3].any(function(a){ return a % 2 == 0; })',
+    ref: 'Array/any'
+  },
+  {
+    name: 'include',
+    description: 'Returns true if the value is present in the list.',
+    sugar_compatibility: 3,
+    sugar_notes: '_.include exists in Sugar as browser native Array#some, which it augments to search on primitive types, deep objects, or against regexes. Sugar also has its own alias Array#any, which has identical functionality.',
+    original_code: '_.include([1,2,3], 3)',
+    sugar_code: '[1,2,3].any(3)',
+    ref: 'Array/any'
+  },
+  {
+    name: 'invoke',
+    description: 'Calls the passed method name for each value in the list.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.invoke does not exist in Sugar. In most cases, invoking functions through standard methods is more readable. Array#map effectively provides an alias for this, however.',
+    original_code: "_.invoke([5,1,7],[3,2,1], 'sort')",
+    sugar_code: "[[5,1,7],[3,2,1]].map('sort')",
+    ref: 'Array/map'
+  },
+  {
+    name: 'pluck',
+    description: 'Returns the property for each value in the list.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.pluck exists in Sugar as browser native Array#map, which it augments to accept a string as a shortcut.',
+    original_code: "_.pluck([{name:'moe'},{name:'larry'},{name:'curly'}], 'name')",
+    sugar_code: "[{name:'moe'},{name:'larry'},{name:'curly'}].map('name')",
+    ref: 'Array/map'
+  },
+  {
+    name: 'max',
+    description: 'Returns the maximum value in the list.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.max exists in Sugar as Array#max, and can additionally return an array when more than one max values exist.',
+    original_code: '_.max([1,2,3])',
+    sugar_code: '[1,2,3].max()',
+    ref: 'Array/max'
+  },
+  {
+    name: 'min',
+    description: 'Returns the minimum value in the list.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.min exists in Sugar as Array#min, and can additionally return an array when more than one max values exist.',
+    original_code: '_.min([1,2,3])',
+    sugar_code: '[1,2,3].min()',
+    ref: 'Array/min'
+  },
+  {
+    name: 'sortBy',
+    description: 'Returns a copy of the list sorted by the result of the iterator.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.sortBy exists in Sugar as Array#sortBy. In addition to an iterating function, it will also accept a string as a shortcut to the property to sort by.',
+    original_code: '_.sortBy([1,2,3], Math.sin)',
+    sugar_code: '[1,2,3].sortBy(Math.sin)',
+    ref: 'Array/sortBy'
+  },
+  {
+    name: 'groupBy',
+    description: 'Splits a collection into sets, grouping them by the result of running each value through the iterator.',
+    sugar_compatibility: 3,
+    sugar_notes: '_.groupBy exists in Sugar as Array#groupBy. It allows passing a string as a shortcut to a property and additionally allows an optional callback to be called for each group.',
+    original_code: '_.groupBy([1,2,3,4], function(n){ return n > 2; })',
+    sugar_code: '[1,2,3,4].groupBy(function(n){ return n > 2; })',
+    ref: 'Array/groupBy'
+  },
+  {
+    name: 'sortedIndex',
+    description: 'Determine the index at which the value should be inserted into the list in order to maintain the sorted order.',
+    sugar_compatibility: 0,
+    sugar_notes: '_.sortedIndex does not exist in Sugar. Clever use of Array#reduce can achieve something similar, depending on the case.',
+    original_code: '_.sortedIndex([1,2,3,5], 4)',
+    sugar_code: '[1,2,3,5].reduce(function(a, b, i){ if(b > 4) return i - 1; })',
+    ref: 'Array/reduce'
+  },
+  {
+    name: 'shuffle',
+    description: 'Returns a randomized copy of the list.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.shuffle exists in Sugar as Array#randomize. Sugar also uses a Fisher-Yates algorithm.',
+    original_code: '_.shuffle([1,2,3,4])',
+    sugar_code: '[1,2,3,4].randomize()',
+    ref: 'Array/randomize'
+  },
+  {
+    name: 'toArray',
+    description: 'Converts any enumerable object into an array.',
+    sugar_compatibility: 3,
+    sugar_notes: '_.toArray exists in Sugar as Array.create, which can accept multiple arguments.',
+    original_code: '_.toArray(arguments)',
+    sugar_code: 'Array.create(arguments)',
+    ref: 'Array/create'
+  },
+  {
+    name: 'size',
+    description: 'Returns the number of values in the list.',
+    sugar_compatibility: 0,
+    sugar_notes: "_.size does not exist in Sugar. If you need to know the \"size\" of a hash, you can get the length of Object.keys, although in that case it's likely that you should be using an array in any case.",
+    original_code: '_.size(obj)',
+    sugar_code: 'Object.keys(obj).length',
+    ref: 'Object/keys'
+  },
+  {
+    name: 'first',
+    description: 'Returns the first element(s) of the array.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.first exists in Sugar as Array#first, and is identical.',
+    original_code: '_.first([1,2,3])',
+    sugar_code: '[1,2,3].first()',
+    ref: 'Array/first'
+  },
+  {
+    name: 'initial',
+    description: 'Returns all but the last n entries of the array.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.initial does not exist in Sugar. Use a negative value for Array#to for the same effect.',
+    original_code: '_.initial([1,2,3], 2)',
+    sugar_code: '[1,2,3].to(-2)',
+    ref: 'Array/to'
+  },
+  {
+    name: 'last',
+    description: 'Returns the last n entries of the array.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.last exists in Sugar as Array#last, and is identical.',
+    original_code: '_.last([1,2,3])',
+    sugar_code: '[1,2,3].last()',
+    ref: 'Array/last'
+  },
+  {
+    name: 'rest',
+    description: 'Returns the rest of the entries from a given index.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.rest exists in Sugar as Array#from.',
+    original_code: '_.rest([1,2,3], 2)',
+    sugar_code: '[1,2,3].from(2)',
+    ref: 'Array/from'
+  },
+  {
+    name: 'compact',
+    description: 'Returns a copy of the array with all falsy values removed.',
+    sugar_compatibility: 3,
+    sugar_notes: '_.compact exists in Sugar as Array#compact. Note that only undefined, null, and NaN are removed by default. To remove all falsy values, pass true as the first argument.',
+    original_code: '_.compact([1,0,3,null])',
+    sugar_code: '[1,0,3,null].compact(true)',
+    ref: 'Array/compact'
+  },
+  {
+    name: 'flatten',
+    description: 'Flattens a nested array.',
+    sugar_compatibility: 3,
+    sugar_notes: '_.flatten exists in Sugar as Array#flatten. Sugar can additionally flatten to any level of depth, specified in the first argument (all levels by default).',
+    original_code: '_.flatten([1,[2,3]])',
+    sugar_code: '[1,[2,3]].flatten()',
+    ref: 'Array/flatten'
+  },
+  {
+    name: 'without',
+    description: 'Returns a copy of the array with all instances of the values passed removed.',
+    sugar_compatibility: 3,
+    sugar_notes: '_.without exists in Sugar as Array#exclude, and is identical.',
+    original_code: '_.without([1,2,3], 1)',
+    sugar_code: '[1,2,3].exclude(1)',
+    ref: 'Array/exclude'
+  },
+  {
+    name: 'union',
+    description: 'Computes the union of the arrays, or the unique items present in all arrays.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.union exists in Sugar as Array#union, and is identical.',
+    original_code: '_.union([1,2,3], [3,4,5])',
+    sugar_code: '[1,2,3].union([3,4,5])',
+    ref: 'Array/union'
+  },
+  {
+    name: 'intersection',
+    description: 'Computes the intersection of the arrays, or the values that are common to all arrays.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.intersection exists in Sugar as Array#intersect, and is identical.',
+    original_code: '_.intersect([1,2,3], [3,4,5])',
+    sugar_code: '[1,2,3].intersect([3,4,5])',
+    ref: 'Array/intersect'
+  },
+  {
+    name: 'difference',
+    description: 'Returns the values in the array that are not present in the others.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.difference exists in Sugar as Array#subtract, which can subtract an indefinite number of arrays.',
+    original_code: '_.difference([1,2,3], [3,4,5])',
+    sugar_code: '[1,2,3].subtract([3,4,5])',
+    ref: 'Array/subtract'
+  },
+  {
+    name: 'uniq',
+    description: 'Returns a duplicate-free version of the array.',
+    sugar_compatibility: 3,
+    sugar_notes: '_.uniq exists in Sugar as Array#unique. In addition to accepting a function to transform (map) the property on which to unique, Sugar will also accept a string that is a shortcut to this function. This would most commonly be the unique key of a JSON object, etc.',
+    original_code: '_.uniq([1,2,1,3,1,4])',
+    sugar_code: '[1,2,1,3,1,4].unique()',
+    ref: 'Array/unique'
+  },
+  {
+    name: 'zip',
+    description: 'Merges together multiple arrays, creating a multi-dimensional array as the result.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.zip exists in Sugar as Array#zip and is identical.',
+    original_code: '_.zip(arr1, arr2)',
+    sugar_code: 'arr1.zip(arr2)',
+    ref: 'Array/zip'
+  },
+  {
+    name: 'indexOf',
+    description: 'Returns the index of the first value that matches in the array or -1 if not present.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.indexOf exists natively in modern browsing engines as Array#indexOf. Sugar provides this method when it is not supported. Sugar also provides Array#findIndex for more complex index finding operations.',
+    original_code: '_.indexOf([1,2,3], 1)',
+    sugar_code: '[1,2,3].indexOf(1)',
+    ref: 'Array/indexOf'
+  },
+  {
+    name: 'lastIndexOf',
+    description: 'Returns the index of the last value that matches in the array or -1 if not present.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.lastIndexOf exists natively in modern browsing engines as Array#lastIndexOf. Sugar provides this method when it is not supported. Sugar also provides Array#findIndex for more complex index finding operations.',
+    original_code: '_.lastIndexOf([1,2,3], 1)',
+    sugar_code: '[1,2,3].lastIndexOf(1)',
+    ref: 'Array/lastIndexOf'
+  },
+  {
+    name: 'range',
+    description: 'Shortcut to quickly create lists of integers.',
+    sugar_compatibility: 3,
+    sugar_notes: 'Ranges exist in Sugar and are created with Number.range. They can then be iterated over with the method "every".',
+    original_code: '_.range(0, 30, 5)',
+    sugar_code: 'Number.range(0, 30)',
+    ref: 'Number/upto'
+  },
+  {
+    name: 'bind',
+    description: 'Binds an object to a function, making that object its "this" argument when called.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.bind exists natively in modern browsing engines as Function#bind. Sugar provides this method when it is not supported.',
+    original_code: '_.bind(fn, obj, 1)',
+    sugar_code: 'fn.bind(obj, 1)',
+    ref: 'Function/bind'
+  },
+  {
+    name: 'bindAll',
+    description: 'Binds a number of methods on the object.',
+    sugar_compatibility: 0,
+    sugar_notes: '_.bindAll does not exist in Sugar. However, the same functionality can be achieved through a workaround.',
+    original_code: '_.bindAll(obj)',
+    sugar_code: 'Object.each(obj, function(key, val){ if(Object.isFunction(val)) obj[key] = val.bind(obj); })',
+    ref: 'Function/bind'
+  },
+  {
+    name: 'memoize',
+    description: 'Memoizes a given function by caching the computed result. Useful for speeding up slow-running computations.',
+    sugar_compatibility: 1,
+    sugar_notes: '_.memoize exists in Sugar as Function#once. It does not have the optional [hashFunction] parameter.',
+    original_code: '_.memoize(fn)',
+    sugar_code: 'fn.once()',
+    ref: 'Function/once'
+  },
+  {
+    name: 'delay',
+    description: 'Invokes the function after n milliseconds.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.delay exists in Sugar as Function#delay, and is identical.',
+    original_code: '_.delay(fn, 1000, 1)',
+    sugar_code: 'fn.delay(1000, 1)',
+    ref: 'Function/delay'
+  },
+  {
+    name: 'defer',
+    description: 'Invokes the function after the current stack has cleared.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.defer exists in Sugar as Function#delay, called with no arguments.',
+    original_code: '_.defer(fn)',
+    sugar_code: 'fn.delay()',
+    ref: 'Function/delay'
+  },
+  {
+    name: 'throttle',
+    description: 'Creates a throttled version of the function that when invoked will only call the function at most once per n milliseconds. Useful for rate-limiting events.',
+    sugar_compatibility: 3,
+    sugar_notes: '_.throttle exists in Sugar as Function#throttle. In addition, Function#lazy has other options like accepting an upper limit to the queue of functions waiting to execute or execute the first time immediately.',
+    original_code: '_.throttle(fn, 100)',
+    sugar_code: 'fn.throttle(100)',
+    ref: 'Function/lazy'
+  },
+  {
+    name: 'debounce',
+    description: 'Returns a "debounced" version of the function that will only execute once after n milliseconds have passed.',
+    sugar_compatibility: 3,
+    sugar_notes: '_.debounce exists in Sugar as Function#debounce.',
+    original_code: '_.debounce(fn, 100)',
+    sugar_code: 'fn.debounce(100)',
+    ref: 'Function/debounce'
+  },
+  {
+    name: 'once',
+    description: 'Returns a version of the function that will return the value of the original call if called repeated times.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.once exists in Sugar as Function#once. In Sugar it is identical to the functionality of _.memoize.',
+    original_code: '_.once(fn)',
+    sugar_code: 'fn.once()',
+    ref: 'Function/once'
+  },
+  {
+    name: 'after',
+    description: 'Returns a version of the function that will only be run after being called n times.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.after exists in Sugar as Function#after. The final callback will be passed an array of all argument objects collected (converted to proper arrays).',
+    original_code: '_.after(5, fn)',
+    sugar_code: 'fn.after(5)',
+    ref: 'Function/after'
+  },
+  {
+    name: 'wrap',
+    description: 'Wraps the first function inside of the wrapper function, passing it as the first argument.',
+    sugar_compatibility: 0,
+    sugar_notes: '_.wrap does not exist in Sugar. However, Function#bind can be used to achieve the same effect in a pinch.',
+    original_code: "_.wrap(fn, function(fn){ return fn() + 3; })",
+    sugar_code: "(function(fn){ return fn() + 3; }).bind(null, fn)",
+    ref: 'Function/bind'
+  },
+  {
+    name: 'compose',
+    description: 'Returns the composition of a list of functions.',
+    sugar_compatibility: 0,
+    sugar_notes: '_.compose does not exist in Sugar. However, Function#bind can help to achieve the same effect in a pinch.',
+    original_code: "_.compose(fn1, fn2, *)",
+    sugar_code: "Array.prototype.compose = function(){ var i = 0, fn; while(i < arguments.length){ fn = (function(){ return this.apply(this, arguments); }).bind(arguments[i]); i++;  } return fn; } fn1.compose(fn2)",
+    ref: 'Function/bind'
+  },
+  {
+    name: 'keys',
+    description: "Retrieves all the names of an object's properties.",
+    sugar_compatibility: 3,
+    sugar_notes: '_.keys exists natively in modern browsing engines as Object.keys. Sugar provides this method when it is not supported, and additionally allows a callback to be run against each key. Sugar can also create extended objects which have this method available as an instance method.',
+    original_code: '_.keys(obj)',
+    sugar_code: 'Object.keys(obj)',
+    ref: 'Object/keys'
+  },
+  {
+    name: 'values',
+    description: "Retrieves all the values of an object's properties.",
+    sugar_compatibility: 3,
+    sugar_notes: '_.values exists in Sugar as Object.values, which can also accept a callback. Sugar can also create extended objects which have this method available as an instance method.',
+    original_code: '_.values(obj)',
+    sugar_code: 'Object.values(obj)',
+    ref: 'Object/values'
+  },
+  {
+    name: 'functions',
+    description: 'Returns a sorted list of every method in the object.',
+    sugar_compatibility: 0,
+    sugar_notes: '_.functions does not exist in Sugar. However, Sugar makes it easy to reproduce the result.',
+    original_code: '_.functions(obj)',
+    sugar_code: 'Object.keys(obj).filter(function(key){ return Object.isFunction(obj[key]); })'
+  },
+  {
+    name: 'extend',
+    description: 'Copies all of the properties of the source object to the destination.',
+    sugar_compatibility: 3,
+    sugar_notes: "_.extend exists in Sugar as Object.merge. In the place of the ability to merge an unlimited number of objects, Sugar instead includes a parameter to determine how property conflicts should be resolved. However, extended objects can chain for the same effect.",
+    original_code: '_.extend(obj1, obj2, obj3)',
+    sugar_code: 'Object.extended(obj1).merge(obj2).merge(obj3)',
+    ref: 'Object/merge'
+  },
+  {
+    name: 'defaults',
+    description: 'Fills in missing properties in the object with default values.',
+    sugar_compatibility: 3,
+    sugar_notes: "_.defaults can be achieved in Sugar by passing false as the last argument to Object.merge. This will indicate that conflicts should preserve the target object's properties. The third parameter is to indicate a shallow merge.",
+    original_code: '_.defaults(obj, defaultProperties)',
+    sugar_code: 'Object.merge(obj, defaultProperties, false, false)',
+    ref: 'Object/merge'
+  },
+  {
+    name: 'clone',
+    description: 'Creates a shallow clone of the object.',
+    sugar_compatibility: 3,
+    sugar_notes: '_.clone exists in Sugar as Object.clone. Cloning is shallow by default but there is an option for deep cloning as well.',
+    original_code: '_.clone(obj)',
+    sugar_code: 'Object.clone(obj)',
+    ref: 'Object/clone'
+  },
+  {
+    name: 'tap',
+    description: 'Invokes interceptor with the object, and then returns object. The primary purpose of this method is to "tap into" a method chain, in order to perform operations on intermediate results within the chain.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.tap exists in Sugar as Object.tap. This method is mostly only useful when using extended objects or modifying the Object.prototype with Object.extend().',
+    original_code: '_.tap(obj)',
+    sugar_code: 'Object.tap(obj)',
+    ref: 'Object/tap'
+  },
+  {
+    name: 'isEqual',
+    description: 'Performs a deep comparison between two objects to determine if they are equal.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.isEqual exists in Sugar as Object.equal. Note also that in its instance method form the naming changes to "equals" for better readability.',
+    original_code: '_.isEqual(obj1, obj2)',
+    sugar_code: 'Object.equal(obj1, obj2)',
+    ref: 'Object/equal'
+  },
+  {
+    name: 'isElement',
+    description: 'Returns true if the object is a DOM element.',
+    sugar_compatibility: 0,
+    sugar_notes: "_.isElement does not exist in Sugar, as it has no direct association with the DOM. However this functionality can be easily replicated (taken from Underscore's own implementation).",
+    original_code: '_.isElement(obj1)',
+    sugar_code: 'Object.isElement = function(obj){ return !!(obj && obj.nodeType == 1); }'
+  },
+  {
+    name: 'isArray',
+    description: 'Returns true if the object is an array.',
+    sugar_compatibility: 2,
+    sugar_notes: "_.isArray exists natively in modern browsing engines as Array.isArray. Sugar provides this when it is not supported and also implements it as Object.isArray to maintain a parallel with other type checking methods.",
+    original_code: '_.isArray(obj)',
+    sugar_code: 'Array.isArray(obj)',
+    ref: 'Array/isArray'
+  },
+  {
+    name: 'isArguments',
+    description: 'Returns true if the object is an Arguments object.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.isArguments does not exist in Sugar. A simple check of the "callee" parameter may be enough to simulate this (and is also cross-browser). Note that Sugar does have Array.create(), which will convert an arguments object into a standard array.',
+    original_code: 'if(_.isArguments(obj))',
+    sugar_code: 'if(obj.callee)'
+  },
+  {
+    name: 'isFunction',
+    description: 'Returns true if the object is a function.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.isFunction exists as Object.isFunction and is identical.',
+    original_code: '_.isFunction(obj)',
+    sugar_code: 'Object.isFunction(obj)',
+    ref: 'Object/isFunction'
+  },
+  {
+    name: 'isString',
+    description: 'Returns true if the object is a string.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.isString exists as Object.isString and is identical.',
+    original_code: '_.isString(obj)',
+    sugar_code: 'Object.isString(obj)',
+    ref: 'Object/isString'
+  },
+  {
+    name: 'isNumber',
+    description: 'Returns true if the object is a number or NaN.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.isNumber exists as Object.isNumber and is identical.',
+    original_code: '_.isNumber(obj)',
+    sugar_code: 'Object.isNumber(obj)',
+    ref: 'Object/isNumber'
+  },
+  {
+    name: 'isBoolean',
+    description: 'Returns true if the object is a boolean.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.isBoolean exists as Object.isBoolean and is identical.',
+    original_code: '_.isBoolean(obj)',
+    sugar_code: 'Object.isBoolean(obj)',
+    ref: 'Object/isBoolean'
+  },
+  {
+    name: 'isDate',
+    description: 'Returns true if the object is a date.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.isDate exists as Object.isDate and is identical.',
+    original_code: '_.isDate(obj)',
+    sugar_code: 'Object.isDate(obj)',
+    ref: 'Object/isDate'
+  },
+  {
+    name: 'isRegExp',
+    description: 'Returns true if the object is a RegExp.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.isRegExp exists as Object.isRegExp and is identical.',
+    original_code: '_.isRegExp(obj)',
+    sugar_code: 'Object.isRegExp(obj)',
+    ref: 'Object/isRegExp'
+  },
+  {
+    name: 'isNaN',
+    description: 'Returns true if the object is NaN.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.isNaN exists as Object.isNaN and is identical.',
+    original_code: '_.isNaN(obj)',
+    sugar_code: 'Object.isNaN(obj)',
+    ref: 'Object/isNaN'
+  },
+  {
+    name: 'isNull',
+    description: 'Returns true if the object is null.',
+    sugar_compatibility: 0,
+    sugar_notes: '_.isNull does not exist in Sugar. Just use a straight equality comparison.',
+    original_code: '_.isNull(obj)',
+    sugar_code: 'obj === null'
+  },
+  {
+    name: 'isUndefined',
+    description: 'Returns true if the object is undefined.',
+    sugar_compatibility: 0,
+    sugar_notes: '_.isUndefined does not exist in Sugar. Just use a straight equality comparison.',
+    original_code: '_.isUndefined(obj)',
+    sugar_code: 'obj === undefined'
+  },
+  {
+    name: 'times',
+    description: 'Invokes the passed iterator n times.',
+    sugar_compatibility: 2,
+    sugar_notes: '_.times exists in Sugar as Number#times and is identical.',
+    original_code: '_.times(3, fn)',
+    sugar_code: '(3).times(fn)',
+    ref: 'Number/times'
+  },
+  {
+    name: 'template',
+    description: 'Compiles Javascript templates into functions that can be evaluated for rendering.',
+    sugar_compatibility: 1,
+    sugar_notes: '_.template exists in Sugar as String#assign with slightly different syntax. Although it does not have the complex "eval" functionality of Underscore, it can be useful for quickly assigning a value inside a string. String#assign will also accept numbers for arguments passed.',
+    original_code: "_.template('hello: <%= name %>')({ name: 'joe' })",
+    sugar_code: "'hello: {name}'.assign({ name: 'joe' })",
+    ref: 'String/assign'
+  }
+  ]
+}
+];
+
+
+  // END LIBS
+
+
+  var URL_MATCH = /((?:https?|file):[^:]+(?::\d{4})?[^:]+):(\d+)(?::(\d+))?/;
+  var warned = {};
+
+  var warn = function(message, stackLevel, skipMeta, docs, logLevel) {
+    var stack, files, match, file, line;
+    if(SUGAR_ANALYZER_UNIQUE_MESSAGES && hasBeenWarned(message)) {
+      return;
+    }
+    stack = new Error().stack;
+    message = message.replace(/\t/g, TS);
+    if(stack) {
+      files = stack.match(new RegExp(URL_MATCH.source, 'g'));
+      var isConsole = stack.match(/console|Object\._evaluateOn/);
+      file = files[stackLevel];
+      if(!isConsole && (!file || file.match(new RegExp('(' + baseExcludePackages.concat(SUGAR_ANALYZER_EXCLUDES).join('|') + ')[^\/]*\.js')))) {
+        return;
+      }
+      warned[message] = true;
+      if(!skipMeta) {
+        message += '\n\n';
+        if(isConsole) {
+          message += '----------- File: Console ---------';
+        } else {
+          match = file.match(URL_MATCH);
+          message += '----------- File: ' + match[1] + ' ---------';
+          if(match[2]) message += '\n----------- Line: ' + match[2] + ' --------------';
+          if(match[3]) message += '\n----------- Char: ' + match[3] + ' --------------';
+        }
+        if(docs){
+          message += '\n----------- Docs: http://sugarjs.com/api/' + docs + ' ---------';
+        }
+      }
+    }
+    if(SUGAR_ANALYZER_FIRST_LINE_ONLY) {
+      message = message.replace(/\n[\S\s]+$/gm, '');
+    }
+    console[logLevel || globalLogLevel](message);
+  };
+
+
+  var hasBeenWarned = function(message) {
+    return message in warned;
+  }
+
+  var wrapAll = function(all) {
+    for (var i = 0; i < all.length; i += 1) {
+      wrapModule(all[i]);
+    }
+  }
+
+  var wrapModule = function(module){
+    var namespace = this;
+    if(module.namespace) {
+      if(!namespace[module.namespace]) {
+        namespace[module.namespace] = function(){};
+      }
+      namespace = namespace[module.namespace];
+    }
+    if(namespace && module.type == 'instance') namespace = namespace.prototype;
+    for (var i = 0; i < module.methods.length; i++) {
+      wrapMethod(namespace, module.methods[i])
+    }
+  }
+
+  var wrapMethod = function(namespace, method) {
+    var fn = namespace[method.name] || function(){};
+    namespace[method.name] = function() {
+      var level, text = method.live_notes || method.sugar_notes;
+      if(!method.hasOwnProperty('conflict')) method.conflict = true;
+      var result = method.conflict && method.conflict.apply ? method.conflict.apply(this, arguments) : method.conflict;
+      var cond = result && result.length ? result[0] : result;
+      if(!cond && typeof method.conflict != 'function' && SUGAR_ANALYZER_INFO) {
+        level = 'info';
+        cond = true;
+      }
+      if(cond) {
+        text = supplant(text, result);
+        if(method.original_code && SUGAR_ANALYZER_SHOW_EXAMPLES){
+          text += '\n\n';
+          text += '\n'+library+':    ' + method.original_code;
+          text += '\nSugar:        ' + method.sugar_code;
+          text += '\n';
+        }
+        warn(text, 2, false, method.ref, level);
+      }
+      if(fn === PrototypeHash) {
+        return new fn(arguments);
+      } else {
+        return fn.apply(this, arguments);
+      }
+    }
+  };
+
+  function supplant(str, obj) {
+    var val;
+    return  str.replace(/\{(.+?)\}/g, function(m, d) {
+      val = obj[d];
+      return val !== undefined ? jsonify(val) : m;
+    });
+  }
+
+  function jsonify(o){
+    if(typeof JSON != 'undefined') {
+      return JSON.stringify(o);
+    } else {
+      return o.toString();
+    }
+  }
+
+  function setDefault(name, defaultValue) {
+    if(context[name] === undefined) {
+      context[name] = defaultValue;
+    }
+  }
+
+
+  var initialize = function(force) {
+    var noneFound = true;
+    if(typeof _ != 'undefined' || force) {
+      noneFound = false;
+      library = 'Underscore';
+      globalLogLevel = 'info';
+      wrapAll(SugarUnderscoreMethods);
+    }
+    if(typeof $A != 'undefined' || force) {
+      noneFound = false;
+      library = 'Prototype';
+      globalLogLevel = 'warn';
+      wrapAll(SugarPrototypeMethods);
+    }
+
+    if(noneFound) {
+      // No libs found, try initializing again after page load...
+      window.addEventListener('load', function() {
+        initialize(true);
+      });
+      return;
+    }
+
+    var welcome =
+      '### Welcome to the Sugar analyzer script! ###\n\n' +
+      "As your program calls various methods, it will warn you about incompatibilities with Sugar, and give\n" +
+      'suggestions about how to refactor. You can run this before refactoring to get a general idea about what needs to change\n' +
+      'or you can immediately remove Prototype/Underscore for Sugar, let breakages happen, and fix as you go!' +
+      '\n\nAnalyzer options (set these as globals):\n\n' +
+      'SUGAR_ANALYZER_UNIQUE_MESSAGES    = true/false       |  Display each message only once (default is true)\n' +
+      'SUGAR_ANALYZER_FIRST_LINE_ONLY    = true/false       |  Only display the first line of the message (default is false)\n' +
+      'SUGAR_ANALYZER_SHOW_EXAMPLES      = true/false       |  Show usage examples inline (default is true)\n' +
+      "SUGAR_ANALYZER_EXCLUDES           = ['a', 'b', ...]  |  Array of filenames to exclude messages from (default is [], can be partial match, leave off .js at the end)\n" +
+      'SUGAR_ANALYZER_INFO               = true/false       |  Display messages even when methods do not conflict (default is true)';
+    //welcome += '\n\n#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#';
+    console.info(welcome + '\n\n\n');
+    //console.info('-------------------------------------------------------------------------------------------------------------------');
+  }
+
+  var TS = '              ';
+  var globalLogLevel;
+  var library;
+  var baseExcludePackages = ['prototype','underscore','analyzer'];
+  var PrototypeHash = typeof Hash != 'undefined' ? Hash : null;
+
+  setDefault('SUGAR_ANALYZER_FIRST_LINE_ONLY', false);
+  setDefault('SUGAR_ANALYZER_SHOW_EXAMPLES', true);
+  setDefault('SUGAR_ANALYZER_INFO', true);
+  setDefault('SUGAR_ANALYZER_UNIQUE_MESSAGES', true);
+  setDefault('SUGAR_ANALYZER_EXCLUDES', []);
+
+  initialize();
+
+})(this);
level2/node_modules/sugar/lib/array.js
@@ -0,0 +1,1353 @@
+
+  'use strict';
+
+  /***
+   * @package Array
+   * @dependency core
+   * @description Array manipulation and traversal, "fuzzy matching" against elements, alphanumeric sorting and collation, enumerable methods on Object.
+   *
+   ***/
+
+
+  function regexMatcher(reg) {
+    reg = regexp(reg);
+    return function (el) {
+      return reg.test(el);
+    }
+  }
+
+  function dateMatcher(d) {
+    var ms = d.getTime();
+    return function (el) {
+      return !!(el && el.getTime) && el.getTime() === ms;
+    }
+  }
+
+  function functionMatcher(fn) {
+    return function (el, i, arr) {
+      // Return true up front if match by reference
+      return el === fn || fn.call(this, el, i, arr);
+    }
+  }
+
+  function invertedArgsFunctionMatcher(fn) {
+    return function (value, key, obj) {
+      // Return true up front if match by reference
+      return value === fn || fn.call(obj, key, value, obj);
+    }
+  }
+
+  function fuzzyMatcher(obj, isObject) {
+    var matchers = {};
+    return function (el, i, arr) {
+      var key;
+      if(!isObjectType(el)) {
+        return false;
+      }
+      for(key in obj) {
+        matchers[key] = matchers[key] || getMatcher(obj[key], isObject);
+        if(matchers[key].call(arr, el[key], i, arr) === false) {
+          return false;
+        }
+      }
+      return true;
+    }
+  }
+
+  function defaultMatcher(f) {
+    return function (el) {
+      return el === f || isEqual(el, f);
+    }
+  }
+
+  function getMatcher(f, isObject) {
+    if(isPrimitiveType(f)) {
+      // Do nothing and fall through to the
+      // default matcher below.
+    } else if(isRegExp(f)) {
+      // Match against a regexp
+      return regexMatcher(f);
+    } else if(isDate(f)) {
+      // Match against a date. isEqual below should also
+      // catch this but matching directly up front for speed.
+      return dateMatcher(f);
+    } else if(isFunction(f)) {
+      // Match against a filtering function
+      if(isObject) {
+        return invertedArgsFunctionMatcher(f);
+      } else {
+        return functionMatcher(f);
+      }
+    } else if(isPlainObject(f)) {
+      // Match against a fuzzy hash or array.
+      return fuzzyMatcher(f, isObject);
+    }
+    // Default is standard isEqual
+    return defaultMatcher(f);
+  }
+
+  function transformArgument(el, map, context, mapArgs) {
+    if(!map) {
+      return el;
+    } else if(map.apply) {
+      return map.apply(context, mapArgs || []);
+    } else if(isFunction(el[map])) {
+      return el[map].call(el);
+    } else {
+      return el[map];
+    }
+  }
+
+  // Basic array internal methods
+
+  function arrayEach(arr, fn, startIndex, loop) {
+    var index, i, length = +arr.length;
+    if(startIndex < 0) startIndex = arr.length + startIndex;
+    i = isNaN(startIndex) ? 0 : startIndex;
+    if(loop === true) {
+      length += i;
+    }
+    while(i < length) {
+      index = i % arr.length;
+      if(!(index in arr)) {
+        return iterateOverSparseArray(arr, fn, i, loop);
+      } else if(fn.call(arr, arr[index], index, arr) === false) {
+        break;
+      }
+      i++;
+    }
+  }
+
+  function iterateOverSparseArray(arr, fn, fromIndex, loop) {
+    var indexes = [], i;
+    for(i in arr) {
+      if(isArrayIndex(arr, i) && i >= fromIndex) {
+        indexes.push(parseInt(i));
+      }
+    }
+    indexes.sort().each(function(index) {
+      return fn.call(arr, arr[index], index, arr);
+    });
+    return arr;
+  }
+
+  function isArrayIndex(arr, i) {
+    return i in arr && toUInt32(i) == i && i != 0xffffffff;
+  }
+
+  function toUInt32(i) {
+    return i >>> 0;
+  }
+
+  function arrayFind(arr, f, startIndex, loop, returnIndex, context) {
+    var result, index, matcher;
+    if(arr.length > 0) {
+      matcher = getMatcher(f);
+      arrayEach(arr, function(el, i) {
+        if(matcher.call(context, el, i, arr)) {
+          result = el;
+          index = i;
+          return false;
+        }
+      }, startIndex, loop);
+    }
+    return returnIndex ? index : result;
+  }
+
+  function arrayUnique(arr, map) {
+    var result = [], o = {}, transformed;
+    arrayEach(arr, function(el, i) {
+      transformed = map ? transformArgument(el, map, arr, [el, i, arr]) : el;
+      if(!checkForElementInHashAndSet(o, transformed)) {
+        result.push(el);
+      }
+    })
+    return result;
+  }
+
+  function arrayIntersect(arr1, arr2, subtract) {
+    var result = [], o = {};
+    arr2.each(function(el) {
+      checkForElementInHashAndSet(o, el);
+    });
+    arr1.each(function(el) {
+      var stringified = stringify(el),
+          isReference = !objectIsMatchedByValue(el);
+      // Add the result to the array if:
+      // 1. We're subtracting intersections or it doesn't already exist in the result and
+      // 2. It exists in the compared array and we're adding, or it doesn't exist and we're removing.
+      if(elementExistsInHash(o, stringified, el, isReference) !== subtract) {
+        discardElementFromHash(o, stringified, el, isReference);
+        result.push(el);
+      }
+    });
+    return result;
+  }
+
+  function arrayFlatten(arr, level, current) {
+    level = level || Infinity;
+    current = current || 0;
+    var result = [];
+    arrayEach(arr, function(el) {
+      if(isArray(el) && current < level) {
+        result = result.concat(arrayFlatten(el, level, current + 1));
+      } else {
+        result.push(el);
+      }
+    });
+    return result;
+  }
+
+  function isArrayLike(obj) {
+    return hasProperty(obj, 'length') && !isString(obj) && !isPlainObject(obj);
+  }
+
+  function isArgumentsObject(obj) {
+    // .callee exists on Arguments objects in < IE8
+    return hasProperty(obj, 'length') && (className(obj) === '[object Arguments]' || !!obj.callee);
+  }
+
+  function flatArguments(args) {
+    var result = [];
+    multiArgs(args, function(arg) {
+      result = result.concat(arg);
+    });
+    return result;
+  }
+
+  function elementExistsInHash(hash, key, element, isReference) {
+    var exists = key in hash;
+    if(isReference) {
+      if(!hash[key]) {
+        hash[key] = [];
+      }
+      exists = hash[key].indexOf(element) !== -1;
+    }
+    return exists;
+  }
+
+  function checkForElementInHashAndSet(hash, element) {
+    var stringified = stringify(element),
+        isReference = !objectIsMatchedByValue(element),
+        exists      = elementExistsInHash(hash, stringified, element, isReference);
+    if(isReference) {
+      hash[stringified].push(element);
+    } else {
+      hash[stringified] = element;
+    }
+    return exists;
+  }
+
+  function discardElementFromHash(hash, key, element, isReference) {
+    var arr, i = 0;
+    if(isReference) {
+      arr = hash[key];
+      while(i < arr.length) {
+        if(arr[i] === element) {
+          arr.splice(i, 1);
+        } else {
+          i += 1;
+        }
+      }
+    } else {
+      delete hash[key];
+    }
+  }
+
+  // Support methods
+
+  function getMinOrMax(obj, map, which, all) {
+    var el,
+        key,
+        edge,
+        test,
+        result = [],
+        max = which === 'max',
+        min = which === 'min',
+        isArray = array.isArray(obj);
+    for(key in obj) {
+      if(!obj.hasOwnProperty(key)) continue;
+      el   = obj[key];
+      test = transformArgument(el, map, obj, isArray ? [el, parseInt(key), obj] : []);
+      if(isUndefined(test)) {
+        throw new TypeError('Cannot compare with undefined');
+      }
+      if(test === edge) {
+        result.push(el);
+      } else if(isUndefined(edge) || (max && test > edge) || (min && test < edge)) {
+        result = [el];
+        edge = test;
+      }
+    }
+    if(!isArray) result = arrayFlatten(result, 1);
+    return all ? result : result[0];
+  }
+
+
+  // Alphanumeric collation helpers
+
+  function collateStrings(a, b) {
+    var aValue, bValue, aChar, bChar, aEquiv, bEquiv, index = 0, tiebreaker = 0;
+
+    var sortIgnore      = array[AlphanumericSortIgnore];
+    var sortIgnoreCase  = array[AlphanumericSortIgnoreCase];
+    var sortEquivalents = array[AlphanumericSortEquivalents];
+    var sortOrder       = array[AlphanumericSortOrder];
+    var naturalSort     = array[AlphanumericSortNatural];
+
+    a = getCollationReadyString(a, sortIgnore, sortIgnoreCase);
+    b = getCollationReadyString(b, sortIgnore, sortIgnoreCase);
+
+    do {
+
+      aChar  = getCollationCharacter(a, index, sortEquivalents);
+      bChar  = getCollationCharacter(b, index, sortEquivalents);
+      aValue = getSortOrderIndex(aChar, sortOrder);
+      bValue = getSortOrderIndex(bChar, sortOrder);
+
+      if(aValue === -1 || bValue === -1) {
+        aValue = a.charCodeAt(index) || null;
+        bValue = b.charCodeAt(index) || null;
+        if(naturalSort && codeIsNumeral(aValue) && codeIsNumeral(bValue)) {
+          aValue = stringToNumber(a.slice(index));
+          bValue = stringToNumber(b.slice(index));
+        }
+      } else {
+        aEquiv = aChar !== a.charAt(index);
+        bEquiv = bChar !== b.charAt(index);
+        if(aEquiv !== bEquiv && tiebreaker === 0) {
+          tiebreaker = aEquiv - bEquiv;
+        }
+      }
+      index += 1;
+    } while(aValue != null && bValue != null && aValue === bValue);
+    if(aValue === bValue) return tiebreaker;
+    return aValue - bValue;
+  }
+
+  function getCollationReadyString(str, sortIgnore, sortIgnoreCase) {
+    if(!isString(str)) str = string(str);
+    if(sortIgnoreCase) {
+      str = str.toLowerCase();
+    }
+    if(sortIgnore) {
+      str = str.replace(sortIgnore, '');
+    }
+    return str;
+  }
+
+  function getCollationCharacter(str, index, sortEquivalents) {
+    var chr = str.charAt(index);
+    return sortEquivalents[chr] || chr;
+  }
+
+  function getSortOrderIndex(chr, sortOrder) {
+    if(!chr) {
+      return null;
+    } else {
+      return sortOrder.indexOf(chr);
+    }
+  }
+
+  var AlphanumericSort            = 'AlphanumericSort';
+  var AlphanumericSortOrder       = 'AlphanumericSortOrder';
+  var AlphanumericSortIgnore      = 'AlphanumericSortIgnore';
+  var AlphanumericSortIgnoreCase  = 'AlphanumericSortIgnoreCase';
+  var AlphanumericSortEquivalents = 'AlphanumericSortEquivalents';
+  var AlphanumericSortNatural     = 'AlphanumericSortNatural';
+
+
+
+  function buildEnhancements() {
+    var nativeMap = array.prototype.map;
+    var callbackCheck = function() {
+      var args = arguments;
+      return args.length > 0 && !isFunction(args[0]);
+    };
+    extendSimilar(array, true, callbackCheck, 'every,all,some,filter,any,none,find,findIndex', function(methods, name) {
+      var nativeFn = array.prototype[name]
+      methods[name] = function(f) {
+        var matcher = getMatcher(f);
+        return nativeFn.call(this, function(el, index) {
+          return matcher(el, index, this);
+        });
+      }
+    });
+    extend(array, true, callbackCheck, {
+      'map': function(f) {
+        return nativeMap.call(this, function(el, index) {
+          return transformArgument(el, f, this, [el, index, this]);
+        });
+      }
+    });
+  }
+
+  function buildAlphanumericSort() {
+    var order = 'Aรร€ร‚รƒฤ„BCฤ†ฤŒร‡DฤŽรEร‰รˆฤšรŠร‹ฤ˜FGฤžHฤฑIรรŒฤฐรŽรJKLลMNลƒล‡ร‘Oร“ร’ร”PQRล˜Sลšล ลžTลคUรšร™ลฎร›รœVWXYรZลนลปลฝรžร†ล’ร˜ร•ร…ร„ร–';
+    var equiv = 'Aรร€ร‚รƒร„,Cร‡,Eร‰รˆรŠร‹,IรรŒฤฐรŽร,Oร“ร’ร”ร•ร–,SรŸ,Uรšร™ร›รœ';
+    array[AlphanumericSortOrder] = order.split('').map(function(str) {
+      return str + str.toLowerCase();
+    }).join('');
+    var equivalents = {};
+    arrayEach(equiv.split(','), function(set) {
+      var equivalent = set.charAt(0);
+      arrayEach(set.slice(1).split(''), function(chr) {
+        equivalents[chr] = equivalent;
+        equivalents[chr.toLowerCase()] = equivalent.toLowerCase();
+      });
+    });
+    array[AlphanumericSortNatural] = true;
+    array[AlphanumericSortIgnoreCase] = true;
+    array[AlphanumericSortEquivalents] = equivalents;
+  }
+
+  extend(array, false, true, {
+
+    /***
+     *
+     * @method Array.create(<obj1>, <obj2>, ...)
+     * @returns Array
+     * @short Alternate array constructor.
+     * @extra This method will create a single array by calling %concat% on all arguments passed. In addition to ensuring that an unknown variable is in a single, flat array (the standard constructor will create nested arrays, this one will not), it is also a useful shorthand to convert a function's arguments object into a standard array.
+     * @example
+     *
+     *   Array.create('one', true, 3)   -> ['one', true, 3]
+     *   Array.create(['one', true, 3]) -> ['one', true, 3]
+     +   Array.create(function(n) {
+     *     return arguments;
+     *   }('howdy', 'doody'));
+     *
+     ***/
+    'create': function() {
+      var result = [];
+      multiArgs(arguments, function(a) {
+        if(isArgumentsObject(a) || isArrayLike(a)) {
+          a = array.prototype.slice.call(a, 0);
+        }
+        result = result.concat(a);
+      });
+      return result;
+    }
+
+  });
+
+  extend(array, true, false, {
+
+    /***
+     * @method find(<f>, [context] = undefined)
+     * @returns Mixed
+     * @short Returns the first element that matches <f>.
+     * @extra [context] is the %this% object if passed. When <f> is a function, will use native implementation if it exists. <f> will also match a string, number, array, object, or alternately test against a function or regex. This method implements @array_matching.
+     * @example
+     *
+     +   [{a:1,b:2},{a:1,b:3},{a:1,b:4}].find(function(n) {
+     *     return n['a'] == 1;
+     *   });                                  -> {a:1,b:3}
+     *   ['cuba','japan','canada'].find(/^c/) -> 'cuba'
+     *
+     ***/
+    'find': function(f, context) {
+      checkCallback(f);
+      return arrayFind(this, f, 0, false, false, context);
+    },
+
+    /***
+     * @method findIndex(<f>, [context] = undefined)
+     * @returns Number
+     * @short Returns the index of the first element that matches <f> or -1 if not found.
+     * @extra [context] is the %this% object if passed. When <f> is a function, will use native implementation if it exists. <f> will also match a string, number, array, object, or alternately test against a function or regex. This method implements @array_matching.
+     *
+     * @example
+     *
+     +   [1,2,3,4].findIndex(function(n) {
+     *     return n % 2 == 0;
+     *   }); -> 1
+     +   [1,2,3,4].findIndex(3);               -> 2
+     +   ['one','two','three'].findIndex(/t/); -> 1
+     *
+     ***/
+    'findIndex': function(f, context) {
+      var index;
+      checkCallback(f);
+      index = arrayFind(this, f, 0, false, true, context);
+      return isUndefined(index) ? -1 : index;
+    }
+
+  });
+
+  extend(array, true, true, {
+
+    /***
+     * @method findFrom(<f>, [index] = 0, [loop] = false)
+     * @returns Array
+     * @short Returns any element that matches <f>, beginning from [index].
+     * @extra <f> will match a string, number, array, object, or alternately test against a function or regex. Will continue from index = 0 if [loop] is true. This method implements @array_matching.
+     * @example
+     *
+     *   ['cuba','japan','canada'].findFrom(/^c/, 2) -> 'canada'
+     *
+     ***/
+    'findFrom': function(f, index, loop) {
+      return arrayFind(this, f, index, loop);
+    },
+
+    /***
+     * @method findIndexFrom(<f>, [index] = 0, [loop] = false)
+     * @returns Array
+     * @short Returns the index of any element that matches <f>, beginning from [index].
+     * @extra <f> will match a string, number, array, object, or alternately test against a function or regex. Will continue from index = 0 if [loop] is true. This method implements @array_matching.
+     * @example
+     *
+     *   ['cuba','japan','canada'].findIndexFrom(/^c/, 2) -> 2
+     *
+     ***/
+    'findIndexFrom': function(f, index, loop) {
+      var index = arrayFind(this, f, index, loop, true);
+      return isUndefined(index) ? -1 : index;
+    },
+
+    /***
+     * @method findAll(<f>, [index] = 0, [loop] = false)
+     * @returns Array
+     * @short Returns all elements that match <f>.
+     * @extra <f> will match a string, number, array, object, or alternately test against a function or regex. Starts at [index], and will continue once from index = 0 if [loop] is true. This method implements @array_matching.
+     * @example
+     *
+     +   [{a:1,b:2},{a:1,b:3},{a:2,b:4}].findAll(function(n) {
+     *     return n['a'] == 1;
+     *   });                                        -> [{a:1,b:3},{a:1,b:4}]
+     *   ['cuba','japan','canada'].findAll(/^c/)    -> 'cuba','canada'
+     *   ['cuba','japan','canada'].findAll(/^c/, 2) -> 'canada'
+     *
+     ***/
+    'findAll': function(f, index, loop) {
+      var result = [], matcher;
+      if(this.length > 0) {
+        matcher = getMatcher(f);
+        arrayEach(this, function(el, i, arr) {
+          if(matcher(el, i, arr)) {
+            result.push(el);
+          }
+        }, index, loop);
+      }
+      return result;
+    },
+
+    /***
+     * @method count(<f>)
+     * @returns Number
+     * @short Counts all elements in the array that match <f>.
+     * @extra <f> will match a string, number, array, object, or alternately test against a function or regex. This method implements @array_matching.
+     * @example
+     *
+     *   [1,2,3,1].count(1)       -> 2
+     *   ['a','b','c'].count(/b/) -> 1
+     +   [{a:1},{b:2}].count(function(n) {
+     *     return n['a'] > 1;
+     *   });                      -> 0
+     *
+     ***/
+    'count': function(f) {
+      if(isUndefined(f)) return this.length;
+      return this.findAll(f).length;
+    },
+
+    /***
+     * @method removeAt(<start>, [end])
+     * @returns Array
+     * @short Removes element at <start>. If [end] is specified, removes the range between <start> and [end]. This method will change the array! If you don't intend the array to be changed use %clone% first.
+     * @example
+     *
+     *   ['a','b','c'].removeAt(0) -> ['b','c']
+     *   [1,2,3,4].removeAt(1, 3)  -> [1]
+     *
+     ***/
+    'removeAt': function(start, end) {
+      if(isUndefined(start)) return this;
+      if(isUndefined(end))   end = start;
+      this.splice(start, end - start + 1);
+      return this;
+    },
+
+    /***
+     * @method include(<el>, [index])
+     * @returns Array
+     * @short Adds <el> to the array.
+     * @extra This is a non-destructive alias for %add%. It will not change the original array.
+     * @example
+     *
+     *   [1,2,3,4].include(5)       -> [1,2,3,4,5]
+     *   [1,2,3,4].include(8, 1)    -> [1,8,2,3,4]
+     *   [1,2,3,4].include([5,6,7]) -> [1,2,3,4,5,6,7]
+     *
+     ***/
+    'include': function(el, index) {
+      return this.clone().add(el, index);
+    },
+
+    /***
+     * @method exclude([f1], [f2], ...)
+     * @returns Array
+     * @short Removes any element in the array that matches [f1], [f2], etc.
+     * @extra This is a non-destructive alias for %remove%. It will not change the original array. This method implements @array_matching.
+     * @example
+     *
+     *   [1,2,3].exclude(3)         -> [1,2]
+     *   ['a','b','c'].exclude(/b/) -> ['a','c']
+     +   [{a:1},{b:2}].exclude(function(n) {
+     *     return n['a'] == 1;
+     *   });                       -> [{b:2}]
+     *
+     ***/
+    'exclude': function() {
+      return array.prototype.remove.apply(this.clone(), arguments);
+    },
+
+    /***
+     * @method clone()
+     * @returns Array
+     * @short Makes a shallow clone of the array.
+     * @example
+     *
+     *   [1,2,3].clone() -> [1,2,3]
+     *
+     ***/
+    'clone': function() {
+      return simpleMerge([], this);
+    },
+
+    /***
+     * @method unique([map] = null)
+     * @returns Array
+     * @short Removes all duplicate elements in the array.
+     * @extra [map] may be a function mapping the value to be uniqued on or a string acting as a shortcut. This is most commonly used when you have a key that ensures the object's uniqueness, and don't need to check all fields. This method will also correctly operate on arrays of objects.
+     * @example
+     *
+     *   [1,2,2,3].unique()                 -> [1,2,3]
+     *   [{foo:'bar'},{foo:'bar'}].unique() -> [{foo:'bar'}]
+     +   [{foo:'bar'},{foo:'bar'}].unique(function(obj){
+     *     return obj.foo;
+     *   }); -> [{foo:'bar'}]
+     *   [{foo:'bar'},{foo:'bar'}].unique('foo') -> [{foo:'bar'}]
+     *
+     ***/
+    'unique': function(map) {
+      return arrayUnique(this, map);
+    },
+
+    /***
+     * @method flatten([limit] = Infinity)
+     * @returns Array
+     * @short Returns a flattened, one-dimensional copy of the array.
+     * @extra You can optionally specify a [limit], which will only flatten that depth.
+     * @example
+     *
+     *   [[1], 2, [3]].flatten()      -> [1,2,3]
+     *   [['a'],[],'b','c'].flatten() -> ['a','b','c']
+     *
+     ***/
+    'flatten': function(limit) {
+      return arrayFlatten(this, limit);
+    },
+
+    /***
+     * @method union([a1], [a2], ...)
+     * @returns Array
+     * @short Returns an array containing all elements in all arrays with duplicates removed.
+     * @extra This method will also correctly operate on arrays of objects.
+     * @example
+     *
+     *   [1,3,5].union([5,7,9])     -> [1,3,5,7,9]
+     *   ['a','b'].union(['b','c']) -> ['a','b','c']
+     *
+     ***/
+    'union': function() {
+      return arrayUnique(this.concat(flatArguments(arguments)));
+    },
+
+    /***
+     * @method intersect([a1], [a2], ...)
+     * @returns Array
+     * @short Returns an array containing the elements all arrays have in common.
+     * @extra This method will also correctly operate on arrays of objects.
+     * @example
+     *
+     *   [1,3,5].intersect([5,7,9])   -> [5]
+     *   ['a','b'].intersect('b','c') -> ['b']
+     *
+     ***/
+    'intersect': function() {
+      return arrayIntersect(this, flatArguments(arguments), false);
+    },
+
+    /***
+     * @method subtract([a1], [a2], ...)
+     * @returns Array
+     * @short Subtracts from the array all elements in [a1], [a2], etc.
+     * @extra This method will also correctly operate on arrays of objects.
+     * @example
+     *
+     *   [1,3,5].subtract([5,7,9])   -> [1,3]
+     *   [1,3,5].subtract([3],[5])   -> [1]
+     *   ['a','b'].subtract('b','c') -> ['a']
+     *
+     ***/
+    'subtract': function(a) {
+      return arrayIntersect(this, flatArguments(arguments), true);
+    },
+
+    /***
+     * @method at(<index>, [loop] = true)
+     * @returns Mixed
+     * @short Gets the element(s) at a given index.
+     * @extra When [loop] is true, overshooting the end of the array (or the beginning) will begin counting from the other end. As an alternate syntax, passing multiple indexes will get the elements at those indexes.
+     * @example
+     *
+     *   [1,2,3].at(0)        -> 1
+     *   [1,2,3].at(2)        -> 3
+     *   [1,2,3].at(4)        -> 2
+     *   [1,2,3].at(4, false) -> null
+     *   [1,2,3].at(-1)       -> 3
+     *   [1,2,3].at(0,1)      -> [1,2]
+     *
+     ***/
+    'at': function() {
+      return getEntriesForIndexes(this, arguments);
+    },
+
+    /***
+     * @method first([num] = 1)
+     * @returns Mixed
+     * @short Returns the first element(s) in the array.
+     * @extra When <num> is passed, returns the first <num> elements in the array.
+     * @example
+     *
+     *   [1,2,3].first()        -> 1
+     *   [1,2,3].first(2)       -> [1,2]
+     *
+     ***/
+    'first': function(num) {
+      if(isUndefined(num)) return this[0];
+      if(num < 0) num = 0;
+      return this.slice(0, num);
+    },
+
+    /***
+     * @method last([num] = 1)
+     * @returns Mixed
+     * @short Returns the last element(s) in the array.
+     * @extra When <num> is passed, returns the last <num> elements in the array.
+     * @example
+     *
+     *   [1,2,3].last()        -> 3
+     *   [1,2,3].last(2)       -> [2,3]
+     *
+     ***/
+    'last': function(num) {
+      if(isUndefined(num)) return this[this.length - 1];
+      var start = this.length - num < 0 ? 0 : this.length - num;
+      return this.slice(start);
+    },
+
+    /***
+     * @method from(<index>)
+     * @returns Array
+     * @short Returns a slice of the array from <index>.
+     * @example
+     *
+     *   [1,2,3].from(1)  -> [2,3]
+     *   [1,2,3].from(2)  -> [3]
+     *
+     ***/
+    'from': function(num) {
+      return this.slice(num);
+    },
+
+    /***
+     * @method to(<index>)
+     * @returns Array
+     * @short Returns a slice of the array up to <index>.
+     * @example
+     *
+     *   [1,2,3].to(1)  -> [1]
+     *   [1,2,3].to(2)  -> [1,2]
+     *
+     ***/
+    'to': function(num) {
+      if(isUndefined(num)) num = this.length;
+      return this.slice(0, num);
+    },
+
+    /***
+     * @method min([map], [all] = false)
+     * @returns Mixed
+     * @short Returns the element in the array with the lowest value.
+     * @extra [map] may be a function mapping the value to be checked or a string acting as a shortcut. If [all] is true, will return all min values in an array.
+     * @example
+     *
+     *   [1,2,3].min()                          -> 1
+     *   ['fee','fo','fum'].min('length')       -> 'fo'
+     *   ['fee','fo','fum'].min('length', true) -> ['fo']
+     +   ['fee','fo','fum'].min(function(n) {
+     *     return n.length;
+     *   });                              -> ['fo']
+     +   [{a:3,a:2}].min(function(n) {
+     *     return n['a'];
+     *   });                              -> [{a:2}]
+     *
+     ***/
+    'min': function(map, all) {
+      return getMinOrMax(this, map, 'min', all);
+    },
+
+    /***
+     * @method max([map], [all] = false)
+     * @returns Mixed
+     * @short Returns the element in the array with the greatest value.
+     * @extra [map] may be a function mapping the value to be checked or a string acting as a shortcut. If [all] is true, will return all max values in an array.
+     * @example
+     *
+     *   [1,2,3].max()                          -> 3
+     *   ['fee','fo','fum'].max('length')       -> 'fee'
+     *   ['fee','fo','fum'].max('length', true) -> ['fee']
+     +   [{a:3,a:2}].max(function(n) {
+     *     return n['a'];
+     *   });                              -> {a:3}
+     *
+     ***/
+    'max': function(map, all) {
+      return getMinOrMax(this, map, 'max', all);
+    },
+
+    /***
+     * @method least([map])
+     * @returns Array
+     * @short Returns the elements in the array with the least commonly occuring value.
+     * @extra [map] may be a function mapping the value to be checked or a string acting as a shortcut.
+     * @example
+     *
+     *   [3,2,2].least()                   -> [3]
+     *   ['fe','fo','fum'].least('length') -> ['fum']
+     +   [{age:35,name:'ken'},{age:12,name:'bob'},{age:12,name:'ted'}].least(function(n) {
+     *     return n.age;
+     *   });                               -> [{age:35,name:'ken'}]
+     *
+     ***/
+    'least': function(map, all) {
+      return getMinOrMax(this.groupBy.apply(this, [map]), 'length', 'min', all);
+    },
+
+    /***
+     * @method most([map])
+     * @returns Array
+     * @short Returns the elements in the array with the most commonly occuring value.
+     * @extra [map] may be a function mapping the value to be checked or a string acting as a shortcut.
+     * @example
+     *
+     *   [3,2,2].most()                   -> [2]
+     *   ['fe','fo','fum'].most('length') -> ['fe','fo']
+     +   [{age:35,name:'ken'},{age:12,name:'bob'},{age:12,name:'ted'}].most(function(n) {
+     *     return n.age;
+     *   });                              -> [{age:12,name:'bob'},{age:12,name:'ted'}]
+     *
+     ***/
+    'most': function(map, all) {
+      return getMinOrMax(this.groupBy.apply(this, [map]), 'length', 'max', all);
+    },
+
+    /***
+     * @method sum([map])
+     * @returns Number
+     * @short Sums all values in the array.
+     * @extra [map] may be a function mapping the value to be summed or a string acting as a shortcut.
+     * @example
+     *
+     *   [1,2,2].sum()                           -> 5
+     +   [{age:35},{age:12},{age:12}].sum(function(n) {
+     *     return n.age;
+     *   });                                     -> 59
+     *   [{age:35},{age:12},{age:12}].sum('age') -> 59
+     *
+     ***/
+    'sum': function(map) {
+      var arr = map ? this.map(map) : this;
+      return arr.length > 0 ? arr.reduce(function(a,b) { return a + b; }) : 0;
+    },
+
+    /***
+     * @method average([map])
+     * @returns Number
+     * @short Gets the mean average for all values in the array.
+     * @extra [map] may be a function mapping the value to be averaged or a string acting as a shortcut.
+     * @example
+     *
+     *   [1,2,3].average()                           -> 2
+     +   [{age:35},{age:11},{age:11}].average(function(n) {
+     *     return n.age;
+     *   });                                         -> 19
+     *   [{age:35},{age:11},{age:11}].average('age') -> 19
+     *
+     ***/
+    'average': function(map) {
+      var arr = map ? this.map(map) : this;
+      return arr.length > 0 ? arr.sum() / arr.length : 0;
+    },
+
+    /***
+     * @method inGroups(<num>, [padding])
+     * @returns Array
+     * @short Groups the array into <num> arrays.
+     * @extra [padding] specifies a value with which to pad the last array so that they are all equal length.
+     * @example
+     *
+     *   [1,2,3,4,5,6,7].inGroups(3)         -> [ [1,2,3], [4,5,6], [7] ]
+     *   [1,2,3,4,5,6,7].inGroups(3, 'none') -> [ [1,2,3], [4,5,6], [7,'none','none'] ]
+     *
+     ***/
+    'inGroups': function(num, padding) {
+      var pad = arguments.length > 1;
+      var arr = this;
+      var result = [];
+      var divisor = ceil(this.length / num);
+      simpleRepeat(num, function(i) {
+        var index = i * divisor;
+        var group = arr.slice(index, index + divisor);
+        if(pad && group.length < divisor) {
+          simpleRepeat(divisor - group.length, function() {
+            group = group.add(padding);
+          });
+        }
+        result.push(group);
+      });
+      return result;
+    },
+
+    /***
+     * @method inGroupsOf(<num>, [padding] = null)
+     * @returns Array
+     * @short Groups the array into arrays of <num> elements each.
+     * @extra [padding] specifies a value with which to pad the last array so that they are all equal length.
+     * @example
+     *
+     *   [1,2,3,4,5,6,7].inGroupsOf(4)         -> [ [1,2,3,4], [5,6,7] ]
+     *   [1,2,3,4,5,6,7].inGroupsOf(4, 'none') -> [ [1,2,3,4], [5,6,7,'none'] ]
+     *
+     ***/
+    'inGroupsOf': function(num, padding) {
+      var result = [], len = this.length, arr = this, group;
+      if(len === 0 || num === 0) return arr;
+      if(isUndefined(num)) num = 1;
+      if(isUndefined(padding)) padding = null;
+      simpleRepeat(ceil(len / num), function(i) {
+        group = arr.slice(num * i, num * i + num);
+        while(group.length < num) {
+          group.push(padding);
+        }
+        result.push(group);
+      });
+      return result;
+    },
+
+    /***
+     * @method isEmpty()
+     * @returns Boolean
+     * @short Returns true if the array is empty.
+     * @extra This is true if the array has a length of zero, or contains only %undefined%, %null%, or %NaN%.
+     * @example
+     *
+     *   [].isEmpty()               -> true
+     *   [null,undefined].isEmpty() -> true
+     *
+     ***/
+    'isEmpty': function() {
+      return this.compact().length == 0;
+    },
+
+    /***
+     * @method sortBy(<map>, [desc] = false)
+     * @returns Array
+     * @short Sorts the array by <map>.
+     * @extra <map> may be a function, a string acting as a shortcut, or blank (direct comparison of array values). [desc] will sort the array in descending order. When the field being sorted on is a string, the resulting order will be determined by an internal collation algorithm that is optimized for major Western languages, but can be customized. For more information see @array_sorting.
+     * @example
+     *
+     *   ['world','a','new'].sortBy('length')       -> ['a','new','world']
+     *   ['world','a','new'].sortBy('length', true) -> ['world','new','a']
+     +   [{age:72},{age:13},{age:18}].sortBy(function(n) {
+     *     return n.age;
+     *   });                                        -> [{age:13},{age:18},{age:72}]
+     *
+     ***/
+    'sortBy': function(map, desc) {
+      var arr = this.clone();
+      arr.sort(function(a, b) {
+        var aProperty, bProperty, comp;
+        aProperty = transformArgument(a, map, arr, [a]);
+        bProperty = transformArgument(b, map, arr, [b]);
+        if(isString(aProperty) && isString(bProperty)) {
+          comp = collateStrings(aProperty, bProperty);
+        } else if(aProperty < bProperty) {
+          comp = -1;
+        } else if(aProperty > bProperty) {
+          comp = 1;
+        } else {
+          comp = 0;
+        }
+        return comp * (desc ? -1 : 1);
+      });
+      return arr;
+    },
+
+    /***
+     * @method randomize()
+     * @returns Array
+     * @short Returns a copy of the array with the elements randomized.
+     * @extra Uses Fisher-Yates algorithm.
+     * @example
+     *
+     *   [1,2,3,4].randomize()  -> [?,?,?,?]
+     *
+     ***/
+    'randomize': function() {
+      var arr = this.concat(), i = arr.length, j, x;
+      while(i) {
+        j = (math.random() * i) | 0;
+        x = arr[--i];
+        arr[i] = arr[j];
+        arr[j] = x;
+      }
+      return arr;
+    },
+
+    /***
+     * @method zip([arr1], [arr2], ...)
+     * @returns Array
+     * @short Merges multiple arrays together.
+     * @extra This method "zips up" smaller arrays into one large whose elements are "all elements at index 0", "all elements at index 1", etc. Useful when you have associated data that is split over separated arrays. If the arrays passed have more elements than the original array, they will be discarded. If they have fewer elements, the missing elements will filled with %null%.
+     * @example
+     *
+     *   [1,2,3].zip([4,5,6])                                       -> [[1,2], [3,4], [5,6]]
+     *   ['Martin','John'].zip(['Luther','F.'], ['King','Kennedy']) -> [['Martin','Luther','King'], ['John','F.','Kennedy']]
+     *
+     ***/
+    'zip': function() {
+      var args = multiArgs(arguments);
+      return this.map(function(el, i) {
+        return [el].concat(args.map(function(k) {
+          return (i in k) ? k[i] : null;
+        }));
+      });
+    },
+
+    /***
+     * @method sample([num])
+     * @returns Mixed
+     * @short Returns a random element from the array.
+     * @extra If [num] is passed, will return [num] samples from the array.
+     * @example
+     *
+     *   [1,2,3,4,5].sample()  -> // Random element
+     *   [1,2,3,4,5].sample(3) -> // Array of 3 random elements
+     *
+     ***/
+    'sample': function(num) {
+      var arr = this.randomize();
+      return arguments.length > 0 ? arr.slice(0, num) : arr[0];
+    },
+
+    /***
+     * @method each(<fn>, [index] = 0, [loop] = false)
+     * @returns Array
+     * @short Runs <fn> against each element in the array. Enhanced version of %Array#forEach%.
+     * @extra Parameters passed to <fn> are identical to %forEach%, ie. the first parameter is the current element, second parameter is the current index, and third parameter is the array itself. If <fn> returns %false% at any time it will break out of the loop. Once %each% finishes, it will return the array. If [index] is passed, <fn> will begin at that index and work its way to the end. If [loop] is true, it will then start over from the beginning of the array and continue until it reaches [index] - 1.
+     * @example
+     *
+     *   [1,2,3,4].each(function(n) {
+     *     // Called 4 times: 1, 2, 3, 4
+     *   });
+     *   [1,2,3,4].each(function(n) {
+     *     // Called 4 times: 3, 4, 1, 2
+     *   }, 2, true);
+     *
+     ***/
+    'each': function(fn, index, loop) {
+      arrayEach(this, fn, index, loop);
+      return this;
+    },
+
+    /***
+     * @method add(<el>, [index])
+     * @returns Array
+     * @short Adds <el> to the array.
+     * @extra If [index] is specified, it will add at [index], otherwise adds to the end of the array. %add% behaves like %concat% in that if <el> is an array it will be joined, not inserted. This method will change the array! Use %include% for a non-destructive alias. Also, %insert% is provided as an alias that reads better when using an index.
+     * @example
+     *
+     *   [1,2,3,4].add(5)       -> [1,2,3,4,5]
+     *   [1,2,3,4].add([5,6,7]) -> [1,2,3,4,5,6,7]
+     *   [1,2,3,4].insert(8, 1) -> [1,8,2,3,4]
+     *
+     ***/
+    'add': function(el, index) {
+      if(!isNumber(number(index)) || isNaN(index)) index = this.length;
+      array.prototype.splice.apply(this, [index, 0].concat(el));
+      return this;
+    },
+
+    /***
+     * @method remove([f1], [f2], ...)
+     * @returns Array
+     * @short Removes any element in the array that matches [f1], [f2], etc.
+     * @extra Will match a string, number, array, object, or alternately test against a function or regex. This method will change the array! Use %exclude% for a non-destructive alias. This method implements @array_matching.
+     * @example
+     *
+     *   [1,2,3].remove(3)         -> [1,2]
+     *   ['a','b','c'].remove(/b/) -> ['a','c']
+     +   [{a:1},{b:2}].remove(function(n) {
+     *     return n['a'] == 1;
+     *   });                       -> [{b:2}]
+     *
+     ***/
+    'remove': function() {
+      var arr = this;
+      multiArgs(arguments, function(f) {
+        var i = 0, matcher = getMatcher(f);
+        while(i < arr.length) {
+          if(matcher(arr[i], i, arr)) {
+            arr.splice(i, 1);
+          } else {
+            i++;
+          }
+        }
+      });
+      return arr;
+    },
+
+    /***
+     * @method compact([all] = false)
+     * @returns Array
+     * @short Removes all instances of %undefined%, %null%, and %NaN% from the array.
+     * @extra If [all] is %true%, all "falsy" elements will be removed. This includes empty strings, 0, and false.
+     * @example
+     *
+     *   [1,null,2,undefined,3].compact() -> [1,2,3]
+     *   [1,'',2,false,3].compact()       -> [1,'',2,false,3]
+     *   [1,'',2,false,3].compact(true)   -> [1,2,3]
+     *
+     ***/
+    'compact': function(all) {
+      var result = [];
+      arrayEach(this, function(el, i) {
+        if(isArray(el)) {
+          result.push(el.compact());
+        } else if(all && el) {
+          result.push(el);
+        } else if(!all && el != null && el.valueOf() === el.valueOf()) {
+          result.push(el);
+        }
+      });
+      return result;
+    },
+
+    /***
+     * @method groupBy(<map>, [fn])
+     * @returns Object
+     * @short Groups the array by <map>.
+     * @extra Will return an object with keys equal to the grouped values. <map> may be a mapping function, or a string acting as a shortcut. Optionally calls [fn] for each group.
+     * @example
+     *
+     *   ['fee','fi','fum'].groupBy('length') -> { 2: ['fi'], 3: ['fee','fum'] }
+     +   [{age:35,name:'ken'},{age:15,name:'bob'}].groupBy(function(n) {
+     *     return n.age;
+     *   });                                  -> { 35: [{age:35,name:'ken'}], 15: [{age:15,name:'bob'}] }
+     *
+     ***/
+    'groupBy': function(map, fn) {
+      var arr = this, result = {}, key;
+      arrayEach(arr, function(el, index) {
+        key = transformArgument(el, map, arr, [el, index, arr]);
+        if(!result[key]) result[key] = [];
+        result[key].push(el);
+      });
+      if(fn) {
+        iterateOverObject(result, fn);
+      }
+      return result;
+    },
+
+    /***
+     * @method none(<f>)
+     * @returns Boolean
+     * @short Returns true if none of the elements in the array match <f>.
+     * @extra <f> will match a string, number, array, object, or alternately test against a function or regex. This method implements @array_matching.
+     * @example
+     *
+     *   [1,2,3].none(5)         -> true
+     *   ['a','b','c'].none(/b/) -> false
+     +   [{a:1},{b:2}].none(function(n) {
+     *     return n['a'] > 1;
+     *   });                     -> true
+     *
+     ***/
+    'none': function() {
+      return !this.any.apply(this, arguments);
+    }
+
+
+  });
+
+
+  // Aliases
+
+  extend(array, true, true, {
+
+    /***
+     * @method all()
+     * @alias every
+     *
+     ***/
+    'all': array.prototype.every,
+
+    /*** @method any()
+     * @alias some
+     *
+     ***/
+    'any': array.prototype.some,
+
+    /***
+     * @method insert()
+     * @alias add
+     *
+     ***/
+    'insert': array.prototype.add
+
+  });
+
+
+  /***
+   * Object module
+   * Enumerable methods on objects
+   *
+   ***/
+
+   function keysWithObjectCoercion(obj) {
+     return object.keys(coercePrimitiveToObject(obj));
+   }
+
+  /***
+   * @method [enumerable](<obj>)
+   * @returns Boolean
+   * @short Enumerable methods in the Array package are also available to the Object class. They will perform their normal operations for every property in <obj>.
+   * @extra In cases where a callback is used, instead of %element, index%, the callback will instead be passed %key, value%. Enumerable methods are also available to extended objects as instance methods.
+   *
+   * @set
+   *   each
+   *   map
+   *   any
+   *   all
+   *   none
+   *   count
+   *   find
+   *   findAll
+   *   reduce
+   *   isEmpty
+   *   sum
+   *   average
+   *   min
+   *   max
+   *   least
+   *   most
+   *
+   * @example
+   *
+   *   Object.any({foo:'bar'}, 'bar')            -> true
+   *   Object.extended({foo:'bar'}).any('bar')   -> true
+   *   Object.isEmpty({})                        -> true
+   +   Object.map({ fred: { age: 52 } }, 'age'); -> { fred: 52 }
+   *
+   ***/
+
+  function buildEnumerableMethods(names, mapping) {
+    extendSimilar(object, false, true, names, function(methods, name) {
+      methods[name] = function(obj, arg1, arg2) {
+        var result, coerced = keysWithObjectCoercion(obj), matcher;
+        if(!mapping) {
+          matcher = getMatcher(arg1, true);
+        }
+        result = array.prototype[name].call(coerced, function(key) {
+          var value = obj[key];
+          if(mapping) {
+            return transformArgument(value, arg1, obj, [key, value, obj]);
+          } else {
+            return matcher(value, key, obj);
+          }
+        }, arg2);
+        if(isArray(result)) {
+          // The method has returned an array of keys so use this array
+          // to build up the resulting object in the form we want it in.
+          result = result.reduce(function(o, key, i) {
+            o[key] = obj[key];
+            return o;
+          }, {});
+        }
+        return result;
+      };
+    });
+    buildObjectInstanceMethods(names, Hash);
+  }
+
+  function exportSortAlgorithm() {
+    array[AlphanumericSort] = collateStrings;
+  }
+
+  extend(object, false, true, {
+
+    'map': function(obj, map) {
+      var result = {}, key, value;
+      for(key in obj) {
+        if(!hasOwnProperty(obj, key)) continue;
+        value = obj[key];
+        result[key] = transformArgument(value, map, obj, [key, value, obj]);
+      }
+      return result;
+    },
+
+    'reduce': function(obj) {
+      var values = keysWithObjectCoercion(obj).map(function(key) {
+        return obj[key];
+      });
+      return values.reduce.apply(values, multiArgs(arguments, null, 1));
+    },
+
+    'each': function(obj, fn) {
+      checkCallback(fn);
+      iterateOverObject(obj, fn);
+      return obj;
+    },
+
+    /***
+     * @method size(<obj>)
+     * @returns Number
+     * @short Returns the number of properties in <obj>.
+     * @extra %size% is available as an instance method on extended objects.
+     * @example
+     *
+     *   Object.size({ foo: 'bar' }) -> 1
+     *
+     ***/
+    'size': function (obj) {
+      return keysWithObjectCoercion(obj).length;
+    }
+
+  });
+
+  var EnumerableFindingMethods = 'any,all,none,count,find,findAll,isEmpty'.split(',');
+  var EnumerableMappingMethods = 'sum,average,min,max,least,most'.split(',');
+  var EnumerableOtherMethods   = 'map,reduce,size'.split(',');
+  var EnumerableMethods        = EnumerableFindingMethods.concat(EnumerableMappingMethods).concat(EnumerableOtherMethods);
+
+  buildEnhancements();
+  buildAlphanumericSort();
+  buildEnumerableMethods(EnumerableFindingMethods);
+  buildEnumerableMethods(EnumerableMappingMethods, true);
+  buildObjectInstanceMethods(EnumerableOtherMethods, Hash);
+  exportSortAlgorithm();
+
level2/node_modules/sugar/lib/core.js
@@ -0,0 +1,577 @@
+
+  'use strict';
+
+  /***
+   * @package Core
+   * @description Internal utility and common methods.
+   ***/
+
+
+  // A few optimizations for Google Closure Compiler will save us a couple kb in the release script.
+  var object = Object, array = Array, regexp = RegExp, date = Date, string = String, number = Number, math = Math, Undefined;
+
+  // The global context
+  var globalContext = typeof global !== 'undefined' ? global : this;
+
+  // Internal toString
+  var internalToString = object.prototype.toString;
+
+  // Internal hasOwnProperty
+  var internalHasOwnProperty = object.prototype.hasOwnProperty;
+
+  // defineProperty exists in IE8 but will error when trying to define a property on
+  // native objects. IE8 does not have defineProperies, however, so this check saves a try/catch block.
+  var definePropertySupport = object.defineProperty && object.defineProperties;
+
+  // Are regexes type function?
+  var regexIsFunction = typeof regexp() === 'function';
+
+  // Do strings have no keys?
+  var noKeysInStringObjects = !('0' in new string('a'));
+
+  // Type check methods need a way to be accessed dynamically.
+  var typeChecks = {};
+
+  // Classes that can be matched by value
+  var matchedByValueReg = /^\[object Date|Array|String|Number|RegExp|Boolean|Arguments\]$/;
+
+  // Class initializers and class helpers
+  var ClassNames = 'Boolean,Number,String,Array,Date,RegExp,Function'.split(',');
+
+  var isBoolean  = buildPrimitiveClassCheck('boolean', ClassNames[0]);
+  var isNumber   = buildPrimitiveClassCheck('number',  ClassNames[1]);
+  var isString   = buildPrimitiveClassCheck('string',  ClassNames[2]);
+
+  var isArray    = buildClassCheck(ClassNames[3]);
+  var isDate     = buildClassCheck(ClassNames[4]);
+  var isRegExp   = buildClassCheck(ClassNames[5]);
+
+
+  // Wanted to enhance performance here by using simply "typeof"
+  // but Firefox has two major issues that make this impossible,
+  // one fixed, the other not. Despite being typeof "function"
+  // the objects below still report in as [object Function], so
+  // we need to perform a full class check here.
+  //
+  // 1. Regexes can be typeof "function" in FF < 3
+  //    https://bugzilla.mozilla.org/show_bug.cgi?id=61911 (fixed)
+  //
+  // 2. HTMLEmbedElement and HTMLObjectElement are be typeof "function"
+  //    https://bugzilla.mozilla.org/show_bug.cgi?id=268945 (won't fix)
+  //
+  var isFunction = buildClassCheck(ClassNames[6]);
+
+  function isClass(obj, klass, cached) {
+    var k = cached || className(obj);
+    return k === '[object '+klass+']';
+  }
+
+  function buildClassCheck(klass) {
+    var fn = (klass === 'Array' && array.isArray) || function(obj, cached) {
+      return isClass(obj, klass, cached);
+    };
+    typeChecks[klass] = fn;
+    return fn;
+  }
+
+  function buildPrimitiveClassCheck(type, klass) {
+    var fn = function(obj) {
+      if(isObjectType(obj)) {
+        return isClass(obj, klass);
+      }
+      return typeof obj === type;
+    }
+    typeChecks[klass] = fn;
+    return fn;
+  }
+
+  function className(obj) {
+    return internalToString.call(obj);
+  }
+
+  function initializeClasses() {
+    initializeClass(object);
+    iterateOverObject(ClassNames, function(i,name) {
+      initializeClass(globalContext[name]);
+    });
+  }
+
+  function initializeClass(klass) {
+    if(klass['SugarMethods']) return;
+    defineProperty(klass, 'SugarMethods', {});
+    extend(klass, false, true, {
+      'extend': function(methods, override, instance) {
+        extend(klass, instance !== false, override, methods);
+      },
+      'sugarRestore': function() {
+        return batchMethodExecute(this, klass, arguments, function(target, name, m) {
+          defineProperty(target, name, m.method);
+        });
+      },
+      'sugarRevert': function() {
+        return batchMethodExecute(this, klass, arguments, function(target, name, m) {
+          if(m['existed']) {
+            defineProperty(target, name, m['original']);
+          } else {
+            delete target[name];
+          }
+        });
+      }
+    });
+  }
+
+  // Class extending methods
+
+  function extend(klass, instance, override, methods) {
+    var extendee = instance ? klass.prototype : klass;
+    initializeClass(klass);
+    iterateOverObject(methods, function(name, extendedFn) {
+      var nativeFn = extendee[name],
+          existed  = hasOwnProperty(extendee, name);
+      if(isFunction(override) && nativeFn) {
+        extendedFn = wrapNative(nativeFn, extendedFn, override);
+      }
+      if(override !== false || !nativeFn) {
+        defineProperty(extendee, name, extendedFn);
+      }
+      // If the method is internal to Sugar, then
+      // store a reference so it can be restored later.
+      klass['SugarMethods'][name] = {
+        'method':   extendedFn,
+        'existed':  existed,
+        'original': nativeFn,
+        'instance': instance
+      };
+    });
+  }
+
+  function extendSimilar(klass, instance, override, set, fn) {
+    var methods = {};
+    set = isString(set) ? set.split(',') : set;
+    set.forEach(function(name, i) {
+      fn(methods, name, i);
+    });
+    extend(klass, instance, override, methods);
+  }
+
+  function batchMethodExecute(target, klass, args, fn) {
+    var all = args.length === 0, methods = multiArgs(args), changed = false;
+    iterateOverObject(klass['SugarMethods'], function(name, m) {
+      if(all || methods.indexOf(name) !== -1) {
+        changed = true;
+        fn(m['instance'] ? target.prototype : target, name, m);
+      }
+    });
+    return changed;
+  }
+
+  function wrapNative(nativeFn, extendedFn, condition) {
+    return function(a) {
+      return condition.apply(this, arguments) ?
+             extendedFn.apply(this, arguments) :
+             nativeFn.apply(this, arguments);
+    }
+  }
+
+  function defineProperty(target, name, method) {
+    if(definePropertySupport) {
+      object.defineProperty(target, name, {
+        'value': method,
+        'configurable': true,
+        'enumerable': false,
+        'writable': true
+      });
+    } else {
+      target[name] = method;
+    }
+  }
+
+
+  // Argument helpers
+
+  function multiArgs(args, fn, from) {
+    var result = [], i = from || 0, len;
+    for(len = args.length; i < len; i++) {
+      result.push(args[i]);
+      if(fn) fn.call(args, args[i], i);
+    }
+    return result;
+  }
+
+  function flattenedArgs(args, fn, from) {
+    var arg = args[from || 0];
+    if(isArray(arg)) {
+      args = arg;
+      from = 0;
+    }
+    return multiArgs(args, fn, from);
+  }
+
+  function checkCallback(fn) {
+    if(!fn || !fn.call) {
+      throw new TypeError('Callback is not callable');
+    }
+  }
+
+
+  // General helpers
+
+  function isDefined(o) {
+    return o !== Undefined;
+  }
+
+  function isUndefined(o) {
+    return o === Undefined;
+  }
+
+
+  // Object helpers
+
+  function hasProperty(obj, prop) {
+    return !isPrimitiveType(obj) && prop in obj;
+  }
+
+  function hasOwnProperty(obj, prop) {
+    return !!obj && internalHasOwnProperty.call(obj, prop);
+  }
+
+  function isObjectType(obj) {
+    // 1. Check for null
+    // 2. Check for regexes in environments where they are "functions".
+    return !!obj && (typeof obj === 'object' || (regexIsFunction && isRegExp(obj)));
+  }
+
+  function isPrimitiveType(obj) {
+    var type = typeof obj;
+    return obj == null || type === 'string' || type === 'number' || type === 'boolean';
+  }
+
+  function isPlainObject(obj, klass) {
+    klass = klass || className(obj);
+    try {
+      // Not own constructor property must be Object
+      // This code was borrowed from jQuery.isPlainObject
+      if (obj && obj.constructor &&
+            !hasOwnProperty(obj, 'constructor') &&
+            !hasOwnProperty(obj.constructor.prototype, 'isPrototypeOf')) {
+        return false;
+      }
+    } catch (e) {
+      // IE8,9 Will throw exceptions on certain host objects.
+      return false;
+    }
+    // === on the constructor is not safe across iframes
+    // 'hasOwnProperty' ensures that the object also inherits
+    // from Object, which is false for DOMElements in IE.
+    return !!obj && klass === '[object Object]' && 'hasOwnProperty' in obj;
+  }
+
+  function iterateOverObject(obj, fn) {
+    var key;
+    for(key in obj) {
+      if(!hasOwnProperty(obj, key)) continue;
+      if(fn.call(obj, key, obj[key], obj) === false) break;
+    }
+  }
+
+  function simpleRepeat(n, fn) {
+    for(var i = 0; i < n; i++) {
+      fn(i);
+    }
+  }
+
+  function simpleMerge(target, source) {
+    iterateOverObject(source, function(key) {
+      target[key] = source[key];
+    });
+    return target;
+  }
+
+   // Make primtives types like strings into objects.
+   function coercePrimitiveToObject(obj) {
+     if(isPrimitiveType(obj)) {
+       obj = object(obj);
+     }
+     if(noKeysInStringObjects && isString(obj)) {
+       forceStringCoercion(obj);
+     }
+     return obj;
+   }
+
+   // Force strings to have their indexes set in
+   // environments that don't do this automatically.
+   function forceStringCoercion(obj) {
+     var i = 0, chr;
+     while(chr = obj.charAt(i)) {
+       obj[i++] = chr;
+     }
+   }
+
+  // Hash definition
+
+  function Hash(obj) {
+    simpleMerge(this, coercePrimitiveToObject(obj));
+  };
+
+  Hash.prototype.constructor = object;
+
+  // Math helpers
+
+  var abs   = math.abs;
+  var pow   = math.pow;
+  var ceil  = math.ceil;
+  var floor = math.floor;
+  var round = math.round;
+  var min   = math.min;
+  var max   = math.max;
+
+  function withPrecision(val, precision, fn) {
+    var multiplier = pow(10, abs(precision || 0));
+    fn = fn || round;
+    if(precision < 0) multiplier = 1 / multiplier;
+    return fn(val * multiplier) / multiplier;
+  }
+
+  // Full width number helpers
+
+  var HalfWidthZeroCode = 0x30;
+  var HalfWidthNineCode = 0x39;
+  var FullWidthZeroCode = 0xff10;
+  var FullWidthNineCode = 0xff19;
+
+  var HalfWidthPeriod = '.';
+  var FullWidthPeriod = '๏ผŽ';
+  var HalfWidthComma  = ',';
+
+  // Used here and later in the Date package.
+  var FullWidthDigits   = '';
+
+  var NumberNormalizeMap = {};
+  var NumberNormalizeReg;
+
+  function codeIsNumeral(code) {
+    return (code >= HalfWidthZeroCode && code <= HalfWidthNineCode) ||
+           (code >= FullWidthZeroCode && code <= FullWidthNineCode);
+  }
+
+  function buildNumberHelpers() {
+    var digit, i;
+    for(i = 0; i <= 9; i++) {
+      digit = chr(i + FullWidthZeroCode);
+      FullWidthDigits += digit;
+      NumberNormalizeMap[digit] = chr(i + HalfWidthZeroCode);
+    }
+    NumberNormalizeMap[HalfWidthComma] = '';
+    NumberNormalizeMap[FullWidthPeriod] = HalfWidthPeriod;
+    // Mapping this to itself to easily be able to easily
+    // capture it in stringToNumber to detect decimals later.
+    NumberNormalizeMap[HalfWidthPeriod] = HalfWidthPeriod;
+    NumberNormalizeReg = regexp('[' + FullWidthDigits + FullWidthPeriod + HalfWidthComma + HalfWidthPeriod + ']', 'g');
+  }
+
+  // String helpers
+
+  function chr(num) {
+    return string.fromCharCode(num);
+  }
+
+  // WhiteSpace/LineTerminator as defined in ES5.1 plus Unicode characters in the Space, Separator category.
+  function getTrimmableCharacters() {
+    return '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF';
+  }
+
+  function repeatString(str, num) {
+    var result = '', str = str.toString();
+    while (num > 0) {
+      if (num & 1) {
+        result += str;
+      }
+      if (num >>= 1) {
+        str += str;
+      }
+    }
+    return result;
+  }
+
+  // Returns taking into account full-width characters, commas, and decimals.
+  function stringToNumber(str, base) {
+    var sanitized, isDecimal;
+    sanitized = str.replace(NumberNormalizeReg, function(chr) {
+      var replacement = NumberNormalizeMap[chr];
+      if(replacement === HalfWidthPeriod) {
+        isDecimal = true;
+      }
+      return replacement;
+    });
+    return isDecimal ? parseFloat(sanitized) : parseInt(sanitized, base || 10);
+  }
+
+
+  // Used by Number and Date
+
+  function padNumber(num, place, sign, base) {
+    var str = abs(num).toString(base || 10);
+    str = repeatString('0', place - str.replace(/\.\d+/, '').length) + str;
+    if(sign || num < 0) {
+      str = (num < 0 ? '-' : '+') + str;
+    }
+    return str;
+  }
+
+  function getOrdinalizedSuffix(num) {
+    if(num >= 11 && num <= 13) {
+      return 'th';
+    } else {
+      switch(num % 10) {
+        case 1:  return 'st';
+        case 2:  return 'nd';
+        case 3:  return 'rd';
+        default: return 'th';
+      }
+    }
+  }
+
+
+  // RegExp helpers
+
+  function getRegExpFlags(reg, add) {
+    var flags = '';
+    add = add || '';
+    function checkFlag(prop, flag) {
+      if(prop || add.indexOf(flag) > -1) {
+        flags += flag;
+      }
+    }
+    checkFlag(reg.multiline, 'm');
+    checkFlag(reg.ignoreCase, 'i');
+    checkFlag(reg.global, 'g');
+    checkFlag(reg.sticky, 'y');
+    return flags;
+  }
+
+  function escapeRegExp(str) {
+    if(!isString(str)) str = string(str);
+    return str.replace(/([\\/\'*+?|()\[\]{}.^$])/g,'\\$1');
+  }
+
+
+  // Date helpers
+
+  function callDateGet(d, method) {
+    return d['get' + (d._utc ? 'UTC' : '') + method]();
+  }
+
+  function callDateSet(d, method, value) {
+    return d['set' + (d._utc && method != 'ISOWeek' ? 'UTC' : '') + method](value);
+  }
+
+  // Used by Array#unique and Object.equal
+
+  function stringify(thing, stack) {
+    var type = typeof thing,
+        thingIsObject,
+        thingIsArray,
+        klass, value,
+        arr, key, i, len;
+
+    // Return quickly if string to save cycles
+    if(type === 'string') return thing;
+
+    klass         = internalToString.call(thing)
+    thingIsObject = isPlainObject(thing, klass);
+    thingIsArray  = isArray(thing, klass);
+
+    if(thing != null && thingIsObject || thingIsArray) {
+      // This method for checking for cyclic structures was egregiously stolen from
+      // the ingenious method by @kitcambridge from the Underscore script:
+      // https://github.com/documentcloud/underscore/issues/240
+      if(!stack) stack = [];
+      // Allowing a step into the structure before triggering this
+      // script to save cycles on standard JSON structures and also to
+      // try as hard as possible to catch basic properties that may have
+      // been modified.
+      if(stack.length > 1) {
+        i = stack.length;
+        while (i--) {
+          if (stack[i] === thing) {
+            return 'CYC';
+          }
+        }
+      }
+      stack.push(thing);
+      value = thing.valueOf() + string(thing.constructor);
+      arr = thingIsArray ? thing : object.keys(thing).sort();
+      for(i = 0, len = arr.length; i < len; i++) {
+        key = thingIsArray ? i : arr[i];
+        value += key + stringify(thing[key], stack);
+      }
+      stack.pop();
+    } else if(1 / thing === -Infinity) {
+      value = '-0';
+    } else {
+      value = string(thing && thing.valueOf ? thing.valueOf() : thing);
+    }
+    return type + klass + value;
+  }
+
+  function isEqual(a, b) {
+    if(a === b) {
+      // Return quickly up front when matching by reference,
+      // but be careful about 0 !== -0.
+      return a !== 0 || 1 / a === 1 / b;
+    } else if(objectIsMatchedByValue(a) && objectIsMatchedByValue(b)) {
+      return stringify(a) === stringify(b);
+    }
+    return false;
+  }
+
+  function objectIsMatchedByValue(obj) {
+    // Only known objects are matched by value. This is notably excluding functions, DOM Elements, and instances of
+    // user-created classes. The latter can arguably be matched by value, but distinguishing between these and
+    // host objects -- which should never be compared by value -- is very tricky so not dealing with it here.
+    var klass = className(obj);
+    return matchedByValueReg.test(klass) || isPlainObject(obj, klass);
+  }
+
+
+  // Used by Array#at and String#at
+
+  function getEntriesForIndexes(obj, args, isString) {
+    var result,
+        length    = obj.length,
+        argsLen   = args.length,
+        overshoot = args[argsLen - 1] !== false,
+        multiple  = argsLen > (overshoot ? 1 : 2);
+    if(!multiple) {
+      return entryAtIndex(obj, length, args[0], overshoot, isString);
+    }
+    result = [];
+    multiArgs(args, function(index) {
+      if(isBoolean(index)) return false;
+      result.push(entryAtIndex(obj, length, index, overshoot, isString));
+    });
+    return result;
+  }
+
+  function entryAtIndex(obj, length, index, overshoot, isString) {
+    if(overshoot) {
+      index = index % length;
+      if(index < 0) index = length + index;
+    }
+    return isString ? obj.charAt(index) : obj[index];
+  }
+
+
+  // Object class methods implemented as instance methods
+
+  function buildObjectInstanceMethods(set, target) {
+    extendSimilar(target, true, false, set, function(methods, name) {
+      methods[name + (name === 'equal' ? 's' : '')] = function() {
+        return object[name].apply(null, [this].concat(multiArgs(arguments)));
+      }
+    });
+  }
+
+  initializeClasses();
+  buildNumberHelpers();
+
level2/node_modules/sugar/lib/date.js
@@ -0,0 +1,2453 @@
+
+  'use strict';
+
+  /***
+   * @package Date
+   * @dependency core
+   * @description Date parsing and formatting, relative formats like "1 minute ago", Number methods like "daysAgo", localization support with default English locale definition.
+   *
+   ***/
+
+  var English;
+  var CurrentLocalization;
+
+  var TimeFormat = ['ampm','hour','minute','second','ampm','utc','offset_sign','offset_hours','offset_minutes','ampm']
+  var DecimalReg = '(?:[,.]\\d+)?';
+  var HoursReg   = '\\d{1,2}' + DecimalReg;
+  var SixtyReg   = '[0-5]\\d' + DecimalReg;
+  var RequiredTime = '({t})?\\s*('+HoursReg+')(?:{h}('+SixtyReg+')?{m}(?::?('+SixtyReg+'){s})?\\s*(?:({t})|(Z)|(?:([+-])(\\d{2,2})(?::?(\\d{2,2}))?)?)?|\\s*({t}))';
+
+  var KanjiDigits = 'ใ€‡ไธ€ไบŒไธ‰ๅ››ไบ”ๅ…ญไธƒๅ…ซไนๅ็™พๅƒไธ‡';
+  var AsianDigitMap = {};
+  var AsianDigitReg;
+
+  var DateArgumentUnits;
+  var DateUnitsReversed;
+  var CoreDateFormats = [];
+  var CompiledOutputFormats = {};
+
+  var DateFormatTokens = {
+
+    'yyyy': function(d) {
+      return callDateGet(d, 'FullYear');
+    },
+
+    'yy': function(d) {
+      return callDateGet(d, 'FullYear') % 100;
+    },
+
+    'ord': function(d) {
+      var date = callDateGet(d, 'Date');
+      return date + getOrdinalizedSuffix(date);
+    },
+
+    'tz': function(d) {
+      return d.getUTCOffset();
+    },
+
+    'isotz': function(d) {
+      return d.getUTCOffset(true);
+    },
+
+    'Z': function(d) {
+      return d.getUTCOffset();
+    },
+
+    'ZZ': function(d) {
+      return d.getUTCOffset().replace(/(\d{2})$/, ':$1');
+    }
+
+  };
+
+  var DateUnits = [
+    {
+      name: 'year',
+      method: 'FullYear',
+      ambiguous: true,
+      multiplier: function(d) {
+        var adjust = d ? (d.isLeapYear() ? 1 : 0) : 0.25;
+        return (365 + adjust) * 24 * 60 * 60 * 1000;
+      }
+    },
+    {
+      name: 'month',
+      error: 0.919, // Feb 1-28 over 1 month
+      method: 'Month',
+      ambiguous: true,
+      multiplier: function(d, ms) {
+        var days = 30.4375, inMonth;
+        if(d) {
+          inMonth = d.daysInMonth();
+          if(ms <= inMonth.days()) {
+            days = inMonth;
+          }
+        }
+        return days * 24 * 60 * 60 * 1000;
+      }
+    },
+    {
+      name: 'week',
+      method: 'ISOWeek',
+      multiplier: function() {
+        return 7 * 24 * 60 * 60 * 1000;
+      }
+    },
+    {
+      name: 'day',
+      error: 0.958, // DST traversal over 1 day
+      method: 'Date',
+      ambiguous: true,
+      multiplier: function() {
+        return 24 * 60 * 60 * 1000;
+      }
+    },
+    {
+      name: 'hour',
+      method: 'Hours',
+      multiplier: function() {
+        return 60 * 60 * 1000;
+      }
+    },
+    {
+      name: 'minute',
+      method: 'Minutes',
+      multiplier: function() {
+        return 60 * 1000;
+      }
+    },
+    {
+      name: 'second',
+      method: 'Seconds',
+      multiplier: function() {
+        return 1000;
+      }
+    },
+    {
+      name: 'millisecond',
+      method: 'Milliseconds',
+      multiplier: function() {
+        return 1;
+      }
+    }
+  ];
+
+
+
+
+  // Date Localization
+
+  var Localizations = {};
+
+  // Localization object
+
+  function Localization(l) {
+    simpleMerge(this, l);
+    this.compiledFormats = CoreDateFormats.concat();
+  }
+
+  Localization.prototype = {
+
+    getMonth: function(n) {
+      if(isNumber(n)) {
+        return n - 1;
+      } else {
+        return this['months'].indexOf(n) % 12;
+      }
+    },
+
+    getWeekday: function(n) {
+      return this['weekdays'].indexOf(n) % 7;
+    },
+
+    getNumber: function(n) {
+      var i;
+      if(isNumber(n)) {
+        return n;
+      } else if(n && (i = this['numbers'].indexOf(n)) !== -1) {
+        return (i + 1) % 10;
+      } else {
+        return 1;
+      }
+    },
+
+    getNumericDate: function(n) {
+      var self = this;
+      return n.replace(regexp(this['num'], 'g'), function(d) {
+        var num = self.getNumber(d);
+        return num || '';
+      });
+    },
+
+    getUnitIndex: function(n) {
+      return this['units'].indexOf(n) % 8;
+    },
+
+    getRelativeFormat: function(adu) {
+      return this.convertAdjustedToFormat(adu, adu[2] > 0 ? 'future' : 'past');
+    },
+
+    getDuration: function(ms) {
+      return this.convertAdjustedToFormat(getAdjustedUnit(ms), 'duration');
+    },
+
+    hasVariant: function(code) {
+      code = code || this.code;
+      return code === 'en' || code === 'en-US' ? true : this['variant'];
+    },
+
+    matchAM: function(str) {
+      return str === this['ampm'][0];
+    },
+
+    matchPM: function(str) {
+      return str && str === this['ampm'][1];
+    },
+
+    convertAdjustedToFormat: function(adu, mode) {
+      var sign, unit, mult,
+          num    = adu[0],
+          u      = adu[1],
+          ms     = adu[2],
+          format = this[mode] || this['relative'];
+      if(isFunction(format)) {
+        return format.call(this, num, u, ms, mode);
+      }
+      mult = this['plural'] && num > 1 ? 1 : 0;
+      unit = this['units'][mult * 8 + u] || this['units'][u];
+      if(this['capitalizeUnit']) unit = simpleCapitalize(unit);
+      sign = this['modifiers'].filter(function(m) { return m.name == 'sign' && m.value == (ms > 0 ? 1 : -1); })[0];
+      return format.replace(/\{(.*?)\}/g, function(full, match) {
+        switch(match) {
+          case 'num': return num;
+          case 'unit': return unit;
+          case 'sign': return sign.src;
+        }
+      });
+    },
+
+    getFormats: function() {
+      return this.cachedFormat ? [this.cachedFormat].concat(this.compiledFormats) : this.compiledFormats;
+    },
+
+    addFormat: function(src, allowsTime, match, variant, iso) {
+      var to = match || [], loc = this, time, timeMarkers, lastIsNumeral;
+
+      src = src.replace(/\s+/g, '[,. ]*');
+      src = src.replace(/\{([^,]+?)\}/g, function(all, k) {
+        var value, arr, result,
+            opt   = k.match(/\?$/),
+            nc    = k.match(/^(\d+)\??$/),
+            slice = k.match(/(\d)(?:-(\d))?/),
+            key   = k.replace(/[^a-z]+$/, '');
+        if(nc) {
+          value = loc['tokens'][nc[1]];
+        } else if(loc[key]) {
+          value = loc[key];
+        } else if(loc[key + 's']) {
+          value = loc[key + 's'];
+          if(slice) {
+            // Can't use filter here as Prototype hijacks the method and doesn't
+            // pass an index, so use a simple loop instead!
+            arr = [];
+            value.forEach(function(m, i) {
+              var mod = i % (loc['units'] ? 8 : value.length);
+              if(mod >= slice[1] && mod <= (slice[2] || slice[1])) {
+                arr.push(m);
+              }
+            });
+            value = arr;
+          }
+          value = arrayToAlternates(value);
+        }
+        if(nc) {
+          result = '(?:' + value + ')';
+        } else {
+          if(!match) {
+            to.push(key);
+          }
+          result = '(' + value + ')';
+        }
+        if(opt) {
+          result += '?';
+        }
+        return result;
+      });
+      if(allowsTime) {
+        time = prepareTime(RequiredTime, loc, iso);
+        timeMarkers = ['t','[\\s\\u3000]'].concat(loc['timeMarker']);
+        lastIsNumeral = src.match(/\\d\{\d,\d\}\)+\??$/);
+        addDateInputFormat(loc, '(?:' + time + ')[,\\s\\u3000]+?' + src, TimeFormat.concat(to), variant);
+        addDateInputFormat(loc, src + '(?:[,\\s]*(?:' + timeMarkers.join('|') + (lastIsNumeral ? '+' : '*') +')' + time + ')?', to.concat(TimeFormat), variant);
+      } else {
+        addDateInputFormat(loc, src, to, variant);
+      }
+    }
+
+  };
+
+
+  // Localization helpers
+
+  function getLocalization(localeCode, fallback) {
+    var loc;
+    if(!isString(localeCode)) localeCode = '';
+    loc = Localizations[localeCode] || Localizations[localeCode.slice(0,2)];
+    if(fallback === false && !loc) {
+      throw new TypeError('Invalid locale.');
+    }
+    return loc || CurrentLocalization;
+  }
+
+  function setLocalization(localeCode, set) {
+    var loc, canAbbreviate;
+
+    function initializeField(name) {
+      var val = loc[name];
+      if(isString(val)) {
+        loc[name] = val.split(',');
+      } else if(!val) {
+        loc[name] = [];
+      }
+    }
+
+    function eachAlternate(str, fn) {
+      str = str.split('+').map(function(split) {
+        return split.replace(/(.+):(.+)$/, function(full, base, suffixes) {
+          return suffixes.split('|').map(function(suffix) {
+            return base + suffix;
+          }).join('|');
+        });
+      }).join('|');
+      return str.split('|').forEach(fn);
+    }
+
+    function setArray(name, abbreviate, multiple) {
+      var arr = [];
+      loc[name].forEach(function(full, i) {
+        if(abbreviate) {
+          full += '+' + full.slice(0,3);
+        }
+        eachAlternate(full, function(day, j) {
+          arr[j * multiple + i] = day.toLowerCase();
+        });
+      });
+      loc[name] = arr;
+    }
+
+    function getDigit(start, stop, allowNumbers) {
+      var str = '\\d{' + start + ',' + stop + '}';
+      if(allowNumbers) str += '|(?:' + arrayToAlternates(loc['numbers']) + ')+';
+      return str;
+    }
+
+    function getNum() {
+      var arr = ['-?\\d+'].concat(loc['articles']);
+      if(loc['numbers']) arr = arr.concat(loc['numbers']);
+      return arrayToAlternates(arr);
+    }
+
+    function setDefault(name, value) {
+      loc[name] = loc[name] || value;
+    }
+
+    function setModifiers() {
+      var arr = [];
+      loc.modifiersByName = {};
+      loc['modifiers'].push({ 'name': 'day', 'src': 'yesterday', 'value': -1 });
+      loc['modifiers'].push({ 'name': 'day', 'src': 'today', 'value': 0 });
+      loc['modifiers'].push({ 'name': 'day', 'src': 'tomorrow', 'value': 1 });
+      loc['modifiers'].forEach(function(modifier) {
+        var name = modifier.name;
+        eachAlternate(modifier.src, function(t) {
+          var locEntry = loc[name];
+          loc.modifiersByName[t] = modifier;
+          arr.push({ name: name, src: t, value: modifier.value });
+          loc[name] = locEntry ? locEntry + '|' + t : t;
+        });
+      });
+      loc['day'] += '|' + arrayToAlternates(loc['weekdays']);
+      loc['modifiers'] = arr;
+    }
+
+    // Initialize the locale
+    loc = new Localization(set);
+    initializeField('modifiers');
+    'months,weekdays,units,numbers,articles,tokens,timeMarker,ampm,timeSuffixes,dateParse,timeParse'.split(',').forEach(initializeField);
+
+    canAbbreviate = !loc['monthSuffix'];
+
+    setArray('months',   canAbbreviate, 12);
+    setArray('weekdays', canAbbreviate, 7);
+    setArray('units', false, 8);
+    setArray('numbers', false, 10);
+
+    setDefault('code', localeCode);
+    setDefault('date', getDigit(1,2, loc['digitDate']));
+    setDefault('year', "'\\d{2}|" + getDigit(4,4));
+    setDefault('num', getNum());
+
+    setModifiers();
+
+    if(loc['monthSuffix']) {
+      loc['month'] = getDigit(1,2);
+      loc['months'] = '1,2,3,4,5,6,7,8,9,10,11,12'.split(',').map(function(n) { return n + loc['monthSuffix']; });
+    }
+    loc['full_month'] = getDigit(1,2) + '|' + arrayToAlternates(loc['months']);
+
+    // The order of these formats is very important. Order is reversed so formats that come
+    // later will take precedence over formats that come before. This generally means that
+    // more specific formats should come later, however, the {year} format should come before
+    // {day}, as 2011 needs to be parsed as a year (2011) and not date (20) + hours (11)
+
+    // If the locale has time suffixes then add a time only format for that locale
+    // that is separate from the core English-based one.
+    if(loc['timeSuffixes'].length > 0) {
+      loc.addFormat(prepareTime(RequiredTime, loc), false, TimeFormat)
+    }
+
+    loc.addFormat('{day}', true);
+    loc.addFormat('{month}' + (loc['monthSuffix'] || ''));
+    loc.addFormat('{year}' + (loc['yearSuffix'] || ''));
+
+    loc['timeParse'].forEach(function(src) {
+      loc.addFormat(src, true);
+    });
+
+    loc['dateParse'].forEach(function(src) {
+      loc.addFormat(src);
+    });
+
+    return Localizations[localeCode] = loc;
+  }
+
+
+  // General helpers
+
+  function addDateInputFormat(locale, format, match, variant) {
+    locale.compiledFormats.unshift({
+      variant: variant,
+      locale: locale,
+      reg: regexp('^' + format + '$', 'i'),
+      to: match
+    });
+  }
+
+  function simpleCapitalize(str) {
+    return str.slice(0,1).toUpperCase() + str.slice(1);
+  }
+
+  function arrayToAlternates(arr) {
+    return arr.filter(function(el) {
+      return !!el;
+    }).join('|');
+  }
+
+  function getNewDate() {
+    var fn = date.SugarNewDate;
+    return fn ? fn() : new date;
+  }
+
+  // Date argument helpers
+
+  function collectDateArguments(args, allowDuration) {
+    var obj;
+    if(isObjectType(args[0])) {
+      return args;
+    } else if (isNumber(args[0]) && !isNumber(args[1])) {
+      return [args[0]];
+    } else if (isString(args[0]) && allowDuration) {
+      return [getDateParamsFromString(args[0]), args[1]];
+    }
+    obj = {};
+    DateArgumentUnits.forEach(function(u,i) {
+      obj[u.name] = args[i];
+    });
+    return [obj];
+  }
+
+  function getDateParamsFromString(str, num) {
+    var match, params = {};
+    match = str.match(/^(\d+)?\s?(\w+?)s?$/i);
+    if(match) {
+      if(isUndefined(num)) {
+        num = parseInt(match[1]) || 1;
+      }
+      params[match[2].toLowerCase()] = num;
+    }
+    return params;
+  }
+
+  // Date iteration helpers
+
+  function iterateOverDateUnits(fn, from, to) {
+    var i, unit;
+    if(isUndefined(to)) to = DateUnitsReversed.length;
+    for(i = from || 0; i < to; i++) {
+      unit = DateUnitsReversed[i];
+      if(fn(unit.name, unit, i) === false) {
+        break;
+      }
+    }
+  }
+
+  // Date parsing helpers
+
+  function getFormatMatch(match, arr) {
+    var obj = {}, value, num;
+    arr.forEach(function(key, i) {
+      value = match[i + 1];
+      if(isUndefined(value) || value === '') return;
+      if(key === 'year') {
+        obj.yearAsString = value.replace(/'/, '');
+      }
+      num = parseFloat(value.replace(/'/, '').replace(/,/, '.'));
+      obj[key] = !isNaN(num) ? num : value.toLowerCase();
+    });
+    return obj;
+  }
+
+  function cleanDateInput(str) {
+    str = str.trim().replace(/^just (?=now)|\.+$/i, '');
+    return convertAsianDigits(str);
+  }
+
+  function convertAsianDigits(str) {
+    return str.replace(AsianDigitReg, function(full, disallowed, match) {
+      var sum = 0, place = 1, lastWasHolder, lastHolder;
+      if(disallowed) return full;
+      match.split('').reverse().forEach(function(letter) {
+        var value = AsianDigitMap[letter], holder = value > 9;
+        if(holder) {
+          if(lastWasHolder) sum += place;
+          place *= value / (lastHolder || 1);
+          lastHolder = value;
+        } else {
+          if(lastWasHolder === false) {
+            place *= 10;
+          }
+          sum += place * value;
+        }
+        lastWasHolder = holder;
+      });
+      if(lastWasHolder) sum += place;
+      return sum;
+    });
+  }
+
+  function getExtendedDate(f, localeCode, prefer, forceUTC) {
+    var d, relative, baseLocalization, afterCallbacks, loc, set, unit, unitIndex, weekday, num, tmp;
+
+    d = getNewDate();
+    afterCallbacks = [];
+
+    function afterDateSet(fn) {
+      afterCallbacks.push(fn);
+    }
+
+    function fireCallbacks() {
+      afterCallbacks.forEach(function(fn) {
+        fn.call();
+      });
+    }
+
+    function setWeekdayOfMonth() {
+      var w = d.getWeekday();
+      d.setWeekday((7 * (set['num'] - 1)) + (w > weekday ? weekday + 7 : weekday));
+    }
+
+    function setUnitEdge() {
+      var modifier = loc.modifiersByName[set['edge']];
+      iterateOverDateUnits(function(name) {
+        if(isDefined(set[name])) {
+          unit = name;
+          return false;
+        }
+      }, 4);
+      if(unit === 'year') set.specificity = 'month';
+      else if(unit === 'month' || unit === 'week') set.specificity = 'day';
+      d[(modifier.value < 0 ? 'endOf' : 'beginningOf') + simpleCapitalize(unit)]();
+      // This value of -2 is arbitrary but it's a nice clean way to hook into this system.
+      if(modifier.value === -2) d.reset();
+    }
+
+    function separateAbsoluteUnits() {
+      var params;
+      iterateOverDateUnits(function(name, u, i) {
+        if(name === 'day') name = 'date';
+        if(isDefined(set[name])) {
+          // If there is a time unit set that is more specific than
+          // the matched unit we have a string like "5:30am in 2 minutes",
+          // which is meaningless, so invalidate the date...
+          if(i >= unitIndex) {
+            invalidateDate(d);
+            return false;
+          }
+          // ...otherwise set the params to set the absolute date
+          // as a callback after the relative date has been set.
+          params = params || {};
+          params[name] = set[name];
+          delete set[name];
+        }
+      });
+      if(params) {
+        afterDateSet(function() {
+          d.set(params, true);
+        });
+      }
+    }
+
+    d.utc(forceUTC);
+
+    if(isDate(f)) {
+      // If the source here is already a date object, then the operation
+      // is the same as cloning the date, which preserves the UTC flag.
+      d.utc(f.isUTC()).setTime(f.getTime());
+    } else if(isNumber(f)) {
+      d.setTime(f);
+    } else if(isObjectType(f)) {
+      d.set(f, true);
+      set = f;
+    } else if(isString(f)) {
+
+      // The act of getting the localization will pre-initialize
+      // if it is missing and add the required formats.
+      baseLocalization = getLocalization(localeCode);
+
+      // Clean the input and convert Kanji based numerals if they exist.
+      f = cleanDateInput(f);
+
+      if(baseLocalization) {
+        iterateOverObject(baseLocalization.getFormats(), function(i, dif) {
+          var match = f.match(dif.reg);
+          if(match) {
+
+            loc = dif.locale;
+            set = getFormatMatch(match, dif.to, loc);
+            loc.cachedFormat = dif;
+
+
+            if(set['utc']) {
+              d.utc();
+            }
+
+            if(set.timestamp) {
+              set = set.timestamp;
+              return false;
+            }
+
+            // If there's a variant (crazy Endian American format), swap the month and day.
+            if(dif.variant && !isString(set['month']) && (isString(set['date']) || baseLocalization.hasVariant(localeCode))) {
+              tmp = set['month'];
+              set['month'] = set['date'];
+              set['date']  = tmp;
+            }
+
+            // If the year is 2 digits then get the implied century.
+            if(set['year'] && set.yearAsString.length === 2) {
+              set['year'] = getYearFromAbbreviation(set['year']);
+            }
+
+            // Set the month which may be localized.
+            if(set['month']) {
+              set['month'] = loc.getMonth(set['month']);
+              if(set['shift'] && !set['unit']) set['unit'] = loc['units'][7];
+            }
+
+            // If there is both a weekday and a date, the date takes precedence.
+            if(set['weekday'] && set['date']) {
+              delete set['weekday'];
+            // Otherwise set a localized weekday.
+            } else if(set['weekday']) {
+              set['weekday'] = loc.getWeekday(set['weekday']);
+              if(set['shift'] && !set['unit']) set['unit'] = loc['units'][5];
+            }
+
+            // Relative day localizations such as "today" and "tomorrow".
+            if(set['day'] && (tmp = loc.modifiersByName[set['day']])) {
+              set['day'] = tmp.value;
+              d.reset();
+              relative = true;
+            // If the day is a weekday, then set that instead.
+            } else if(set['day'] && (weekday = loc.getWeekday(set['day'])) > -1) {
+              delete set['day'];
+              if(set['num'] && set['month']) {
+                // If we have "the 2nd tuesday of June", set the day to the beginning of the month, then
+                // set the weekday after all other properties have been set. The weekday needs to be set
+                // after the actual set because it requires overriding the "prefer" argument which
+                // could unintentionally send the year into the future, past, etc.
+                afterDateSet(setWeekdayOfMonth);
+                set['day'] = 1;
+              } else {
+                set['weekday'] = weekday;
+              }
+            }
+
+            if(set['date'] && !isNumber(set['date'])) {
+              set['date'] = loc.getNumericDate(set['date']);
+            }
+
+            // If the time is 1pm-11pm advance the time by 12 hours.
+            if(loc.matchPM(set['ampm']) && set['hour'] < 12) {
+              set['hour'] += 12;
+            } else if(loc.matchAM(set['ampm']) && set['hour'] === 12) {
+              set['hour'] = 0;
+            }
+
+            // Adjust for timezone offset
+            if('offset_hours' in set || 'offset_minutes' in set) {
+              d.utc();
+              set['offset_minutes'] = set['offset_minutes'] || 0;
+              set['offset_minutes'] += set['offset_hours'] * 60;
+              if(set['offset_sign'] === '-') {
+                set['offset_minutes'] *= -1;
+              }
+              set['minute'] -= set['offset_minutes'];
+            }
+
+            // Date has a unit like "days", "months", etc. are all relative to the current date.
+            if(set['unit']) {
+              relative  = true;
+              num       = loc.getNumber(set['num']);
+              unitIndex = loc.getUnitIndex(set['unit']);
+              unit      = English['units'][unitIndex];
+
+              // Formats like "the 15th of last month" or "6:30pm of next week"
+              // contain absolute units in addition to relative ones, so separate
+              // them here, remove them from the params, and set up a callback to
+              // set them after the relative ones have been set.
+              separateAbsoluteUnits();
+
+              // Shift and unit, ie "next month", "last week", etc.
+              if(set['shift']) {
+                num *= (tmp = loc.modifiersByName[set['shift']]) ? tmp.value : 0;
+              }
+
+              // Unit and sign, ie "months ago", "weeks from now", etc.
+              if(set['sign'] && (tmp = loc.modifiersByName[set['sign']])) {
+                num *= tmp.value;
+              }
+
+              // Units can be with non-relative dates, set here. ie "the day after monday"
+              if(isDefined(set['weekday'])) {
+                d.set({'weekday': set['weekday'] }, true);
+                delete set['weekday'];
+              }
+
+              // Finally shift the unit.
+              set[unit] = (set[unit] || 0) + num;
+            }
+
+            // If there is an "edge" it needs to be set after the
+            // other fields are set. ie "the end of February"
+            if(set['edge']) {
+              afterDateSet(setUnitEdge);
+            }
+
+            if(set['year_sign'] === '-') {
+              set['year'] *= -1;
+            }
+
+            iterateOverDateUnits(function(name, unit, i) {
+              var value = set[name], fraction = value % 1;
+              if(fraction) {
+                set[DateUnitsReversed[i - 1].name] = round(fraction * (name === 'second' ? 1000 : 60));
+                set[name] = floor(value);
+              }
+            }, 1, 4);
+            return false;
+          }
+        });
+      }
+      if(!set) {
+        // The Date constructor does something tricky like checking the number
+        // of arguments so simply passing in undefined won't work.
+        if(f !== 'now') {
+          d = new date(f);
+        }
+        if(forceUTC) {
+          // Falling back to system date here which cannot be parsed as UTC,
+          // so if we're forcing UTC then simply add the offset.
+          d.addMinutes(-d.getTimezoneOffset());
+        }
+      } else if(relative) {
+        d.advance(set);
+      } else {
+        if(d._utc) {
+          // UTC times can traverse into other days or even months,
+          // so preemtively reset the time here to prevent this.
+          d.reset();
+        }
+        updateDate(d, set, true, false, prefer);
+      }
+      fireCallbacks();
+      // A date created by parsing a string presumes that the format *itself* is UTC, but
+      // not that the date, once created, should be manipulated as such. In other words,
+      // if you are creating a date object from a server time "2012-11-15T12:00:00Z",
+      // in the majority of cases you are using it to create a date that will, after creation,
+      // be manipulated as local, so reset the utc flag here.
+      d.utc(false);
+    }
+    return {
+      date: d,
+      set: set
+    }
+  }
+
+  // If the year is two digits, add the most appropriate century prefix.
+  function getYearFromAbbreviation(year) {
+    return round(callDateGet(getNewDate(), 'FullYear') / 100) * 100 - round(year / 100) * 100 + year;
+  }
+
+  function getShortHour(d) {
+    var hours = callDateGet(d, 'Hours');
+    return hours === 0 ? 12 : hours - (floor(hours / 13) * 12);
+  }
+
+  // weeksSince won't work here as the result needs to be floored, not rounded.
+  function getWeekNumber(date) {
+    date = date.clone();
+    var dow = callDateGet(date, 'Day') || 7;
+    date.addDays(4 - dow).reset();
+    return 1 + floor(date.daysSince(date.clone().beginningOfYear()) / 7);
+  }
+
+  function getAdjustedUnit(ms) {
+    var next, ams = abs(ms), value = ams, unitIndex = 0;
+    iterateOverDateUnits(function(name, unit, i) {
+      next = floor(withPrecision(ams / unit.multiplier(), 1));
+      if(next >= 1) {
+        value = next;
+        unitIndex = i;
+      }
+    }, 1);
+    return [value, unitIndex, ms];
+  }
+
+  function getRelativeWithMonthFallback(date) {
+    var adu = getAdjustedUnit(date.millisecondsFromNow());
+    if(allowMonthFallback(date, adu)) {
+      // If the adjusted unit is in months, then better to use
+      // the "monthsfromNow" which applies a special error margin
+      // for edge cases such as Jan-09 - Mar-09 being less than
+      // 2 months apart (when using a strict numeric definition).
+      // The third "ms" element in the array will handle the sign
+      // (past or future), so simply take the absolute value here.
+      adu[0] = abs(date.monthsFromNow());
+      adu[1] = 6;
+    }
+    return adu;
+  }
+
+  function allowMonthFallback(date, adu) {
+    // Allow falling back to monthsFromNow if the unit is in months...
+    return adu[1] === 6 ||
+    // ...or if it's === 4 weeks and there are more days than in the given month
+    (adu[1] === 5 && adu[0] === 4 && date.daysFromNow() >= getNewDate().daysInMonth());
+  }
+
+
+  // Date format token helpers
+
+  function createMeridianTokens(slice, caps) {
+    var fn = function(d, localeCode) {
+      var hours = callDateGet(d, 'Hours');
+      return getLocalization(localeCode)['ampm'][floor(hours / 12)] || '';
+    }
+    createFormatToken('t', fn, 1);
+    createFormatToken('tt', fn);
+    createFormatToken('T', fn, 1, 1);
+    createFormatToken('TT', fn, null, 2);
+  }
+
+  function createWeekdayTokens(slice, caps) {
+    var fn = function(d, localeCode) {
+      var dow = callDateGet(d, 'Day');
+      return getLocalization(localeCode)['weekdays'][dow];
+    }
+    createFormatToken('dow', fn, 3);
+    createFormatToken('Dow', fn, 3, 1);
+    createFormatToken('weekday', fn);
+    createFormatToken('Weekday', fn, null, 1);
+  }
+
+  function createMonthTokens(slice, caps) {
+    createMonthToken('mon', 0, 3);
+    createMonthToken('month', 0);
+
+    // For inflected month forms, namely Russian.
+    createMonthToken('month2', 1);
+    createMonthToken('month3', 2);
+  }
+
+  function createMonthToken(token, multiplier, slice) {
+    var fn = function(d, localeCode) {
+      var month = callDateGet(d, 'Month');
+      return getLocalization(localeCode)['months'][month + (multiplier * 12)];
+    };
+    createFormatToken(token, fn, slice);
+    createFormatToken(simpleCapitalize(token), fn, slice, 1);
+  }
+
+  function createFormatToken(t, fn, slice, caps) {
+    DateFormatTokens[t] = function(d, localeCode) {
+      var str = fn(d, localeCode);
+      if(slice) str = str.slice(0, slice);
+      if(caps)  str = str.slice(0, caps).toUpperCase() + str.slice(caps);
+      return str;
+    }
+  }
+
+  function createPaddedToken(t, fn, ms) {
+    DateFormatTokens[t] = fn;
+    DateFormatTokens[t + t] = function (d, localeCode) {
+      return padNumber(fn(d, localeCode), 2);
+    };
+    if(ms) {
+      DateFormatTokens[t + t + t] = function (d, localeCode) {
+        return padNumber(fn(d, localeCode), 3);
+      };
+      DateFormatTokens[t + t + t + t] = function (d, localeCode) {
+        return padNumber(fn(d, localeCode), 4);
+      };
+    }
+  }
+
+
+  // Date formatting helpers
+
+  function buildCompiledOutputFormat(format) {
+    var match = format.match(/(\{\w+\})|[^{}]+/g);
+    CompiledOutputFormats[format] = match.map(function(p) {
+      p.replace(/\{(\w+)\}/, function(full, token) {
+        p = DateFormatTokens[token] || token;
+        return token;
+      });
+      return p;
+    });
+  }
+
+  function executeCompiledOutputFormat(date, format, localeCode) {
+    var compiledFormat, length, i, t, result = '';
+    compiledFormat = CompiledOutputFormats[format];
+    for(i = 0, length = compiledFormat.length; i < length; i++) {
+      t = compiledFormat[i];
+      result += isFunction(t) ? t(date, localeCode) : t;
+    }
+    return result;
+  }
+
+  function formatDate(date, format, relative, localeCode) {
+    var adu;
+    if(!date.isValid()) {
+      return 'Invalid Date';
+    } else if(Date[format]) {
+      format = Date[format];
+    } else if(isFunction(format)) {
+      adu = getRelativeWithMonthFallback(date);
+      format = format.apply(date, adu.concat(getLocalization(localeCode)));
+    }
+    if(!format && relative) {
+      adu = adu || getRelativeWithMonthFallback(date);
+      // Adjust up if time is in ms, as this doesn't
+      // look very good for a standard relative date.
+      if(adu[1] === 0) {
+        adu[1] = 1;
+        adu[0] = 1;
+      }
+      return getLocalization(localeCode).getRelativeFormat(adu);
+    }
+    format = format || 'long';
+    if(format === 'short' || format === 'long' || format === 'full') {
+      format = getLocalization(localeCode)[format];
+    }
+
+    if(!CompiledOutputFormats[format]) {
+      buildCompiledOutputFormat(format);
+    }
+
+    return executeCompiledOutputFormat(date, format, localeCode);
+  }
+
+  // Date comparison helpers
+
+  function compareDate(d, find, localeCode, buffer, forceUTC) {
+    var p, t, min, max, override, capitalized, accuracy = 0, loBuffer = 0, hiBuffer = 0;
+    p = getExtendedDate(find, localeCode, null, forceUTC);
+    if(buffer > 0) {
+      loBuffer = hiBuffer = buffer;
+      override = true;
+    }
+    if(!p.date.isValid()) return false;
+    if(p.set && p.set.specificity) {
+      DateUnits.forEach(function(u, i) {
+        if(u.name === p.set.specificity) {
+          accuracy = u.multiplier(p.date, d - p.date) - 1;
+        }
+      });
+      capitalized = simpleCapitalize(p.set.specificity);
+      if(p.set['edge'] || p.set['shift']) {
+        p.date['beginningOf' + capitalized]();
+      }
+      if(p.set.specificity === 'month') {
+        max = p.date.clone()['endOf' + capitalized]().getTime();
+      }
+      if(!override && p.set['sign'] && p.set.specificity != 'millisecond') {
+        // If the time is relative, there can occasionally be an disparity between the relative date
+        // and "now", which it is being compared to, so set an extra buffer to account for this.
+        loBuffer = 50;
+        hiBuffer = -50;
+      }
+    }
+    t   = d.getTime();
+    min = p.date.getTime();
+    max = max || (min + accuracy);
+    max = compensateForTimezoneTraversal(d, min, max);
+    return t >= (min - loBuffer) && t <= (max + hiBuffer);
+  }
+
+  function compensateForTimezoneTraversal(d, min, max) {
+    var dMin, dMax, minOffset, maxOffset;
+    dMin = new date(min);
+    dMax = new date(max).utc(d.isUTC());
+    if(callDateGet(dMax, 'Hours') !== 23) {
+      minOffset = dMin.getTimezoneOffset();
+      maxOffset = dMax.getTimezoneOffset();
+      if(minOffset !== maxOffset) {
+        max += (maxOffset - minOffset).minutes();
+      }
+    }
+    return max;
+  }
+
+  function updateDate(d, params, reset, advance, prefer) {
+    var weekday, specificityIndex;
+
+    function getParam(key) {
+      return isDefined(params[key]) ? params[key] : params[key + 's'];
+    }
+
+    function paramExists(key) {
+      return isDefined(getParam(key));
+    }
+
+    function uniqueParamExists(key, isDay) {
+      return paramExists(key) || (isDay && paramExists('weekday'));
+    }
+
+    function canDisambiguate() {
+      switch(prefer) {
+        case -1: return d > getNewDate();
+        case  1: return d < getNewDate();
+      }
+    }
+
+    if(isNumber(params) && advance) {
+      // If param is a number and we're advancing, the number is presumed to be milliseconds.
+      params = { 'milliseconds': params };
+    } else if(isNumber(params)) {
+      // Otherwise just set the timestamp and return.
+      d.setTime(params);
+      return d;
+    }
+
+    // "date" can also be passed for the day
+    if(isDefined(params['date'])) {
+      params['day'] = params['date'];
+    }
+
+    // Reset any unit lower than the least specific unit set. Do not do this for weeks
+    // or for years. This needs to be performed before the acutal setting of the date
+    // because the order needs to be reversed in order to get the lowest specificity,
+    // also because higher order units can be overwritten by lower order units, such
+    // as setting hour: 3, minute: 345, etc.
+    iterateOverDateUnits(function(name, unit, i) {
+      var isDay = name === 'day';
+      if(uniqueParamExists(name, isDay)) {
+        params.specificity = name;
+        specificityIndex = +i;
+        return false;
+      } else if(reset && name !== 'week' && (!isDay || !paramExists('week'))) {
+        // Days are relative to months, not weeks, so don't reset if a week exists.
+        callDateSet(d, unit.method, (isDay ? 1 : 0));
+      }
+    });
+
+    // Now actually set or advance the date in order, higher units first.
+    DateUnits.forEach(function(u, i) {
+      var name = u.name, method = u.method, higherUnit = DateUnits[i - 1], value;
+      value = getParam(name)
+      if(isUndefined(value)) return;
+      if(advance) {
+        if(name === 'week') {
+          value  = (params['day'] || 0) + (value * 7);
+          method = 'Date';
+        }
+        value = (value * advance) + callDateGet(d, method);
+      } else if(name === 'month' && paramExists('day')) {
+        // When setting the month, there is a chance that we will traverse into a new month.
+        // This happens in DST shifts, for example June 1st DST jumping to January 1st
+        // (non-DST) will have a shift of -1:00 which will traverse into the previous year.
+        // Prevent this by proactively setting the day when we know it will be set again anyway.
+        // It can also happen when there are not enough days in the target month. This second
+        // situation is identical to checkMonthTraversal below, however when we are advancing
+        // we want to reset the date to "the last date in the target month". In the case of
+        // DST shifts, however, we want to avoid the "edges" of months as that is where this
+        // unintended traversal can happen. This is the reason for the different handling of
+        // two similar but slightly different situations.
+        //
+        // TL;DR This method avoids the edges of a month IF not advancing and the date is going
+        // to be set anyway, while checkMonthTraversal resets the date to the last day if advancing.
+        //
+        callDateSet(d, 'Date', 15);
+      }
+      callDateSet(d, method, value);
+      if(advance && name === 'month') {
+        checkMonthTraversal(d, value);
+      }
+    });
+
+
+    // If a weekday is included in the params, set it ahead of time and set the params
+    // to reflect the updated date so that resetting works properly.
+    if(!advance && !paramExists('day') && paramExists('weekday')) {
+      var weekday = getParam('weekday'), isAhead, futurePreferred;
+      d.setWeekday(weekday);
+    }
+
+    // If past or future is preferred, then the process of "disambiguation" will ensure that an
+    // ambiguous time/date ("4pm", "thursday", "June", etc.) will be in the past or future.
+    if(canDisambiguate()) {
+      iterateOverDateUnits(function(name, unit) {
+        var ambiguous = unit.ambiguous || (name === 'week' && paramExists('weekday'));
+        if(ambiguous && !uniqueParamExists(name, name === 'day')) {
+          d[unit.addMethod](prefer);
+          return false;
+        }
+      }, specificityIndex + 1);
+    }
+    return d;
+  }
+
+  // The ISO format allows times strung together without a demarcating ":", so make sure
+  // that these markers are now optional.
+  function prepareTime(format, loc, iso) {
+    var timeSuffixMapping = {'h':0,'m':1,'s':2}, add;
+    loc = loc || English;
+    return format.replace(/{([a-z])}/g, function(full, token) {
+      var separators = [],
+          isHours = token === 'h',
+          tokenIsRequired = isHours && !iso;
+      if(token === 't') {
+        return loc['ampm'].join('|');
+      } else {
+        if(isHours) {
+          separators.push(':');
+        }
+        if(add = loc['timeSuffixes'][timeSuffixMapping[token]]) {
+          separators.push(add + '\\s*');
+        }
+        return separators.length === 0 ? '' : '(?:' + separators.join('|') + ')' + (tokenIsRequired ? '' : '?');
+      }
+    });
+  }
+
+
+  // If the month is being set, then we don't want to accidentally
+  // traverse into a new month just because the target month doesn't have enough
+  // days. In other words, "5 months ago" from July 30th is still February, even
+  // though there is no February 30th, so it will of necessity be February 28th
+  // (or 29th in the case of a leap year).
+
+  function checkMonthTraversal(date, targetMonth) {
+    if(targetMonth < 0) {
+      targetMonth = targetMonth % 12 + 12;
+    }
+    if(targetMonth % 12 != callDateGet(date, 'Month')) {
+      callDateSet(date, 'Date', 0);
+    }
+  }
+
+  function createDate(args, prefer, forceUTC) {
+    var f, localeCode;
+    if(isNumber(args[1])) {
+      // If the second argument is a number, then we have an enumerated constructor type as in "new Date(2003, 2, 12);"
+      f = collectDateArguments(args)[0];
+    } else {
+      f          = args[0];
+      localeCode = args[1];
+    }
+    return getExtendedDate(f, localeCode, prefer, forceUTC).date;
+  }
+
+  function invalidateDate(d) {
+    d.setTime(NaN);
+  }
+
+  function buildDateUnits() {
+    DateUnitsReversed = DateUnits.concat().reverse();
+    DateArgumentUnits = DateUnits.concat();
+    DateArgumentUnits.splice(2,1);
+  }
+
+
+  /***
+   * @method [units]Since([d], [locale] = currentLocale)
+   * @returns Number
+   * @short Returns the time since [d] in the appropriate unit.
+   * @extra [d] will accept a date object, timestamp, or text format. If not specified, [d] is assumed to be now. [locale] can be passed to specify the locale that the date is in. %[unit]Ago% is provided as an alias to make this more readable when [d] is assumed to be the current date. For more see @date_format.
+   *
+   * @set
+   *   millisecondsSince
+   *   secondsSince
+   *   minutesSince
+   *   hoursSince
+   *   daysSince
+   *   weeksSince
+   *   monthsSince
+   *   yearsSince
+   *
+   * @example
+   *
+   *   Date.create().millisecondsSince('1 hour ago') -> 3,600,000
+   *   Date.create().daysSince('1 week ago')         -> 7
+   *   Date.create().yearsSince('15 years ago')      -> 15
+   *   Date.create('15 years ago').yearsAgo()        -> 15
+   *
+   ***
+   * @method [units]Ago()
+   * @returns Number
+   * @short Returns the time ago in the appropriate unit.
+   *
+   * @set
+   *   millisecondsAgo
+   *   secondsAgo
+   *   minutesAgo
+   *   hoursAgo
+   *   daysAgo
+   *   weeksAgo
+   *   monthsAgo
+   *   yearsAgo
+   *
+   * @example
+   *
+   *   Date.create('last year').millisecondsAgo() -> 3,600,000
+   *   Date.create('last year').daysAgo()         -> 7
+   *   Date.create('last year').yearsAgo()        -> 15
+   *
+   ***
+   * @method [units]Until([d], [locale] = currentLocale)
+   * @returns Number
+   * @short Returns the time until [d] in the appropriate unit.
+   * @extra [d] will accept a date object, timestamp, or text format. If not specified, [d] is assumed to be now. [locale] can be passed to specify the locale that the date is in. %[unit]FromNow% is provided as an alias to make this more readable when [d] is assumed to be the current date. For more see @date_format.
+   *
+   * @set
+   *   millisecondsUntil
+   *   secondsUntil
+   *   minutesUntil
+   *   hoursUntil
+   *   daysUntil
+   *   weeksUntil
+   *   monthsUntil
+   *   yearsUntil
+   *
+   * @example
+   *
+   *   Date.create().millisecondsUntil('1 hour from now') -> 3,600,000
+   *   Date.create().daysUntil('1 week from now')         -> 7
+   *   Date.create().yearsUntil('15 years from now')      -> 15
+   *   Date.create('15 years from now').yearsFromNow()    -> 15
+   *
+   ***
+   * @method [units]FromNow()
+   * @returns Number
+   * @short Returns the time from now in the appropriate unit.
+   *
+   * @set
+   *   millisecondsFromNow
+   *   secondsFromNow
+   *   minutesFromNow
+   *   hoursFromNow
+   *   daysFromNow
+   *   weeksFromNow
+   *   monthsFromNow
+   *   yearsFromNow
+   *
+   * @example
+   *
+   *   Date.create('next year').millisecondsFromNow() -> 3,600,000
+   *   Date.create('next year').daysFromNow()         -> 7
+   *   Date.create('next year').yearsFromNow()        -> 15
+   *
+   ***
+   * @method add[Units](<num>, [reset] = false)
+   * @returns Date
+   * @short Adds <num> of the unit to the date. If [reset] is true, all lower units will be reset.
+   * @extra Note that "months" is ambiguous as a unit of time. If the target date falls on a day that does not exist (ie. August 31 -> February 31), the date will be shifted to the last day of the month. Don't use %addMonths% if you need precision.
+   *
+   * @set
+   *   addMilliseconds
+   *   addSeconds
+   *   addMinutes
+   *   addHours
+   *   addDays
+   *   addWeeks
+   *   addMonths
+   *   addYears
+   *
+   * @example
+   *
+   *   Date.create().addMilliseconds(5) -> current time + 5 milliseconds
+   *   Date.create().addDays(5)         -> current time + 5 days
+   *   Date.create().addYears(5)        -> current time + 5 years
+   *
+   ***
+   * @method isLast[Unit]()
+   * @returns Boolean
+   * @short Returns true if the date is last week/month/year.
+   *
+   * @set
+   *   isLastWeek
+   *   isLastMonth
+   *   isLastYear
+   *
+   * @example
+   *
+   *   Date.create('yesterday').isLastWeek()  -> true or false?
+   *   Date.create('yesterday').isLastMonth() -> probably not...
+   *   Date.create('yesterday').isLastYear()  -> even less likely...
+   *
+   ***
+   * @method isThis[Unit]()
+   * @returns Boolean
+   * @short Returns true if the date is this week/month/year.
+   *
+   * @set
+   *   isThisWeek
+   *   isThisMonth
+   *   isThisYear
+   *
+   * @example
+   *
+   *   Date.create('tomorrow').isThisWeek()  -> true or false?
+   *   Date.create('tomorrow').isThisMonth() -> probably...
+   *   Date.create('tomorrow').isThisYear()  -> signs point to yes...
+   *
+   ***
+   * @method isNext[Unit]()
+   * @returns Boolean
+   * @short Returns true if the date is next week/month/year.
+   *
+   * @set
+   *   isNextWeek
+   *   isNextMonth
+   *   isNextYear
+   *
+   * @example
+   *
+   *   Date.create('tomorrow').isNextWeek()  -> true or false?
+   *   Date.create('tomorrow').isNextMonth() -> probably not...
+   *   Date.create('tomorrow').isNextYear()  -> even less likely...
+   *
+   ***
+   * @method beginningOf[Unit]()
+   * @returns Date
+   * @short Sets the date to the beginning of the appropriate unit.
+   *
+   * @set
+   *   beginningOfDay
+   *   beginningOfWeek
+   *   beginningOfMonth
+   *   beginningOfYear
+   *
+   * @example
+   *
+   *   Date.create().beginningOfDay()   -> the beginning of today (resets the time)
+   *   Date.create().beginningOfWeek()  -> the beginning of the week
+   *   Date.create().beginningOfMonth() -> the beginning of the month
+   *   Date.create().beginningOfYear()  -> the beginning of the year
+   *
+   ***
+   * @method endOf[Unit]()
+   * @returns Date
+   * @short Sets the date to the end of the appropriate unit.
+   *
+   * @set
+   *   endOfDay
+   *   endOfWeek
+   *   endOfMonth
+   *   endOfYear
+   *
+   * @example
+   *
+   *   Date.create().endOfDay()   -> the end of today (sets the time to 23:59:59.999)
+   *   Date.create().endOfWeek()  -> the end of the week
+   *   Date.create().endOfMonth() -> the end of the month
+   *   Date.create().endOfYear()  -> the end of the year
+   *
+   ***/
+
+  function buildDateMethods() {
+    extendSimilar(date, true, true, DateUnits, function(methods, u, i) {
+      var name = u.name, caps = simpleCapitalize(name), multiplier = u.multiplier(), since, until;
+      u.addMethod = 'add' + caps + 's';
+      // "since/until now" only count "past" an integer, i.e. "2 days ago" is
+      // anything between 2 - 2.999 days. The default margin of error is 0.999,
+      // but "months" have an inherently larger margin, as the number of days
+      // in a given month may be significantly less than the number of days in
+      // the average month, so for example "30 days" before March 15 may in fact
+      // be 1 month ago. Years also have a margin of error due to leap years,
+      // but this is roughly 0.999 anyway (365 / 365.25). Other units do not
+      // technically need the error margin applied to them but this accounts
+      // for discrepancies like (15).hoursAgo() which technically creates the
+      // current date first, then creates a date 15 hours before and compares
+      // them, the discrepancy between the creation of the 2 dates means that
+      // they may actually be 15.0001 hours apart. Milliseconds don't have
+      // fractions, so they won't be subject to this error margin.
+      function applyErrorMargin(ms) {
+        var num      = ms / multiplier,
+            fraction = num % 1,
+            error    = u.error || 0.999;
+        if(fraction && abs(fraction % 1) > error) {
+          num = round(num);
+        }
+        return num < 0 ? ceil(num) : floor(num);
+      }
+      since = function(f, localeCode) {
+        return applyErrorMargin(this.getTime() - date.create(f, localeCode).getTime());
+      };
+      until = function(f, localeCode) {
+        return applyErrorMargin(date.create(f, localeCode).getTime() - this.getTime());
+      };
+      methods[name+'sAgo']     = until;
+      methods[name+'sUntil']   = until;
+      methods[name+'sSince']   = since;
+      methods[name+'sFromNow'] = since;
+      methods[u.addMethod] = function(num, reset) {
+        var set = {};
+        set[name] = num;
+        return this.advance(set, reset);
+      };
+      buildNumberToDateAlias(u, multiplier);
+      if(i < 3) {
+        ['Last','This','Next'].forEach(function(shift) {
+          methods['is' + shift + caps] = function() {
+            return compareDate(this, shift + ' ' + name, 'en');
+          };
+        });
+      }
+      if(i < 4) {
+        methods['beginningOf' + caps] = function() {
+          var set = {};
+          switch(name) {
+            case 'year':  set['year']    = callDateGet(this, 'FullYear'); break;
+            case 'month': set['month']   = callDateGet(this, 'Month');    break;
+            case 'day':   set['day']     = callDateGet(this, 'Date');     break;
+            case 'week':  set['weekday'] = 0; break;
+          }
+          return this.set(set, true);
+        };
+        methods['endOf' + caps] = function() {
+          var set = { 'hours': 23, 'minutes': 59, 'seconds': 59, 'milliseconds': 999 };
+          switch(name) {
+            case 'year':  set['month']   = 11; set['day'] = 31; break;
+            case 'month': set['day']     = this.daysInMonth();  break;
+            case 'week':  set['weekday'] = 6;                   break;
+          }
+          return this.set(set, true);
+        };
+      }
+    });
+  }
+
+  function buildCoreInputFormats() {
+    English.addFormat('([+-])?(\\d{4,4})[-.]?{full_month}[-.]?(\\d{1,2})?', true, ['year_sign','year','month','date'], false, true);
+    English.addFormat('(\\d{1,2})[-.\\/]{full_month}(?:[-.\\/](\\d{2,4}))?', true, ['date','month','year'], true);
+    English.addFormat('{full_month}[-.](\\d{4,4})', false, ['month','year']);
+    English.addFormat('\\/Date\\((\\d+(?:[+-]\\d{4,4})?)\\)\\/', false, ['timestamp'])
+    English.addFormat(prepareTime(RequiredTime, English), false, TimeFormat)
+
+    // When a new locale is initialized it will have the CoreDateFormats initialized by default.
+    // From there, adding new formats will push them in front of the previous ones, so the core
+    // formats will be the last to be reached. However, the core formats themselves have English
+    // months in them, which means that English needs to first be initialized and creates a race
+    // condition. I'm getting around this here by adding these generalized formats in the order
+    // specific -> general, which will mean they will be added to the English localization in
+    // general -> specific order, then chopping them off the front and reversing to get the correct
+    // order. Note that there are 7 formats as 2 have times which adds a front and a back format.
+    CoreDateFormats = English.compiledFormats.slice(0,7).reverse();
+    English.compiledFormats = English.compiledFormats.slice(7).concat(CoreDateFormats);
+  }
+
+  function buildFormatTokens() {
+
+    createPaddedToken('f', function(d) {
+      return callDateGet(d, 'Milliseconds');
+    }, true);
+
+    createPaddedToken('s', function(d) {
+      return callDateGet(d, 'Seconds');
+    });
+
+    createPaddedToken('m', function(d) {
+      return callDateGet(d, 'Minutes');
+    });
+
+    createPaddedToken('h', function(d) {
+      return callDateGet(d, 'Hours') % 12 || 12;
+    });
+
+    createPaddedToken('H', function(d) {
+      return callDateGet(d, 'Hours');
+    });
+
+    createPaddedToken('d', function(d) {
+      return callDateGet(d, 'Date');
+    });
+
+    createPaddedToken('M', function(d) {
+      return callDateGet(d, 'Month') + 1;
+    });
+
+    createMeridianTokens();
+    createWeekdayTokens();
+    createMonthTokens();
+
+    // Aliases
+    DateFormatTokens['ms']           = DateFormatTokens['f'];
+    DateFormatTokens['milliseconds'] = DateFormatTokens['f'];
+    DateFormatTokens['seconds']      = DateFormatTokens['s'];
+    DateFormatTokens['minutes']      = DateFormatTokens['m'];
+    DateFormatTokens['hours']        = DateFormatTokens['h'];
+    DateFormatTokens['24hr']         = DateFormatTokens['H'];
+    DateFormatTokens['12hr']         = DateFormatTokens['h'];
+    DateFormatTokens['date']         = DateFormatTokens['d'];
+    DateFormatTokens['day']          = DateFormatTokens['d'];
+    DateFormatTokens['year']         = DateFormatTokens['yyyy'];
+
+  }
+
+  function buildFormatShortcuts() {
+    extendSimilar(date, true, true, 'short,long,full', function(methods, name) {
+      methods[name] = function(localeCode) {
+        return formatDate(this, name, false, localeCode);
+      }
+    });
+  }
+
+  function buildAsianDigits() {
+    KanjiDigits.split('').forEach(function(digit, value) {
+      var holder;
+      if(value > 9) {
+        value = pow(10, value - 9);
+      }
+      AsianDigitMap[digit] = value;
+    });
+    simpleMerge(AsianDigitMap, NumberNormalizeMap);
+    // Kanji numerals may also be included in phrases which are text-based rather
+    // than actual numbers such as Chinese weekdays (ไธŠๅ‘จไธ‰), and "the day before
+    // yesterday" (ไธ€ๆ˜จๆ—ฅ) in Japanese, so don't match these.
+    AsianDigitReg = regexp('([ๆœŸ้€ฑๅ‘จ])?([' + KanjiDigits + FullWidthDigits + ']+)(?!ๆ˜จ)', 'g');
+  }
+
+   /***
+   * @method is[Day]()
+   * @returns Boolean
+   * @short Returns true if the date falls on that day.
+   * @extra Also available: %isYesterday%, %isToday%, %isTomorrow%, %isWeekday%, and %isWeekend%.
+   *
+   * @set
+   *   isToday
+   *   isYesterday
+   *   isTomorrow
+   *   isWeekday
+   *   isWeekend
+   *   isSunday
+   *   isMonday
+   *   isTuesday
+   *   isWednesday
+   *   isThursday
+   *   isFriday
+   *   isSaturday
+   *
+   * @example
+   *
+   *   Date.create('tomorrow').isToday() -> false
+   *   Date.create('thursday').isTomorrow() -> ?
+   *   Date.create('yesterday').isWednesday() -> ?
+   *   Date.create('today').isWeekend() -> ?
+   *
+   ***
+   * @method isFuture()
+   * @returns Boolean
+   * @short Returns true if the date is in the future.
+   * @example
+   *
+   *   Date.create('next week').isFuture() -> true
+   *   Date.create('last week').isFuture() -> false
+   *
+   ***
+   * @method isPast()
+   * @returns Boolean
+   * @short Returns true if the date is in the past.
+   * @example
+   *
+   *   Date.create('last week').isPast() -> true
+   *   Date.create('next week').isPast() -> false
+   *
+   ***/
+  function buildRelativeAliases() {
+    var special  = 'today,yesterday,tomorrow,weekday,weekend,future,past'.split(',');
+    var weekdays = English['weekdays'].slice(0,7);
+    var months   = English['months'].slice(0,12);
+    extendSimilar(date, true, true, special.concat(weekdays).concat(months), function(methods, name) {
+      methods['is'+ simpleCapitalize(name)] = function(utc) {
+       return this.is(name, 0, utc);
+      };
+    });
+  }
+
+  function buildUTCAliases() {
+    // Don't want to use extend here as it will override
+    // the actual "utc" method on the prototype.
+    if(date['utc']) return;
+    date['utc'] = {
+
+        'create': function() {
+          return createDate(arguments, 0, true);
+        },
+
+        'past': function() {
+          return createDate(arguments, -1, true);
+        },
+
+        'future': function() {
+          return createDate(arguments, 1, true);
+        }
+    };
+  }
+
+  function setDateProperties() {
+    extend(date, false , true, {
+      'RFC1123': '{Dow}, {dd} {Mon} {yyyy} {HH}:{mm}:{ss} {tz}',
+      'RFC1036': '{Weekday}, {dd}-{Mon}-{yy} {HH}:{mm}:{ss} {tz}',
+      'ISO8601_DATE': '{yyyy}-{MM}-{dd}',
+      'ISO8601_DATETIME': '{yyyy}-{MM}-{dd}T{HH}:{mm}:{ss}.{fff}{isotz}'
+    });
+  }
+
+
+  extend(date, false, true, {
+
+     /***
+     * @method Date.create(<d>, [locale] = currentLocale)
+     * @returns Date
+     * @short Alternate Date constructor which understands many different text formats, a timestamp, or another date.
+     * @extra If no argument is given, date is assumed to be now. %Date.create% additionally can accept enumerated parameters as with the standard date constructor. [locale] can be passed to specify the locale that the date is in. When unspecified, the current locale (default is English) is assumed. UTC-based dates can be created through the %utc% object. For more see @date_format.
+     * @set
+     *   Date.utc.create
+     *
+     * @example
+     *
+     *   Date.create('July')          -> July of this year
+     *   Date.create('1776')          -> 1776
+     *   Date.create('today')         -> today
+     *   Date.create('wednesday')     -> This wednesday
+     *   Date.create('next friday')   -> Next friday
+     *   Date.create('July 4, 1776')  -> July 4, 1776
+     *   Date.create(-446806800000)   -> November 5, 1955
+     *   Date.create(1776, 6, 4)      -> July 4, 1776
+     *   Date.create('1776ๅนด07ๆœˆ04ๆ—ฅ', 'ja') -> July 4, 1776
+     *   Date.utc.create('July 4, 1776', 'en')  -> July 4, 1776
+     *
+     ***/
+    'create': function() {
+      return createDate(arguments);
+    },
+
+     /***
+     * @method Date.past(<d>, [locale] = currentLocale)
+     * @returns Date
+     * @short Alternate form of %Date.create% with any ambiguity assumed to be the past.
+     * @extra For example %"Sunday"% can be either "the Sunday coming up" or "the Sunday last" depending on context. Note that dates explicitly in the future ("next Sunday") will remain in the future. This method simply provides a hint when ambiguity exists. UTC-based dates can be created through the %utc% object. For more, see @date_format.
+     * @set
+     *   Date.utc.past
+     *
+     * @example
+     *
+     *   Date.past('July')          -> July of this year or last depending on the current month
+     *   Date.past('Wednesday')     -> This wednesday or last depending on the current weekday
+     *
+     ***/
+    'past': function() {
+      return createDate(arguments, -1);
+    },
+
+     /***
+     * @method Date.future(<d>, [locale] = currentLocale)
+     * @returns Date
+     * @short Alternate form of %Date.create% with any ambiguity assumed to be the future.
+     * @extra For example %"Sunday"% can be either "the Sunday coming up" or "the Sunday last" depending on context. Note that dates explicitly in the past ("last Sunday") will remain in the past. This method simply provides a hint when ambiguity exists. UTC-based dates can be created through the %utc% object. For more, see @date_format.
+     * @set
+     *   Date.utc.future
+     *
+     * @example
+     *
+     *   Date.future('July')          -> July of this year or next depending on the current month
+     *   Date.future('Wednesday')     -> This wednesday or next depending on the current weekday
+     *
+     ***/
+    'future': function() {
+      return createDate(arguments, 1);
+    },
+
+     /***
+     * @method Date.addLocale(<code>, <set>)
+     * @returns Locale
+     * @short Adds a locale <set> to the locales understood by Sugar.
+     * @extra For more see @date_format.
+     *
+     ***/
+    'addLocale': function(localeCode, set) {
+      return setLocalization(localeCode, set);
+    },
+
+     /***
+     * @method Date.setLocale(<code>)
+     * @returns Locale
+     * @short Sets the current locale to be used with dates.
+     * @extra Sugar has support for 13 locales that are available through the "Date Locales" package. In addition you can define a new locale with %Date.addLocale%. For more see @date_format.
+     *
+     ***/
+    'setLocale': function(localeCode, set) {
+      var loc = getLocalization(localeCode, false);
+      CurrentLocalization = loc;
+      // The code is allowed to be more specific than the codes which are required:
+      // i.e. zh-CN or en-US. Currently this only affects US date variants such as 8/10/2000.
+      if(localeCode && localeCode != loc['code']) {
+        loc['code'] = localeCode;
+      }
+      return loc;
+    },
+
+     /***
+     * @method Date.getLocale([code] = current)
+     * @returns Locale
+     * @short Gets the locale for the given code, or the current locale.
+     * @extra The resulting locale object can be manipulated to provide more control over date localizations. For more about locales, see @date_format.
+     *
+     ***/
+    'getLocale': function(localeCode) {
+      return !localeCode ? CurrentLocalization : getLocalization(localeCode, false);
+    },
+
+     /**
+     * @method Date.addFormat(<format>, <match>, [code] = null)
+     * @returns Nothing
+     * @short Manually adds a new date input format.
+     * @extra This method allows fine grained control for alternate formats. <format> is a string that can have regex tokens inside. <match> is an array of the tokens that each regex capturing group will map to, for example %year%, %date%, etc. For more, see @date_format.
+     *
+     **/
+    'addFormat': function(format, match, localeCode) {
+      addDateInputFormat(getLocalization(localeCode), format, match);
+    }
+
+  });
+
+  extend(date, true, true, {
+
+     /***
+     * @method set(<set>, [reset] = false)
+     * @returns Date
+     * @short Sets the date object.
+     * @extra This method can accept multiple formats including a single number as a timestamp, an object, or enumerated parameters (as with the Date constructor). If [reset] is %true%, any units more specific than those passed will be reset.
+     *
+     * @example
+     *
+     *   new Date().set({ year: 2011, month: 11, day: 31 }) -> December 31, 2011
+     *   new Date().set(2011, 11, 31)                       -> December 31, 2011
+     *   new Date().set(86400000)                           -> 1 day after Jan 1, 1970
+     *   new Date().set({ year: 2004, month: 6 }, true)     -> June 1, 2004, 00:00:00.000
+     *
+     ***/
+    'set': function() {
+      var args = collectDateArguments(arguments);
+      return updateDate(this, args[0], args[1])
+    },
+
+     /***
+     * @method setWeekday()
+     * @returns Nothing
+     * @short Sets the weekday of the date.
+     * @extra In order to maintain a parallel with %getWeekday% (which itself is an alias for Javascript native %getDay%), Sunday is considered day %0%. This contrasts with ISO-8601 standard (used in %getISOWeek% and %setISOWeek%) which places Sunday at the end of the week (day 7). This effectively means that passing %0% to this method while in the middle of a week will rewind the date, where passing %7% will advance it.
+     *
+     * @example
+     *
+     *   d = new Date(); d.setWeekday(1); d; -> Monday of this week
+     *   d = new Date(); d.setWeekday(6); d; -> Saturday of this week
+     *
+     ***/
+    'setWeekday': function(dow) {
+      if(isUndefined(dow)) return;
+      return callDateSet(this, 'Date', callDateGet(this, 'Date') + dow - callDateGet(this, 'Day'));
+    },
+
+     /***
+     * @method setISOWeek()
+     * @returns Nothing
+     * @short Sets the week (of the year) as defined by the ISO-8601 standard.
+     * @extra Note that this standard places Sunday at the end of the week (day 7).
+     *
+     * @example
+     *
+     *   d = new Date(); d.setISOWeek(15); d; -> 15th week of the year
+     *
+     ***/
+    'setISOWeek': function(week) {
+      var weekday = callDateGet(this, 'Day') || 7;
+      if(isUndefined(week)) return;
+      this.set({ 'month': 0, 'date': 4 });
+      this.set({ 'weekday': 1 });
+      if(week > 1) {
+        this.addWeeks(week - 1);
+      }
+      if(weekday !== 1) {
+        this.advance({ 'days': weekday - 1 });
+      }
+      return this.getTime();
+    },
+
+     /***
+     * @method getISOWeek()
+     * @returns Number
+     * @short Gets the date's week (of the year) as defined by the ISO-8601 standard.
+     * @extra Note that this standard places Sunday at the end of the week (day 7). If %utc% is set on the date, the week will be according to UTC time.
+     *
+     * @example
+     *
+     *   new Date().getISOWeek()    -> today's week of the year
+     *
+     ***/
+    'getISOWeek': function() {
+      return getWeekNumber(this);
+    },
+
+     /***
+     * @method beginningOfISOWeek()
+     * @returns Date
+     * @short Set the date to the beginning of week as defined by this ISO-8601 standard.
+     * @extra Note that this standard places Monday at the start of the week.
+     * @example
+     *
+     *   Date.create().beginningOfISOWeek() -> Monday
+     *
+     ***/
+    'beginningOfISOWeek': function() {
+      var day = this.getDay();
+      if(day === 0) {
+        day = -6;
+      } else if(day !== 1) {
+        day = 1;
+      }
+      this.setWeekday(day);
+      return this.reset();
+    },
+
+     /***
+     * @method endOfISOWeek()
+     * @returns Date
+     * @short Set the date to the end of week as defined by this ISO-8601 standard.
+     * @extra Note that this standard places Sunday at the end of the week.
+     * @example
+     *
+     *   Date.create().endOfISOWeek() -> Sunday
+     *
+     ***/
+    'endOfISOWeek': function() {
+      if(this.getDay() !== 0) {
+        this.setWeekday(7);
+      }
+      return this.endOfDay()
+    },
+
+     /***
+     * @method getUTCOffset([iso])
+     * @returns String
+     * @short Returns a string representation of the offset from UTC time. If [iso] is true the offset will be in ISO8601 format.
+     * @example
+     *
+     *   new Date().getUTCOffset()     -> "+0900"
+     *   new Date().getUTCOffset(true) -> "+09:00"
+     *
+     ***/
+    'getUTCOffset': function(iso) {
+      var offset = this._utc ? 0 : this.getTimezoneOffset();
+      var colon  = iso === true ? ':' : '';
+      if(!offset && iso) return 'Z';
+      return padNumber(floor(-offset / 60), 2, true) + colon + padNumber(abs(offset % 60), 2);
+    },
+
+     /***
+     * @method utc([on] = true)
+     * @returns Date
+     * @short Sets the internal utc flag for the date. When on, UTC-based methods will be called internally.
+     * @extra For more see @date_format.
+     * @example
+     *
+     *   new Date().utc(true)
+     *   new Date().utc(false)
+     *
+     ***/
+    'utc': function(set) {
+      defineProperty(this, '_utc', set === true || arguments.length === 0);
+      return this;
+    },
+
+     /***
+     * @method isUTC()
+     * @returns Boolean
+     * @short Returns true if the date has no timezone offset.
+     * @extra This will also return true for utc-based dates (dates that have the %utc% method set true). Note that even if the utc flag is set, %getTimezoneOffset% will always report the same thing as Javascript always reports that based on the environment's locale.
+     * @example
+     *
+     *   new Date().isUTC()           -> true or false?
+     *   new Date().utc(true).isUTC() -> true
+     *
+     ***/
+    'isUTC': function() {
+      return !!this._utc || this.getTimezoneOffset() === 0;
+    },
+
+     /***
+     * @method advance(<set>, [reset] = false)
+     * @returns Date
+     * @short Sets the date forward.
+     * @extra This method can accept multiple formats including an object, a string in the format %3 days%, a single number as milliseconds, or enumerated parameters (as with the Date constructor). If [reset] is %true%, any units more specific than those passed will be reset. For more see @date_format.
+     * @example
+     *
+     *   new Date().advance({ year: 2 }) -> 2 years in the future
+     *   new Date().advance('2 days')    -> 2 days in the future
+     *   new Date().advance(0, 2, 3)     -> 2 months 3 days in the future
+     *   new Date().advance(86400000)    -> 1 day in the future
+     *
+     ***/
+    'advance': function() {
+      var args = collectDateArguments(arguments, true);
+      return updateDate(this, args[0], args[1], 1);
+    },
+
+     /***
+     * @method rewind(<set>, [reset] = false)
+     * @returns Date
+     * @short Sets the date back.
+     * @extra This method can accept multiple formats including a single number as a timestamp, an object, or enumerated parameters (as with the Date constructor). If [reset] is %true%, any units more specific than those passed will be reset. For more see @date_format.
+     * @example
+     *
+     *   new Date().rewind({ year: 2 }) -> 2 years in the past
+     *   new Date().rewind(0, 2, 3)     -> 2 months 3 days in the past
+     *   new Date().rewind(86400000)    -> 1 day in the past
+     *
+     ***/
+    'rewind': function() {
+      var args = collectDateArguments(arguments, true);
+      return updateDate(this, args[0], args[1], -1);
+    },
+
+     /***
+     * @method isValid()
+     * @returns Boolean
+     * @short Returns true if the date is valid.
+     * @example
+     *
+     *   new Date().isValid()         -> true
+     *   new Date('flexor').isValid() -> false
+     *
+     ***/
+    'isValid': function() {
+      return !isNaN(this.getTime());
+    },
+
+     /***
+     * @method isAfter(<d>, [margin] = 0)
+     * @returns Boolean
+     * @short Returns true if the date is after the <d>.
+     * @extra [margin] is to allow extra margin of error (in ms). <d> will accept a date object, timestamp, or text format. If not specified, <d> is assumed to be now. See @date_format for more.
+     * @example
+     *
+     *   new Date().isAfter('tomorrow')  -> false
+     *   new Date().isAfter('yesterday') -> true
+     *
+     ***/
+    'isAfter': function(d, margin, utc) {
+      return this.getTime() > date.create(d).getTime() - (margin || 0);
+    },
+
+     /***
+     * @method isBefore(<d>, [margin] = 0)
+     * @returns Boolean
+     * @short Returns true if the date is before <d>.
+     * @extra [margin] is to allow extra margin of error (in ms). <d> will accept a date object, timestamp, or text format. If not specified, <d> is assumed to be now. See @date_format for more.
+     * @example
+     *
+     *   new Date().isBefore('tomorrow')  -> true
+     *   new Date().isBefore('yesterday') -> false
+     *
+     ***/
+    'isBefore': function(d, margin) {
+      return this.getTime() < date.create(d).getTime() + (margin || 0);
+    },
+
+     /***
+     * @method isBetween(<d1>, <d2>, [margin] = 0)
+     * @returns Boolean
+     * @short Returns true if the date falls between <d1> and <d2>.
+     * @extra [margin] is to allow extra margin of error (in ms). <d1> and <d2> will accept a date object, timestamp, or text format. If not specified, they are assumed to be now. See @date_format for more.
+     * @example
+     *
+     *   new Date().isBetween('yesterday', 'tomorrow')    -> true
+     *   new Date().isBetween('last year', '2 years ago') -> false
+     *
+     ***/
+    'isBetween': function(d1, d2, margin) {
+      var t  = this.getTime();
+      var t1 = date.create(d1).getTime();
+      var t2 = date.create(d2).getTime();
+      var lo = min(t1, t2);
+      var hi = max(t1, t2);
+      margin = margin || 0;
+      return (lo - margin < t) && (hi + margin > t);
+    },
+
+     /***
+     * @method isLeapYear()
+     * @returns Boolean
+     * @short Returns true if the date is a leap year.
+     * @example
+     *
+     *   Date.create('2000').isLeapYear() -> true
+     *
+     ***/
+    'isLeapYear': function() {
+      var year = callDateGet(this, 'FullYear');
+      return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
+    },
+
+     /***
+     * @method daysInMonth()
+     * @returns Number
+     * @short Returns the number of days in the date's month.
+     * @example
+     *
+     *   Date.create('May').daysInMonth()            -> 31
+     *   Date.create('February, 2000').daysInMonth() -> 29
+     *
+     ***/
+    'daysInMonth': function() {
+      return 32 - callDateGet(new date(callDateGet(this, 'FullYear'), callDateGet(this, 'Month'), 32), 'Date');
+    },
+
+     /***
+     * @method format(<format>, [locale] = currentLocale)
+     * @returns String
+     * @short Formats and outputs the date.
+     * @extra <format> can be a number of pre-determined formats or a string of tokens. Locale-specific formats are %short%, %long%, and %full% which have their own aliases and can be called with %date.short()%, etc. If <format> is not specified the %long% format is assumed. [locale] specifies a locale code to use (if not specified the current locale is used). See @date_format for more details.
+     *
+     * @set
+     *   short
+     *   long
+     *   full
+     *
+     * @example
+     *
+     *   Date.create().format()                                   -> ex. July 4, 2003
+     *   Date.create().format('{Weekday} {d} {Month}, {yyyy}')    -> ex. Monday July 4, 2003
+     *   Date.create().format('{hh}:{mm}')                        -> ex. 15:57
+     *   Date.create().format('{12hr}:{mm}{tt}')                  -> ex. 3:57pm
+     *   Date.create().format(Date.ISO8601_DATETIME)              -> ex. 2011-07-05 12:24:55.528Z
+     *   Date.create('last week').format('short', 'ja')                -> ex. ๅ…ˆ้€ฑ
+     *   Date.create('yesterday').format(function(value,unit,ms,loc) {
+     *     // value = 1, unit = 3, ms = -86400000, loc = [current locale object]
+     *   });                                                      -> ex. 1 day ago
+     *
+     ***/
+    'format': function(f, localeCode) {
+      return formatDate(this, f, false, localeCode);
+    },
+
+     /***
+     * @method relative([fn], [locale] = currentLocale)
+     * @returns String
+     * @short Returns a relative date string offset to the current time.
+     * @extra [fn] can be passed to provide for more granular control over the resulting string. [fn] is passed 4 arguments: the adjusted value, unit, offset in milliseconds, and a localization object. As an alternate syntax, [locale] can also be passed as the first (and only) parameter. For more, see @date_format.
+     * @example
+     *
+     *   Date.create('90 seconds ago').relative() -> 1 minute ago
+     *   Date.create('January').relative()        -> ex. 5 months ago
+     *   Date.create('January').relative('ja')    -> 3ใƒถๆœˆๅ‰
+     *   Date.create('120 minutes ago').relative(function(val,unit,ms,loc) {
+     *     // value = 2, unit = 3, ms = -7200, loc = [current locale object]
+     *   });                                      -> ex. 5 months ago
+     *
+     ***/
+    'relative': function(fn, localeCode) {
+      if(isString(fn)) {
+        localeCode = fn;
+        fn = null;
+      }
+      return formatDate(this, fn, true, localeCode);
+    },
+
+     /***
+     * @method is(<d>, [margin] = 0)
+     * @returns Boolean
+     * @short Returns true if the date is <d>.
+     * @extra <d> will accept a date object, timestamp, or text format. %is% additionally understands more generalized expressions like month/weekday names, 'today', etc, and compares to the precision implied in <d>. [margin] allows an extra margin of error in milliseconds.  For more, see @date_format.
+     * @example
+     *
+     *   Date.create().is('July')               -> true or false?
+     *   Date.create().is('1776')               -> false
+     *   Date.create().is('today')              -> true
+     *   Date.create().is('weekday')            -> true or false?
+     *   Date.create().is('July 4, 1776')       -> false
+     *   Date.create().is(-6106093200000)       -> false
+     *   Date.create().is(new Date(1776, 6, 4)) -> false
+     *
+     ***/
+    'is': function(d, margin, utc) {
+      var tmp, comp;
+      if(!this.isValid()) return;
+      if(isString(d)) {
+        d = d.trim().toLowerCase();
+        comp = this.clone().utc(utc);
+        switch(true) {
+          case d === 'future':  return this.getTime() > getNewDate().getTime();
+          case d === 'past':    return this.getTime() < getNewDate().getTime();
+          case d === 'weekday': return callDateGet(comp, 'Day') > 0 && callDateGet(comp, 'Day') < 6;
+          case d === 'weekend': return callDateGet(comp, 'Day') === 0 || callDateGet(comp, 'Day') === 6;
+          case (tmp = English['weekdays'].indexOf(d) % 7) > -1: return callDateGet(comp, 'Day') === tmp;
+          case (tmp = English['months'].indexOf(d) % 12) > -1:  return callDateGet(comp, 'Month') === tmp;
+        }
+      }
+      return compareDate(this, d, null, margin, utc);
+    },
+
+     /***
+     * @method reset([unit] = 'hours')
+     * @returns Date
+     * @short Resets the unit passed and all smaller units. Default is "hours", effectively resetting the time.
+     * @example
+     *
+     *   Date.create().reset('day')   -> Beginning of today
+     *   Date.create().reset('month') -> 1st of the month
+     *
+     ***/
+    'reset': function(unit) {
+      var params = {}, recognized;
+      unit = unit || 'hours';
+      if(unit === 'date') unit = 'days';
+      recognized = DateUnits.some(function(u) {
+        return unit === u.name || unit === u.name + 's';
+      });
+      params[unit] = unit.match(/^days?/) ? 1 : 0;
+      return recognized ? this.set(params, true) : this;
+    },
+
+     /***
+     * @method clone()
+     * @returns Date
+     * @short Clones the date.
+     * @example
+     *
+     *   Date.create().clone() -> Copy of now
+     *
+     ***/
+    'clone': function() {
+      var d = new date(this.getTime());
+      d.utc(!!this._utc);
+      return d;
+    }
+
+  });
+
+
+  // Instance aliases
+  extend(date, true, true, {
+
+     /***
+     * @method iso()
+     * @alias toISOString
+     *
+     ***/
+    'iso': function() {
+      return this.toISOString();
+    },
+
+     /***
+     * @method getWeekday()
+     * @returns Number
+     * @short Alias for %getDay%.
+     * @set
+     *   getUTCWeekday
+     *
+     * @example
+     *
+     +   Date.create().getWeekday();    -> (ex.) 3
+     +   Date.create().getUTCWeekday();    -> (ex.) 3
+     *
+     ***/
+    'getWeekday':    date.prototype.getDay,
+    'getUTCWeekday':    date.prototype.getUTCDay
+
+  });
+
+
+
+  /***
+   * Number module
+   *
+   ***/
+
+  /***
+   * @method [unit]()
+   * @returns Number
+   * @short Takes the number as a corresponding unit of time and converts to milliseconds.
+   * @extra Method names can be singular or plural.  Note that as "a month" is ambiguous as a unit of time, %months% will be equivalent to 30.4375 days, the average number in a month. Be careful using %months% if you need exact precision.
+   *
+   * @set
+   *   millisecond
+   *   milliseconds
+   *   second
+   *   seconds
+   *   minute
+   *   minutes
+   *   hour
+   *   hours
+   *   day
+   *   days
+   *   week
+   *   weeks
+   *   month
+   *   months
+   *   year
+   *   years
+   *
+   * @example
+   *
+   *   (5).milliseconds() -> 5
+   *   (10).hours()       -> 36000000
+   *   (1).day()          -> 86400000
+   *
+   ***
+   * @method [unit]Before([d], [locale] = currentLocale)
+   * @returns Date
+   * @short Returns a date that is <n> units before [d], where <n> is the number.
+   * @extra [d] will accept a date object, timestamp, or text format. Note that "months" is ambiguous as a unit of time. If the target date falls on a day that does not exist (ie. August 31 -> February 31), the date will be shifted to the last day of the month. Be careful using %monthsBefore% if you need exact precision. See @date_format for more.
+   *
+   * @set
+   *   millisecondBefore
+   *   millisecondsBefore
+   *   secondBefore
+   *   secondsBefore
+   *   minuteBefore
+   *   minutesBefore
+   *   hourBefore
+   *   hoursBefore
+   *   dayBefore
+   *   daysBefore
+   *   weekBefore
+   *   weeksBefore
+   *   monthBefore
+   *   monthsBefore
+   *   yearBefore
+   *   yearsBefore
+   *
+   * @example
+   *
+   *   (5).daysBefore('tuesday')          -> 5 days before tuesday of this week
+   *   (1).yearBefore('January 23, 1997') -> January 23, 1996
+   *
+   ***
+   * @method [unit]Ago()
+   * @returns Date
+   * @short Returns a date that is <n> units ago.
+   * @extra Note that "months" is ambiguous as a unit of time. If the target date falls on a day that does not exist (ie. August 31 -> February 31), the date will be shifted to the last day of the month. Be careful using %monthsAgo% if you need exact precision.
+   *
+   * @set
+   *   millisecondAgo
+   *   millisecondsAgo
+   *   secondAgo
+   *   secondsAgo
+   *   minuteAgo
+   *   minutesAgo
+   *   hourAgo
+   *   hoursAgo
+   *   dayAgo
+   *   daysAgo
+   *   weekAgo
+   *   weeksAgo
+   *   monthAgo
+   *   monthsAgo
+   *   yearAgo
+   *   yearsAgo
+   *
+   * @example
+   *
+   *   (5).weeksAgo() -> 5 weeks ago
+   *   (1).yearAgo()  -> January 23, 1996
+   *
+   ***
+   * @method [unit]After([d], [locale] = currentLocale)
+   * @returns Date
+   * @short Returns a date <n> units after [d], where <n> is the number.
+   * @extra [d] will accept a date object, timestamp, or text format. Note that "months" is ambiguous as a unit of time. If the target date falls on a day that does not exist (ie. August 31 -> February 31), the date will be shifted to the last day of the month. Be careful using %monthsAfter% if you need exact precision. See @date_format for more.
+   *
+   * @set
+   *   millisecondAfter
+   *   millisecondsAfter
+   *   secondAfter
+   *   secondsAfter
+   *   minuteAfter
+   *   minutesAfter
+   *   hourAfter
+   *   hoursAfter
+   *   dayAfter
+   *   daysAfter
+   *   weekAfter
+   *   weeksAfter
+   *   monthAfter
+   *   monthsAfter
+   *   yearAfter
+   *   yearsAfter
+   *
+   * @example
+   *
+   *   (5).daysAfter('tuesday')          -> 5 days after tuesday of this week
+   *   (1).yearAfter('January 23, 1997') -> January 23, 1998
+   *
+   ***
+   * @method [unit]FromNow()
+   * @returns Date
+   * @short Returns a date <n> units from now.
+   * @extra Note that "months" is ambiguous as a unit of time. If the target date falls on a day that does not exist (ie. August 31 -> February 31), the date will be shifted to the last day of the month. Be careful using %monthsFromNow% if you need exact precision.
+   *
+   * @set
+   *   millisecondFromNow
+   *   millisecondsFromNow
+   *   secondFromNow
+   *   secondsFromNow
+   *   minuteFromNow
+   *   minutesFromNow
+   *   hourFromNow
+   *   hoursFromNow
+   *   dayFromNow
+   *   daysFromNow
+   *   weekFromNow
+   *   weeksFromNow
+   *   monthFromNow
+   *   monthsFromNow
+   *   yearFromNow
+   *   yearsFromNow
+   *
+   * @example
+   *
+   *   (5).weeksFromNow() -> 5 weeks ago
+   *   (1).yearFromNow()  -> January 23, 1998
+   *
+   ***/
+  function buildNumberToDateAlias(u, multiplier) {
+    var name = u.name, methods = {};
+    function base() { return round(this * multiplier); }
+    function after() { return createDate(arguments)[u.addMethod](this);  }
+    function before() { return createDate(arguments)[u.addMethod](-this); }
+    methods[name] = base;
+    methods[name + 's'] = base;
+    methods[name + 'Before'] = before;
+    methods[name + 'sBefore'] = before;
+    methods[name + 'Ago'] = before;
+    methods[name + 'sAgo'] = before;
+    methods[name + 'After'] = after;
+    methods[name + 'sAfter'] = after;
+    methods[name + 'FromNow'] = after;
+    methods[name + 'sFromNow'] = after;
+    number.extend(methods);
+  }
+
+  extend(number, true, true, {
+
+     /***
+     * @method duration([locale] = currentLocale)
+     * @returns String
+     * @short Takes the number as milliseconds and returns a unit-adjusted localized string.
+     * @extra This method is the same as %Date#relative% without the localized equivalent of "from now" or "ago". [locale] can be passed as the first (and only) parameter. Note that this method is only available when the dates package is included.
+     * @example
+     *
+     *   (500).duration() -> '500 milliseconds'
+     *   (1200).duration() -> '1 second'
+     *   (75).minutes().duration() -> '1 hour'
+     *   (75).minutes().duration('es') -> '1 hora'
+     *
+     ***/
+    'duration': function(localeCode) {
+      return getLocalization(localeCode).getDuration(this);
+    }
+
+  });
+
+
+  English = CurrentLocalization = date.addLocale('en', {
+    'plural':     true,
+    'timeMarker': 'at',
+    'ampm':       'am,pm',
+    'months':     'January,February,March,April,May,June,July,August,September,October,November,December',
+    'weekdays':   'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday',
+    'units':      'millisecond:|s,second:|s,minute:|s,hour:|s,day:|s,week:|s,month:|s,year:|s',
+    'numbers':    'one,two,three,four,five,six,seven,eight,nine,ten',
+    'articles':   'a,an,the',
+    'tokens':     'the,st|nd|rd|th,of',
+    'short':      '{Month} {d}, {yyyy}',
+    'long':       '{Month} {d}, {yyyy} {h}:{mm}{tt}',
+    'full':       '{Weekday} {Month} {d}, {yyyy} {h}:{mm}:{ss}{tt}',
+    'past':       '{num} {unit} {sign}',
+    'future':     '{num} {unit} {sign}',
+    'duration':   '{num} {unit}',
+    'modifiers': [
+      { 'name': 'sign',  'src': 'ago|before', 'value': -1 },
+      { 'name': 'sign',  'src': 'from now|after|from|in|later', 'value': 1 },
+      { 'name': 'edge',  'src': 'last day', 'value': -2 },
+      { 'name': 'edge',  'src': 'end', 'value': -1 },
+      { 'name': 'edge',  'src': 'first day|beginning', 'value': 1 },
+      { 'name': 'shift', 'src': 'last', 'value': -1 },
+      { 'name': 'shift', 'src': 'the|this', 'value': 0 },
+      { 'name': 'shift', 'src': 'next', 'value': 1 }
+    ],
+    'dateParse': [
+      '{month} {year}',
+      '{shift} {unit=5-7}',
+      '{0?} {date}{1}',
+      '{0?} {edge} of {shift?} {unit=4-7?}{month?}{year?}'
+    ],
+    'timeParse': [
+      '{num} {unit} {sign}',
+      '{sign} {num} {unit}',
+      '{0} {num}{1} {day} of {month} {year?}',
+      '{weekday?} {month} {date}{1?} {year?}',
+      '{date} {month} {year}',
+      '{date} {month}',
+      '{shift} {weekday}',
+      '{shift} week {weekday}',
+      '{weekday} {2?} {shift} week',
+      '{num} {unit=4-5} {sign} {day}',
+      '{0?} {date}{1} of {month}',
+      '{0?}{month?} {date?}{1?} of {shift} {unit=6-7}'
+    ]
+  });
+
+  buildDateUnits();
+  buildDateMethods();
+  buildCoreInputFormats();
+  buildFormatTokens();
+  buildFormatShortcuts();
+  buildAsianDigits();
+  buildRelativeAliases();
+  buildUTCAliases();
+  setDateProperties();
+
level2/node_modules/sugar/lib/es5.js
@@ -0,0 +1,479 @@
+
+  'use strict';
+
+  /***
+   * @package ES5
+   * @description Shim methods that provide ES5 compatible functionality. This package can be excluded if you do not require legacy browser support (IE8 and below).
+   *
+   ***/
+
+
+  /***
+   * Object module
+   *
+   ***/
+
+  extend(object, false, false, {
+
+    'keys': function(obj) {
+      var keys = [];
+      if(!isObjectType(obj) && !isRegExp(obj) && !isFunction(obj)) {
+        throw new TypeError('Object required');
+      }
+      iterateOverObject(obj, function(key, value) {
+        keys.push(key);
+      });
+      return keys;
+    }
+
+  });
+
+
+  /***
+   * Array module
+   *
+   ***/
+
+  // ECMA5 methods
+
+  function arrayIndexOf(arr, search, fromIndex, increment) {
+    var length = arr.length,
+        fromRight = increment == -1,
+        start = fromRight ? length - 1 : 0,
+        index = toIntegerWithDefault(fromIndex, start);
+    if(index < 0) {
+      index = length + index;
+    }
+    if((!fromRight && index < 0) || (fromRight && index >= length)) {
+      index = start;
+    }
+    while((fromRight && index >= 0) || (!fromRight && index < length)) {
+      if(arr[index] === search) {
+        return index;
+      }
+      index += increment;
+    }
+    return -1;
+  }
+
+  function arrayReduce(arr, fn, initialValue, fromRight) {
+    var length = arr.length, count = 0, defined = isDefined(initialValue), result, index;
+    checkCallback(fn);
+    if(length == 0 && !defined) {
+      throw new TypeError('Reduce called on empty array with no initial value');
+    } else if(defined) {
+      result = initialValue;
+    } else {
+      result = arr[fromRight ? length - 1 : count];
+      count++;
+    }
+    while(count < length) {
+      index = fromRight ? length - count - 1 : count;
+      if(index in arr) {
+        result = fn(result, arr[index], index, arr);
+      }
+      count++;
+    }
+    return result;
+  }
+
+  function toIntegerWithDefault(i, d) {
+    if(isNaN(i)) {
+      return d;
+    } else {
+      return parseInt(i >> 0);
+    }
+  }
+
+  function checkFirstArgumentExists(args) {
+    if(args.length === 0) {
+      throw new TypeError('First argument must be defined');
+    }
+  }
+
+
+
+
+  extend(array, false, false, {
+
+    /***
+     *
+     * @method Array.isArray(<obj>)
+     * @returns Boolean
+     * @short Returns true if <obj> is an Array.
+     * @extra This method is provided for browsers that don't support it internally.
+     * @example
+     *
+     *   Array.isArray(3)        -> false
+     *   Array.isArray(true)     -> false
+     *   Array.isArray('wasabi') -> false
+     *   Array.isArray([1,2,3])  -> true
+     *
+     ***/
+    'isArray': function(obj) {
+      return isArray(obj);
+    }
+
+  });
+
+
+  extend(array, true, false, {
+
+    /***
+     * @method every(<f>, [scope])
+     * @returns Boolean
+     * @short Returns true if all elements in the array match <f>.
+     * @extra [scope] is the %this% object. %all% is provided an alias. In addition to providing this method for browsers that don't support it natively, this method also implements @array_matching.
+     * @example
+     *
+     +   ['a','a','a'].every(function(n) {
+     *     return n == 'a';
+     *   });
+     *   ['a','a','a'].every('a')   -> true
+     *   [{a:2},{a:2}].every({a:2}) -> true
+     ***/
+    'every': function(fn, scope) {
+      var length = this.length, index = 0;
+      checkFirstArgumentExists(arguments);
+      while(index < length) {
+        if(index in this && !fn.call(scope, this[index], index, this)) {
+          return false;
+        }
+        index++;
+      }
+      return true;
+    },
+
+    /***
+     * @method some(<f>, [scope])
+     * @returns Boolean
+     * @short Returns true if any element in the array matches <f>.
+     * @extra [scope] is the %this% object. %any% is provided as an alias. In addition to providing this method for browsers that don't support it natively, this method also implements @array_matching.
+     * @example
+     *
+     +   ['a','b','c'].some(function(n) {
+     *     return n == 'a';
+     *   });
+     +   ['a','b','c'].some(function(n) {
+     *     return n == 'd';
+     *   });
+     *   ['a','b','c'].some('a')   -> true
+     *   [{a:2},{b:5}].some({a:2}) -> true
+     ***/
+    'some': function(fn, scope) {
+      var length = this.length, index = 0;
+      checkFirstArgumentExists(arguments);
+      while(index < length) {
+        if(index in this && fn.call(scope, this[index], index, this)) {
+          return true;
+        }
+        index++;
+      }
+      return false;
+    },
+
+    /***
+     * @method map(<map>, [scope])
+     * @returns Array
+     * @short Maps the array to another array containing the values that are the result of calling <map> on each element.
+     * @extra [scope] is the %this% object. When <map> is a function, it receives three arguments: the current element, the current index, and a reference to the array. In addition to providing this method for browsers that don't support it natively, this enhanced method also directly accepts a string, which is a shortcut for a function that gets that property (or invokes a function) on each element.
+     * @example
+     *
+     *   [1,2,3].map(function(n) {
+     *     return n * 3;
+     *   });                                  -> [3,6,9]
+     *   ['one','two','three'].map(function(n) {
+     *     return n.length;
+     *   });                                  -> [3,3,5]
+     *   ['one','two','three'].map('length')  -> [3,3,5]
+     *
+     ***/
+    'map': function(fn, scope) {
+      var scope = arguments[1], length = this.length, index = 0, result = new Array(length);
+      checkFirstArgumentExists(arguments);
+      while(index < length) {
+        if(index in this) {
+          result[index] = fn.call(scope, this[index], index, this);
+        }
+        index++;
+      }
+      return result;
+    },
+
+    /***
+     * @method filter(<f>, [scope])
+     * @returns Array
+     * @short Returns any elements in the array that match <f>.
+     * @extra [scope] is the %this% object. In addition to providing this method for browsers that don't support it natively, this method also implements @array_matching.
+     * @example
+     *
+     +   [1,2,3].filter(function(n) {
+     *     return n > 1;
+     *   });
+     *   [1,2,2,4].filter(2) -> 2
+     *
+     ***/
+    'filter': function(fn) {
+      var scope = arguments[1];
+      var length = this.length, index = 0, result = [];
+      checkFirstArgumentExists(arguments);
+      while(index < length) {
+        if(index in this && fn.call(scope, this[index], index, this)) {
+          result.push(this[index]);
+        }
+        index++;
+      }
+      return result;
+    },
+
+    /***
+     * @method indexOf(<search>, [fromIndex])
+     * @returns Number
+     * @short Searches the array and returns the first index where <search> occurs, or -1 if the element is not found.
+     * @extra [fromIndex] is the index from which to begin the search. This method performs a simple strict equality comparison on <search>. It does not support enhanced functionality such as searching the contents against a regex, callback, or deep comparison of objects. For such functionality, use the %findIndex% method instead.
+     * @example
+     *
+     *   [1,2,3].indexOf(3)           -> 1
+     *   [1,2,3].indexOf(7)           -> -1
+     *
+     ***/
+    'indexOf': function(search) {
+      var fromIndex = arguments[1];
+      if(isString(this)) return this.indexOf(search, fromIndex);
+      return arrayIndexOf(this, search, fromIndex, 1);
+    },
+
+    /***
+     * @method lastIndexOf(<search>, [fromIndex])
+     * @returns Number
+     * @short Searches the array and returns the last index where <search> occurs, or -1 if the element is not found.
+     * @extra [fromIndex] is the index from which to begin the search. This method performs a simple strict equality comparison on <search>.
+     * @example
+     *
+     *   [1,2,1].lastIndexOf(1)                 -> 2
+     *   [1,2,1].lastIndexOf(7)                 -> -1
+     *
+     ***/
+    'lastIndexOf': function(search) {
+      var fromIndex = arguments[1];
+      if(isString(this)) return this.lastIndexOf(search, fromIndex);
+      return arrayIndexOf(this, search, fromIndex, -1);
+    },
+
+    /***
+     * @method forEach([fn], [scope])
+     * @returns Nothing
+     * @short Iterates over the array, calling [fn] on each loop.
+     * @extra This method is only provided for those browsers that do not support it natively. [scope] becomes the %this% object.
+     * @example
+     *
+     *   ['a','b','c'].forEach(function(a) {
+     *     // Called 3 times: 'a','b','c'
+     *   });
+     *
+     ***/
+    'forEach': function(fn) {
+      var length = this.length, index = 0, scope = arguments[1];
+      checkCallback(fn);
+      while(index < length) {
+        if(index in this) {
+          fn.call(scope, this[index], index, this);
+        }
+        index++;
+      }
+    },
+
+    /***
+     * @method reduce(<fn>, [init])
+     * @returns Mixed
+     * @short Reduces the array to a single result.
+     * @extra If [init] is passed as a starting value, that value will be passed as the first argument to the callback. The second argument will be the first element in the array. From that point, the result of the callback will then be used as the first argument of the next iteration. This is often refered to as "accumulation", and [init] is often called an "accumulator". If [init] is not passed, then <fn> will be called n - 1 times, where n is the length of the array. In this case, on the first iteration only, the first argument will be the first element of the array, and the second argument will be the second. After that callbacks work as normal, using the result of the previous callback as the first argument of the next. This method is only provided for those browsers that do not support it natively.
+     *
+     * @example
+     *
+     +   [1,2,3,4].reduce(function(a, b) {
+     *     return a - b;
+     *   });
+     +   [1,2,3,4].reduce(function(a, b) {
+     *     return a - b;
+     *   }, 100);
+     *
+     ***/
+    'reduce': function(fn) {
+      return arrayReduce(this, fn, arguments[1]);
+    },
+
+    /***
+     * @method reduceRight([fn], [init])
+     * @returns Mixed
+     * @short Identical to %Array#reduce%, but operates on the elements in reverse order.
+     * @extra This method is only provided for those browsers that do not support it natively.
+     *
+     *
+     *
+     *
+     * @example
+     *
+     +   [1,2,3,4].reduceRight(function(a, b) {
+     *     return a - b;
+     *   });
+     *
+     ***/
+    'reduceRight': function(fn) {
+      return arrayReduce(this, fn, arguments[1], true);
+    }
+
+
+  });
+
+
+
+
+  /***
+   * String module
+   *
+   ***/
+
+
+  function buildTrim() {
+    var support = getTrimmableCharacters().match(/^\s+$/);
+    try { string.prototype.trim.call([1]); } catch(e) { support = false; }
+    extend(string, true, !support, {
+
+      /***
+       * @method trim[Side]()
+       * @returns String
+       * @short Removes leading and/or trailing whitespace from the string.
+       * @extra Whitespace is defined as line breaks, tabs, and any character in the "Space, Separator" Unicode category, conforming to the the ES5 spec. The standard %trim% method is only added when not fully supported natively.
+       *
+       * @set
+       *   trim
+       *   trimLeft
+       *   trimRight
+       *
+       * @example
+       *
+       *   '   wasabi   '.trim()      -> 'wasabi'
+       *   '   wasabi   '.trimLeft()  -> 'wasabi   '
+       *   '   wasabi   '.trimRight() -> '   wasabi'
+       *
+       ***/
+      'trim': function() {
+        return this.toString().trimLeft().trimRight();
+      },
+
+      'trimLeft': function() {
+        return this.replace(regexp('^['+getTrimmableCharacters()+']+'), '');
+      },
+
+      'trimRight': function() {
+        return this.replace(regexp('['+getTrimmableCharacters()+']+$'), '');
+      }
+    });
+  }
+
+
+
+  /***
+   * Function module
+   *
+   ***/
+
+
+  extend(Function, true, false, {
+
+     /***
+     * @method bind(<scope>, [arg1], ...)
+     * @returns Function
+     * @short Binds <scope> as the %this% object for the function when it is called. Also allows currying an unlimited number of parameters.
+     * @extra "currying" means setting parameters ([arg1], [arg2], etc.) ahead of time so that they are passed when the function is called later. If you pass additional parameters when the function is actually called, they will be added will be added to the end of the curried parameters. This method is provided for browsers that don't support it internally.
+     * @example
+     *
+     +   (function() {
+     *     return this;
+     *   }).bind('woof')(); -> returns 'woof'; function is bound with 'woof' as the this object.
+     *   (function(a) {
+     *     return a;
+     *   }).bind(1, 2)();   -> returns 2; function is bound with 1 as the this object and 2 curried as the first parameter
+     *   (function(a, b) {
+     *     return a + b;
+     *   }).bind(1, 2)(3);  -> returns 5; function is bound with 1 as the this object, 2 curied as the first parameter and 3 passed as the second when calling the function
+     *
+     ***/
+    'bind': function(scope) {
+      var fn = this, args = multiArgs(arguments, null, 1), bound;
+      if(!isFunction(this)) {
+        throw new TypeError('Function.prototype.bind called on a non-function');
+      }
+      bound = function() {
+        return fn.apply(fn.prototype && this instanceof fn ? this : scope, args.concat(multiArgs(arguments)));
+      }
+      bound.prototype = this.prototype;
+      return bound;
+    }
+
+  });
+
+  /***
+   * Date module
+   *
+   ***/
+
+   /***
+   * @method toISOString()
+   * @returns String
+   * @short Formats the string to ISO8601 format.
+   * @extra This will always format as UTC time. Provided for browsers that do not support this method.
+   * @example
+   *
+   *   Date.create().toISOString() -> ex. 2011-07-05 12:24:55.528Z
+   *
+   ***
+   * @method toJSON()
+   * @returns String
+   * @short Returns a JSON representation of the date.
+   * @extra This is effectively an alias for %toISOString%. Will always return the date in UTC time. Provided for browsers that do not support this method.
+   * @example
+   *
+   *   Date.create().toJSON() -> ex. 2011-07-05 12:24:55.528Z
+   *
+   ***/
+
+  extend(date, false, false, {
+
+     /***
+     * @method Date.now()
+     * @returns String
+     * @short Returns the number of milliseconds since January 1st, 1970 00:00:00 (UTC time).
+     * @extra Provided for browsers that do not support this method.
+     * @example
+     *
+     *   Date.now() -> ex. 1311938296231
+     *
+     ***/
+    'now': function() {
+      return new date().getTime();
+    }
+
+  });
+
+   function buildISOString() {
+    var d = new date(date.UTC(1999, 11, 31)), target = '1999-12-31T00:00:00.000Z';
+    var support = d.toISOString && d.toISOString() === target;
+    extendSimilar(date, true, !support, 'toISOString,toJSON', function(methods, name) {
+      methods[name] = function() {
+        return padNumber(this.getUTCFullYear(), 4) + '-' +
+               padNumber(this.getUTCMonth() + 1, 2) + '-' +
+               padNumber(this.getUTCDate(), 2) + 'T' +
+               padNumber(this.getUTCHours(), 2) + ':' +
+               padNumber(this.getUTCMinutes(), 2) + ':' +
+               padNumber(this.getUTCSeconds(), 2) + '.' +
+               padNumber(this.getUTCMilliseconds(), 3) + 'Z';
+      }
+    });
+   }
+
+  // Initialize
+  buildTrim();
+  buildISOString();
+
level2/node_modules/sugar/lib/function.js
@@ -0,0 +1,266 @@
+
+  'use strict';
+
+  /***
+   * @package Function
+   * @dependency core
+   * @description Lazy, throttled, and memoized functions, delayed functions and handling of timers, argument currying.
+   *
+   ***/
+
+  function setDelay(fn, ms, after, scope, args) {
+    // Delay of infinity is never called of course...
+    if(ms === Infinity) return;
+    if(!fn.timers) fn.timers = [];
+    if(!isNumber(ms)) ms = 1;
+    // This is a workaround for <= IE8, which apparently has the
+    // ability to call timeouts in the queue on the same tick (ms?)
+    // even if functionally they have already been cleared.
+    fn._canceled = false;
+    fn.timers.push(setTimeout(function(){
+      if(!fn._canceled) {
+        after.apply(scope, args || []);
+      }
+    }, ms));
+  }
+
+  extend(Function, true, true, {
+
+     /***
+     * @method lazy([ms] = 1, [immediate] = false, [limit] = Infinity)
+     * @returns Function
+     * @short Creates a lazy function that, when called repeatedly, will queue execution and wait [ms] milliseconds to execute.
+     * @extra If [immediate] is %true%, first execution will happen immediately, then lock. If [limit] is a fininte number, calls past [limit] will be ignored while execution is locked. Compare this to %throttle%, which will execute only once per [ms] milliseconds. Note that [ms] can also be a fraction. Calling %cancel% on a lazy function will clear the entire queue. For more see @functions.
+     * @example
+     *
+     *   (function() {
+     *     // Executes immediately.
+     *   }).lazy()();
+     *   (3).times(function() {
+     *     // Executes 3 times, with each execution 20ms later than the last.
+     *   }.lazy(20));
+     *   (100).times(function() {
+     *     // Executes 50 times, with each execution 20ms later than the last.
+     *   }.lazy(20, false, 50));
+     *
+     ***/
+    'lazy': function(ms, immediate, limit) {
+      var fn = this, queue = [], locked = false, execute, rounded, perExecution, result;
+      ms = ms || 1;
+      limit = limit || Infinity;
+      rounded = ceil(ms);
+      perExecution = round(rounded / ms) || 1;
+      execute = function() {
+        var queueLength = queue.length, maxPerRound;
+        if(queueLength == 0) return;
+        // Allow fractions of a millisecond by calling
+        // multiple times per actual timeout execution
+        maxPerRound = max(queueLength - perExecution, 0);
+        while(queueLength > maxPerRound) {
+          // Getting uber-meta here...
+          result = Function.prototype.apply.apply(fn, queue.shift());
+          queueLength--;
+        }
+        setDelay(lazy, rounded, function() {
+          locked = false;
+          execute();
+        });
+      }
+      function lazy() {
+        // If the execution has locked and it's immediate, then
+        // allow 1 less in the queue as 1 call has already taken place.
+        if(queue.length < limit - (locked && immediate ? 1 : 0)) {
+          queue.push([this, arguments]);
+        }
+        if(!locked) {
+          locked = true;
+          if(immediate) {
+            execute();
+          } else {
+            setDelay(lazy, rounded, execute);
+          }
+        }
+        // Return the memoized result
+        return result;
+      }
+      return lazy;
+    },
+
+     /***
+     * @method throttle([ms] = 1)
+     * @returns Function
+     * @short Creates a "throttled" version of the function that will only be executed once per <ms> milliseconds.
+     * @extra This is functionally equivalent to calling %lazy% with a [limit] of %1% and [immediate] as %true%. %throttle% is appropriate when you want to make sure a function is only executed at most once for a given duration. For more see @functions.
+     * @example
+     *
+     *   (3).times(function() {
+     *     // called only once. will wait 50ms until it responds again
+     *   }.throttle(50));
+     *
+     ***/
+    'throttle': function(ms) {
+      return this.lazy(ms, true, 1);
+    },
+
+     /***
+     * @method debounce([ms] = 1)
+     * @returns Function
+     * @short Creates a "debounced" function that postpones its execution until after <ms> milliseconds have passed.
+     * @extra This method is useful to execute a function after things have "settled down". A good example of this is when a user tabs quickly through form fields, execution of a heavy operation should happen after a few milliseconds when they have "settled" on a field. For more see @functions.
+     * @example
+     *
+     *   var fn = (function(arg1) {
+     *     // called once 50ms later
+     *   }).debounce(50); fn() fn() fn();
+     *
+     ***/
+    'debounce': function(ms) {
+      var fn = this;
+      function debounced() {
+        debounced.cancel();
+        setDelay(debounced, ms, fn, this, arguments);
+      };
+      return debounced;
+    },
+
+     /***
+     * @method delay([ms] = 1, [arg1], ...)
+     * @returns Function
+     * @short Executes the function after <ms> milliseconds.
+     * @extra Returns a reference to itself. %delay% is also a way to execute non-blocking operations that will wait until the CPU is free. Delayed functions can be canceled using the %cancel% method. Can also curry arguments passed in after <ms>.
+     * @example
+     *
+     *   (function(arg1) {
+     *     // called 1s later
+     *   }).delay(1000, 'arg1');
+     *
+     ***/
+    'delay': function(ms) {
+      var fn = this;
+      var args = multiArgs(arguments, null, 1);
+      setDelay(fn, ms, fn, fn, args);
+      return fn;
+    },
+
+     /***
+     * @method every([ms] = 1, [arg1], ...)
+     * @returns Function
+     * @short Executes the function every <ms> milliseconds.
+     * @extra Returns a reference to itself. Repeating functions with %every% can be canceled using the %cancel% method. Can also curry arguments passed in after <ms>.
+     * @example
+     *
+     *   (function(arg1) {
+     *     // called every 1s
+     *   }).every(1000, 'arg1');
+     *
+     ***/
+    'every': function(ms) {
+      var fn = this, args = arguments;
+      args = args.length > 1 ? multiArgs(args, null, 1) : [];
+      function execute () {
+        fn.apply(fn, args);
+        setDelay(fn, ms, execute);
+      }
+      setDelay(fn, ms, execute);
+      return fn;
+    },
+
+     /***
+     * @method cancel()
+     * @returns Function
+     * @short Cancels a delayed function scheduled to be run.
+     * @extra %delay%, %lazy%, %throttle%, and %debounce% can all set delays.
+     * @example
+     *
+     *   (function() {
+     *     alert('hay'); // Never called
+     *   }).delay(500).cancel();
+     *
+     ***/
+    'cancel': function() {
+      var timers = this.timers, timer;
+      if(isArray(timers)) {
+        while(timer = timers.shift()) {
+          clearTimeout(timer);
+        }
+      }
+      this._canceled = true;
+      return this;
+    },
+
+     /***
+     * @method after([num] = 1)
+     * @returns Function
+     * @short Creates a function that will execute after [num] calls.
+     * @extra %after% is useful for running a final callback after a series of asynchronous operations, when the order in which the operations will complete is unknown.
+     * @example
+     *
+     *   var fn = (function() {
+     *     // Will be executed once only
+     *   }).after(3); fn(); fn(); fn();
+     *
+     ***/
+    'after': function(num) {
+      var fn = this, counter = 0, storedArguments = [];
+      if(!isNumber(num)) {
+        num = 1;
+      } else if(num === 0) {
+        fn.call();
+        return fn;
+      }
+      return function() {
+        var ret;
+        storedArguments.push(multiArgs(arguments));
+        counter++;
+        if(counter == num) {
+          ret = fn.call(this, storedArguments);
+          counter = 0;
+          storedArguments = [];
+          return ret;
+        }
+      }
+    },
+
+     /***
+     * @method once()
+     * @returns Function
+     * @short Creates a function that will execute only once and store the result.
+     * @extra %once% is useful for creating functions that will cache the result of an expensive operation and use it on subsequent calls. Also it can be useful for creating initialization functions that only need to be run once.
+     * @example
+     *
+     *   var fn = (function() {
+     *     // Will be executed once only
+     *   }).once(); fn(); fn(); fn();
+     *
+     ***/
+    'once': function() {
+      return this.throttle(Infinity, true);
+    },
+
+     /***
+     * @method fill(<arg1>, <arg2>, ...)
+     * @returns Function
+     * @short Returns a new version of the function which when called will have some of its arguments pre-emptively filled in, also known as "currying".
+     * @extra Arguments passed to a "filled" function are generally appended to the curried arguments. However, if %undefined% is passed as any of the arguments to %fill%, it will be replaced, when the "filled" function is executed. This allows currying of arguments even when they occur toward the end of an argument list (the example demonstrates this much more clearly).
+     * @example
+     *
+     *   var delayOneSecond = setTimeout.fill(undefined, 1000);
+     *   delayOneSecond(function() {
+     *     // Will be executed 1s later
+     *   });
+     *
+     ***/
+    'fill': function() {
+      var fn = this, curried = multiArgs(arguments);
+      return function() {
+        var args = multiArgs(arguments);
+        curried.forEach(function(arg, index) {
+          if(arg != null || index >= args.length) args.splice(index, 0, arg);
+        });
+        return fn.apply(this, args);
+      }
+    }
+
+
+  });
+
level2/node_modules/sugar/lib/inflections.js
@@ -0,0 +1,411 @@
+
+  'use strict';
+
+  /***
+   *
+   * @package Inflections
+   * @dependency string
+   * @description Pluralization similar to ActiveSupport including uncountable words and acronyms. Humanized and URL-friendly strings.
+   *
+   ***/
+
+  /***
+   * String module
+   *
+   ***/
+
+
+  var plurals      = [],
+      singulars    = [],
+      uncountables = [],
+      humans       = [],
+      acronyms     = {},
+      Downcased,
+      Inflector;
+
+  function removeFromArray(arr, find) {
+    var index = arr.indexOf(find);
+    if(index > -1) {
+      arr.splice(index, 1);
+    }
+  }
+
+  function removeFromUncountablesAndAddTo(arr, rule, replacement) {
+    if(isString(rule)) {
+      removeFromArray(uncountables, rule);
+    }
+    removeFromArray(uncountables, replacement);
+    arr.unshift({ rule: rule, replacement: replacement })
+  }
+
+  function paramMatchesType(param, type) {
+    return param == type || param == 'all' || !param;
+  }
+
+  function isUncountable(word) {
+    return uncountables.some(function(uncountable) {
+      return new regexp('\\b' + uncountable + '$', 'i').test(word);
+    });
+  }
+
+  function inflect(word, pluralize) {
+    word = isString(word) ? word.toString() : '';
+    if(word.isBlank() || isUncountable(word)) {
+      return word;
+    } else {
+      return runReplacements(word, pluralize ? plurals : singulars);
+    }
+  }
+
+  function runReplacements(word, table) {
+    iterateOverObject(table, function(i, inflection) {
+      if(word.match(inflection.rule)) {
+        word = word.replace(inflection.rule, inflection.replacement);
+        return false;
+      }
+    });
+    return word;
+  }
+
+  function capitalize(word) {
+    return word.replace(/^\W*[a-z]/, function(w){
+      return w.toUpperCase();
+    });
+  }
+
+  Inflector = {
+
+    /*
+     * Specifies a new acronym. An acronym must be specified as it will appear in a camelized string.  An underscore
+     * string that contains the acronym will retain the acronym when passed to %camelize%, %humanize%, or %titleize%.
+     * A camelized string that contains the acronym will maintain the acronym when titleized or humanized, and will
+     * convert the acronym into a non-delimited single lowercase word when passed to String#underscore.
+     *
+     * Examples:
+     *   String.Inflector.acronym('HTML')
+     *   'html'.titleize()     -> 'HTML'
+     *   'html'.camelize()     -> 'HTML'
+     *   'MyHTML'.underscore() -> 'my_html'
+     *
+     * The acronym, however, must occur as a delimited unit and not be part of another word for conversions to recognize it:
+     *
+     *   String.Inflector.acronym('HTTP')
+     *   'my_http_delimited'.camelize() -> 'MyHTTPDelimited'
+     *   'https'.camelize()             -> 'Https', not 'HTTPs'
+     *   'HTTPS'.underscore()           -> 'http_s', not 'https'
+     *
+     *   String.Inflector.acronym('HTTPS')
+     *   'https'.camelize()   -> 'HTTPS'
+     *   'HTTPS'.underscore() -> 'https'
+     *
+     * Note: Acronyms that are passed to %pluralize% will no longer be recognized, since the acronym will not occur as
+     * a delimited unit in the pluralized result. To work around this, you must specify the pluralized form as an
+     * acronym as well:
+     *
+     *    String.Inflector.acronym('API')
+     *    'api'.pluralize().camelize() -> 'Apis'
+     *
+     *    String.Inflector.acronym('APIs')
+     *    'api'.pluralize().camelize() -> 'APIs'
+     *
+     * %acronym% may be used to specify any word that contains an acronym or otherwise needs to maintain a non-standard
+     * capitalization. The only restriction is that the word must begin with a capital letter.
+     *
+     * Examples:
+     *   String.Inflector.acronym('RESTful')
+     *   'RESTful'.underscore()           -> 'restful'
+     *   'RESTfulController'.underscore() -> 'restful_controller'
+     *   'RESTfulController'.titleize()   -> 'RESTful Controller'
+     *   'restful'.camelize()             -> 'RESTful'
+     *   'restful_controller'.camelize()  -> 'RESTfulController'
+     *
+     *   String.Inflector.acronym('McDonald')
+     *   'McDonald'.underscore() -> 'mcdonald'
+     *   'mcdonald'.camelize()   -> 'McDonald'
+     */
+    'acronym': function(word) {
+      acronyms[word.toLowerCase()] = word;
+      var all = object.keys(acronyms).map(function(key) {
+        return acronyms[key];
+      });
+      Inflector.acronymRegExp = regexp(all.join('|'), 'g');
+    },
+
+    /*
+     * Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
+     * The replacement should always be a string that may include references to the matched data from the rule.
+     */
+    'plural': function(rule, replacement) {
+      removeFromUncountablesAndAddTo(plurals, rule, replacement);
+    },
+
+    /*
+     * Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
+     * The replacement should always be a string that may include references to the matched data from the rule.
+     */
+    'singular': function(rule, replacement) {
+      removeFromUncountablesAndAddTo(singulars, rule, replacement);
+    },
+
+    /*
+     * Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
+     * for strings, not regular expressions. You simply pass the irregular in singular and plural form.
+     *
+     * Examples:
+     *   String.Inflector.irregular('octopus', 'octopi')
+     *   String.Inflector.irregular('person', 'people')
+     */
+    'irregular': function(singular, plural) {
+      var singularFirst      = singular.first(),
+          singularRest       = singular.from(1),
+          pluralFirst        = plural.first(),
+          pluralRest         = plural.from(1),
+          pluralFirstUpper   = pluralFirst.toUpperCase(),
+          pluralFirstLower   = pluralFirst.toLowerCase(),
+          singularFirstUpper = singularFirst.toUpperCase(),
+          singularFirstLower = singularFirst.toLowerCase();
+      removeFromArray(uncountables, singular);
+      removeFromArray(uncountables, plural);
+      if(singularFirstUpper == pluralFirstUpper) {
+        Inflector.plural(new regexp('({1}){2}$'.assign(singularFirst, singularRest), 'i'), '$1' + pluralRest);
+        Inflector.plural(new regexp('({1}){2}$'.assign(pluralFirst, pluralRest), 'i'), '$1' + pluralRest);
+        Inflector.singular(new regexp('({1}){2}$'.assign(pluralFirst, pluralRest), 'i'), '$1' + singularRest);
+      } else {
+        Inflector.plural(new regexp('{1}{2}$'.assign(singularFirstUpper, singularRest)), pluralFirstUpper + pluralRest);
+        Inflector.plural(new regexp('{1}{2}$'.assign(singularFirstLower, singularRest)), pluralFirstLower + pluralRest);
+        Inflector.plural(new regexp('{1}{2}$'.assign(pluralFirstUpper, pluralRest)), pluralFirstUpper + pluralRest);
+        Inflector.plural(new regexp('{1}{2}$'.assign(pluralFirstLower, pluralRest)), pluralFirstLower + pluralRest);
+        Inflector.singular(new regexp('{1}{2}$'.assign(pluralFirstUpper, pluralRest)), singularFirstUpper + singularRest);
+        Inflector.singular(new regexp('{1}{2}$'.assign(pluralFirstLower, pluralRest)), singularFirstLower + singularRest);
+      }
+    },
+
+    /*
+     * Add uncountable words that shouldn't be attempted inflected.
+     *
+     * Examples:
+     *   String.Inflector.uncountable('money')
+     *   String.Inflector.uncountable('money', 'information')
+     *   String.Inflector.uncountable(['money', 'information', 'rice'])
+     */
+    'uncountable': function(first) {
+      var add = array.isArray(first) ? first : multiArgs(arguments);
+      uncountables = uncountables.concat(add);
+    },
+
+    /*
+     * Specifies a humanized form of a string by a regular expression rule or by a string mapping.
+     * When using a regular expression based replacement, the normal humanize formatting is called after the replacement.
+     * When a string is used, the human form should be specified as desired (example: 'The name', not 'the_name')
+     *
+     * Examples:
+     *   String.Inflector.human(/_cnt$/i, '_count')
+     *   String.Inflector.human('legacy_col_person_name', 'Name')
+     */
+    'human': function(rule, replacement) {
+      humans.unshift({ rule: rule, replacement: replacement })
+    },
+
+
+    /*
+     * Clears the loaded inflections within a given scope (default is 'all').
+     * Options are: 'all', 'plurals', 'singulars', 'uncountables', 'humans'.
+     *
+     * Examples:
+     *   String.Inflector.clear('all')
+     *   String.Inflector.clear('plurals')
+     */
+    'clear': function(type) {
+      if(paramMatchesType(type, 'singulars'))    singulars    = [];
+      if(paramMatchesType(type, 'plurals'))      plurals      = [];
+      if(paramMatchesType(type, 'uncountables')) uncountables = [];
+      if(paramMatchesType(type, 'humans'))       humans       = [];
+      if(paramMatchesType(type, 'acronyms'))     acronyms     = {};
+    }
+
+  };
+
+  Downcased = [
+    'and', 'or', 'nor', 'a', 'an', 'the', 'so', 'but', 'to', 'of', 'at',
+    'by', 'from', 'into', 'on', 'onto', 'off', 'out', 'in', 'over',
+    'with', 'for'
+  ];
+
+  Inflector.plural(/$/, 's');
+  Inflector.plural(/s$/gi, 's');
+  Inflector.plural(/(ax|test)is$/gi, '$1es');
+  Inflector.plural(/(octop|vir|fung|foc|radi|alumn)(i|us)$/gi, '$1i');
+  Inflector.plural(/(census|alias|status)$/gi, '$1es');
+  Inflector.plural(/(bu)s$/gi, '$1ses');
+  Inflector.plural(/(buffal|tomat)o$/gi, '$1oes');
+  Inflector.plural(/([ti])um$/gi, '$1a');
+  Inflector.plural(/([ti])a$/gi, '$1a');
+  Inflector.plural(/sis$/gi, 'ses');
+  Inflector.plural(/f+e?$/gi, 'ves');
+  Inflector.plural(/(cuff|roof)$/gi, '$1s');
+  Inflector.plural(/([ht]ive)$/gi, '$1s');
+  Inflector.plural(/([^aeiouy]o)$/gi, '$1es');
+  Inflector.plural(/([^aeiouy]|qu)y$/gi, '$1ies');
+  Inflector.plural(/(x|ch|ss|sh)$/gi, '$1es');
+  Inflector.plural(/(matr|vert|ind)(?:ix|ex)$/gi, '$1ices');
+  Inflector.plural(/([ml])ouse$/gi, '$1ice');
+  Inflector.plural(/([ml])ice$/gi, '$1ice');
+  Inflector.plural(/^(ox)$/gi, '$1en');
+  Inflector.plural(/^(oxen)$/gi, '$1');
+  Inflector.plural(/(quiz)$/gi, '$1zes');
+  Inflector.plural(/(phot|cant|hom|zer|pian|portic|pr|quart|kimon)o$/gi, '$1os');
+  Inflector.plural(/(craft)$/gi, '$1');
+  Inflector.plural(/([ft])[eo]{2}(th?)$/gi, '$1ee$2');
+
+  Inflector.singular(/s$/gi, '');
+  Inflector.singular(/([pst][aiu]s)$/gi, '$1');
+  Inflector.singular(/([aeiouy])ss$/gi, '$1ss');
+  Inflector.singular(/(n)ews$/gi, '$1ews');
+  Inflector.singular(/([ti])a$/gi, '$1um');
+  Inflector.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/gi, '$1$2sis');
+  Inflector.singular(/(^analy)ses$/gi, '$1sis');
+  Inflector.singular(/(i)(f|ves)$/i, '$1fe');
+  Inflector.singular(/([aeolr]f?)(f|ves)$/i, '$1f');
+  Inflector.singular(/([ht]ive)s$/gi, '$1');
+  Inflector.singular(/([^aeiouy]|qu)ies$/gi, '$1y');
+  Inflector.singular(/(s)eries$/gi, '$1eries');
+  Inflector.singular(/(m)ovies$/gi, '$1ovie');
+  Inflector.singular(/(x|ch|ss|sh)es$/gi, '$1');
+  Inflector.singular(/([ml])(ous|ic)e$/gi, '$1ouse');
+  Inflector.singular(/(bus)(es)?$/gi, '$1');
+  Inflector.singular(/(o)es$/gi, '$1');
+  Inflector.singular(/(shoe)s?$/gi, '$1');
+  Inflector.singular(/(cris|ax|test)[ie]s$/gi, '$1is');
+  Inflector.singular(/(octop|vir|fung|foc|radi|alumn)(i|us)$/gi, '$1us');
+  Inflector.singular(/(census|alias|status)(es)?$/gi, '$1');
+  Inflector.singular(/^(ox)(en)?/gi, '$1');
+  Inflector.singular(/(vert|ind)(ex|ices)$/gi, '$1ex');
+  Inflector.singular(/(matr)(ix|ices)$/gi, '$1ix');
+  Inflector.singular(/(quiz)(zes)?$/gi, '$1');
+  Inflector.singular(/(database)s?$/gi, '$1');
+  Inflector.singular(/ee(th?)$/gi, 'oo$1');
+
+  Inflector.irregular('person', 'people');
+  Inflector.irregular('man', 'men');
+  Inflector.irregular('child', 'children');
+  Inflector.irregular('sex', 'sexes');
+  Inflector.irregular('move', 'moves');
+  Inflector.irregular('save', 'saves');
+  Inflector.irregular('cow', 'kine');
+  Inflector.irregular('goose', 'geese');
+  Inflector.irregular('zombie', 'zombies');
+
+  Inflector.uncountable('equipment,information,rice,money,species,series,fish,sheep,jeans'.split(','));
+
+
+  extend(string, true, true, {
+
+    /***
+     * @method pluralize()
+     * @returns String
+     * @short Returns the plural form of the word in the string.
+     * @example
+     *
+     *   'post'.pluralize()         -> 'posts'
+     *   'octopus'.pluralize()      -> 'octopi'
+     *   'sheep'.pluralize()        -> 'sheep'
+     *   'words'.pluralize()        -> 'words'
+     *   'CamelOctopus'.pluralize() -> 'CamelOctopi'
+     *
+     ***/
+    'pluralize': function() {
+      return inflect(this, true);
+    },
+
+    /***
+     * @method singularize()
+     * @returns String
+     * @short The reverse of String#pluralize. Returns the singular form of a word in a string.
+     * @example
+     *
+     *   'posts'.singularize()       -> 'post'
+     *   'octopi'.singularize()      -> 'octopus'
+     *   'sheep'.singularize()       -> 'sheep'
+     *   'word'.singularize()        -> 'word'
+     *   'CamelOctopi'.singularize() -> 'CamelOctopus'
+     *
+     ***/
+    'singularize': function() {
+      return inflect(this, false);
+    },
+
+    /***
+     * @method humanize()
+     * @returns String
+     * @short Creates a human readable string.
+     * @extra Capitalizes the first word and turns underscores into spaces and strips a trailing '_id', if any. Like String#titleize, this is meant for creating pretty output.
+     * @example
+     *
+     *   'employee_salary'.humanize() -> 'Employee salary'
+     *   'author_id'.humanize()       -> 'Author'
+     *
+     ***/
+    'humanize': function() {
+      var str = runReplacements(this, humans), acronym;
+      str = str.replace(/_id$/g, '');
+      str = str.replace(/(_)?([a-z\d]*)/gi, function(match, _, word){
+        acronym = hasOwnProperty(acronyms, word) ? acronyms[word] : null;
+        return (_ ? ' ' : '') + (acronym || word.toLowerCase());
+      });
+      return capitalize(str);
+    },
+
+    /***
+     * @method titleize()
+     * @returns String
+     * @short Creates a title version of the string.
+     * @extra Capitalizes all the words and replaces some characters in the string to create a nicer looking title. String#titleize is meant for creating pretty output.
+     * @example
+     *
+     *   'man from the boondocks'.titleize() -> 'Man from the Boondocks'
+     *   'x-men: the last stand'.titleize() -> 'X Men: The Last Stand'
+     *   'TheManWithoutAPast'.titleize() -> 'The Man Without a Past'
+     *   'raiders_of_the_lost_ark'.titleize() -> 'Raiders of the Lost Ark'
+     *
+     ***/
+    'titleize': function() {
+      var fullStopPunctuation = /[.:;!]$/, hasPunctuation, lastHadPunctuation, isFirstOrLast;
+      return this.spacify().humanize().words(function(word, index, words) {
+        hasPunctuation = fullStopPunctuation.test(word);
+        isFirstOrLast = index == 0 || index == words.length - 1 || hasPunctuation || lastHadPunctuation;
+        lastHadPunctuation = hasPunctuation;
+        if(isFirstOrLast || Downcased.indexOf(word) === -1) {
+          return capitalize(word);
+        } else {
+          return word;
+        }
+      }).join(' ');
+    },
+
+    /***
+     * @method parameterize()
+     * @returns String
+     * @short Replaces special characters in a string so that it may be used as part of a pretty URL.
+     * @example
+     *
+     *   'hell, no!'.parameterize() -> 'hell-no'
+     *
+     ***/
+    'parameterize': function(separator) {
+      var str = this;
+      if(separator === undefined) separator = '-';
+      if(str.normalize) {
+        str = str.normalize();
+      }
+      str = str.replace(/[^a-z0-9\-_]+/gi, separator)
+      if(separator) {
+        str = str.replace(new regexp('^{sep}+|{sep}+$|({sep}){sep}+'.assign({ 'sep': escapeRegExp(separator) }), 'g'), '$1');
+      }
+      return encodeURI(str.toLowerCase());
+    }
+
+  });
+
+  string.Inflector = Inflector;
+  string.Inflector.acronyms = acronyms;
+
level2/node_modules/sugar/lib/language.js
@@ -0,0 +1,261 @@
+
+  'use strict';
+
+  /***
+   *
+   * @package Language
+   * @dependency string
+   * @description Detecting language by character block. Full-width <-> half-width character conversion. Hiragana and Katakana conversions.
+   *
+   ***/
+
+  /***
+   * String module
+   *
+   ***/
+
+
+  /***
+   * @method has[Script]()
+   * @returns Boolean
+   * @short Returns true if the string contains any characters in that script.
+   *
+   * @set
+   *   hasArabic
+   *   hasCyrillic
+   *   hasGreek
+   *   hasHangul
+   *   hasHan
+   *   hasKanji
+   *   hasHebrew
+   *   hasHiragana
+   *   hasKana
+   *   hasKatakana
+   *   hasLatin
+   *   hasThai
+   *   hasDevanagari
+   *
+   * @example
+   *
+   *   'ุฃุชูƒู„ู…'.hasArabic()          -> true
+   *   'ะฒะธะทะธั‚'.hasCyrillic()        -> true
+   *   '์ž˜ ๋จน๊ฒ ์Šต๋‹ˆ๋‹ค!'.hasHangul() -> true
+   *   'ใƒŸใƒƒใ‚ฏใ‚นใงใ™'.hasKatakana() -> true
+   *   "l'annรฉe".hasLatin()         -> true
+   *
+   ***
+   * @method is[Script]()
+   * @returns Boolean
+   * @short Returns true if the string contains only characters in that script. Whitespace is ignored.
+   *
+   * @set
+   *   isArabic
+   *   isCyrillic
+   *   isGreek
+   *   isHangul
+   *   isHan
+   *   isKanji
+   *   isHebrew
+   *   isHiragana
+   *   isKana
+   *   isKatakana
+   *   isKatakana
+   *   isThai
+   *   isDevanagari
+   *
+   * @example
+   *
+   *   'ุฃุชูƒู„ู…'.isArabic()          -> true
+   *   'ะฒะธะทะธั‚'.isCyrillic()        -> true
+   *   '์ž˜ ๋จน๊ฒ ์Šต๋‹ˆ๋‹ค!'.isHangul() -> true
+   *   'ใƒŸใƒƒใ‚ฏใ‚นใงใ™'.isKatakana() -> false
+   *   "l'annรฉe".isLatin()         -> true
+   *
+   ***/
+  var unicodeScripts = [
+    { names: ['Arabic'],      source: '\u0600-\u06FF' },
+    { names: ['Cyrillic'],    source: '\u0400-\u04FF' },
+    { names: ['Devanagari'],  source: '\u0900-\u097F' },
+    { names: ['Greek'],       source: '\u0370-\u03FF' },
+    { names: ['Hangul'],      source: '\uAC00-\uD7AF\u1100-\u11FF' },
+    { names: ['Han','Kanji'], source: '\u4E00-\u9FFF\uF900-\uFAFF' },
+    { names: ['Hebrew'],      source: '\u0590-\u05FF' },
+    { names: ['Hiragana'],    source: '\u3040-\u309F\u30FB-\u30FC' },
+    { names: ['Kana'],        source: '\u3040-\u30FF\uFF61-\uFF9F' },
+    { names: ['Katakana'],    source: '\u30A0-\u30FF\uFF61-\uFF9F' },
+    { names: ['Latin'],       source: '\u0001-\u007F\u0080-\u00FF\u0100-\u017F\u0180-\u024F' },
+    { names: ['Thai'],        source: '\u0E00-\u0E7F' }
+  ];
+
+  function buildUnicodeScripts() {
+    unicodeScripts.forEach(function(s) {
+      var is = regexp('^['+s.source+'\\s]+$');
+      var has = regexp('['+s.source+']');
+      s.names.forEach(function(name) {
+        defineProperty(string.prototype, 'is' + name, function() { return is.test(this.trim()); });
+        defineProperty(string.prototype, 'has' + name, function() { return has.test(this); });
+      });
+    });
+  }
+
+  // Support for converting character widths and katakana to hiragana.
+
+  var HALF_WIDTH_TO_FULL_WIDTH_TRAVERSAL = 65248;
+
+  var widthConversionRanges = [
+    { type: 'a', start: 65,  end: 90  },
+    { type: 'a', start: 97,  end: 122 },
+    { type: 'n', start: 48,  end: 57  },
+    { type: 'p', start: 33,  end: 47  },
+    { type: 'p', start: 58,  end: 64  },
+    { type: 'p', start: 91,  end: 96  },
+    { type: 'p', start: 123, end: 126 }
+  ];
+
+  var WidthConversionTable;
+  var allHankaku   = /[\u0020-\u00A5]|[\uFF61-\uFF9F][๏พž๏พŸ]?/g;
+  var allZenkaku   = /[\u3000-\u301C]|[\u301A-\u30FC]|[\uFF01-\uFF60]|[\uFFE0-\uFFE6]/g;
+  var hankakuPunctuation  = '๏ฝก๏ฝค๏ฝข๏ฝฃยฅยขยฃ';
+  var zenkakuPunctuation  = 'ใ€‚ใ€ใ€Œใ€๏ฟฅ๏ฟ ๏ฟก';
+  var voicedKatakana      = /[ใ‚ซใ‚ญใ‚ฏใ‚ฑใ‚ณใ‚ตใ‚ทใ‚นใ‚ปใ‚ฝใ‚ฟใƒใƒ„ใƒ†ใƒˆใƒใƒ’ใƒ•ใƒ˜ใƒ›]/;
+  var semiVoicedKatakana  = /[ใƒใƒ’ใƒ•ใƒ˜ใƒ›ใƒฒ]/;
+  var hankakuKatakana     = '๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝถ๏ฝท๏ฝธ๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏ฝฏ๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏ฝฌ๏พ•๏ฝญ๏พ–๏ฝฎ๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏ฝฆ๏พ๏ฝฐ๏ฝฅ';
+  var zenkakuKatakana     = 'ใ‚ขใ‚คใ‚ฆใ‚จใ‚ชใ‚กใ‚ฃใ‚ฅใ‚งใ‚ฉใ‚ซใ‚ญใ‚ฏใ‚ฑใ‚ณใ‚ตใ‚ทใ‚นใ‚ปใ‚ฝใ‚ฟใƒใƒ„ใƒƒใƒ†ใƒˆใƒŠใƒ‹ใƒŒใƒใƒŽใƒใƒ’ใƒ•ใƒ˜ใƒ›ใƒžใƒŸใƒ ใƒกใƒขใƒคใƒฃใƒฆใƒฅใƒจใƒงใƒฉใƒชใƒซใƒฌใƒญใƒฏใƒฒใƒณใƒผใƒป';
+
+  function convertCharacterWidth(str, args, reg, type) {
+    if(!WidthConversionTable) {
+      buildWidthConversionTables();
+    }
+    var mode = multiArgs(args).join(''), table = WidthConversionTable[type];
+    mode = mode.replace(/all/, '').replace(/(\w)lphabet|umbers?|atakana|paces?|unctuation/g, '$1');
+    return str.replace(reg, function(c) {
+      if(table[c] && (!mode || mode.has(table[c].type))) {
+        return table[c].to;
+      } else {
+        return c;
+      }
+    });
+  }
+
+  function buildWidthConversionTables() {
+    var hankaku;
+    WidthConversionTable = {
+      'zenkaku': {},
+      'hankaku': {}
+    };
+    widthConversionRanges.forEach(function(r) {
+      simpleRepeat(r.end - r.start + 1, function(n) {
+        n += r.start;
+        setWidthConversion(r.type, chr(n), chr(n + HALF_WIDTH_TO_FULL_WIDTH_TRAVERSAL));
+      });
+    });
+    zenkakuKatakana.each(function(c, i) {
+      hankaku = hankakuKatakana.charAt(i);
+      setWidthConversion('k', hankaku, c);
+      if(c.match(voicedKatakana)) {
+        setWidthConversion('k', hankaku + '๏พž', c.shift(1));
+      }
+      if(c.match(semiVoicedKatakana)) {
+        setWidthConversion('k', hankaku + '๏พŸ', c.shift(2));
+      }
+    });
+    zenkakuPunctuation.each(function(c, i) {
+      setWidthConversion('p', hankakuPunctuation.charAt(i), c);
+    });
+    setWidthConversion('k', '๏ฝณ๏พž', 'ใƒด');
+    setWidthConversion('k', '๏ฝฆ๏พž', 'ใƒบ');
+    setWidthConversion('s', ' ', 'ใ€€');
+  }
+
+  function setWidthConversion(type, half, full) {
+    WidthConversionTable['zenkaku'][half] = { type: type, to: full };
+    WidthConversionTable['hankaku'][full] = { type: type, to: half };
+  }
+
+
+  extend(string, true, true, {
+
+    /***
+     * @method hankaku([mode] = 'all')
+     * @returns String
+     * @short Converts full-width characters (zenkaku) to half-width (hankaku).
+     * @extra [mode] accepts any combination of "a" (alphabet), "n" (numbers), "k" (katakana), "s" (spaces), "p" (punctuation), or "all".
+     * @example
+     *
+     *   'ใ‚ฟใƒญใ‚ฆใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™๏ผ'.hankaku()                      -> '๏พ€๏พ›๏ฝณ YAMADAใงใ™!'
+     *   'ใ‚ฟใƒญใ‚ฆใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™๏ผ'.hankaku('a')                   -> 'ใ‚ฟใƒญใ‚ฆใ€€YAMADAใงใ™๏ผ'
+     *   'ใ‚ฟใƒญใ‚ฆใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™๏ผ'.hankaku('alphabet')            -> 'ใ‚ฟใƒญใ‚ฆใ€€YAMADAใงใ™๏ผ'
+     *   'ใ‚ฟใƒญใ‚ฆใงใ™๏ผใ€€๏ผ’๏ผ•ๆญณใงใ™๏ผ'.hankaku('katakana', 'numbers') -> '๏พ€๏พ›๏ฝณใงใ™๏ผใ€€25ๆญณใงใ™๏ผ'
+     *   'ใ‚ฟใƒญใ‚ฆใงใ™๏ผใ€€๏ผ’๏ผ•ๆญณใงใ™๏ผ'.hankaku('k', 'n')              -> '๏พ€๏พ›๏ฝณใงใ™๏ผใ€€25ๆญณใงใ™๏ผ'
+     *   'ใ‚ฟใƒญใ‚ฆใงใ™๏ผใ€€๏ผ’๏ผ•ๆญณใงใ™๏ผ'.hankaku('kn')                  -> '๏พ€๏พ›๏ฝณใงใ™๏ผใ€€25ๆญณใงใ™๏ผ'
+     *   'ใ‚ฟใƒญใ‚ฆใงใ™๏ผใ€€๏ผ’๏ผ•ๆญณใงใ™๏ผ'.hankaku('sp')                  -> 'ใ‚ฟใƒญใ‚ฆใงใ™! ๏ผ’๏ผ•ๆญณใงใ™!'
+     *
+     ***/
+    'hankaku': function() {
+      return convertCharacterWidth(this, arguments, allZenkaku, 'hankaku');
+    },
+
+    /***
+     * @method zenkaku([mode] = 'all')
+     * @returns String
+     * @short Converts half-width characters (hankaku) to full-width (zenkaku).
+     * @extra [mode] accepts any combination of "a" (alphabet), "n" (numbers), "k" (katakana), "s" (spaces), "p" (punctuation), or "all".
+     * @example
+     *
+     *   '๏พ€๏พ›๏ฝณ YAMADAใงใ™!'.zenkaku()                         -> 'ใ‚ฟใƒญใ‚ฆใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™๏ผ'
+     *   '๏พ€๏พ›๏ฝณ YAMADAใงใ™!'.zenkaku('a')                      -> '๏พ€๏พ›๏ฝณ ๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™!'
+     *   '๏พ€๏พ›๏ฝณ YAMADAใงใ™!'.zenkaku('alphabet')               -> '๏พ€๏พ›๏ฝณ ๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™!'
+     *   '๏พ€๏พ›๏ฝณใงใ™! 25ๆญณใงใ™!'.zenkaku('katakana', 'numbers') -> 'ใ‚ฟใƒญใ‚ฆใงใ™! ๏ผ’๏ผ•ๆญณใงใ™!'
+     *   '๏พ€๏พ›๏ฝณใงใ™! 25ๆญณใงใ™!'.zenkaku('k', 'n')              -> 'ใ‚ฟใƒญใ‚ฆใงใ™! ๏ผ’๏ผ•ๆญณใงใ™!'
+     *   '๏พ€๏พ›๏ฝณใงใ™! 25ๆญณใงใ™!'.zenkaku('kn')                  -> 'ใ‚ฟใƒญใ‚ฆใงใ™! ๏ผ’๏ผ•ๆญณใงใ™!'
+     *   '๏พ€๏พ›๏ฝณใงใ™! 25ๆญณใงใ™!'.zenkaku('sp')                  -> '๏พ€๏พ›๏ฝณใงใ™๏ผใ€€25ๆญณใงใ™๏ผ'
+     *
+     ***/
+    'zenkaku': function() {
+      return convertCharacterWidth(this, arguments, allHankaku, 'zenkaku');
+    },
+
+    /***
+     * @method hiragana([all] = true)
+     * @returns String
+     * @short Converts katakana into hiragana.
+     * @extra If [all] is false, only full-width katakana will be converted.
+     * @example
+     *
+     *   'ใ‚ซใ‚ฟใ‚ซใƒŠ'.hiragana()   -> 'ใ‹ใŸใ‹ใช'
+     *   'ใ‚ณใƒณใƒ‹ใƒใƒ'.hiragana() -> 'ใ“ใ‚“ใซใกใฏ'
+     *   '๏ฝถ๏พ€๏ฝถ๏พ…'.hiragana()       -> 'ใ‹ใŸใ‹ใช'
+     *   '๏ฝถ๏พ€๏ฝถ๏พ…'.hiragana(false)  -> '๏ฝถ๏พ€๏ฝถ๏พ…'
+     *
+     ***/
+    'hiragana': function(all) {
+      var str = this;
+      if(all !== false) {
+        str = str.zenkaku('k');
+      }
+      return str.replace(/[\u30A1-\u30F6]/g, function(c) {
+        return c.shift(-96);
+      });
+    },
+
+    /***
+     * @method katakana()
+     * @returns String
+     * @short Converts hiragana into katakana.
+     * @example
+     *
+     *   'ใ‹ใŸใ‹ใช'.katakana()   -> 'ใ‚ซใ‚ฟใ‚ซใƒŠ'
+     *   'ใ“ใ‚“ใซใกใฏ'.katakana() -> 'ใ‚ณใƒณใƒ‹ใƒใƒ'
+     *
+     ***/
+    'katakana': function() {
+      return this.replace(/[\u3041-\u3096]/g, function(c) {
+        return c.shift(96);
+      });
+    }
+
+
+  });
+
+  buildUnicodeScripts();
+
level2/node_modules/sugar/lib/number.js
@@ -0,0 +1,399 @@
+
+  'use strict';
+
+  /***
+   * @package Number
+   * @dependency core
+   * @description Number formatting, rounding (with precision), and ranges. Aliases to Math methods.
+   *
+   ***/
+
+
+  function abbreviateNumber(num, roundTo, str, mid, limit, bytes) {
+    var fixed        = num.toFixed(20),
+        decimalPlace = fixed.search(/\./),
+        numeralPlace = fixed.search(/[1-9]/),
+        significant  = decimalPlace - numeralPlace,
+        unit, i, divisor;
+    if(significant > 0) {
+      significant -= 1;
+    }
+    i = max(min(floor(significant / 3), limit === false ? str.length : limit), -mid);
+    unit = str.charAt(i + mid - 1);
+    if(significant < -9) {
+      i = -3;
+      roundTo = abs(significant) - 9;
+      unit = str.slice(0,1);
+    }
+    divisor = bytes ? pow(2, 10 * i) : pow(10, i * 3);
+    return withPrecision(num / divisor, roundTo || 0).format() + unit.trim();
+  }
+
+
+  extend(number, false, true, {
+
+    /***
+     * @method Number.random([n1], [n2])
+     * @returns Number
+     * @short Returns a random integer between [n1] and [n2].
+     * @extra If only 1 number is passed, the other will be 0. If none are passed, the number will be either 0 or 1.
+     * @example
+     *
+     *   Number.random(50, 100) -> ex. 85
+     *   Number.random(50)      -> ex. 27
+     *   Number.random()        -> ex. 0
+     *
+     ***/
+    'random': function(n1, n2) {
+      var minNum, maxNum;
+      if(arguments.length == 1) n2 = n1, n1 = 0;
+      minNum = min(n1 || 0, isUndefined(n2) ? 1 : n2);
+      maxNum = max(n1 || 0, isUndefined(n2) ? 1 : n2) + 1;
+      return floor((math.random() * (maxNum - minNum)) + minNum);
+    }
+
+  });
+
+  extend(number, true, true, {
+
+    /***
+     * @method log(<base> = Math.E)
+     * @returns Number
+     * @short Returns the logarithm of the number with base <base>, or natural logarithm of the number if <base> is undefined.
+     * @example
+     *
+     *   (64).log(2) -> 6
+     *   (9).log(3)  -> 2
+     *   (5).log()   -> 1.6094379124341003
+     *
+     ***/
+
+    'log': function(base) {
+       return math.log(this) / (base ? math.log(base) : 1);
+     },
+
+    /***
+     * @method abbr([precision] = 0)
+     * @returns String
+     * @short Returns an abbreviated form of the number.
+     * @extra [precision] will round to the given precision.
+     * @example
+     *
+     *   (1000).abbr()    -> "1k"
+     *   (1000000).abbr() -> "1m"
+     *   (1280).abbr(1)   -> "1.3k"
+     *
+     ***/
+    'abbr': function(precision) {
+      return abbreviateNumber(this, precision, 'kmbt', 0, 4);
+    },
+
+    /***
+     * @method metric([precision] = 0, [limit] = 1)
+     * @returns String
+     * @short Returns the number as a string in metric notation.
+     * @extra [precision] will round to the given precision. Both very large numbers and very small numbers are supported. [limit] is the upper limit for the units. The default is %1%, which is "kilo". If [limit] is %false%, the upper limit will be "exa". The lower limit is "nano", and cannot be changed.
+     * @example
+     *
+     *   (1000).metric()            -> "1k"
+     *   (1000000).metric()         -> "1,000k"
+     *   (1000000).metric(0, false) -> "1M"
+     *   (1249).metric(2) + 'g'     -> "1.25kg"
+     *   (0.025).metric() + 'm'     -> "25mm"
+     *
+     ***/
+    'metric': function(precision, limit) {
+      return abbreviateNumber(this, precision, 'nฮผm kMGTPE', 4, isUndefined(limit) ? 1 : limit);
+    },
+
+    /***
+     * @method bytes([precision] = 0, [limit] = 4)
+     * @returns String
+     * @short Returns an abbreviated form of the number, considered to be "Bytes".
+     * @extra [precision] will round to the given precision. [limit] is the upper limit for the units. The default is %4%, which is "terabytes" (TB). If [limit] is %false%, the upper limit will be "exa".
+     * @example
+     *
+     *   (1000).bytes()                 -> "1kB"
+     *   (1000).bytes(2)                -> "0.98kB"
+     *   ((10).pow(20)).bytes()         -> "90,949,470TB"
+     *   ((10).pow(20)).bytes(0, false) -> "87EB"
+     *
+     ***/
+    'bytes': function(precision, limit) {
+      return abbreviateNumber(this, precision, 'kMGTPE', 0, isUndefined(limit) ? 4 : limit, true) + 'B';
+    },
+
+    /***
+     * @method isInteger()
+     * @returns Boolean
+     * @short Returns true if the number has no trailing decimal.
+     * @example
+     *
+     *   (420).isInteger() -> true
+     *   (4.5).isInteger() -> false
+     *
+     ***/
+    'isInteger': function() {
+      return this % 1 == 0;
+    },
+
+    /***
+     * @method isOdd()
+     * @returns Boolean
+     * @short Returns true if the number is odd.
+     * @example
+     *
+     *   (3).isOdd()  -> true
+     *   (18).isOdd() -> false
+     *
+     ***/
+    'isOdd': function() {
+      return !isNaN(this) && !this.isMultipleOf(2);
+    },
+
+    /***
+     * @method isEven()
+     * @returns Boolean
+     * @short Returns true if the number is even.
+     * @example
+     *
+     *   (6).isEven()  -> true
+     *   (17).isEven() -> false
+     *
+     ***/
+    'isEven': function() {
+      return this.isMultipleOf(2);
+    },
+
+    /***
+     * @method isMultipleOf(<num>)
+     * @returns Boolean
+     * @short Returns true if the number is a multiple of <num>.
+     * @example
+     *
+     *   (6).isMultipleOf(2)  -> true
+     *   (17).isMultipleOf(2) -> false
+     *   (32).isMultipleOf(4) -> true
+     *   (34).isMultipleOf(4) -> false
+     *
+     ***/
+    'isMultipleOf': function(num) {
+      return this % num === 0;
+    },
+
+
+    /***
+     * @method format([place] = 0, [thousands] = ',', [decimal] = '.')
+     * @returns String
+     * @short Formats the number to a readable string.
+     * @extra If [place] is %undefined%, will automatically determine the place. [thousands] is the character used for the thousands separator. [decimal] is the character used for the decimal point.
+     * @example
+     *
+     *   (56782).format()           -> '56,782'
+     *   (56782).format(2)          -> '56,782.00'
+     *   (4388.43).format(2, ' ')      -> '4 388.43'
+     *   (4388.43).format(2, '.', ',') -> '4.388,43'
+     *
+     ***/
+    'format': function(place, thousands, decimal) {
+      var i, str, split, integer, fraction, result = '';
+      if(isUndefined(thousands)) {
+        thousands = ',';
+      }
+      if(isUndefined(decimal)) {
+        decimal = '.';
+      }
+      str      = (isNumber(place) ? withPrecision(this, place || 0).toFixed(max(place, 0)) : this.toString()).replace(/^-/, '');
+      split    = str.split('.');
+      integer  = split[0];
+      fraction = split[1];
+      for(i = integer.length; i > 0; i -= 3) {
+        if(i < integer.length) {
+          result = thousands + result;
+        }
+        result = integer.slice(max(0, i - 3), i) + result;
+      }
+      if(fraction) {
+        result += decimal + repeatString('0', (place || 0) - fraction.length) + fraction;
+      }
+      return (this < 0 ? '-' : '') + result;
+    },
+
+    /***
+     * @method hex([pad] = 1)
+     * @returns String
+     * @short Converts the number to hexidecimal.
+     * @extra [pad] will pad the resulting string to that many places.
+     * @example
+     *
+     *   (255).hex()   -> 'ff';
+     *   (255).hex(4)  -> '00ff';
+     *   (23654).hex() -> '5c66';
+     *
+     ***/
+    'hex': function(pad) {
+      return this.pad(pad || 1, false, 16);
+    },
+
+    /***
+     * @method times(<fn>)
+     * @returns Number
+     * @short Calls <fn> a number of times equivalent to the number.
+     * @example
+     *
+     *   (8).times(function(i) {
+     *     // This function is called 8 times.
+     *   });
+     *
+     ***/
+    'times': function(fn) {
+      if(fn) {
+        for(var i = 0; i < this; i++) {
+          fn.call(this, i);
+        }
+      }
+      return this.toNumber();
+    },
+
+    /***
+     * @method chr()
+     * @returns String
+     * @short Returns a string at the code point of the number.
+     * @example
+     *
+     *   (65).chr() -> "A"
+     *   (75).chr() -> "K"
+     *
+     ***/
+    'chr': function() {
+      return string.fromCharCode(this);
+    },
+
+    /***
+     * @method pad(<place> = 0, [sign] = false, [base] = 10)
+     * @returns String
+     * @short Pads a number with "0" to <place>.
+     * @extra [sign] allows you to force the sign as well (+05, etc). [base] can change the base for numeral conversion.
+     * @example
+     *
+     *   (5).pad(2)        -> '05'
+     *   (-5).pad(4)       -> '-0005'
+     *   (82).pad(3, true) -> '+082'
+     *
+     ***/
+    'pad': function(place, sign, base) {
+      return padNumber(this, place, sign, base);
+    },
+
+    /***
+     * @method ordinalize()
+     * @returns String
+     * @short Returns an ordinalized (English) string, i.e. "1st", "2nd", etc.
+     * @example
+     *
+     *   (1).ordinalize() -> '1st';
+     *   (2).ordinalize() -> '2nd';
+     *   (8).ordinalize() -> '8th';
+     *
+     ***/
+    'ordinalize': function() {
+      var suffix, num = abs(this), last = parseInt(num.toString().slice(-2));
+      return this + getOrdinalizedSuffix(last);
+    },
+
+    /***
+     * @method toNumber()
+     * @returns Number
+     * @short Returns a number. This is mostly for compatibility reasons.
+     * @example
+     *
+     *   (420).toNumber() -> 420
+     *
+     ***/
+    'toNumber': function() {
+      return parseFloat(this, 10);
+    }
+
+  });
+
+  /***
+   * @method round(<precision> = 0)
+   * @returns Number
+   * @short Shortcut for %Math.round% that also allows a <precision>.
+   *
+   * @example
+   *
+   *   (3.241).round()  -> 3
+   *   (-3.841).round() -> -4
+   *   (3.241).round(2) -> 3.24
+   *   (3748).round(-2) -> 3800
+   *
+   ***
+   * @method ceil(<precision> = 0)
+   * @returns Number
+   * @short Shortcut for %Math.ceil% that also allows a <precision>.
+   *
+   * @example
+   *
+   *   (3.241).ceil()  -> 4
+   *   (-3.241).ceil() -> -3
+   *   (3.241).ceil(2) -> 3.25
+   *   (3748).ceil(-2) -> 3800
+   *
+   ***
+   * @method floor(<precision> = 0)
+   * @returns Number
+   * @short Shortcut for %Math.floor% that also allows a <precision>.
+   *
+   * @example
+   *
+   *   (3.241).floor()  -> 3
+   *   (-3.841).floor() -> -4
+   *   (3.241).floor(2) -> 3.24
+   *   (3748).floor(-2) -> 3700
+   *
+   ***
+   * @method [math]()
+   * @returns Number
+   * @short Math related functions are mapped as shortcuts to numbers and are identical. Note that %Number#log% provides some special defaults.
+   *
+   * @set
+   *   abs
+   *   sin
+   *   asin
+   *   cos
+   *   acos
+   *   tan
+   *   atan
+   *   sqrt
+   *   exp
+   *   pow
+   *
+   * @example
+   *
+   *   (3).pow(3) -> 27
+   *   (-3).abs() -> 3
+   *   (1024).sqrt() -> 32
+   *
+   ***/
+
+  function buildNumber() {
+    function createRoundingFunction(fn) {
+      return function (precision) {
+        return precision ? withPrecision(this, precision, fn) : fn(this);
+      }
+    }
+    extend(number, true, true, {
+      'ceil':   createRoundingFunction(ceil),
+      'round':  createRoundingFunction(round),
+      'floor':  createRoundingFunction(floor)
+    });
+    extendSimilar(number, true, true, 'abs,pow,sin,asin,cos,acos,tan,atan,exp,pow,sqrt', function(methods, name) {
+      methods[name] = function(a, b) {
+        return math[name](this, a, b);
+      }
+    });
+  }
+
+  buildNumber();
+
level2/node_modules/sugar/lib/object.js
@@ -0,0 +1,472 @@
+
+  'use strict';
+
+  /***
+   * @package Object
+   * @dependency core
+   * @description Object manipulation, type checking (isNumber, isString, ...), extended objects with hash-like methods available as instance methods.
+   *
+   * Much thanks to kangax for his informative aricle about how problems with instanceof and constructor
+   * http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
+   *
+   ***/
+
+  var ObjectTypeMethods = 'isObject,isNaN'.split(',');
+  var ObjectHashMethods = 'keys,values,select,reject,each,merge,clone,equal,watch,tap,has,toQueryString'.split(',');
+
+  function setParamsObject(obj, param, value, castBoolean) {
+    var reg = /^(.+?)(\[.*\])$/, paramIsArray, match, allKeys, key;
+    if(match = param.match(reg)) {
+      key = match[1];
+      allKeys = match[2].replace(/^\[|\]$/g, '').split('][');
+      allKeys.forEach(function(k) {
+        paramIsArray = !k || k.match(/^\d+$/);
+        if(!key && isArray(obj)) key = obj.length;
+        if(!hasOwnProperty(obj, key)) {
+          obj[key] = paramIsArray ? [] : {};
+        }
+        obj = obj[key];
+        key = k;
+      });
+      if(!key && paramIsArray) key = obj.length.toString();
+      setParamsObject(obj, key, value, castBoolean);
+    } else if(castBoolean && value === 'true') {
+      obj[param] = true;
+    } else if(castBoolean && value === 'false') {
+      obj[param] = false;
+    } else {
+      obj[param] = value;
+    }
+  }
+
+  function objectToQueryString(base, obj) {
+    var tmp;
+    // If a custom toString exists bail here and use that instead
+    if(isArray(obj) || (isObjectType(obj) && obj.toString === internalToString)) {
+      tmp = [];
+      iterateOverObject(obj, function(key, value) {
+        if(base) {
+          key = base + '[' + key + ']';
+        }
+        tmp.push(objectToQueryString(key, value));
+      });
+      return tmp.join('&');
+    } else {
+      if(!base) return '';
+      return sanitizeURIComponent(base) + '=' + (isDate(obj) ? obj.getTime() : sanitizeURIComponent(obj));
+    }
+  }
+
+  function sanitizeURIComponent(obj) {
+    // undefined, null, and NaN are represented as a blank string,
+    // while false and 0 are stringified. "+" is allowed in query string
+    return !obj && obj !== false && obj !== 0 ? '' : encodeURIComponent(obj).replace(/%20/g, '+');
+  }
+
+  function matchInObject(match, key, value) {
+    if(isRegExp(match)) {
+      return match.test(key);
+    } else if(isObjectType(match)) {
+      return match[key] === value;
+    } else {
+      return key === string(match);
+    }
+  }
+
+  function selectFromObject(obj, args, select) {
+    var match, result = obj instanceof Hash ? new Hash : {};
+    iterateOverObject(obj, function(key, value) {
+      match = false;
+      flattenedArgs(args, function(arg) {
+        if(matchInObject(arg, key, value)) {
+          match = true;
+        }
+      }, 1);
+      if(match === select) {
+        result[key] = value;
+      }
+    });
+    return result;
+  }
+
+
+  /***
+   * @method Object.is[Type](<obj>)
+   * @returns Boolean
+   * @short Returns true if <obj> is an object of that type.
+   * @extra %isObject% will return false on anything that is not an object literal, including instances of inherited classes. Note also that %isNaN% will ONLY return true if the object IS %NaN%. It does not mean the same as browser native %isNaN%, which returns true for anything that is "not a number".
+   *
+   * @set
+   *   isArray
+   *   isObject
+   *   isBoolean
+   *   isDate
+   *   isFunction
+   *   isNaN
+   *   isNumber
+   *   isString
+   *   isRegExp
+   *
+   * @example
+   *
+   *   Object.isArray([1,2,3])            -> true
+   *   Object.isDate(3)                   -> false
+   *   Object.isRegExp(/wasabi/)          -> true
+   *   Object.isObject({ broken:'wear' }) -> true
+   *
+   ***/
+  function buildTypeMethods() {
+    extendSimilar(object, false, true, ClassNames, function(methods, name) {
+      var method = 'is' + name;
+      ObjectTypeMethods.push(method);
+      methods[method] = typeChecks[name];
+    });
+  }
+
+  function buildObjectExtend() {
+    extend(object, false, function(){ return arguments.length === 0; }, {
+      'extend': function() {
+        var methods = ObjectTypeMethods.concat(ObjectHashMethods)
+        if(typeof EnumerableMethods !== 'undefined') {
+          methods = methods.concat(EnumerableMethods);
+        }
+        buildObjectInstanceMethods(methods, object);
+      }
+    });
+  }
+
+  extend(object, false, true, {
+      /***
+       * @method watch(<obj>, <prop>, <fn>)
+       * @returns Nothing
+       * @short Watches a property of <obj> and runs <fn> when it changes.
+       * @extra <fn> is passed three arguments: the property <prop>, the old value, and the new value. The return value of [fn] will be set as the new value. This method is useful for things such as validating or cleaning the value when it is set. Warning: this method WILL NOT work in browsers that don't support %Object.defineProperty% (IE 8 and below). This is the only method in Sugar that is not fully compatible with all browsers. %watch% is available as an instance method on extended objects.
+       * @example
+       *
+       *   Object.watch({ foo: 'bar' }, 'foo', function(prop, oldVal, newVal) {
+       *     // Will be run when the property 'foo' is set on the object.
+       *   });
+       *   Object.extended().watch({ foo: 'bar' }, 'foo', function(prop, oldVal, newVal) {
+       *     // Will be run when the property 'foo' is set on the object.
+       *   });
+       *
+       ***/
+    'watch': function(obj, prop, fn) {
+      if(!definePropertySupport) return;
+      var value = obj[prop];
+      object.defineProperty(obj, prop, {
+        'enumerable'  : true,
+        'configurable': true,
+        'get': function() {
+          return value;
+        },
+        'set': function(to) {
+          value = fn.call(obj, prop, value, to);
+        }
+      });
+    }
+  });
+
+  extend(object, false, function() { return arguments.length > 1; }, {
+
+    /***
+     * @method keys(<obj>, [fn])
+     * @returns Array
+     * @short Returns an array containing the keys in <obj>. Optionally calls [fn] for each key.
+     * @extra This method is provided for browsers that don't support it natively, and additionally is enhanced to accept the callback [fn]. Returned keys are in no particular order. %keys% is available as an instance method on extended objects.
+     * @example
+     *
+     *   Object.keys({ broken: 'wear' }) -> ['broken']
+     *   Object.keys({ broken: 'wear' }, function(key, value) {
+     *     // Called once for each key.
+     *   });
+     *   Object.extended({ broken: 'wear' }).keys() -> ['broken']
+     *
+     ***/
+    'keys': function(obj, fn) {
+      var keys = object.keys(obj);
+      keys.forEach(function(key) {
+        fn.call(obj, key, obj[key]);
+      });
+      return keys;
+    }
+
+  });
+
+  extend(object, false, true, {
+
+    'isObject': function(obj) {
+      return isPlainObject(obj);
+    },
+
+    'isNaN': function(obj) {
+      // This is only true of NaN
+      return isNumber(obj) && obj.valueOf() !== obj.valueOf();
+    },
+
+    /***
+     * @method equal(<a>, <b>)
+     * @returns Boolean
+     * @short Returns true if <a> and <b> are equal.
+     * @extra %equal% in Sugar is "egal", meaning the values are equal if they are "not observably distinguishable". Note that on extended objects the name is %equals% for readability.
+     * @example
+     *
+     *   Object.equal({a:2}, {a:2}) -> true
+     *   Object.equal({a:2}, {a:3}) -> false
+     *   Object.extended({a:2}).equals({a:3}) -> false
+     *
+     ***/
+    'equal': function(a, b) {
+      return isEqual(a, b);
+    },
+
+    /***
+     * @method Object.extended(<obj> = {})
+     * @returns Extended object
+     * @short Creates a new object, equivalent to %new Object()% or %{}%, but with extended methods.
+     * @extra See extended objects for more.
+     * @example
+     *
+     *   Object.extended()
+     *   Object.extended({ happy:true, pappy:false }).keys() -> ['happy','pappy']
+     *   Object.extended({ happy:true, pappy:false }).values() -> [true, false]
+     *
+     ***/
+    'extended': function(obj) {
+      return new Hash(obj);
+    },
+
+    /***
+     * @method merge(<target>, <source>, [deep] = false, [resolve] = true)
+     * @returns Merged object
+     * @short Merges all the properties of <source> into <target>.
+     * @extra Merges are shallow unless [deep] is %true%. Properties of <source> will win in the case of conflicts, unless [resolve] is %false%. [resolve] can also be a function that resolves the conflict. In this case it will be passed 3 arguments, %key%, %targetVal%, and %sourceVal%, with the context set to <source>. This will allow you to solve conflict any way you want, ie. adding two numbers together, etc. %merge% is available as an instance method on extended objects.
+     * @example
+     *
+     *   Object.merge({a:1},{b:2}) -> { a:1, b:2 }
+     *   Object.merge({a:1},{a:2}, false, false) -> { a:1 }
+     +   Object.merge({a:1},{a:2}, false, function(key, a, b) {
+     *     return a + b;
+     *   }); -> { a:3 }
+     *   Object.extended({a:1}).merge({b:2}) -> { a:1, b:2 }
+     *
+     ***/
+    'merge': function(target, source, deep, resolve) {
+      var key, sourceIsObject, targetIsObject, sourceVal, targetVal, conflict, result;
+      // Strings cannot be reliably merged thanks to
+      // their properties not being enumerable in < IE8.
+      if(target && typeof source !== 'string') {
+        for(key in source) {
+          if(!hasOwnProperty(source, key) || !target) continue;
+          sourceVal      = source[key];
+          targetVal      = target[key];
+          conflict       = isDefined(targetVal);
+          sourceIsObject = isObjectType(sourceVal);
+          targetIsObject = isObjectType(targetVal);
+          result         = conflict && resolve === false ? targetVal : sourceVal;
+
+          if(conflict) {
+            if(isFunction(resolve)) {
+              // Use the result of the callback as the result.
+              result = resolve.call(source, key, targetVal, sourceVal)
+            }
+          }
+
+          // Going deep
+          if(deep && (sourceIsObject || targetIsObject)) {
+            if(isDate(sourceVal)) {
+              result = new date(sourceVal.getTime());
+            } else if(isRegExp(sourceVal)) {
+              result = new regexp(sourceVal.source, getRegExpFlags(sourceVal));
+            } else {
+              if(!targetIsObject) target[key] = array.isArray(sourceVal) ? [] : {};
+              object.merge(target[key], sourceVal, deep, resolve);
+              continue;
+            }
+          }
+          target[key] = result;
+        }
+      }
+      return target;
+    },
+
+    /***
+     * @method values(<obj>, [fn])
+     * @returns Array
+     * @short Returns an array containing the values in <obj>. Optionally calls [fn] for each value.
+     * @extra Returned values are in no particular order. %values% is available as an instance method on extended objects.
+     * @example
+     *
+     *   Object.values({ broken: 'wear' }) -> ['wear']
+     *   Object.values({ broken: 'wear' }, function(value) {
+     *     // Called once for each value.
+     *   });
+     *   Object.extended({ broken: 'wear' }).values() -> ['wear']
+     *
+     ***/
+    'values': function(obj, fn) {
+      var values = [];
+      iterateOverObject(obj, function(k,v) {
+        values.push(v);
+        if(fn) fn.call(obj,v);
+      });
+      return values;
+    },
+
+    /***
+     * @method clone(<obj> = {}, [deep] = false)
+     * @returns Cloned object
+     * @short Creates a clone (copy) of <obj>.
+     * @extra Default is a shallow clone, unless [deep] is true. %clone% is available as an instance method on extended objects.
+     * @example
+     *
+     *   Object.clone({foo:'bar'})            -> { foo: 'bar' }
+     *   Object.clone()                       -> {}
+     *   Object.extended({foo:'bar'}).clone() -> { foo: 'bar' }
+     *
+     ***/
+    'clone': function(obj, deep) {
+      var target, klass;
+      if(!isObjectType(obj)) {
+        return obj;
+      }
+      klass = className(obj);
+      if(isDate(obj, klass) && obj.clone) {
+        // Preserve internal UTC flag when applicable.
+        return obj.clone();
+      } else if(isDate(obj, klass) || isRegExp(obj, klass)) {
+        return new obj.constructor(obj);
+      } else if(obj instanceof Hash) {
+        target = new Hash;
+      } else if(isArray(obj, klass)) {
+        target = [];
+      } else if(isPlainObject(obj, klass)) {
+        target = {};
+      } else {
+        throw new TypeError('Clone must be a basic data type.');
+      }
+      return object.merge(target, obj, deep);
+    },
+
+    /***
+     * @method Object.fromQueryString(<str>, [booleans] = false)
+     * @returns Object
+     * @short Converts the query string of a URL into an object.
+     * @extra If [booleans] is true, then %"true"% and %"false"% will be cast into booleans. All other values, including numbers will remain their string values.
+     * @example
+     *
+     *   Object.fromQueryString('foo=bar&broken=wear') -> { foo: 'bar', broken: 'wear' }
+     *   Object.fromQueryString('foo[]=1&foo[]=2')     -> { foo: ['1','2'] }
+     *   Object.fromQueryString('foo=true', true)      -> { foo: true }
+     *
+     ***/
+    'fromQueryString': function(str, castBoolean) {
+      var result = object.extended(), split;
+      str = str && str.toString ? str.toString() : '';
+      str.replace(/^.*?\?/, '').split('&').forEach(function(p) {
+        var split = p.split('=');
+        if(split.length !== 2) return;
+        setParamsObject(result, split[0], decodeURIComponent(split[1]), castBoolean);
+      });
+      return result;
+    },
+
+    /***
+     * @method Object.toQueryString(<obj>, [namespace] = null)
+     * @returns Object
+     * @short Converts the object into a query string.
+     * @extra Accepts deep nested objects and arrays. If [namespace] is passed, it will be prefixed to all param names.
+     * @example
+     *
+     *   Object.toQueryString({foo:'bar'})          -> 'foo=bar'
+     *   Object.toQueryString({foo:['a','b','c']})  -> 'foo[0]=a&foo[1]=b&foo[2]=c'
+     *   Object.toQueryString({name:'Bob'}, 'user') -> 'user[name]=Bob'
+     *
+     ***/
+    'toQueryString': function(obj, namespace) {
+      return objectToQueryString(namespace, obj);
+    },
+
+    /***
+     * @method tap(<obj>, <fn>)
+     * @returns Object
+     * @short Runs <fn> and returns <obj>.
+     * @extra  A string can also be used as a shortcut to a method. This method is used to run an intermediary function in the middle of method chaining. As a standalone method on the Object class it doesn't have too much use. The power of %tap% comes when using extended objects or modifying the Object prototype with Object.extend().
+     * @example
+     *
+     *   Object.extend();
+     *   [2,4,6].map(Math.exp).tap(function(arr) {
+     *     arr.pop()
+     *   });
+     *   [2,4,6].map(Math.exp).tap('pop').map(Math.round); ->  [7,55]
+     *
+     ***/
+    'tap': function(obj, arg) {
+      var fn = arg;
+      if(!isFunction(arg)) {
+        fn = function() {
+          if(arg) obj[arg]();
+        }
+      }
+      fn.call(obj, obj);
+      return obj;
+    },
+
+    /***
+     * @method has(<obj>, <key>)
+     * @returns Boolean
+     * @short Checks if <obj> has <key> using hasOwnProperty from Object.prototype.
+     * @extra This method is considered safer than %Object#hasOwnProperty% when using objects as hashes. See http://www.devthought.com/2012/01/18/an-object-is-not-a-hash/ for more.
+     * @example
+     *
+     *   Object.has({ foo: 'bar' }, 'foo') -> true
+     *   Object.has({ foo: 'bar' }, 'baz') -> false
+     *   Object.has({ hasOwnProperty: true }, 'foo') -> false
+     *
+     ***/
+    'has': function (obj, key) {
+      return hasOwnProperty(obj, key);
+    },
+
+    /***
+     * @method select(<obj>, <find>, ...)
+     * @returns Object
+     * @short Builds a new object containing the values specified in <find>.
+     * @extra When <find> is a string, that single key will be selected. It can also be a regex, selecting any key that matches, or an object which will match if the key also exists in that object, effectively doing an "intersect" operation on that object. Multiple selections may also be passed as an array or directly as enumerated arguments. %select% is available as an instance method on extended objects.
+     * @example
+     *
+     *   Object.select({a:1,b:2}, 'a')        -> {a:1}
+     *   Object.select({a:1,b:2}, /[a-z]/)    -> {a:1,ba:2}
+     *   Object.select({a:1,b:2}, {a:1})      -> {a:1}
+     *   Object.select({a:1,b:2}, 'a', 'b')   -> {a:1,b:2}
+     *   Object.select({a:1,b:2}, ['a', 'b']) -> {a:1,b:2}
+     *
+     ***/
+    'select': function (obj) {
+      return selectFromObject(obj, arguments, true);
+    },
+
+    /***
+     * @method reject(<obj>, <find>, ...)
+     * @returns Object
+     * @short Builds a new object containing all values except those specified in <find>.
+     * @extra When <find> is a string, that single key will be rejected. It can also be a regex, rejecting any key that matches, or an object which will match if the key also exists in that object, effectively "subtracting" that object. Multiple selections may also be passed as an array or directly as enumerated arguments. %reject% is available as an instance method on extended objects.
+     * @example
+     *
+     *   Object.reject({a:1,b:2}, 'a')        -> {b:2}
+     *   Object.reject({a:1,b:2}, /[a-z]/)    -> {}
+     *   Object.reject({a:1,b:2}, {a:1})      -> {b:2}
+     *   Object.reject({a:1,b:2}, 'a', 'b')   -> {}
+     *   Object.reject({a:1,b:2}, ['a', 'b']) -> {}
+     *
+     ***/
+    'reject': function (obj) {
+      return selectFromObject(obj, arguments, false);
+    }
+
+  });
+
+
+  buildTypeMethods();
+  buildObjectExtend();
+  buildObjectInstanceMethods(ObjectHashMethods, Hash);
level2/node_modules/sugar/lib/range.js
@@ -0,0 +1,416 @@
+
+  'use strict';
+
+  /***
+   * @package Range
+   * @dependency core
+   * @description Ranges allow creating spans of numbers, strings, or dates. They can enumerate over specific points within that range, and be manipulated and compared.
+   *
+   ***/
+
+  function Range(start, end) {
+    this.start = cloneRangeMember(start);
+    this.end   = cloneRangeMember(end);
+  };
+
+  function getRangeMemberNumericValue(m) {
+    return isString(m) ? m.charCodeAt(0) : m;
+  }
+
+  function getRangeMemberPrimitiveValue(m) {
+    if(m == null) return m;
+    return isDate(m) ? m.getTime() : m.valueOf();
+  }
+
+  function cloneRangeMember(m) {
+    if(isDate(m)) {
+      return new date(m.getTime());
+    } else {
+      return getRangeMemberPrimitiveValue(m);
+    }
+  }
+
+  function isValidRangeMember(m) {
+    var val = getRangeMemberPrimitiveValue(m);
+    return !!val || val === 0;
+  }
+
+  function getDuration(amt) {
+    var match, val, unit;
+    if(isNumber(amt)) {
+      return amt;
+    }
+    match = amt.toLowerCase().match(/^(\d+)?\s?(\w+?)s?$/i);
+    val = parseInt(match[1]) || 1;
+    unit = match[2].slice(0,1).toUpperCase() + match[2].slice(1);
+    if(unit.match(/hour|minute|second/i)) {
+      unit += 's';
+    } else if(unit === 'Year') {
+      unit = 'FullYear';
+    } else if(unit === 'Day') {
+      unit = 'Date';
+    }
+    return [val, unit];
+  }
+
+  function incrementDate(current, amount) {
+    var num, unit, val, d;
+    if(isNumber(amount)) {
+      return new date(current.getTime() + amount);
+    }
+    num  = amount[0];
+    unit = amount[1];
+    val  = callDateGet(current, unit);
+    d    = new date(current.getTime());
+    callDateSet(d, unit, val + num);
+    return d;
+  }
+
+  function incrementString(current, amount) {
+    return string.fromCharCode(current.charCodeAt(0) + amount);
+  }
+
+  function incrementNumber(current, amount) {
+    return current + amount;
+  }
+
+  /***
+   * @method toString()
+   * @returns String
+   * @short Returns a string representation of the range.
+   * @example
+   *
+   *   Number.range(1, 5).toString()                               -> 1..5
+   *   Date.range(new Date(2003, 0), new Date(2005, 0)).toString() -> January 1, 2003..January 1, 2005
+   *
+   ***/
+
+  // Note: 'toString' doesn't appear in a for..in loop in IE even though
+  // hasOwnProperty reports true, so extend() can't be used here.
+  // Also tried simply setting the prototype = {} up front for all
+  // methods but GCC very oddly started dropping properties in the
+  // object randomly (maybe because of the global scope?) hence
+  // the need for the split logic here.
+  Range.prototype.toString = function() {
+    return this.isValid() ? this.start + ".." + this.end : 'Invalid Range';
+  };
+
+  extend(Range, true, true, {
+
+    /***
+     * @method isValid()
+     * @returns Boolean
+     * @short Returns true if the range is valid, false otherwise.
+     * @example
+     *
+     *   Date.range(new Date(2003, 0), new Date(2005, 0)).isValid() -> true
+     *   Number.range(NaN, NaN).isValid()                           -> false
+     *
+     ***/
+    'isValid': function() {
+      return isValidRangeMember(this.start) && isValidRangeMember(this.end) && typeof this.start === typeof this.end;
+    },
+
+    /***
+     * @method span()
+     * @returns Number
+     * @short Returns the span of the range. If the range is a date range, the value is in milliseconds.
+     * @extra The span includes both the start and the end.
+     * @example
+     *
+     *   Number.range(5, 10).span()                              -> 6
+     *   Date.range(new Date(2003, 0), new Date(2005, 0)).span() -> 94694400000
+     *
+     ***/
+    'span': function() {
+      return this.isValid() ? abs(
+        getRangeMemberNumericValue(this.end) - getRangeMemberNumericValue(this.start)
+      ) + 1 : NaN;
+    },
+
+    /***
+     * @method contains(<obj>)
+     * @returns Boolean
+     * @short Returns true if <obj> is contained inside the range. <obj> may be a value or another range.
+     * @example
+     *
+     *   Number.range(5, 10).contains(7)                                              -> true
+     *   Date.range(new Date(2003, 0), new Date(2005, 0)).contains(new Date(2004, 0)) -> true
+     *
+     ***/
+    'contains': function(obj) {
+      var self = this, arr;
+      if(obj == null) return false;
+      if(obj.start && obj.end) {
+        return obj.start >= this.start && obj.start <= this.end &&
+               obj.end   >= this.start && obj.end   <= this.end;
+      } else {
+        return obj >= this.start && obj <= this.end;
+      }
+    },
+
+    /***
+     * @method every(<amount>, [fn])
+     * @returns Array
+     * @short Iterates through the range for every <amount>, calling [fn] if it is passed. Returns an array of each increment visited.
+     * @extra In the case of date ranges, <amount> can also be a string, in which case it will increment a number of  units. Note that %(2).months()% first resolves to a number, which will be interpreted as milliseconds and is an approximation, so stepping through the actual months by passing %"2 months"% is usually preferable.
+     * @example
+     *
+     *   Number.range(2, 8).every(2)                                       -> [2,4,6,8]
+     *   Date.range(new Date(2003, 1), new Date(2003,3)).every("2 months") -> [...]
+     *
+     ***/
+    'every': function(amount, fn) {
+      var increment,
+          start   = this.start,
+          end     = this.end,
+          inverse = end < start,
+          current = start,
+          index   = 0,
+          result  = [];
+
+      if(isFunction(amount)) {
+        fn = amount;
+        amount = null;
+      }
+      amount = amount || 1;
+      if(isNumber(start)) {
+        increment = incrementNumber;
+      } else if(isString(start)) {
+        increment = incrementString;
+      } else if(isDate(start)) {
+        amount    = getDuration(amount);
+        increment = incrementDate;
+      }
+      // Avoiding infinite loops
+      if(inverse && amount > 0) {
+        amount *= -1;
+      }
+      while(inverse ? current >= end : current <= end) {
+        result.push(current);
+        if(fn) {
+          fn(current, index);
+        }
+        current = increment(current, amount);
+        index++;
+      }
+      return result;
+    },
+
+    /***
+     * @method union(<range>)
+     * @returns Range
+     * @short Returns a new range with the earliest starting point as its start, and the latest ending point as its end. If the two ranges do not intersect this will effectively remove the "gap" between them.
+     * @example
+     *
+     *   Number.range(1, 3).union(Number.range(2, 5)) -> 1..5
+     *   Date.range(new Date(2003, 1), new Date(2005, 1)).union(Date.range(new Date(2004, 1), new Date(2006, 1))) -> Jan 1, 2003..Jan 1, 2006
+     *
+     ***/
+    'union': function(range) {
+      return new Range(
+        this.start < range.start ? this.start : range.start,
+        this.end   > range.end   ? this.end   : range.end
+      );
+    },
+
+    /***
+     * @method intersect(<range>)
+     * @returns Range
+     * @short Returns a new range with the latest starting point as its start, and the earliest ending point as its end. If the two ranges do not intersect this will effectively produce an invalid range.
+     * @example
+     *
+     *   Number.range(1, 5).intersect(Number.range(4, 8)) -> 4..5
+     *   Date.range(new Date(2003, 1), new Date(2005, 1)).intersect(Date.range(new Date(2004, 1), new Date(2006, 1))) -> Jan 1, 2004..Jan 1, 2005
+     *
+     ***/
+    'intersect': function(range) {
+      if(range.start > this.end || range.end < this.start) {
+        return new Range(NaN, NaN);
+      }
+      return new Range(
+        this.start > range.start ? this.start : range.start,
+        this.end   < range.end   ? this.end   : range.end
+      );
+    },
+
+    /***
+     * @method clone()
+     * @returns Range
+     * @short Clones the range.
+     * @extra Members of the range will also be cloned.
+     * @example
+     *
+     *   Number.range(1, 5).clone() -> Returns a copy of the range.
+     *
+     ***/
+    'clone': function(range) {
+      return new Range(this.start, this.end);
+    },
+
+    /***
+     * @method clamp(<obj>)
+     * @returns Mixed
+     * @short Clamps <obj> to be within the range if it falls outside.
+     * @example
+     *
+     *   Number.range(1, 5).clamp(8) -> 5
+     *   Date.range(new Date(2010, 0), new Date(2012, 0)).clamp(new Date(2013, 0)) -> 2012-01
+     *
+     ***/
+    'clamp': function(obj) {
+      var clamped,
+          start = this.start,
+          end = this.end,
+          min = end < start ? end : start,
+          max = start > end ? start : end;
+      if(obj < min) {
+        clamped = min;
+      } else if(obj > max) {
+        clamped = max;
+      } else {
+        clamped = obj;
+      }
+      return cloneRangeMember(clamped);
+    }
+
+  });
+
+
+  /***
+   * Number module
+   ***
+   * @method Number.range([start], [end])
+   * @returns Range
+   * @short Creates a new range between [start] and [end]. See @ranges for more.
+   * @example
+   *
+   *   Number.range(5, 10)
+   *
+   ***
+   * String module
+   ***
+   * @method String.range([start], [end])
+   * @returns Range
+   * @short Creates a new range between [start] and [end]. See @ranges for more.
+   * @example
+   *
+   *   String.range('a', 'z')
+   *
+   ***
+   * Date module
+   ***
+   * @method Date.range([start], [end])
+   * @returns Range
+   * @short Creates a new range between [start] and [end].
+   * @extra If either [start] or [end] are null, they will default to the current date. See @ranges for more.
+   * @example
+   *
+   *   Date.range('today', 'tomorrow')
+   *
+   ***/
+  [number, string, date].forEach(function(klass) {
+     extend(klass, false, true, {
+
+      'range': function(start, end) {
+        if(klass.create) {
+          start = klass.create(start);
+          end   = klass.create(end);
+        }
+        return new Range(start, end);
+      }
+
+    });
+
+  });
+
+  /***
+   * Number module
+   *
+   ***/
+
+  extend(number, true, true, {
+
+    /***
+     * @method upto(<num>, [fn], [step] = 1)
+     * @returns Array
+     * @short Returns an array containing numbers from the number up to <num>.
+     * @extra Optionally calls [fn] callback for each number in that array. [step] allows multiples greater than 1.
+     * @example
+     *
+     *   (2).upto(6) -> [2, 3, 4, 5, 6]
+     *   (2).upto(6, function(n) {
+     *     // This function is called 5 times receiving n as the value.
+     *   });
+     *   (2).upto(8, null, 2) -> [2, 4, 6, 8]
+     *
+     ***/
+    'upto': function(num, fn, step) {
+      return number.range(this, num).every(step, fn);
+    },
+
+     /***
+     * @method clamp([start] = Infinity, [end] = Infinity)
+     * @returns Number
+     * @short Constrains the number so that it is between [start] and [end].
+     * @extra This will build a range object that has an equivalent %clamp% method.
+     * @example
+     *
+     *   (3).clamp(50, 100)  -> 50
+     *   (85).clamp(50, 100) -> 85
+     *
+     ***/
+    'clamp': function(start, end) {
+      return new Range(start, end).clamp(this);
+    },
+
+     /***
+     * @method cap([max] = Infinity)
+     * @returns Number
+     * @short Constrains the number so that it is no greater than [max].
+     * @extra This will build a range object that has an equivalent %cap% method.
+     * @example
+     *
+     *   (100).cap(80) -> 80
+     *
+     ***/
+    'cap': function(max) {
+      return this.clamp(Undefined, max);
+    }
+
+  });
+
+  extend(number, true, true, {
+
+    /***
+     * @method downto(<num>, [fn], [step] = 1)
+     * @returns Array
+     * @short Returns an array containing numbers from the number down to <num>.
+     * @extra Optionally calls [fn] callback for each number in that array. [step] allows multiples greater than 1.
+     * @example
+     *
+     *   (8).downto(3) -> [8, 7, 6, 5, 4, 3]
+     *   (8).downto(3, function(n) {
+     *     // This function is called 6 times receiving n as the value.
+     *   });
+     *   (8).downto(2, null, 2) -> [8, 6, 4, 2]
+     *
+     ***/
+    'downto': number.prototype.upto
+
+  });
+
+
+  /***
+   * Array module
+   *
+   ***/
+
+  extend(array, false, function(a) { return a instanceof Range; }, {
+
+    'create': function(range) {
+      return range.every();
+    }
+
+  });
+
level2/node_modules/sugar/lib/regexp.js
@@ -0,0 +1,90 @@
+
+  'use strict';
+
+  /***
+   * @package RegExp
+   * @dependency core
+   * @description Escaping regexes and manipulating their flags.
+   *
+   * Note here that methods on the RegExp class like .exec and .test will fail in the current version of SpiderMonkey being
+   * used by CouchDB when using shorthand regex notation like /foo/. This is the reason for the intermixed use of shorthand
+   * and compiled regexes here. If you're using JS in CouchDB, it is safer to ALWAYS compile your regexes from a string.
+   *
+   ***/
+
+  extend(regexp, false, true, {
+
+   /***
+    * @method RegExp.escape(<str> = '')
+    * @returns String
+    * @short Escapes all RegExp tokens in a string.
+    * @example
+    *
+    *   RegExp.escape('really?')      -> 'really\?'
+    *   RegExp.escape('yes.')         -> 'yes\.'
+    *   RegExp.escape('(not really)') -> '\(not really\)'
+    *
+    ***/
+    'escape': function(str) {
+      return escapeRegExp(str);
+    }
+
+  });
+
+  extend(regexp, true, true, {
+
+   /***
+    * @method getFlags()
+    * @returns String
+    * @short Returns the flags of the regex as a string.
+    * @example
+    *
+    *   /texty/gim.getFlags('testy') -> 'gim'
+    *
+    ***/
+    'getFlags': function() {
+      return getRegExpFlags(this);
+    },
+
+   /***
+    * @method setFlags(<flags>)
+    * @returns RegExp
+    * @short Sets the flags on a regex and retuns a copy.
+    * @example
+    *
+    *   /texty/.setFlags('gim') -> now has global, ignoreCase, and multiline set
+    *
+    ***/
+    'setFlags': function(flags) {
+      return regexp(this.source, flags);
+    },
+
+   /***
+    * @method addFlag(<flag>)
+    * @returns RegExp
+    * @short Adds <flag> to the regex.
+    * @example
+    *
+    *   /texty/.addFlag('g') -> now has global flag set
+    *
+    ***/
+    'addFlag': function(flag) {
+      return this.setFlags(getRegExpFlags(this, flag));
+    },
+
+   /***
+    * @method removeFlag(<flag>)
+    * @returns RegExp
+    * @short Removes <flag> from the regex.
+    * @example
+    *
+    *   /texty/g.removeFlag('g') -> now has global flag removed
+    *
+    ***/
+    'removeFlag': function(flag) {
+      return this.setFlags(getRegExpFlags(this).replace(flag, ''));
+    }
+
+  });
+
+
level2/node_modules/sugar/lib/string.js
@@ -0,0 +1,901 @@
+
+  'use strict';
+
+  /***
+   * @package String
+   * @dependency core
+   * @description String manupulation, escaping, encoding, truncation, and:conversion.
+   *
+   ***/
+
+  function getAcronym(word) {
+    var inflector = string.Inflector;
+    var word = inflector && inflector.acronyms[word];
+    if(isString(word)) {
+      return word;
+    }
+  }
+
+  function checkRepeatRange(num) {
+    num = +num;
+    if(num < 0 || num === Infinity) {
+      throw new RangeError('Invalid number');
+    }
+    return num;
+  }
+
+  function padString(num, padding) {
+    return repeatString(isDefined(padding) ? padding : ' ', num);
+  }
+
+  function truncateString(str, length, from, ellipsis, split) {
+    var str1, str2, len1, len2;
+    if(str.length <= length) {
+      return str.toString();
+    }
+    ellipsis = isUndefined(ellipsis) ? '...' : ellipsis;
+    switch(from) {
+      case 'left':
+        str2 = split ? truncateOnWord(str, length, true) : str.slice(str.length - length);
+        return ellipsis + str2;
+      case 'middle':
+        len1 = ceil(length / 2);
+        len2 = floor(length / 2);
+        str1 = split ? truncateOnWord(str, len1) : str.slice(0, len1);
+        str2 = split ? truncateOnWord(str, len2, true) : str.slice(str.length - len2);
+        return str1 + ellipsis + str2;
+      default:
+        str1 = split ? truncateOnWord(str, length) : str.slice(0, length);
+        return str1 + ellipsis;
+    }
+  }
+
+  function truncateOnWord(str, limit, fromLeft) {
+    if(fromLeft) {
+      return truncateOnWord(str.reverse(), limit).reverse();
+    }
+    var reg = regexp('(?=[' + getTrimmableCharacters() + '])');
+    var words = str.split(reg);
+    var count = 0;
+    return words.filter(function(word) {
+      count += word.length;
+      return count <= limit;
+    }).join('');
+  }
+
+  function numberOrIndex(str, n, from) {
+    if(isString(n)) {
+      n = str.indexOf(n);
+      if(n === -1) {
+        n = from ? str.length : 0;
+      }
+    }
+    return n;
+  }
+
+  var btoa, atob;
+
+  function buildBase64(key) {
+    if(globalContext.btoa) {
+      btoa = globalContext.btoa;
+      atob = globalContext.atob;
+      return;
+    }
+    var base64reg = /[^A-Za-z0-9\+\/\=]/g;
+    btoa = function(str) {
+      var output = '';
+      var chr1, chr2, chr3;
+      var enc1, enc2, enc3, enc4;
+      var i = 0;
+      do {
+        chr1 = str.charCodeAt(i++);
+        chr2 = str.charCodeAt(i++);
+        chr3 = str.charCodeAt(i++);
+        enc1 = chr1 >> 2;
+        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+        enc4 = chr3 & 63;
+        if (isNaN(chr2)) {
+          enc3 = enc4 = 64;
+        } else if (isNaN(chr3)) {
+          enc4 = 64;
+        }
+        output = output + key.charAt(enc1) + key.charAt(enc2) + key.charAt(enc3) + key.charAt(enc4);
+        chr1 = chr2 = chr3 = '';
+        enc1 = enc2 = enc3 = enc4 = '';
+      } while (i < str.length);
+      return output;
+    }
+    atob = function(input) {
+      var output = '';
+      var chr1, chr2, chr3;
+      var enc1, enc2, enc3, enc4;
+      var i = 0;
+      if(input.match(base64reg)) {
+        throw new Error('String contains invalid base64 characters');
+      }
+      input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
+      do {
+        enc1 = key.indexOf(input.charAt(i++));
+        enc2 = key.indexOf(input.charAt(i++));
+        enc3 = key.indexOf(input.charAt(i++));
+        enc4 = key.indexOf(input.charAt(i++));
+        chr1 = (enc1 << 2) | (enc2 >> 4);
+        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+        chr3 = ((enc3 & 3) << 6) | enc4;
+        output = output + chr(chr1);
+        if (enc3 != 64) {
+          output = output + chr(chr2);
+        }
+        if (enc4 != 64) {
+          output = output + chr(chr3);
+        }
+        chr1 = chr2 = chr3 = '';
+        enc1 = enc2 = enc3 = enc4 = '';
+      } while (i < input.length);
+      return output;
+    }
+  }
+
+  extend(string, true, false, {
+    /***
+     * @method repeat([num] = 0)
+     * @returns String
+     * @short Returns the string repeated [num] times.
+     * @example
+     *
+     *   'jumpy'.repeat(2) -> 'jumpyjumpy'
+     *   'a'.repeat(5)     -> 'aaaaa'
+     *   'a'.repeat(0)     -> ''
+     *
+     ***/
+    'repeat': function(num) {
+      num = checkRepeatRange(num);
+      return repeatString(this, num);
+    }
+
+  });
+
+  extend(string, true, function(reg) { return isRegExp(reg) || arguments.length > 2; }, {
+
+    /***
+     * @method startsWith(<find>, [pos] = 0, [case] = true)
+     * @returns Boolean
+     * @short Returns true if the string starts with <find>.
+     * @extra <find> may be either a string or regex. Search begins at [pos], which defaults to the entire string. Case sensitive if [case] is true.
+     * @example
+     *
+     *   'hello'.startsWith('hell')           -> true
+     *   'hello'.startsWith(/[a-h]/)          -> true
+     *   'hello'.startsWith('HELL')           -> false
+     *   'hello'.startsWith('ell', 1)         -> true
+     *   'hello'.startsWith('HELL', 0, false) -> true
+     *
+     ***/
+    'startsWith': function(reg) {
+      var args = arguments, pos = args[1], c = args[2], str = this, source;
+      if(pos) str = str.slice(pos);
+      if(isUndefined(c)) c = true;
+      source = isRegExp(reg) ? reg.source.replace('^', '') : escapeRegExp(reg);
+      return regexp('^' + source, c ? '' : 'i').test(str);
+    },
+
+    /***
+     * @method endsWith(<find>, [pos] = length, [case] = true)
+     * @returns Boolean
+     * @short Returns true if the string ends with <find>.
+     * @extra <find> may be either a string or regex. Search ends at [pos], which defaults to the entire string. Case sensitive if [case] is true.
+     * @example
+     *
+     *   'jumpy'.endsWith('py')            -> true
+     *   'jumpy'.endsWith(/[q-z]/)         -> true
+     *   'jumpy'.endsWith('MPY')           -> false
+     *   'jumpy'.endsWith('mp', 4)         -> false
+     *   'jumpy'.endsWith('MPY', 5, false) -> true
+     *
+     ***/
+    'endsWith': function(reg) {
+      var args = arguments, pos = args[1], c = args[2], str = this, source;
+      if(isDefined(pos)) str = str.slice(0, pos);
+      if(isUndefined(c)) c = true;
+      source = isRegExp(reg) ? reg.source.replace('$', '') : escapeRegExp(reg);
+      return regexp(source + '$', c ? '' : 'i').test(str);
+    }
+
+  });
+
+  extend(string, true, true, {
+
+     /***
+      * @method escapeRegExp()
+      * @returns String
+      * @short Escapes all RegExp tokens in the string.
+      * @example
+      *
+      *   'really?'.escapeRegExp()       -> 'really\?'
+      *   'yes.'.escapeRegExp()         -> 'yes\.'
+      *   '(not really)'.escapeRegExp() -> '\(not really\)'
+      *
+      ***/
+    'escapeRegExp': function() {
+      return escapeRegExp(this);
+    },
+
+     /***
+      * @method escapeURL([param] = false)
+      * @returns String
+      * @short Escapes characters in a string to make a valid URL.
+      * @extra If [param] is true, it will also escape valid URL characters for use as a URL parameter.
+      * @example
+      *
+      *   'http://foo.com/"bar"'.escapeURL()     -> 'http://foo.com/%22bar%22'
+      *   'http://foo.com/"bar"'.escapeURL(true) -> 'http%3A%2F%2Ffoo.com%2F%22bar%22'
+      *
+      ***/
+    'escapeURL': function(param) {
+      return param ? encodeURIComponent(this) : encodeURI(this);
+    },
+
+     /***
+      * @method unescapeURL([partial] = false)
+      * @returns String
+      * @short Restores escaped characters in a URL escaped string.
+      * @extra If [partial] is true, it will only unescape non-valid URL characters. [partial] is included here for completeness, but should very rarely be needed.
+      * @example
+      *
+      *   'http%3A%2F%2Ffoo.com%2Fthe%20bar'.unescapeURL()     -> 'http://foo.com/the bar'
+      *   'http%3A%2F%2Ffoo.com%2Fthe%20bar'.unescapeURL(true) -> 'http%3A%2F%2Ffoo.com%2Fthe bar'
+      *
+      ***/
+    'unescapeURL': function(param) {
+      return param ? decodeURI(this) : decodeURIComponent(this);
+    },
+
+     /***
+      * @method escapeHTML()
+      * @returns String
+      * @short Converts HTML characters to their entity equivalents.
+      * @example
+      *
+      *   '<p>some text</p>'.escapeHTML() -> '&lt;p&gt;some text&lt;/p&gt;'
+      *   'one & two'.escapeHTML()        -> 'one &amp; two'
+      *
+      ***/
+    'escapeHTML': function() {
+      return this.replace(/&/g,  '&amp;' )
+                 .replace(/</g,  '&lt;'  )
+                 .replace(/>/g,  '&gt;'  )
+                 .replace(/"/g,  '&quot;')
+                 .replace(/'/g,  '&apos;')
+                 .replace(/\//g, '&#x2f;');
+    },
+
+     /***
+      * @method unescapeHTML([partial] = false)
+      * @returns String
+      * @short Restores escaped HTML characters.
+      * @example
+      *
+      *   '&lt;p&gt;some text&lt;/p&gt;'.unescapeHTML() -> '<p>some text</p>'
+      *   'one &amp; two'.unescapeHTML()                -> 'one & two'
+      *
+      ***/
+    'unescapeHTML': function() {
+      return this.replace(/&lt;/g,   '<')
+                 .replace(/&gt;/g,   '>')
+                 .replace(/&quot;/g, '"')
+                 .replace(/&apos;/g, "'")
+                 .replace(/&#x2f;/g, '/')
+                 .replace(/&amp;/g,  '&');
+    },
+
+     /***
+      * @method encodeBase64()
+      * @returns String
+      * @short Encodes the string into base64 encoding.
+      * @extra This method wraps the browser native %btoa% when available, and uses a custom implementation when not available. It can also handle Unicode string encodings.
+      * @example
+      *
+      *   'gonna get encoded!'.encodeBase64()  -> 'Z29ubmEgZ2V0IGVuY29kZWQh'
+      *   'http://twitter.com/'.encodeBase64() -> 'aHR0cDovL3R3aXR0ZXIuY29tLw=='
+      *
+      ***/
+    'encodeBase64': function() {
+      return btoa(unescape(encodeURIComponent(this)));
+    },
+
+     /***
+      * @method decodeBase64()
+      * @returns String
+      * @short Decodes the string from base64 encoding.
+      * @extra This method wraps the browser native %atob% when available, and uses a custom implementation when not available. It can also handle Unicode string encodings.
+      * @example
+      *
+      *   'aHR0cDovL3R3aXR0ZXIuY29tLw=='.decodeBase64() -> 'http://twitter.com/'
+      *   'anVzdCBnb3QgZGVjb2RlZA=='.decodeBase64()     -> 'just got decoded!'
+      *
+      ***/
+    'decodeBase64': function() {
+      return decodeURIComponent(escape(atob(this)));
+    },
+
+    /***
+     * @method each([search] = single character, [fn])
+     * @returns Array
+     * @short Runs callback [fn] against each occurence of [search].
+     * @extra Returns an array of matches. [search] may be either a string or regex, and defaults to every character in the string.
+     * @example
+     *
+     *   'jumpy'.each() -> ['j','u','m','p','y']
+     *   'jumpy'.each(/[r-z]/) -> ['u','y']
+     *   'jumpy'.each(/[r-z]/, function(m) {
+     *     // Called twice: "u", "y"
+     *   });
+     *
+     ***/
+    'each': function(search, fn) {
+      var match, i, len;
+      if(isFunction(search)) {
+        fn = search;
+        search = /[\s\S]/g;
+      } else if(!search) {
+        search = /[\s\S]/g
+      } else if(isString(search)) {
+        search = regexp(escapeRegExp(search), 'gi');
+      } else if(isRegExp(search)) {
+        search = regexp(search.source, getRegExpFlags(search, 'g'));
+      }
+      match = this.match(search) || [];
+      if(fn) {
+        for(i = 0, len = match.length; i < len; i++) {
+          match[i] = fn.call(this, match[i], i, match) || match[i];
+        }
+      }
+      return match;
+    },
+
+    /***
+     * @method shift(<n>)
+     * @returns Array
+     * @short Shifts each character in the string <n> places in the character map.
+     * @example
+     *
+     *   'a'.shift(1)  -> 'b'
+     *   'ใ‚ฏ'.shift(1) -> 'ใ‚ฐ'
+     *
+     ***/
+    'shift': function(n) {
+      var result = '';
+      n = n || 0;
+      this.codes(function(c) {
+        result += chr(c + n);
+      });
+      return result;
+    },
+
+    /***
+     * @method codes([fn])
+     * @returns Array
+     * @short Runs callback [fn] against each character code in the string. Returns an array of character codes.
+     * @example
+     *
+     *   'jumpy'.codes() -> [106,117,109,112,121]
+     *   'jumpy'.codes(function(c) {
+     *     // Called 5 times: 106, 117, 109, 112, 121
+     *   });
+     *
+     ***/
+    'codes': function(fn) {
+      var codes = [], i, len;
+      for(i = 0, len = this.length; i < len; i++) {
+        var code = this.charCodeAt(i);
+        codes.push(code);
+        if(fn) fn.call(this, code, i);
+      }
+      return codes;
+    },
+
+    /***
+     * @method chars([fn])
+     * @returns Array
+     * @short Runs callback [fn] against each character in the string. Returns an array of characters.
+     * @example
+     *
+     *   'jumpy'.chars() -> ['j','u','m','p','y']
+     *   'jumpy'.chars(function(c) {
+     *     // Called 5 times: "j","u","m","p","y"
+     *   });
+     *
+     ***/
+    'chars': function(fn) {
+      return this.each(fn);
+    },
+
+    /***
+     * @method words([fn])
+     * @returns Array
+     * @short Runs callback [fn] against each word in the string. Returns an array of words.
+     * @extra A "word" here is defined as any sequence of non-whitespace characters.
+     * @example
+     *
+     *   'broken wear'.words() -> ['broken','wear']
+     *   'broken wear'.words(function(w) {
+     *     // Called twice: "broken", "wear"
+     *   });
+     *
+     ***/
+    'words': function(fn) {
+      return this.trim().each(/\S+/g, fn);
+    },
+
+    /***
+     * @method lines([fn])
+     * @returns Array
+     * @short Runs callback [fn] against each line in the string. Returns an array of lines.
+     * @example
+     *
+     *   'broken wear\nand\njumpy jump'.lines() -> ['broken wear','and','jumpy jump']
+     *   'broken wear\nand\njumpy jump'.lines(function(l) {
+     *     // Called three times: "broken wear", "and", "jumpy jump"
+     *   });
+     *
+     ***/
+    'lines': function(fn) {
+      return this.trim().each(/^.*$/gm, fn);
+    },
+
+    /***
+     * @method paragraphs([fn])
+     * @returns Array
+     * @short Runs callback [fn] against each paragraph in the string. Returns an array of paragraphs.
+     * @extra A paragraph here is defined as a block of text bounded by two or more line breaks.
+     * @example
+     *
+     *   'Once upon a time.\n\nIn the land of oz...'.paragraphs() -> ['Once upon a time.','In the land of oz...']
+     *   'Once upon a time.\n\nIn the land of oz...'.paragraphs(function(p) {
+     *     // Called twice: "Once upon a time.", "In teh land of oz..."
+     *   });
+     *
+     ***/
+    'paragraphs': function(fn) {
+      var paragraphs = this.trim().split(/[\r\n]{2,}/);
+      paragraphs = paragraphs.map(function(p) {
+        if(fn) var s = fn.call(p);
+        return s ? s : p;
+      });
+      return paragraphs;
+    },
+
+    /***
+     * @method isBlank()
+     * @returns Boolean
+     * @short Returns true if the string has a length of 0 or contains only whitespace.
+     * @example
+     *
+     *   ''.isBlank()      -> true
+     *   '   '.isBlank()   -> true
+     *   'noway'.isBlank() -> false
+     *
+     ***/
+    'isBlank': function() {
+      return this.trim().length === 0;
+    },
+
+    /***
+     * @method has(<find>)
+     * @returns Boolean
+     * @short Returns true if the string matches <find>.
+     * @extra <find> may be a string or regex.
+     * @example
+     *
+     *   'jumpy'.has('py')     -> true
+     *   'broken'.has(/[a-n]/) -> true
+     *   'broken'.has(/[s-z]/) -> false
+     *
+     ***/
+    'has': function(find) {
+      return this.search(isRegExp(find) ? find : escapeRegExp(find)) !== -1;
+    },
+
+
+    /***
+     * @method add(<str>, [index] = length)
+     * @returns String
+     * @short Adds <str> at [index]. Negative values are also allowed.
+     * @extra %insert% is provided as an alias, and is generally more readable when using an index.
+     * @example
+     *
+     *   'schfifty'.add(' five')      -> schfifty five
+     *   'dopamine'.insert('e', 3)       -> dopeamine
+     *   'spelling eror'.insert('r', -3) -> spelling error
+     *
+     ***/
+    'add': function(str, index) {
+      index = isUndefined(index) ? this.length : index;
+      return this.slice(0, index) + str + this.slice(index);
+    },
+
+    /***
+     * @method remove(<f>)
+     * @returns String
+     * @short Removes any part of the string that matches <f>.
+     * @extra <f> can be a string or a regex.
+     * @example
+     *
+     *   'schfifty five'.remove('f')     -> 'schity ive'
+     *   'schfifty five'.remove(/[a-f]/g) -> 'shity iv'
+     *
+     ***/
+    'remove': function(f) {
+      return this.replace(f, '');
+    },
+
+    /***
+     * @method reverse()
+     * @returns String
+     * @short Reverses the string.
+     * @example
+     *
+     *   'jumpy'.reverse()        -> 'ypmuj'
+     *   'lucky charms'.reverse() -> 'smrahc ykcul'
+     *
+     ***/
+    'reverse': function() {
+      return this.split('').reverse().join('');
+    },
+
+    /***
+     * @method compact()
+     * @returns String
+     * @short Compacts all white space in the string to a single space and trims the ends.
+     * @example
+     *
+     *   'too \n much \n space'.compact() -> 'too much space'
+     *   'enough \n '.compact()           -> 'enought'
+     *
+     ***/
+    'compact': function() {
+      return this.trim().replace(/([\r\n\sใ€€])+/g, function(match, whitespace){
+        return whitespace === 'ใ€€' ? whitespace : ' ';
+      });
+    },
+
+    /***
+     * @method at(<index>, [loop] = true)
+     * @returns String or Array
+     * @short Gets the character(s) at a given index.
+     * @extra When [loop] is true, overshooting the end of the string (or the beginning) will begin counting from the other end. As an alternate syntax, passing multiple indexes will get the characters at those indexes.
+     * @example
+     *
+     *   'jumpy'.at(0)               -> 'j'
+     *   'jumpy'.at(2)               -> 'm'
+     *   'jumpy'.at(5)               -> 'j'
+     *   'jumpy'.at(5, false)        -> ''
+     *   'jumpy'.at(-1)              -> 'y'
+     *   'lucky charms'.at(2,4,6,8) -> ['u','k','y',c']
+     *
+     ***/
+    'at': function() {
+      return getEntriesForIndexes(this, arguments, true);
+    },
+
+    /***
+     * @method from([index] = 0)
+     * @returns String
+     * @short Returns a section of the string starting from [index].
+     * @example
+     *
+     *   'lucky charms'.from()   -> 'lucky charms'
+     *   'lucky charms'.from(7)  -> 'harms'
+     *
+     ***/
+    'from': function(from) {
+      return this.slice(numberOrIndex(this, from, true));
+    },
+
+    /***
+     * @method to([index] = end)
+     * @returns String
+     * @short Returns a section of the string ending at [index].
+     * @example
+     *
+     *   'lucky charms'.to()   -> 'lucky charms'
+     *   'lucky charms'.to(7)  -> 'lucky ch'
+     *
+     ***/
+    'to': function(to) {
+      if(isUndefined(to)) to = this.length;
+      return this.slice(0, numberOrIndex(this, to));
+    },
+
+    /***
+     * @method dasherize()
+     * @returns String
+     * @short Converts underscores and camel casing to hypens.
+     * @example
+     *
+     *   'a_farewell_to_arms'.dasherize() -> 'a-farewell-to-arms'
+     *   'capsLock'.dasherize()           -> 'caps-lock'
+     *
+     ***/
+    'dasherize': function() {
+      return this.underscore().replace(/_/g, '-');
+    },
+
+    /***
+     * @method underscore()
+     * @returns String
+     * @short Converts hyphens and camel casing to underscores.
+     * @example
+     *
+     *   'a-farewell-to-arms'.underscore() -> 'a_farewell_to_arms'
+     *   'capsLock'.underscore()           -> 'caps_lock'
+     *
+     ***/
+    'underscore': function() {
+      return this
+        .replace(/[-\s]+/g, '_')
+        .replace(string.Inflector && string.Inflector.acronymRegExp, function(acronym, index) {
+          return (index > 0 ? '_' : '') + acronym.toLowerCase();
+        })
+        .replace(/([A-Z\d]+)([A-Z][a-z])/g,'$1_$2')
+        .replace(/([a-z\d])([A-Z])/g,'$1_$2')
+        .toLowerCase();
+    },
+
+    /***
+     * @method camelize([first] = true)
+     * @returns String
+     * @short Converts underscores and hyphens to camel case. If [first] is true the first letter will also be capitalized.
+     * @extra If the Inflections package is included acryonyms can also be defined that will be used when camelizing.
+     * @example
+     *
+     *   'caps_lock'.camelize()              -> 'CapsLock'
+     *   'moz-border-radius'.camelize()      -> 'MozBorderRadius'
+     *   'moz-border-radius'.camelize(false) -> 'mozBorderRadius'
+     *
+     ***/
+    'camelize': function(first) {
+      return this.underscore().replace(/(^|_)([^_]+)/g, function(match, pre, word, index) {
+        var acronym = getAcronym(word), capitalize = first !== false || index > 0;
+        if(acronym) return capitalize ? acronym : acronym.toLowerCase();
+        return capitalize ? word.capitalize() : word;
+      });
+    },
+
+    /***
+     * @method spacify()
+     * @returns String
+     * @short Converts camel case, underscores, and hyphens to a properly spaced string.
+     * @example
+     *
+     *   'camelCase'.spacify()                         -> 'camel case'
+     *   'an-ugly-string'.spacify()                    -> 'an ugly string'
+     *   'oh-no_youDid-not'.spacify().capitalize(true) -> 'something else'
+     *
+     ***/
+    'spacify': function() {
+      return this.underscore().replace(/_/g, ' ');
+    },
+
+    /***
+     * @method stripTags([tag1], [tag2], ...)
+     * @returns String
+     * @short Strips all HTML tags from the string.
+     * @extra Tags to strip may be enumerated in the parameters, otherwise will strip all.
+     * @example
+     *
+     *   '<p>just <b>some</b> text</p>'.stripTags()    -> 'just some text'
+     *   '<p>just <b>some</b> text</p>'.stripTags('p') -> 'just <b>some</b> text'
+     *
+     ***/
+    'stripTags': function() {
+      var str = this, args = arguments.length > 0 ? arguments : [''];
+      flattenedArgs(args, function(tag) {
+        str = str.replace(regexp('<\/?' + escapeRegExp(tag) + '[^<>]*>', 'gi'), '');
+      });
+      return str;
+    },
+
+    /***
+     * @method removeTags([tag1], [tag2], ...)
+     * @returns String
+     * @short Removes all HTML tags and their contents from the string.
+     * @extra Tags to remove may be enumerated in the parameters, otherwise will remove all.
+     * @example
+     *
+     *   '<p>just <b>some</b> text</p>'.removeTags()    -> ''
+     *   '<p>just <b>some</b> text</p>'.removeTags('b') -> '<p>just text</p>'
+     *
+     ***/
+    'removeTags': function() {
+      var str = this, args = arguments.length > 0 ? arguments : ['\\S+'];
+      flattenedArgs(args, function(t) {
+        var reg = regexp('<(' + t + ')[^<>]*(?:\\/>|>.*?<\\/\\1>)', 'gi');
+        str = str.replace(reg, '');
+      });
+      return str;
+    },
+
+    /***
+     * @method truncate(<length>, [from] = 'right', [ellipsis] = '...')
+     * @returns String
+     * @short Truncates a string.
+     * @extra [from] can be %'right'%, %'left'%, or %'middle'%. If the string is shorter than <length>, [ellipsis] will not be added.
+     * @example
+     *
+     *   'sittin on the dock of the bay'.truncate(18)           -> 'just sittin on the do...'
+     *   'sittin on the dock of the bay'.truncate(18, 'left')   -> '...the dock of the bay'
+     *   'sittin on the dock of the bay'.truncate(18, 'middle') -> 'just sitt...of the bay'
+     *
+     ***/
+    'truncate': function(length, from, ellipsis) {
+      return truncateString(this, length, from, ellipsis);
+    },
+
+    /***
+     * @method truncateOnWord(<length>, [from] = 'right', [ellipsis] = '...')
+     * @returns String
+     * @short Truncates a string without splitting up words.
+     * @extra [from] can be %'right'%, %'left'%, or %'middle'%. If the string is shorter than <length>, [ellipsis] will not be added.
+     * @example
+     *
+     *   'here we go'.truncateOnWord(5)               -> 'here...'
+     *   'here we go'.truncateOnWord(5, 'left')       -> '...we go'
+     *
+     ***/
+    'truncateOnWord': function(length, from, ellipsis) {
+      return truncateString(this, length, from, ellipsis, true);
+    },
+
+    /***
+     * @method pad[Side](<num> = null, [padding] = ' ')
+     * @returns String
+     * @short Pads the string out with [padding] to be exactly <num> characters.
+     *
+     * @set
+     *   pad
+     *   padLeft
+     *   padRight
+     *
+     * @example
+     *
+     *   'wasabi'.pad(8)           -> ' wasabi '
+     *   'wasabi'.padLeft(8)       -> '  wasabi'
+     *   'wasabi'.padRight(8)      -> 'wasabi  '
+     *   'wasabi'.padRight(8, '-') -> 'wasabi--'
+     *
+     ***/
+    'pad': function(num, padding) {
+      var half, front, back;
+      num   = checkRepeatRange(num);
+      half  = max(0, num - this.length) / 2;
+      front = floor(half);
+      back  = ceil(half);
+      return padString(front, padding) + this + padString(back, padding);
+    },
+
+    'padLeft': function(num, padding) {
+      num = checkRepeatRange(num);
+      return padString(max(0, num - this.length), padding) + this;
+    },
+
+    'padRight': function(num, padding) {
+      num = checkRepeatRange(num);
+      return this + padString(max(0, num - this.length), padding);
+    },
+
+    /***
+     * @method first([n] = 1)
+     * @returns String
+     * @short Returns the first [n] characters of the string.
+     * @example
+     *
+     *   'lucky charms'.first()   -> 'l'
+     *   'lucky charms'.first(3)  -> 'luc'
+     *
+     ***/
+    'first': function(num) {
+      if(isUndefined(num)) num = 1;
+      return this.substr(0, num);
+    },
+
+    /***
+     * @method last([n] = 1)
+     * @returns String
+     * @short Returns the last [n] characters of the string.
+     * @example
+     *
+     *   'lucky charms'.last()   -> 's'
+     *   'lucky charms'.last(3)  -> 'rms'
+     *
+     ***/
+    'last': function(num) {
+      if(isUndefined(num)) num = 1;
+      var start = this.length - num < 0 ? 0 : this.length - num;
+      return this.substr(start);
+    },
+
+    /***
+     * @method toNumber([base] = 10)
+     * @returns Number
+     * @short Converts the string into a number.
+     * @extra Any value with a "." fill be converted to a floating point value, otherwise an integer.
+     * @example
+     *
+     *   '153'.toNumber()    -> 153
+     *   '12,000'.toNumber() -> 12000
+     *   '10px'.toNumber()   -> 10
+     *   'ff'.toNumber(16)   -> 255
+     *
+     ***/
+    'toNumber': function(base) {
+      return stringToNumber(this, base);
+    },
+
+    /***
+     * @method capitalize([all] = false)
+     * @returns String
+     * @short Capitalizes the first character in the string and downcases all other letters.
+     * @extra If [all] is true, all words in the string will be capitalized.
+     * @example
+     *
+     *   'hello'.capitalize()           -> 'Hello'
+     *   'hello kitty'.capitalize()     -> 'Hello kitty'
+     *   'hello kitty'.capitalize(true) -> 'Hello Kitty'
+     *
+     *
+     ***/
+    'capitalize': function(all) {
+      var lastResponded;
+      return this.toLowerCase().replace(all ? /[^']/g : /^\S/, function(lower) {
+        var upper = lower.toUpperCase(), result;
+        result = lastResponded ? lower : upper;
+        lastResponded = upper !== lower;
+        return result;
+      });
+    },
+
+    /***
+     * @method assign(<obj1>, <obj2>, ...)
+     * @returns String
+     * @short Assigns variables to tokens in a string, demarcated with `{}`.
+     * @extra If an object is passed, it's properties can be assigned using the object's keys (i.e. {name}). If a non-object (string, number, etc.) is passed it can be accessed by the argument number beginning with {1} (as with regex tokens). Multiple objects can be passed and will be merged together (original objects are unaffected).
+     * @example
+     *
+     *   'Welcome, Mr. {name}.'.assign({ name: 'Franklin' })   -> 'Welcome, Mr. Franklin.'
+     *   'You are {1} years old today.'.assign(14)             -> 'You are 14 years old today.'
+     *   '{n} and {r}'.assign({ n: 'Cheech' }, { r: 'Chong' }) -> 'Cheech and Chong'
+     *
+     ***/
+    'assign': function() {
+      var assign = {};
+      flattenedArgs(arguments, function(a, i) {
+        if(isObjectType(a)) {
+          simpleMerge(assign, a);
+        } else {
+          assign[i + 1] = a;
+        }
+      });
+      return this.replace(/\{([^{]+?)\}/g, function(m, key) {
+        return hasOwnProperty(assign, key) ? assign[key] : m;
+      });
+    }
+
+  });
+
+
+  // Aliases
+
+  extend(string, true, true, {
+
+    /***
+     * @method insert()
+     * @alias add
+     *
+     ***/
+    'insert': string.prototype.add
+  });
+
+  buildBase64('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=');
+
level2/node_modules/sugar/performance/javascripts/vendor/moment.js
@@ -0,0 +1,1400 @@
+// moment.js
+// version : 2.0.0
+// author : Tim Wood
+// license : MIT
+// momentjs.com
+
+(function (undefined) {
+
+    /************************************
+        Constants
+    ************************************/
+
+    var moment,
+        VERSION = "2.0.0",
+        round = Math.round, i,
+        // internal storage for language config files
+        languages = {},
+
+        // check for nodeJS
+        hasModule = (typeof module !== 'undefined' && module.exports),
+
+        // ASP.NET json date format regex
+        aspNetJsonRegex = /^\/?Date\((\-?\d+)/i,
+
+        // format tokens
+        formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|X|zz?|ZZ?|.)/g,
+        localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,
+
+        // parsing tokens
+        parseMultipleFormatChunker = /([0-9a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)/gi,
+
+        // parsing token regexes
+        parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99
+        parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999
+        parseTokenThreeDigits = /\d{3}/, // 000 - 999
+        parseTokenFourDigits = /\d{1,4}/, // 0 - 9999
+        parseTokenSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999
+        parseTokenWord = /[0-9]*[a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF]+\s*?[\u0600-\u06FF]+/i, // any word (or two) characters or numbers including two word month in arabic.
+        parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/i, // +00:00 -00:00 +0000 -0000 or Z
+        parseTokenT = /T/i, // T (ISO seperator)
+        parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123
+
+        // preliminary iso regex
+        // 0000-00-00 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000
+        isoRegex = /^\s*\d{4}-\d\d-\d\d((T| )(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/,
+        isoFormat = 'YYYY-MM-DDTHH:mm:ssZ',
+
+        // iso time formats and regexes
+        isoTimes = [
+            ['HH:mm:ss.S', /(T| )\d\d:\d\d:\d\d\.\d{1,3}/],
+            ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/],
+            ['HH:mm', /(T| )\d\d:\d\d/],
+            ['HH', /(T| )\d\d/]
+        ],
+
+        // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"]
+        parseTimezoneChunker = /([\+\-]|\d\d)/gi,
+
+        // getter and setter names
+        proxyGettersAndSetters = 'Month|Date|Hours|Minutes|Seconds|Milliseconds'.split('|'),
+        unitMillisecondFactors = {
+            'Milliseconds' : 1,
+            'Seconds' : 1e3,
+            'Minutes' : 6e4,
+            'Hours' : 36e5,
+            'Days' : 864e5,
+            'Months' : 2592e6,
+            'Years' : 31536e6
+        },
+
+        // format function strings
+        formatFunctions = {},
+
+        // tokens to ordinalize and pad
+        ordinalizeTokens = 'DDD w W M D d'.split(' '),
+        paddedTokens = 'M D H h m s w W'.split(' '),
+
+        formatTokenFunctions = {
+            M    : function () {
+                return this.month() + 1;
+            },
+            MMM  : function (format) {
+                return this.lang().monthsShort(this, format);
+            },
+            MMMM : function (format) {
+                return this.lang().months(this, format);
+            },
+            D    : function () {
+                return this.date();
+            },
+            DDD  : function () {
+                return this.dayOfYear();
+            },
+            d    : function () {
+                return this.day();
+            },
+            dd   : function (format) {
+                return this.lang().weekdaysMin(this, format);
+            },
+            ddd  : function (format) {
+                return this.lang().weekdaysShort(this, format);
+            },
+            dddd : function (format) {
+                return this.lang().weekdays(this, format);
+            },
+            w    : function () {
+                return this.week();
+            },
+            W    : function () {
+                return this.isoWeek();
+            },
+            YY   : function () {
+                return leftZeroFill(this.year() % 100, 2);
+            },
+            YYYY : function () {
+                return leftZeroFill(this.year(), 4);
+            },
+            YYYYY : function () {
+                return leftZeroFill(this.year(), 5);
+            },
+            a    : function () {
+                return this.lang().meridiem(this.hours(), this.minutes(), true);
+            },
+            A    : function () {
+                return this.lang().meridiem(this.hours(), this.minutes(), false);
+            },
+            H    : function () {
+                return this.hours();
+            },
+            h    : function () {
+                return this.hours() % 12 || 12;
+            },
+            m    : function () {
+                return this.minutes();
+            },
+            s    : function () {
+                return this.seconds();
+            },
+            S    : function () {
+                return ~~(this.milliseconds() / 100);
+            },
+            SS   : function () {
+                return leftZeroFill(~~(this.milliseconds() / 10), 2);
+            },
+            SSS  : function () {
+                return leftZeroFill(this.milliseconds(), 3);
+            },
+            Z    : function () {
+                var a = -this.zone(),
+                    b = "+";
+                if (a < 0) {
+                    a = -a;
+                    b = "-";
+                }
+                return b + leftZeroFill(~~(a / 60), 2) + ":" + leftZeroFill(~~a % 60, 2);
+            },
+            ZZ   : function () {
+                var a = -this.zone(),
+                    b = "+";
+                if (a < 0) {
+                    a = -a;
+                    b = "-";
+                }
+                return b + leftZeroFill(~~(10 * a / 6), 4);
+            },
+            X    : function () {
+                return this.unix();
+            }
+        };
+
+    function padToken(func, count) {
+        return function (a) {
+            return leftZeroFill(func.call(this, a), count);
+        };
+    }
+    function ordinalizeToken(func) {
+        return function (a) {
+            return this.lang().ordinal(func.call(this, a));
+        };
+    }
+
+    while (ordinalizeTokens.length) {
+        i = ordinalizeTokens.pop();
+        formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i]);
+    }
+    while (paddedTokens.length) {
+        i = paddedTokens.pop();
+        formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2);
+    }
+    formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3);
+
+
+    /************************************
+        Constructors
+    ************************************/
+
+    function Language() {
+
+    }
+
+    // Moment prototype object
+    function Moment(config) {
+        extend(this, config);
+    }
+
+    // Duration Constructor
+    function Duration(duration) {
+        var data = this._data = {},
+            years = duration.years || duration.year || duration.y || 0,
+            months = duration.months || duration.month || duration.M || 0,
+            weeks = duration.weeks || duration.week || duration.w || 0,
+            days = duration.days || duration.day || duration.d || 0,
+            hours = duration.hours || duration.hour || duration.h || 0,
+            minutes = duration.minutes || duration.minute || duration.m || 0,
+            seconds = duration.seconds || duration.second || duration.s || 0,
+            milliseconds = duration.milliseconds || duration.millisecond || duration.ms || 0;
+
+        // representation for dateAddRemove
+        this._milliseconds = milliseconds +
+            seconds * 1e3 + // 1000
+            minutes * 6e4 + // 1000 * 60
+            hours * 36e5; // 1000 * 60 * 60
+        // Because of dateAddRemove treats 24 hours as different from a
+        // day when working around DST, we need to store them separately
+        this._days = days +
+            weeks * 7;
+        // It is impossible translate months into days without knowing
+        // which months you are are talking about, so we have to store
+        // it separately.
+        this._months = months +
+            years * 12;
+
+        // The following code bubbles up values, see the tests for
+        // examples of what that means.
+        data.milliseconds = milliseconds % 1000;
+        seconds += absRound(milliseconds / 1000);
+
+        data.seconds = seconds % 60;
+        minutes += absRound(seconds / 60);
+
+        data.minutes = minutes % 60;
+        hours += absRound(minutes / 60);
+
+        data.hours = hours % 24;
+        days += absRound(hours / 24);
+
+        days += weeks * 7;
+        data.days = days % 30;
+
+        months += absRound(days / 30);
+
+        data.months = months % 12;
+        years += absRound(months / 12);
+
+        data.years = years;
+    }
+
+
+    /************************************
+        Helpers
+    ************************************/
+
+
+    function extend(a, b) {
+        for (var i in b) {
+            if (b.hasOwnProperty(i)) {
+                a[i] = b[i];
+            }
+        }
+        return a;
+    }
+
+    function absRound(number) {
+        if (number < 0) {
+            return Math.ceil(number);
+        } else {
+            return Math.floor(number);
+        }
+    }
+
+    // left zero fill a number
+    // see http://jsperf.com/left-zero-filling for performance comparison
+    function leftZeroFill(number, targetLength) {
+        var output = number + '';
+        while (output.length < targetLength) {
+            output = '0' + output;
+        }
+        return output;
+    }
+
+    // helper function for _.addTime and _.subtractTime
+    function addOrSubtractDurationFromMoment(mom, duration, isAdding) {
+        var ms = duration._milliseconds,
+            d = duration._days,
+            M = duration._months,
+            currentDate;
+
+        if (ms) {
+            mom._d.setTime(+mom + ms * isAdding);
+        }
+        if (d) {
+            mom.date(mom.date() + d * isAdding);
+        }
+        if (M) {
+            currentDate = mom.date();
+            mom.date(1)
+                .month(mom.month() + M * isAdding)
+                .date(Math.min(currentDate, mom.daysInMonth()));
+        }
+    }
+
+    // check if is an array
+    function isArray(input) {
+        return Object.prototype.toString.call(input) === '[object Array]';
+    }
+
+    // compare two arrays, return the number of differences
+    function compareArrays(array1, array2) {
+        var len = Math.min(array1.length, array2.length),
+            lengthDiff = Math.abs(array1.length - array2.length),
+            diffs = 0,
+            i;
+        for (i = 0; i < len; i++) {
+            if (~~array1[i] !== ~~array2[i]) {
+                diffs++;
+            }
+        }
+        return diffs + lengthDiff;
+    }
+
+
+    /************************************
+        Languages
+    ************************************/
+
+
+    Language.prototype = {
+        set : function (config) {
+            var prop, i;
+            for (i in config) {
+                prop = config[i];
+                if (typeof prop === 'function') {
+                    this[i] = prop;
+                } else {
+                    this['_' + i] = prop;
+                }
+            }
+        },
+
+        _months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"),
+        months : function (m) {
+            return this._months[m.month()];
+        },
+
+        _monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),
+        monthsShort : function (m) {
+            return this._monthsShort[m.month()];
+        },
+
+        monthsParse : function (monthName) {
+            var i, mom, regex, output;
+
+            if (!this._monthsParse) {
+                this._monthsParse = [];
+            }
+
+            for (i = 0; i < 12; i++) {
+                // make the regex if we don't have it already
+                if (!this._monthsParse[i]) {
+                    mom = moment([2000, i]);
+                    regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
+                    this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
+                }
+                // test the regex
+                if (this._monthsParse[i].test(monthName)) {
+                    return i;
+                }
+            }
+        },
+
+        _weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),
+        weekdays : function (m) {
+            return this._weekdays[m.day()];
+        },
+
+        _weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),
+        weekdaysShort : function (m) {
+            return this._weekdaysShort[m.day()];
+        },
+
+        _weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"),
+        weekdaysMin : function (m) {
+            return this._weekdaysMin[m.day()];
+        },
+
+        _longDateFormat : {
+            LT : "h:mm A",
+            L : "MM/DD/YYYY",
+            LL : "MMMM D YYYY",
+            LLL : "MMMM D YYYY LT",
+            LLLL : "dddd, MMMM D YYYY LT"
+        },
+        longDateFormat : function (key) {
+            var output = this._longDateFormat[key];
+            if (!output && this._longDateFormat[key.toUpperCase()]) {
+                output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) {
+                    return val.slice(1);
+                });
+                this._longDateFormat[key] = output;
+            }
+            return output;
+        },
+
+        meridiem : function (hours, minutes, isLower) {
+            if (hours > 11) {
+                return isLower ? 'pm' : 'PM';
+            } else {
+                return isLower ? 'am' : 'AM';
+            }
+        },
+
+        _calendar : {
+            sameDay : '[Today at] LT',
+            nextDay : '[Tomorrow at] LT',
+            nextWeek : 'dddd [at] LT',
+            lastDay : '[Yesterday at] LT',
+            lastWeek : '[last] dddd [at] LT',
+            sameElse : 'L'
+        },
+        calendar : function (key, mom) {
+            var output = this._calendar[key];
+            return typeof output === 'function' ? output.apply(mom) : output;
+        },
+
+        _relativeTime : {
+            future : "in %s",
+            past : "%s ago",
+            s : "a few seconds",
+            m : "a minute",
+            mm : "%d minutes",
+            h : "an hour",
+            hh : "%d hours",
+            d : "a day",
+            dd : "%d days",
+            M : "a month",
+            MM : "%d months",
+            y : "a year",
+            yy : "%d years"
+        },
+        relativeTime : function (number, withoutSuffix, string, isFuture) {
+            var output = this._relativeTime[string];
+            return (typeof output === 'function') ?
+                output(number, withoutSuffix, string, isFuture) :
+                output.replace(/%d/i, number);
+        },
+        pastFuture : function (diff, output) {
+            var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
+            return typeof format === 'function' ? format(output) : format.replace(/%s/i, output);
+        },
+
+        ordinal : function (number) {
+            return this._ordinal.replace("%d", number);
+        },
+        _ordinal : "%d",
+
+        preparse : function (string) {
+            return string;
+        },
+
+        postformat : function (string) {
+            return string;
+        },
+
+        week : function (mom) {
+            return weekOfYear(mom, this._week.dow, this._week.doy);
+        },
+        _week : {
+            dow : 0, // Sunday is the first day of the week.
+            doy : 6  // The week that contains Jan 1st is the first week of the year.
+        }
+    };
+
+    // Loads a language definition into the `languages` cache.  The function
+    // takes a key and optionally values.  If not in the browser and no values
+    // are provided, it will load the language file module.  As a convenience,
+    // this function also returns the language values.
+    function loadLang(key, values) {
+        values.abbr = key;
+        if (!languages[key]) {
+            languages[key] = new Language();
+        }
+        languages[key].set(values);
+        return languages[key];
+    }
+
+    // Determines which language definition to use and returns it.
+    //
+    // With no parameters, it will return the global language.  If you
+    // pass in a language key, such as 'en', it will return the
+    // definition for 'en', so long as 'en' has already been loaded using
+    // moment.lang.
+    function getLangDefinition(key) {
+        if (!key) {
+            return moment.fn._lang;
+        }
+        if (!languages[key] && hasModule) {
+            require('./lang/' + key);
+        }
+        return languages[key];
+    }
+
+
+    /************************************
+        Formatting
+    ************************************/
+
+
+    function removeFormattingTokens(input) {
+        if (input.match(/\[.*\]/)) {
+            return input.replace(/^\[|\]$/g, "");
+        }
+        return input.replace(/\\/g, "");
+    }
+
+    function makeFormatFunction(format) {
+        var array = format.match(formattingTokens), i, length;
+
+        for (i = 0, length = array.length; i < length; i++) {
+            if (formatTokenFunctions[array[i]]) {
+                array[i] = formatTokenFunctions[array[i]];
+            } else {
+                array[i] = removeFormattingTokens(array[i]);
+            }
+        }
+
+        return function (mom) {
+            var output = "";
+            for (i = 0; i < length; i++) {
+                output += typeof array[i].call === 'function' ? array[i].call(mom, format) : array[i];
+            }
+            return output;
+        };
+    }
+
+    // format date using native date object
+    function formatMoment(m, format) {
+        var i = 5;
+
+        function replaceLongDateFormatTokens(input) {
+            return m.lang().longDateFormat(input) || input;
+        }
+
+        while (i-- && localFormattingTokens.test(format)) {
+            format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
+        }
+
+        if (!formatFunctions[format]) {
+            formatFunctions[format] = makeFormatFunction(format);
+        }
+
+        return formatFunctions[format](m);
+    }
+
+
+    /************************************
+        Parsing
+    ************************************/
+
+
+    // get the regex to find the next token
+    function getParseRegexForToken(token) {
+        switch (token) {
+        case 'DDDD':
+            return parseTokenThreeDigits;
+        case 'YYYY':
+            return parseTokenFourDigits;
+        case 'YYYYY':
+            return parseTokenSixDigits;
+        case 'S':
+        case 'SS':
+        case 'SSS':
+        case 'DDD':
+            return parseTokenOneToThreeDigits;
+        case 'MMM':
+        case 'MMMM':
+        case 'dd':
+        case 'ddd':
+        case 'dddd':
+        case 'a':
+        case 'A':
+            return parseTokenWord;
+        case 'X':
+            return parseTokenTimestampMs;
+        case 'Z':
+        case 'ZZ':
+            return parseTokenTimezone;
+        case 'T':
+            return parseTokenT;
+        case 'MM':
+        case 'DD':
+        case 'YY':
+        case 'HH':
+        case 'hh':
+        case 'mm':
+        case 'ss':
+        case 'M':
+        case 'D':
+        case 'd':
+        case 'H':
+        case 'h':
+        case 'm':
+        case 's':
+            return parseTokenOneOrTwoDigits;
+        default :
+            return new RegExp(token.replace('\\', ''));
+        }
+    }
+
+    // function to convert string input to date
+    function addTimeToArrayFromToken(token, input, config) {
+        var a, b,
+            datePartArray = config._a;
+
+        switch (token) {
+        // MONTH
+        case 'M' : // fall through to MM
+        case 'MM' :
+            datePartArray[1] = (input == null) ? 0 : ~~input - 1;
+            break;
+        case 'MMM' : // fall through to MMMM
+        case 'MMMM' :
+            a = getLangDefinition(config._l).monthsParse(input);
+            // if we didn't find a month name, mark the date as invalid.
+            if (a != null) {
+                datePartArray[1] = a;
+            } else {
+                config._isValid = false;
+            }
+            break;
+        // DAY OF MONTH
+        case 'D' : // fall through to DDDD
+        case 'DD' : // fall through to DDDD
+        case 'DDD' : // fall through to DDDD
+        case 'DDDD' :
+            if (input != null) {
+                datePartArray[2] = ~~input;
+            }
+            break;
+        // YEAR
+        case 'YY' :
+            datePartArray[0] = ~~input + (~~input > 68 ? 1900 : 2000);
+            break;
+        case 'YYYY' :
+        case 'YYYYY' :
+            datePartArray[0] = ~~input;
+            break;
+        // AM / PM
+        case 'a' : // fall through to A
+        case 'A' :
+            config._isPm = ((input + '').toLowerCase() === 'pm');
+            break;
+        // 24 HOUR
+        case 'H' : // fall through to hh
+        case 'HH' : // fall through to hh
+        case 'h' : // fall through to hh
+        case 'hh' :
+            datePartArray[3] = ~~input;
+            break;
+        // MINUTE
+        case 'm' : // fall through to mm
+        case 'mm' :
+            datePartArray[4] = ~~input;
+            break;
+        // SECOND
+        case 's' : // fall through to ss
+        case 'ss' :
+            datePartArray[5] = ~~input;
+            break;
+        // MILLISECOND
+        case 'S' :
+        case 'SS' :
+        case 'SSS' :
+            datePartArray[6] = ~~ (('0.' + input) * 1000);
+            break;
+        // UNIX TIMESTAMP WITH MS
+        case 'X':
+            config._d = new Date(parseFloat(input) * 1000);
+            break;
+        // TIMEZONE
+        case 'Z' : // fall through to ZZ
+        case 'ZZ' :
+            config._useUTC = true;
+            a = (input + '').match(parseTimezoneChunker);
+            if (a && a[1]) {
+                config._tzh = ~~a[1];
+            }
+            if (a && a[2]) {
+                config._tzm = ~~a[2];
+            }
+            // reverse offsets
+            if (a && a[0] === '+') {
+                config._tzh = -config._tzh;
+                config._tzm = -config._tzm;
+            }
+            break;
+        }
+
+        // if the input is null, the date is not valid
+        if (input == null) {
+            config._isValid = false;
+        }
+    }
+
+    // convert an array to a date.
+    // the array should mirror the parameters below
+    // note: all values past the year are optional and will default to the lowest possible value.
+    // [year, month, day , hour, minute, second, millisecond]
+    function dateFromArray(config) {
+        var i, date, input = [];
+
+        if (config._d) {
+            return;
+        }
+
+        for (i = 0; i < 7; i++) {
+            config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
+        }
+
+        // add the offsets to the time to be parsed so that we can have a clean array for checking isValid
+        input[3] += config._tzh || 0;
+        input[4] += config._tzm || 0;
+
+        date = new Date(0);
+
+        if (config._useUTC) {
+            date.setUTCFullYear(input[0], input[1], input[2]);
+            date.setUTCHours(input[3], input[4], input[5], input[6]);
+        } else {
+            date.setFullYear(input[0], input[1], input[2]);
+            date.setHours(input[3], input[4], input[5], input[6]);
+        }
+
+        config._d = date;
+    }
+
+    // date from string and format string
+    function makeDateFromStringAndFormat(config) {
+        // This array is used to make a Date, either with `new Date` or `Date.UTC`
+        var tokens = config._f.match(formattingTokens),
+            string = config._i,
+            i, parsedInput;
+
+        config._a = [];
+
+        for (i = 0; i < tokens.length; i++) {
+            parsedInput = (getParseRegexForToken(tokens[i]).exec(string) || [])[0];
+            if (parsedInput) {
+                string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
+            }
+            // don't parse if its not a known token
+            if (formatTokenFunctions[tokens[i]]) {
+                addTimeToArrayFromToken(tokens[i], parsedInput, config);
+            }
+        }
+        // handle am pm
+        if (config._isPm && config._a[3] < 12) {
+            config._a[3] += 12;
+        }
+        // if is 12 am, change hours to 0
+        if (config._isPm === false && config._a[3] === 12) {
+            config._a[3] = 0;
+        }
+        // return
+        dateFromArray(config);
+    }
+
+    // date from string and array of format strings
+    function makeDateFromStringAndArray(config) {
+        var tempConfig,
+            tempMoment,
+            bestMoment,
+
+            scoreToBeat = 99,
+            i,
+            currentDate,
+            currentScore;
+
+        while (config._f.length) {
+            tempConfig = extend({}, config);
+            tempConfig._f = config._f.pop();
+            makeDateFromStringAndFormat(tempConfig);
+            tempMoment = new Moment(tempConfig);
+
+            if (tempMoment.isValid()) {
+                bestMoment = tempMoment;
+                break;
+            }
+
+            currentScore = compareArrays(tempConfig._a, tempMoment.toArray());
+
+            if (currentScore < scoreToBeat) {
+                scoreToBeat = currentScore;
+                bestMoment = tempMoment;
+            }
+        }
+
+        extend(config, bestMoment);
+    }
+
+    // date from iso format
+    function makeDateFromString(config) {
+        var i,
+            string = config._i;
+        if (isoRegex.exec(string)) {
+            config._f = 'YYYY-MM-DDT';
+            for (i = 0; i < 4; i++) {
+                if (isoTimes[i][1].exec(string)) {
+                    config._f += isoTimes[i][0];
+                    break;
+                }
+            }
+            if (parseTokenTimezone.exec(string)) {
+                config._f += " Z";
+            }
+            makeDateFromStringAndFormat(config);
+        } else {
+            config._d = new Date(string);
+        }
+    }
+
+    function makeDateFromInput(config) {
+        var input = config._i,
+            matched = aspNetJsonRegex.exec(input);
+
+        if (input === undefined) {
+            config._d = new Date();
+        } else if (matched) {
+            config._d = new Date(+matched[1]);
+        } else if (typeof input === 'string') {
+            makeDateFromString(config);
+        } else if (isArray(input)) {
+            config._a = input.slice(0);
+            dateFromArray(config);
+        } else {
+            config._d = input instanceof Date ? new Date(+input) : new Date(input);
+        }
+    }
+
+
+    /************************************
+        Relative Time
+    ************************************/
+
+
+    // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
+    function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) {
+        return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
+    }
+
+    function relativeTime(milliseconds, withoutSuffix, lang) {
+        var seconds = round(Math.abs(milliseconds) / 1000),
+            minutes = round(seconds / 60),
+            hours = round(minutes / 60),
+            days = round(hours / 24),
+            years = round(days / 365),
+            args = seconds < 45 && ['s', seconds] ||
+                minutes === 1 && ['m'] ||
+                minutes < 45 && ['mm', minutes] ||
+                hours === 1 && ['h'] ||
+                hours < 22 && ['hh', hours] ||
+                days === 1 && ['d'] ||
+                days <= 25 && ['dd', days] ||
+                days <= 45 && ['M'] ||
+                days < 345 && ['MM', round(days / 30)] ||
+                years === 1 && ['y'] || ['yy', years];
+        args[2] = withoutSuffix;
+        args[3] = milliseconds > 0;
+        args[4] = lang;
+        return substituteTimeAgo.apply({}, args);
+    }
+
+
+    /************************************
+        Week of Year
+    ************************************/
+
+
+    // firstDayOfWeek       0 = sun, 6 = sat
+    //                      the day of the week that starts the week
+    //                      (usually sunday or monday)
+    // firstDayOfWeekOfYear 0 = sun, 6 = sat
+    //                      the first week is the week that contains the first
+    //                      of this day of the week
+    //                      (eg. ISO weeks use thursday (4))
+    function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) {
+        var end = firstDayOfWeekOfYear - firstDayOfWeek,
+            daysToDayOfWeek = firstDayOfWeekOfYear - mom.day();
+
+
+        if (daysToDayOfWeek > end) {
+            daysToDayOfWeek -= 7;
+        }
+
+        if (daysToDayOfWeek < end - 7) {
+            daysToDayOfWeek += 7;
+        }
+
+        return Math.ceil(moment(mom).add('d', daysToDayOfWeek).dayOfYear() / 7);
+    }
+
+
+    /************************************
+        Top Level Functions
+    ************************************/
+
+    function makeMoment(config) {
+        var input = config._i,
+            format = config._f;
+
+        if (input === null || input === '') {
+            return null;
+        }
+
+        if (typeof input === 'string') {
+            config._i = input = getLangDefinition().preparse(input);
+        }
+
+        if (moment.isMoment(input)) {
+            config = extend({}, input);
+            config._d = new Date(+input._d);
+        } else if (format) {
+            if (isArray(format)) {
+                makeDateFromStringAndArray(config);
+            } else {
+                makeDateFromStringAndFormat(config);
+            }
+        } else {
+            makeDateFromInput(config);
+        }
+
+        return new Moment(config);
+    }
+
+    moment = function (input, format, lang) {
+        return makeMoment({
+            _i : input,
+            _f : format,
+            _l : lang,
+            _isUTC : false
+        });
+    };
+
+    // creating with utc
+    moment.utc = function (input, format, lang) {
+        return makeMoment({
+            _useUTC : true,
+            _isUTC : true,
+            _l : lang,
+            _i : input,
+            _f : format
+        });
+    };
+
+    // creating with unix timestamp (in seconds)
+    moment.unix = function (input) {
+        return moment(input * 1000);
+    };
+
+    // duration
+    moment.duration = function (input, key) {
+        var isDuration = moment.isDuration(input),
+            isNumber = (typeof input === 'number'),
+            duration = (isDuration ? input._data : (isNumber ? {} : input)),
+            ret;
+
+        if (isNumber) {
+            if (key) {
+                duration[key] = input;
+            } else {
+                duration.milliseconds = input;
+            }
+        }
+
+        ret = new Duration(duration);
+
+        if (isDuration && input.hasOwnProperty('_lang')) {
+            ret._lang = input._lang;
+        }
+
+        return ret;
+    };
+
+    // version number
+    moment.version = VERSION;
+
+    // default format
+    moment.defaultFormat = isoFormat;
+
+    // This function will load languages and then set the global language.  If
+    // no arguments are passed in, it will simply return the current global
+    // language key.
+    moment.lang = function (key, values) {
+        var i;
+
+        if (!key) {
+            return moment.fn._lang._abbr;
+        }
+        if (values) {
+            loadLang(key, values);
+        } else if (!languages[key]) {
+            getLangDefinition(key);
+        }
+        moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key);
+    };
+
+    // returns language data
+    moment.langData = function (key) {
+        if (key && key._lang && key._lang._abbr) {
+            key = key._lang._abbr;
+        }
+        return getLangDefinition(key);
+    };
+
+    // compare moment object
+    moment.isMoment = function (obj) {
+        return obj instanceof Moment;
+    };
+
+    // for typechecking Duration objects
+    moment.isDuration = function (obj) {
+        return obj instanceof Duration;
+    };
+
+
+    /************************************
+        Moment Prototype
+    ************************************/
+
+
+    moment.fn = Moment.prototype = {
+
+        clone : function () {
+            return moment(this);
+        },
+
+        valueOf : function () {
+            return +this._d;
+        },
+
+        unix : function () {
+            return Math.floor(+this._d / 1000);
+        },
+
+        toString : function () {
+            return this.format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ");
+        },
+
+        toDate : function () {
+            return this._d;
+        },
+
+        toJSON : function () {
+            return moment.utc(this).format('YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
+        },
+
+        toArray : function () {
+            var m = this;
+            return [
+                m.year(),
+                m.month(),
+                m.date(),
+                m.hours(),
+                m.minutes(),
+                m.seconds(),
+                m.milliseconds()
+            ];
+        },
+
+        isValid : function () {
+            if (this._isValid == null) {
+                if (this._a) {
+                    this._isValid = !compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray());
+                } else {
+                    this._isValid = !isNaN(this._d.getTime());
+                }
+            }
+            return !!this._isValid;
+        },
+
+        utc : function () {
+            this._isUTC = true;
+            return this;
+        },
+
+        local : function () {
+            this._isUTC = false;
+            return this;
+        },
+
+        format : function (inputString) {
+            var output = formatMoment(this, inputString || moment.defaultFormat);
+            return this.lang().postformat(output);
+        },
+
+        add : function (input, val) {
+            var dur;
+            // switch args to support add('s', 1) and add(1, 's')
+            if (typeof input === 'string') {
+                dur = moment.duration(+val, input);
+            } else {
+                dur = moment.duration(input, val);
+            }
+            addOrSubtractDurationFromMoment(this, dur, 1);
+            return this;
+        },
+
+        subtract : function (input, val) {
+            var dur;
+            // switch args to support subtract('s', 1) and subtract(1, 's')
+            if (typeof input === 'string') {
+                dur = moment.duration(+val, input);
+            } else {
+                dur = moment.duration(input, val);
+            }
+            addOrSubtractDurationFromMoment(this, dur, -1);
+            return this;
+        },
+
+        diff : function (input, units, asFloat) {
+            var that = this._isUTC ? moment(input).utc() : moment(input).local(),
+                zoneDiff = (this.zone() - that.zone()) * 6e4,
+                diff, output;
+
+            if (units) {
+                // standardize on singular form
+                units = units.replace(/s$/, '');
+            }
+
+            if (units === 'year' || units === 'month') {
+                diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2
+                output = ((this.year() - that.year()) * 12) + (this.month() - that.month());
+                output += ((this - moment(this).startOf('month')) - (that - moment(that).startOf('month'))) / diff;
+                if (units === 'year') {
+                    output = output / 12;
+                }
+            } else {
+                diff = (this - that) - zoneDiff;
+                output = units === 'second' ? diff / 1e3 : // 1000
+                    units === 'minute' ? diff / 6e4 : // 1000 * 60
+                    units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60
+                    units === 'day' ? diff / 864e5 : // 1000 * 60 * 60 * 24
+                    units === 'week' ? diff / 6048e5 : // 1000 * 60 * 60 * 24 * 7
+                    diff;
+            }
+            return asFloat ? output : absRound(output);
+        },
+
+        from : function (time, withoutSuffix) {
+            return moment.duration(this.diff(time)).lang(this.lang()._abbr).humanize(!withoutSuffix);
+        },
+
+        fromNow : function (withoutSuffix) {
+            return this.from(moment(), withoutSuffix);
+        },
+
+        calendar : function () {
+            var diff = this.diff(moment().startOf('day'), 'days', true),
+                format = diff < -6 ? 'sameElse' :
+                diff < -1 ? 'lastWeek' :
+                diff < 0 ? 'lastDay' :
+                diff < 1 ? 'sameDay' :
+                diff < 2 ? 'nextDay' :
+                diff < 7 ? 'nextWeek' : 'sameElse';
+            return this.format(this.lang().calendar(format, this));
+        },
+
+        isLeapYear : function () {
+            var year = this.year();
+            return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+        },
+
+        isDST : function () {
+            return (this.zone() < moment([this.year()]).zone() ||
+                this.zone() < moment([this.year(), 5]).zone());
+        },
+
+        day : function (input) {
+            var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
+            return input == null ? day :
+                this.add({ d : input - day });
+        },
+
+        startOf: function (units) {
+            units = units.replace(/s$/, '');
+            // the following switch intentionally omits break keywords
+            // to utilize falling through the cases.
+            switch (units) {
+            case 'year':
+                this.month(0);
+                /* falls through */
+            case 'month':
+                this.date(1);
+                /* falls through */
+            case 'week':
+            case 'day':
+                this.hours(0);
+                /* falls through */
+            case 'hour':
+                this.minutes(0);
+                /* falls through */
+            case 'minute':
+                this.seconds(0);
+                /* falls through */
+            case 'second':
+                this.milliseconds(0);
+                /* falls through */
+            }
+
+            // weeks are a special case
+            if (units === 'week') {
+                this.day(0);
+            }
+
+            return this;
+        },
+
+        endOf: function (units) {
+            return this.startOf(units).add(units.replace(/s?$/, 's'), 1).subtract('ms', 1);
+        },
+
+        isAfter: function (input, units) {
+            units = typeof units !== 'undefined' ? units : 'millisecond';
+            return +this.clone().startOf(units) > +moment(input).startOf(units);
+        },
+
+        isBefore: function (input, units) {
+            units = typeof units !== 'undefined' ? units : 'millisecond';
+            return +this.clone().startOf(units) < +moment(input).startOf(units);
+        },
+
+        isSame: function (input, units) {
+            units = typeof units !== 'undefined' ? units : 'millisecond';
+            return +this.clone().startOf(units) === +moment(input).startOf(units);
+        },
+
+        zone : function () {
+            return this._isUTC ? 0 : this._d.getTimezoneOffset();
+        },
+
+        daysInMonth : function () {
+            return moment.utc([this.year(), this.month() + 1, 0]).date();
+        },
+
+        dayOfYear : function (input) {
+            var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1;
+            return input == null ? dayOfYear : this.add("d", (input - dayOfYear));
+        },
+
+        isoWeek : function (input) {
+            var week = weekOfYear(this, 1, 4);
+            return input == null ? week : this.add("d", (input - week) * 7);
+        },
+
+        week : function (input) {
+            var week = this.lang().week(this);
+            return input == null ? week : this.add("d", (input - week) * 7);
+        },
+
+        // If passed a language key, it will set the language for this
+        // instance.  Otherwise, it will return the language configuration
+        // variables for this instance.
+        lang : function (key) {
+            if (key === undefined) {
+                return this._lang;
+            } else {
+                this._lang = getLangDefinition(key);
+                return this;
+            }
+        }
+    };
+
+    // helper for adding shortcuts
+    function makeGetterAndSetter(name, key) {
+        moment.fn[name] = moment.fn[name + 's'] = function (input) {
+            var utc = this._isUTC ? 'UTC' : '';
+            if (input != null) {
+                this._d['set' + utc + key](input);
+                return this;
+            } else {
+                return this._d['get' + utc + key]();
+            }
+        };
+    }
+
+    // loop through and add shortcuts (Month, Date, Hours, Minutes, Seconds, Milliseconds)
+    for (i = 0; i < proxyGettersAndSetters.length; i ++) {
+        makeGetterAndSetter(proxyGettersAndSetters[i].toLowerCase().replace(/s$/, ''), proxyGettersAndSetters[i]);
+    }
+
+    // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear')
+    makeGetterAndSetter('year', 'FullYear');
+
+    // add plural methods
+    moment.fn.days = moment.fn.day;
+    moment.fn.weeks = moment.fn.week;
+    moment.fn.isoWeeks = moment.fn.isoWeek;
+
+    /************************************
+        Duration Prototype
+    ************************************/
+
+
+    moment.duration.fn = Duration.prototype = {
+        weeks : function () {
+            return absRound(this.days() / 7);
+        },
+
+        valueOf : function () {
+            return this._milliseconds +
+              this._days * 864e5 +
+              this._months * 2592e6;
+        },
+
+        humanize : function (withSuffix) {
+            var difference = +this,
+                output = relativeTime(difference, !withSuffix, this.lang());
+
+            if (withSuffix) {
+                output = this.lang().pastFuture(difference, output);
+            }
+
+            return this.lang().postformat(output);
+        },
+
+        lang : moment.fn.lang
+    };
+
+    function makeDurationGetter(name) {
+        moment.duration.fn[name] = function () {
+            return this._data[name];
+        };
+    }
+
+    function makeDurationAsGetter(name, factor) {
+        moment.duration.fn['as' + name] = function () {
+            return +this / factor;
+        };
+    }
+
+    for (i in unitMillisecondFactors) {
+        if (unitMillisecondFactors.hasOwnProperty(i)) {
+            makeDurationAsGetter(i, unitMillisecondFactors[i]);
+            makeDurationGetter(i.toLowerCase());
+        }
+    }
+
+    makeDurationAsGetter('Weeks', 6048e5);
+
+
+    /************************************
+        Default Lang
+    ************************************/
+
+
+    // Set default language, other languages will inherit from English.
+    moment.lang('en', {
+        ordinal : function (number) {
+            var b = number % 10,
+                output = (~~ (number % 100 / 10) === 1) ? 'th' :
+                (b === 1) ? 'st' :
+                (b === 2) ? 'nd' :
+                (b === 3) ? 'rd' : 'th';
+            return number + output;
+        }
+    });
+
+
+    /************************************
+        Exposing Moment
+    ************************************/
+
+
+    // CommonJS module is defined
+    if (hasModule) {
+        module.exports = moment;
+    }
+    /*global ender:false */
+    if (typeof ender === 'undefined') {
+        // here, `this` means `window` in the browser, or `global` on the server
+        // add `moment` as a global object via a string identifier,
+        // for Closure Compiler "advanced" mode
+        this['moment'] = moment;
+    }
+    /*global define:false */
+    if (typeof define === "function" && define.amd) {
+        define("moment", [], function () {
+            return moment;
+        });
+    }
+}).call(this);
level2/node_modules/sugar/performance/javascripts/benchmark.js
@@ -0,0 +1,29 @@
+
+runPerformanceTest = function() {
+  var iterations, fn, start, i = 0, ms;
+  var passedArg = arguments[2];
+  if(arguments.length == 1) {
+    iterations = 10000;
+    fn = arguments[0];
+  } else {
+    iterations = arguments[0];
+    fn = arguments[1];
+  }
+  start = new Date();
+  while(i < iterations) {
+    fn(passedArg);
+    i++;
+  }
+  ms = new Date() - start
+  console.info(iterations + ' iterations finished in ' + ms + ' milliseconds');
+  return ms;
+}
+
+function runMultipleTestsWithArgumentAndAverage(test, arg, iterationsEach, times) {
+  var sum = 0;
+  for (var i = 0; i < times; i += 1) {
+    sum += runPerformanceTest(iterationsEach, test, arg);
+  };
+  return Math.round(sum / times);
+}
+
level2/node_modules/sugar/performance/javascripts/fixtures.js
@@ -0,0 +1,562 @@
+
+normalDate = new Date();
+normalFunction = function() {
+  return 'foo';
+}
+
+zero           = 0;
+smallInteger   = 85;
+hugeNumber     = 893249283429;
+floatingNumber = 463.34534533;
+numberObject   = new Number(53);
+
+
+
+emptyString  = '';
+normalString = 'abcdefg';
+hugeString   = '';
+stringObject = new String('wasabi');
+hugeNumberAsString = '893249283429';
+hugeNumberAsStringWithTrailingLetters = '893249283429alkdf';
+
+
+emptyArray       = [];
+smallNumberArray = [1,2,3];
+smallStringArray = ['a','b','c'];
+
+
+biggerStringArray = [
+  'andere',
+  'รคndere',
+  'chaque',
+  'chemin',
+  'cote',
+  'cotร‰',
+  'cร”te',
+  'cร”tร‰',
+  'Czech',
+  'ฤŒuฤŒet',
+  'hiล a',
+  'irdisch',
+  'lรคvi',
+  'lie',
+  'lire',
+  'llama',
+  'Lร–wen',
+  'lร’za',
+  'Lรœbeck',
+  'luck',
+  'luฤŒ',
+  'lye',
+  'Mรคnner',
+  'mร€ล ta',
+  'mรŽr',
+  'mร–chten',
+  'myndig',
+  'pint',
+  'piร‘a',
+  'pylon',
+  'sรคmtlich',
+  'savoir',
+  'Sietla',
+  'subtle',
+  'symbol',
+  'ลšlub',
+  'ล ร€ran',
+  'vรคga',
+  'verkehrt',
+  'vox',
+  'waffle',
+  'wood',
+  'yen',
+  'yuan',
+  'yucca',
+  'zoo',
+  'Zรœrich',
+  'Zviedrija',
+  'zysk',
+  'ลฝal',
+  'ลฝena'
+];
+
+smallStringArrayWithNumbersMixedIn = [
+  'file 2',
+  'file 200',
+  'file 15',
+  'file 153',
+  'file 1',
+  'file 25225',
+  'file 252',
+  'file 95',
+  'file 932'
+];
+
+
+bigNumberArray = [];
+bigCharacterArray = [];
+bigDateArray   = [];
+
+(function() {
+
+  for(var i = 0; i < 10000; i++) {
+    var rand = Math.floor(Math.random() * 1000);
+    var char = String.fromCharCode(rand);
+    bigNumberArray.push(rand);
+    bigDateArray.push(new Date(rand));
+    bigCharacterArray.push(char);
+    hugeString += char;
+  }
+
+})();
+
+
+
+jsonArray = [
+  {
+    "id":3895380,
+    "uri":"http://iknow.jp/items/3895380",
+    "cue":{
+      "type":"text",
+      "content":{
+        "text":"intensive"
+      },
+      "related":{
+        "language":"en",
+        "part_of_speech":"Adjective",
+        "transcription":"\u026an\u02c8t\u025bns\u026av"
+      }
+    },
+    "response":{
+      "type":"meaning",
+      "content":{
+        "text":"\u96c6\u4e2d\u7684\u306a\u3001\u5f37\u3044"
+      },
+      "related":{
+        "language":"ja",
+        "transliterations":[
+          {
+            "type":"Hira",
+            "text":"\u3057\u3085\u3046\u3061\u3085\u3046\u3066\u304d\u306a\u3001\u3064\u3088\u3044"
+          },
+          {
+            "type":"Hrkt",
+            "text":"\u3057\u3085\u3046\u3061\u3085\u3046\u3066\u304d\u306a\u3001\u3064\u3088\u3044"
+          },
+          {
+            "type":"Latn",
+            "text":"shuuchuutekina,tsuyoi"
+          }
+        ]
+      }
+    }
+  },
+  {
+    "id":3895379,
+    "uri":"http://iknow.jp/items/3895379",
+    "cue":{
+      "type":"text",
+      "content":{
+        "text":"condemn"
+      },
+      "related":{
+        "language":"en",
+        "part_of_speech":"Verb",
+        "transcription":"k\u0259n\u02c8d\u025bm"
+      }
+    },
+    "response":{
+      "type":"meaning",
+      "content":{
+        "text":"\u3068\u304c\u3081\u308b\u3001\u975e\u96e3\u3059\u308b"
+      },
+      "related":{
+        "language":"ja",
+        "transliterations":[
+          {
+            "type":"Hira",
+            "text":"\u3068\u304c\u3081\u308b\u3001\u3072\u306a\u3093\u3059\u308b"
+          },
+          {
+            "type":"Hrkt",
+            "text":"\u3068\u304c\u3081\u308b\u3001\u3072\u306a\u3093\u3059\u308b"
+          },
+          {
+            "type":"Latn",
+            "text":"togameru,hinansuru"
+          }
+        ]
+      }
+    }
+  },
+  {
+    "id":3895378,
+    "uri":"http://iknow.jp/items/3895378",
+    "cue":{
+      "type":"text",
+      "content":{
+        "text":"Catholic"
+      },
+      "related":{
+        "language":"en",
+        "part_of_speech":"Adjective",
+        "transcription":"\u02c8k\u00e6\u03b8\u0259l\u026ak"
+      }
+    },
+    "response":{
+      "type":"meaning",
+      "content":{
+        "text":"\u30ab\u30c8\u30ea\u30c3\u30af\u6559\u5f92"
+      },
+      "related":{
+        "language":"ja",
+        "transliterations":[
+          {
+            "type":"Hira",
+            "text":"\u304b\u3068\u308a\u3063\u304f\u304d\u3087\u3046\u3068"
+          },
+          {
+            "type":"Hrkt",
+            "text":"\u30ab\u30c8\u30ea\u30c3\u30af\u304d\u3087\u3046\u3068"
+          },
+          {
+            "type":"Latn",
+            "text":"katorikkukyouto"
+          }
+        ]
+      }
+    }
+  },
+  {
+    "id":3895377,
+    "uri":"http://iknow.jp/items/3895377",
+    "cue":{
+      "type":"text",
+      "content":{
+        "text":"anxiety"
+      },
+      "related":{
+        "language":"en",
+        "part_of_speech":"Noun",
+        "transcription":"\u00e6\u014b\u02c8za\u026a\u026ati\u02d0"
+      }
+    },
+    "response":{
+      "type":"meaning",
+      "content":{
+        "text":"\u5fc3\u914d\u3001\u4e0d\u5b89"
+      },
+      "related":{
+        "language":"ja",
+        "transliterations":[
+          {
+            "type":"Hira",
+            "text":"\u3057\u3093\u3071\u3044\u3001\u3075\u3042\u3093"
+          },
+          {
+            "type":"Hrkt",
+            "text":"\u3057\u3093\u3071\u3044\u3001\u3075\u3042\u3093"
+          },
+          {
+            "type":"Latn",
+            "text":"shinpai,fuan"
+          }
+        ]
+      }
+    }
+  },
+  {
+    "id":3895376,
+    "uri":"http://iknow.jp/items/3895376",
+    "cue":{
+      "type":"text",
+      "content":{
+        "text":"interfere"
+      },
+      "related":{
+        "language":"en",
+        "part_of_speech":"Verb",
+        "transcription":"\u02cc\u026ant\u0259r\u02c8fi\u02d0r"
+      }
+    },
+    "response":{
+      "type":"meaning",
+      "content":{
+        "text":"\u90aa\u9b54\u3059\u308b\u3001\u5e72\u6e09\u3059\u308b"
+      },
+      "related":{
+        "language":"ja",
+        "transliterations":[
+          {
+            "type":"Hira",
+            "text":"\u3058\u3083\u307e\u3059\u308b\u3001\u304b\u3093\u3057\u3087\u3046\u3059\u308b"
+          },
+          {
+            "type":"Hrkt",
+            "text":"\u3058\u3083\u307e\u3059\u308b\u3001\u304b\u3093\u3057\u3087\u3046\u3059\u308b"
+          },
+          {
+            "type":"Latn",
+            "text":"jamasuru,kanshousuru"
+          }
+        ]
+      }
+    }
+  },
+  {
+    "id":3895375,
+    "uri":"http://iknow.jp/items/3895375",
+    "cue":{
+      "type":"text",
+      "content":{
+        "text":"discard"
+      },
+      "related":{
+        "language":"en",
+        "part_of_speech":"Verb",
+        "transcription":"d\u026a\u02c8sk\u0251\u02d0rd"
+      }
+    },
+    "response":{
+      "type":"meaning",
+      "content":{
+        "text":"\u653e\u68c4\u3059\u308b\u3001\uff08\u4e0d\u8981\u306a\u3082\u306e\u3092\uff09\u6368\u3066\u308b"
+      },
+      "related":{
+        "language":"ja",
+        "transliterations":[
+          {
+            "type":"Hira",
+            "text":"\u307b\u3046\u304d\u3059\u308b\u3001\uff08\u3075\u3088\u3046\u306a\u3082\u306e\u3092\uff09\u3059\u3066\u308b"
+          },
+          {
+            "type":"Hrkt",
+            "text":"\u307b\u3046\u304d\u3059\u308b\u3001\uff08\u3075\u3088\u3046\u306a\u3082\u306e\u3092\uff09\u3059\u3066\u308b"
+          },
+          {
+            "type":"Latn",
+            "text":"houkisuru,(fuyounamonowo)suteru"
+          }
+        ]
+      }
+    }
+  },
+  {
+    "id":3895374,
+    "uri":"http://iknow.jp/items/3895374",
+    "cue":{
+      "type":"text",
+      "content":{
+        "text":"fertile"
+      },
+      "related":{
+        "language":"en",
+        "part_of_speech":"Adjective",
+        "transcription":"\u02c8f\u0259rtl"
+      }
+    },
+    "response":{
+      "type":"meaning",
+      "content":{
+        "text":"\u80a5\u6c83\uff08\u3072\u3088\u304f\uff09\u306a"
+      },
+      "related":{
+        "language":"ja",
+        "transliterations":[
+          {
+            "type":"Hira",
+            "text":"\u3072\u3088\u304f\uff08\u3072\u3088\u304f\uff09\u306a"
+          },
+          {
+            "type":"Hrkt",
+            "text":"\u3072\u3088\u304f\uff08\u3072\u3088\u304f\uff09\u306a"
+          },
+          {
+            "type":"Latn",
+            "text":"hiyoku(hiyoku)na"
+          }
+        ]
+      }
+    }
+  },
+  {
+    "id":3895373,
+    "uri":"http://iknow.jp/items/3895373",
+    "cue":{
+      "type":"text",
+      "content":{
+        "text":"decent"
+      },
+      "related":{
+        "language":"en",
+        "part_of_speech":"Adjective",
+        "transcription":"\u02c8di\u02d0s\u0259nt"
+      }
+    },
+    "response":{
+      "type":"meaning",
+      "content":{
+        "text":"\u793c\u5100\u6b63\u3057\u3044\u3001\u7acb\u6d3e\u306a\u3001\u304b\u306a\u308a\u306e"
+      },
+      "related":{
+        "language":"ja",
+        "transliterations":[
+          {
+            "type":"Hira",
+            "text":"\u308c\u3044\u304e\u305f\u3060\u3057\u3044\u3001\u308a\u3063\u3071\u306a\u3001\u304b\u306a\u308a\u306e"
+          },
+          {
+            "type":"Hrkt",
+            "text":"\u308c\u3044\u304e\u305f\u3060\u3057\u3044\u3001\u308a\u3063\u3071\u306a\u3001\u304b\u306a\u308a\u306e"
+          },
+          {
+            "type":"Latn",
+            "text":"reigitadashii,rippana,kanarino"
+          }
+        ]
+      }
+    }
+  },
+  {
+    "id":3895372,
+    "uri":"http://iknow.jp/items/3895372",
+    "cue":{
+      "type":"text",
+      "content":{
+        "text":"loose"
+      },
+      "related":{
+        "language":"en",
+        "part_of_speech":"Adjective",
+        "transcription":"lu\u02d0s"
+      }
+    },
+    "response":{
+      "type":"meaning",
+      "content":{
+        "text":"\u9589\u3058\u3066\u3044\u306a\u3044\u3001\u3086\u308b\u3093\u3060\u3001\u81ea\u7531\u306a"
+      },
+      "related":{
+        "language":"ja",
+        "transliterations":[
+          {
+            "type":"Hira",
+            "text":"\u3068\u3058\u3066\u3044\u306a\u3044\u3001\u3086\u308b\u3093\u3060\u3001\u3058\u3086\u3046\u306a"
+          },
+          {
+            "type":"Hrkt",
+            "text":"\u3068\u3058\u3066\u3044\u306a\u3044\u3001\u3086\u308b\u3093\u3060\u3001\u3058\u3086\u3046\u306a"
+          },
+          {
+            "type":"Latn",
+            "text":"tojiteinai,yurunda,jiyuuna"
+          }
+        ]
+      }
+    }
+  },
+  {
+    "id":3895371,
+    "uri":"http://iknow.jp/items/3895371",
+    "cue":{
+      "type":"text",
+      "content":{
+        "text":"promote"
+      },
+      "related":{
+        "language":"en",
+        "part_of_speech":"Verb",
+        "transcription":"pr\u0259\u02c8mo\u028at"
+      }
+    },
+    "response":{
+      "type":"meaning",
+      "content":{
+        "text":"\u6607\u9032\u3055\u305b\u308b\u3001\u5ba3\u4f1d\u3059\u308b"
+      },
+      "related":{
+        "language":"ja",
+        "transliterations":[
+          {
+            "type":"Hira",
+            "text":"\u3057\u3087\u3046\u3057\u3093\u3055\u305b\u308b\u3001\u305b\u3093\u3067\u3093\u3059\u308b"
+          },
+          {
+            "type":"Hrkt",
+            "text":"\u3057\u3087\u3046\u3057\u3093\u3055\u305b\u308b\u3001\u305b\u3093\u3067\u3093\u3059\u308b"
+          },
+          {
+            "type":"Latn",
+            "text":"shoushinsaseru,sendensuru"
+          }
+        ]
+      }
+    }
+  }
+];
+
+
+simpleObject = { id:14 };
+deepObject = {
+  foo: {
+    foo: {
+      foo: {
+        foo: {
+          foo: {
+            foo: {
+              foo: {
+                foo: {
+                  foo: {
+                    foo: {
+                      foo: {
+                        foo: {
+                          foo: {
+                            foo: {
+                              foo: {
+                                foo: {
+                                  foo: {
+                                    foo: {
+                                      foo: {
+                                        foo: {
+                                          foo: {
+                                            foo: {
+                                              foo: {
+                                                foo: {
+                                                  foo: {
+                                                    foo: {
+                                                      foo: {
+                                                        foo: {
+                                                          foo: {
+                                                            foo: {
+                                                              foo: 'bar'
+                                                            }
+                                                          }
+                                                        }
+                                                      }
+                                                    }
+                                                  }
+                                                }
+                                              }
+                                            }
+                                          }
+                                        }
+                                      }
+                                    }
+                                  }
+                                }
+                              }
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+};
+jsonObject   = jsonArray[0];
level2/node_modules/sugar/performance/javascripts/tests.js
@@ -0,0 +1,76 @@
+
+var o1 = {foo:'bar'};
+var o2 = {b:{c:new Date()}};
+var o3 = {foo:'bar'};
+
+var arr = [];
+arr.push(o1);
+arr.push(o2);
+arr.push(o3);
+arr.push(o1);
+arr.push(o2);
+arr.push(o3);
+arr.push(o1);
+arr.push(o2);
+arr.push(o3);
+arr.push(o1);
+arr.push(o2);
+arr.push(o3);
+
+
+arr = arr.concat(arr);
+arr = arr.concat(arr);
+arr = arr.concat(arr);
+arr = arr.concat(arr);
+arr = arr.concat(arr);
+arr = arr.concat(arr);
+
+var tests = [
+  {
+    fn: function(arg) {
+      return Date.create('2010-08-25')
+    },
+    targets: [
+      'normalString * 1000'
+    ]
+  },
+  /*
+  {
+    fn: function(arg) {
+      return arg.findAll(/^a/);
+    },
+    targets: [
+      'emptyArray * 200000',
+      'smallNumberArray * 100000',
+      'smallStringArray * 100000',
+      'bigNumberArray * 100',
+      'bigStringArray * 100',
+      'bigDateArray * 10',
+      'jsonArray * 10000'
+    ]
+  },
+  {
+    fn: function(arg) {
+      return Object.map(arg, function() {
+        return 3;
+      });
+    },
+    targets: [
+      'simpleObject * 100000',
+      'deepObject * 100000',
+      'jsonObject * 100000'
+    ]
+  },
+  {
+    fn: function(arg) {
+      return Object.isString(arg);
+    },
+    targets: [
+      'emptyString * 1000000',
+      'normalString * 1000000',
+      'hugeString * 1000000',
+    ]
+  }
+  */
+];
+
level2/node_modules/sugar/release/sugar-full.development.js
@@ -0,0 +1,9245 @@
+/*
+ *  Sugar Library v1.4.1
+ *
+ *  Freely distributable and licensed under the MIT-style license.
+ *  Copyright (c) 2013 Andrew Plummer
+ *  http://sugarjs.com/
+ *
+ * ---------------------------- */
+(function(){
+  'use strict';
+
+  /***
+   * @package Core
+   * @description Internal utility and common methods.
+   ***/
+
+
+  // A few optimizations for Google Closure Compiler will save us a couple kb in the release script.
+  var object = Object, array = Array, regexp = RegExp, date = Date, string = String, number = Number, math = Math, Undefined;
+
+  // The global context
+  var globalContext = typeof global !== 'undefined' ? global : this;
+
+  // Internal toString
+  var internalToString = object.prototype.toString;
+
+  // Internal hasOwnProperty
+  var internalHasOwnProperty = object.prototype.hasOwnProperty;
+
+  // defineProperty exists in IE8 but will error when trying to define a property on
+  // native objects. IE8 does not have defineProperies, however, so this check saves a try/catch block.
+  var definePropertySupport = object.defineProperty && object.defineProperties;
+
+  // Are regexes type function?
+  var regexIsFunction = typeof regexp() === 'function';
+
+  // Do strings have no keys?
+  var noKeysInStringObjects = !('0' in new string('a'));
+
+  // Type check methods need a way to be accessed dynamically.
+  var typeChecks = {};
+
+  // Classes that can be matched by value
+  var matchedByValueReg = /^\[object Date|Array|String|Number|RegExp|Boolean|Arguments\]$/;
+
+  // Class initializers and class helpers
+  var ClassNames = 'Boolean,Number,String,Array,Date,RegExp,Function'.split(',');
+
+  var isBoolean  = buildPrimitiveClassCheck('boolean', ClassNames[0]);
+  var isNumber   = buildPrimitiveClassCheck('number',  ClassNames[1]);
+  var isString   = buildPrimitiveClassCheck('string',  ClassNames[2]);
+
+  var isArray    = buildClassCheck(ClassNames[3]);
+  var isDate     = buildClassCheck(ClassNames[4]);
+  var isRegExp   = buildClassCheck(ClassNames[5]);
+
+
+  // Wanted to enhance performance here by using simply "typeof"
+  // but Firefox has two major issues that make this impossible,
+  // one fixed, the other not. Despite being typeof "function"
+  // the objects below still report in as [object Function], so
+  // we need to perform a full class check here.
+  //
+  // 1. Regexes can be typeof "function" in FF < 3
+  //    https://bugzilla.mozilla.org/show_bug.cgi?id=61911 (fixed)
+  //
+  // 2. HTMLEmbedElement and HTMLObjectElement are be typeof "function"
+  //    https://bugzilla.mozilla.org/show_bug.cgi?id=268945 (won't fix)
+  //
+  var isFunction = buildClassCheck(ClassNames[6]);
+
+  function isClass(obj, klass, cached) {
+    var k = cached || className(obj);
+    return k === '[object '+klass+']';
+  }
+
+  function buildClassCheck(klass) {
+    var fn = (klass === 'Array' && array.isArray) || function(obj, cached) {
+      return isClass(obj, klass, cached);
+    };
+    typeChecks[klass] = fn;
+    return fn;
+  }
+
+  function buildPrimitiveClassCheck(type, klass) {
+    var fn = function(obj) {
+      if(isObjectType(obj)) {
+        return isClass(obj, klass);
+      }
+      return typeof obj === type;
+    }
+    typeChecks[klass] = fn;
+    return fn;
+  }
+
+  function className(obj) {
+    return internalToString.call(obj);
+  }
+
+  function initializeClasses() {
+    initializeClass(object);
+    iterateOverObject(ClassNames, function(i,name) {
+      initializeClass(globalContext[name]);
+    });
+  }
+
+  function initializeClass(klass) {
+    if(klass['SugarMethods']) return;
+    defineProperty(klass, 'SugarMethods', {});
+    extend(klass, false, true, {
+      'extend': function(methods, override, instance) {
+        extend(klass, instance !== false, override, methods);
+      },
+      'sugarRestore': function() {
+        return batchMethodExecute(this, klass, arguments, function(target, name, m) {
+          defineProperty(target, name, m.method);
+        });
+      },
+      'sugarRevert': function() {
+        return batchMethodExecute(this, klass, arguments, function(target, name, m) {
+          if(m['existed']) {
+            defineProperty(target, name, m['original']);
+          } else {
+            delete target[name];
+          }
+        });
+      }
+    });
+  }
+
+  // Class extending methods
+
+  function extend(klass, instance, override, methods) {
+    var extendee = instance ? klass.prototype : klass;
+    initializeClass(klass);
+    iterateOverObject(methods, function(name, extendedFn) {
+      var nativeFn = extendee[name],
+          existed  = hasOwnProperty(extendee, name);
+      if(isFunction(override) && nativeFn) {
+        extendedFn = wrapNative(nativeFn, extendedFn, override);
+      }
+      if(override !== false || !nativeFn) {
+        defineProperty(extendee, name, extendedFn);
+      }
+      // If the method is internal to Sugar, then
+      // store a reference so it can be restored later.
+      klass['SugarMethods'][name] = {
+        'method':   extendedFn,
+        'existed':  existed,
+        'original': nativeFn,
+        'instance': instance
+      };
+    });
+  }
+
+  function extendSimilar(klass, instance, override, set, fn) {
+    var methods = {};
+    set = isString(set) ? set.split(',') : set;
+    set.forEach(function(name, i) {
+      fn(methods, name, i);
+    });
+    extend(klass, instance, override, methods);
+  }
+
+  function batchMethodExecute(target, klass, args, fn) {
+    var all = args.length === 0, methods = multiArgs(args), changed = false;
+    iterateOverObject(klass['SugarMethods'], function(name, m) {
+      if(all || methods.indexOf(name) !== -1) {
+        changed = true;
+        fn(m['instance'] ? target.prototype : target, name, m);
+      }
+    });
+    return changed;
+  }
+
+  function wrapNative(nativeFn, extendedFn, condition) {
+    return function(a) {
+      return condition.apply(this, arguments) ?
+             extendedFn.apply(this, arguments) :
+             nativeFn.apply(this, arguments);
+    }
+  }
+
+  function defineProperty(target, name, method) {
+    if(definePropertySupport) {
+      object.defineProperty(target, name, {
+        'value': method,
+        'configurable': true,
+        'enumerable': false,
+        'writable': true
+      });
+    } else {
+      target[name] = method;
+    }
+  }
+
+
+  // Argument helpers
+
+  function multiArgs(args, fn, from) {
+    var result = [], i = from || 0, len;
+    for(len = args.length; i < len; i++) {
+      result.push(args[i]);
+      if(fn) fn.call(args, args[i], i);
+    }
+    return result;
+  }
+
+  function flattenedArgs(args, fn, from) {
+    var arg = args[from || 0];
+    if(isArray(arg)) {
+      args = arg;
+      from = 0;
+    }
+    return multiArgs(args, fn, from);
+  }
+
+  function checkCallback(fn) {
+    if(!fn || !fn.call) {
+      throw new TypeError('Callback is not callable');
+    }
+  }
+
+
+  // General helpers
+
+  function isDefined(o) {
+    return o !== Undefined;
+  }
+
+  function isUndefined(o) {
+    return o === Undefined;
+  }
+
+
+  // Object helpers
+
+  function hasProperty(obj, prop) {
+    return !isPrimitiveType(obj) && prop in obj;
+  }
+
+  function hasOwnProperty(obj, prop) {
+    return !!obj && internalHasOwnProperty.call(obj, prop);
+  }
+
+  function isObjectType(obj) {
+    // 1. Check for null
+    // 2. Check for regexes in environments where they are "functions".
+    return !!obj && (typeof obj === 'object' || (regexIsFunction && isRegExp(obj)));
+  }
+
+  function isPrimitiveType(obj) {
+    var type = typeof obj;
+    return obj == null || type === 'string' || type === 'number' || type === 'boolean';
+  }
+
+  function isPlainObject(obj, klass) {
+    klass = klass || className(obj);
+    try {
+      // Not own constructor property must be Object
+      // This code was borrowed from jQuery.isPlainObject
+      if (obj && obj.constructor &&
+            !hasOwnProperty(obj, 'constructor') &&
+            !hasOwnProperty(obj.constructor.prototype, 'isPrototypeOf')) {
+        return false;
+      }
+    } catch (e) {
+      // IE8,9 Will throw exceptions on certain host objects.
+      return false;
+    }
+    // === on the constructor is not safe across iframes
+    // 'hasOwnProperty' ensures that the object also inherits
+    // from Object, which is false for DOMElements in IE.
+    return !!obj && klass === '[object Object]' && 'hasOwnProperty' in obj;
+  }
+
+  function iterateOverObject(obj, fn) {
+    var key;
+    for(key in obj) {
+      if(!hasOwnProperty(obj, key)) continue;
+      if(fn.call(obj, key, obj[key], obj) === false) break;
+    }
+  }
+
+  function simpleRepeat(n, fn) {
+    for(var i = 0; i < n; i++) {
+      fn(i);
+    }
+  }
+
+  function simpleMerge(target, source) {
+    iterateOverObject(source, function(key) {
+      target[key] = source[key];
+    });
+    return target;
+  }
+
+   // Make primtives types like strings into objects.
+   function coercePrimitiveToObject(obj) {
+     if(isPrimitiveType(obj)) {
+       obj = object(obj);
+     }
+     if(noKeysInStringObjects && isString(obj)) {
+       forceStringCoercion(obj);
+     }
+     return obj;
+   }
+
+   // Force strings to have their indexes set in
+   // environments that don't do this automatically.
+   function forceStringCoercion(obj) {
+     var i = 0, chr;
+     while(chr = obj.charAt(i)) {
+       obj[i++] = chr;
+     }
+   }
+
+  // Hash definition
+
+  function Hash(obj) {
+    simpleMerge(this, coercePrimitiveToObject(obj));
+  };
+
+  Hash.prototype.constructor = object;
+
+  // Math helpers
+
+  var abs   = math.abs;
+  var pow   = math.pow;
+  var ceil  = math.ceil;
+  var floor = math.floor;
+  var round = math.round;
+  var min   = math.min;
+  var max   = math.max;
+
+  function withPrecision(val, precision, fn) {
+    var multiplier = pow(10, abs(precision || 0));
+    fn = fn || round;
+    if(precision < 0) multiplier = 1 / multiplier;
+    return fn(val * multiplier) / multiplier;
+  }
+
+  // Full width number helpers
+
+  var HalfWidthZeroCode = 0x30;
+  var HalfWidthNineCode = 0x39;
+  var FullWidthZeroCode = 0xff10;
+  var FullWidthNineCode = 0xff19;
+
+  var HalfWidthPeriod = '.';
+  var FullWidthPeriod = '๏ผŽ';
+  var HalfWidthComma  = ',';
+
+  // Used here and later in the Date package.
+  var FullWidthDigits   = '';
+
+  var NumberNormalizeMap = {};
+  var NumberNormalizeReg;
+
+  function codeIsNumeral(code) {
+    return (code >= HalfWidthZeroCode && code <= HalfWidthNineCode) ||
+           (code >= FullWidthZeroCode && code <= FullWidthNineCode);
+  }
+
+  function buildNumberHelpers() {
+    var digit, i;
+    for(i = 0; i <= 9; i++) {
+      digit = chr(i + FullWidthZeroCode);
+      FullWidthDigits += digit;
+      NumberNormalizeMap[digit] = chr(i + HalfWidthZeroCode);
+    }
+    NumberNormalizeMap[HalfWidthComma] = '';
+    NumberNormalizeMap[FullWidthPeriod] = HalfWidthPeriod;
+    // Mapping this to itself to easily be able to easily
+    // capture it in stringToNumber to detect decimals later.
+    NumberNormalizeMap[HalfWidthPeriod] = HalfWidthPeriod;
+    NumberNormalizeReg = regexp('[' + FullWidthDigits + FullWidthPeriod + HalfWidthComma + HalfWidthPeriod + ']', 'g');
+  }
+
+  // String helpers
+
+  function chr(num) {
+    return string.fromCharCode(num);
+  }
+
+  // WhiteSpace/LineTerminator as defined in ES5.1 plus Unicode characters in the Space, Separator category.
+  function getTrimmableCharacters() {
+    return '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF';
+  }
+
+  function repeatString(str, num) {
+    var result = '', str = str.toString();
+    while (num > 0) {
+      if (num & 1) {
+        result += str;
+      }
+      if (num >>= 1) {
+        str += str;
+      }
+    }
+    return result;
+  }
+
+  // Returns taking into account full-width characters, commas, and decimals.
+  function stringToNumber(str, base) {
+    var sanitized, isDecimal;
+    sanitized = str.replace(NumberNormalizeReg, function(chr) {
+      var replacement = NumberNormalizeMap[chr];
+      if(replacement === HalfWidthPeriod) {
+        isDecimal = true;
+      }
+      return replacement;
+    });
+    return isDecimal ? parseFloat(sanitized) : parseInt(sanitized, base || 10);
+  }
+
+
+  // Used by Number and Date
+
+  function padNumber(num, place, sign, base) {
+    var str = abs(num).toString(base || 10);
+    str = repeatString('0', place - str.replace(/\.\d+/, '').length) + str;
+    if(sign || num < 0) {
+      str = (num < 0 ? '-' : '+') + str;
+    }
+    return str;
+  }
+
+  function getOrdinalizedSuffix(num) {
+    if(num >= 11 && num <= 13) {
+      return 'th';
+    } else {
+      switch(num % 10) {
+        case 1:  return 'st';
+        case 2:  return 'nd';
+        case 3:  return 'rd';
+        default: return 'th';
+      }
+    }
+  }
+
+
+  // RegExp helpers
+
+  function getRegExpFlags(reg, add) {
+    var flags = '';
+    add = add || '';
+    function checkFlag(prop, flag) {
+      if(prop || add.indexOf(flag) > -1) {
+        flags += flag;
+      }
+    }
+    checkFlag(reg.multiline, 'm');
+    checkFlag(reg.ignoreCase, 'i');
+    checkFlag(reg.global, 'g');
+    checkFlag(reg.sticky, 'y');
+    return flags;
+  }
+
+  function escapeRegExp(str) {
+    if(!isString(str)) str = string(str);
+    return str.replace(/([\\/\'*+?|()\[\]{}.^$])/g,'\\$1');
+  }
+
+
+  // Date helpers
+
+  function callDateGet(d, method) {
+    return d['get' + (d._utc ? 'UTC' : '') + method]();
+  }
+
+  function callDateSet(d, method, value) {
+    return d['set' + (d._utc && method != 'ISOWeek' ? 'UTC' : '') + method](value);
+  }
+
+  // Used by Array#unique and Object.equal
+
+  function stringify(thing, stack) {
+    var type = typeof thing,
+        thingIsObject,
+        thingIsArray,
+        klass, value,
+        arr, key, i, len;
+
+    // Return quickly if string to save cycles
+    if(type === 'string') return thing;
+
+    klass         = internalToString.call(thing)
+    thingIsObject = isPlainObject(thing, klass);
+    thingIsArray  = isArray(thing, klass);
+
+    if(thing != null && thingIsObject || thingIsArray) {
+      // This method for checking for cyclic structures was egregiously stolen from
+      // the ingenious method by @kitcambridge from the Underscore script:
+      // https://github.com/documentcloud/underscore/issues/240
+      if(!stack) stack = [];
+      // Allowing a step into the structure before triggering this
+      // script to save cycles on standard JSON structures and also to
+      // try as hard as possible to catch basic properties that may have
+      // been modified.
+      if(stack.length > 1) {
+        i = stack.length;
+        while (i--) {
+          if (stack[i] === thing) {
+            return 'CYC';
+          }
+        }
+      }
+      stack.push(thing);
+      value = thing.valueOf() + string(thing.constructor);
+      arr = thingIsArray ? thing : object.keys(thing).sort();
+      for(i = 0, len = arr.length; i < len; i++) {
+        key = thingIsArray ? i : arr[i];
+        value += key + stringify(thing[key], stack);
+      }
+      stack.pop();
+    } else if(1 / thing === -Infinity) {
+      value = '-0';
+    } else {
+      value = string(thing && thing.valueOf ? thing.valueOf() : thing);
+    }
+    return type + klass + value;
+  }
+
+  function isEqual(a, b) {
+    if(a === b) {
+      // Return quickly up front when matching by reference,
+      // but be careful about 0 !== -0.
+      return a !== 0 || 1 / a === 1 / b;
+    } else if(objectIsMatchedByValue(a) && objectIsMatchedByValue(b)) {
+      return stringify(a) === stringify(b);
+    }
+    return false;
+  }
+
+  function objectIsMatchedByValue(obj) {
+    // Only known objects are matched by value. This is notably excluding functions, DOM Elements, and instances of
+    // user-created classes. The latter can arguably be matched by value, but distinguishing between these and
+    // host objects -- which should never be compared by value -- is very tricky so not dealing with it here.
+    var klass = className(obj);
+    return matchedByValueReg.test(klass) || isPlainObject(obj, klass);
+  }
+
+
+  // Used by Array#at and String#at
+
+  function getEntriesForIndexes(obj, args, isString) {
+    var result,
+        length    = obj.length,
+        argsLen   = args.length,
+        overshoot = args[argsLen - 1] !== false,
+        multiple  = argsLen > (overshoot ? 1 : 2);
+    if(!multiple) {
+      return entryAtIndex(obj, length, args[0], overshoot, isString);
+    }
+    result = [];
+    multiArgs(args, function(index) {
+      if(isBoolean(index)) return false;
+      result.push(entryAtIndex(obj, length, index, overshoot, isString));
+    });
+    return result;
+  }
+
+  function entryAtIndex(obj, length, index, overshoot, isString) {
+    if(overshoot) {
+      index = index % length;
+      if(index < 0) index = length + index;
+    }
+    return isString ? obj.charAt(index) : obj[index];
+  }
+
+
+  // Object class methods implemented as instance methods
+
+  function buildObjectInstanceMethods(set, target) {
+    extendSimilar(target, true, false, set, function(methods, name) {
+      methods[name + (name === 'equal' ? 's' : '')] = function() {
+        return object[name].apply(null, [this].concat(multiArgs(arguments)));
+      }
+    });
+  }
+
+  initializeClasses();
+  buildNumberHelpers();
+
+
+  /***
+   * @package ES5
+   * @description Shim methods that provide ES5 compatible functionality. This package can be excluded if you do not require legacy browser support (IE8 and below).
+   *
+   ***/
+
+
+  /***
+   * Object module
+   *
+   ***/
+
+  extend(object, false, false, {
+
+    'keys': function(obj) {
+      var keys = [];
+      if(!isObjectType(obj) && !isRegExp(obj) && !isFunction(obj)) {
+        throw new TypeError('Object required');
+      }
+      iterateOverObject(obj, function(key, value) {
+        keys.push(key);
+      });
+      return keys;
+    }
+
+  });
+
+
+  /***
+   * Array module
+   *
+   ***/
+
+  // ECMA5 methods
+
+  function arrayIndexOf(arr, search, fromIndex, increment) {
+    var length = arr.length,
+        fromRight = increment == -1,
+        start = fromRight ? length - 1 : 0,
+        index = toIntegerWithDefault(fromIndex, start);
+    if(index < 0) {
+      index = length + index;
+    }
+    if((!fromRight && index < 0) || (fromRight && index >= length)) {
+      index = start;
+    }
+    while((fromRight && index >= 0) || (!fromRight && index < length)) {
+      if(arr[index] === search) {
+        return index;
+      }
+      index += increment;
+    }
+    return -1;
+  }
+
+  function arrayReduce(arr, fn, initialValue, fromRight) {
+    var length = arr.length, count = 0, defined = isDefined(initialValue), result, index;
+    checkCallback(fn);
+    if(length == 0 && !defined) {
+      throw new TypeError('Reduce called on empty array with no initial value');
+    } else if(defined) {
+      result = initialValue;
+    } else {
+      result = arr[fromRight ? length - 1 : count];
+      count++;
+    }
+    while(count < length) {
+      index = fromRight ? length - count - 1 : count;
+      if(index in arr) {
+        result = fn(result, arr[index], index, arr);
+      }
+      count++;
+    }
+    return result;
+  }
+
+  function toIntegerWithDefault(i, d) {
+    if(isNaN(i)) {
+      return d;
+    } else {
+      return parseInt(i >> 0);
+    }
+  }
+
+  function checkFirstArgumentExists(args) {
+    if(args.length === 0) {
+      throw new TypeError('First argument must be defined');
+    }
+  }
+
+
+
+
+  extend(array, false, false, {
+
+    /***
+     *
+     * @method Array.isArray(<obj>)
+     * @returns Boolean
+     * @short Returns true if <obj> is an Array.
+     * @extra This method is provided for browsers that don't support it internally.
+     * @example
+     *
+     *   Array.isArray(3)        -> false
+     *   Array.isArray(true)     -> false
+     *   Array.isArray('wasabi') -> false
+     *   Array.isArray([1,2,3])  -> true
+     *
+     ***/
+    'isArray': function(obj) {
+      return isArray(obj);
+    }
+
+  });
+
+
+  extend(array, true, false, {
+
+    /***
+     * @method every(<f>, [scope])
+     * @returns Boolean
+     * @short Returns true if all elements in the array match <f>.
+     * @extra [scope] is the %this% object. %all% is provided an alias. In addition to providing this method for browsers that don't support it natively, this method also implements @array_matching.
+     * @example
+     *
+     +   ['a','a','a'].every(function(n) {
+     *     return n == 'a';
+     *   });
+     *   ['a','a','a'].every('a')   -> true
+     *   [{a:2},{a:2}].every({a:2}) -> true
+     ***/
+    'every': function(fn, scope) {
+      var length = this.length, index = 0;
+      checkFirstArgumentExists(arguments);
+      while(index < length) {
+        if(index in this && !fn.call(scope, this[index], index, this)) {
+          return false;
+        }
+        index++;
+      }
+      return true;
+    },
+
+    /***
+     * @method some(<f>, [scope])
+     * @returns Boolean
+     * @short Returns true if any element in the array matches <f>.
+     * @extra [scope] is the %this% object. %any% is provided as an alias. In addition to providing this method for browsers that don't support it natively, this method also implements @array_matching.
+     * @example
+     *
+     +   ['a','b','c'].some(function(n) {
+     *     return n == 'a';
+     *   });
+     +   ['a','b','c'].some(function(n) {
+     *     return n == 'd';
+     *   });
+     *   ['a','b','c'].some('a')   -> true
+     *   [{a:2},{b:5}].some({a:2}) -> true
+     ***/
+    'some': function(fn, scope) {
+      var length = this.length, index = 0;
+      checkFirstArgumentExists(arguments);
+      while(index < length) {
+        if(index in this && fn.call(scope, this[index], index, this)) {
+          return true;
+        }
+        index++;
+      }
+      return false;
+    },
+
+    /***
+     * @method map(<map>, [scope])
+     * @returns Array
+     * @short Maps the array to another array containing the values that are the result of calling <map> on each element.
+     * @extra [scope] is the %this% object. When <map> is a function, it receives three arguments: the current element, the current index, and a reference to the array. In addition to providing this method for browsers that don't support it natively, this enhanced method also directly accepts a string, which is a shortcut for a function that gets that property (or invokes a function) on each element.
+     * @example
+     *
+     *   [1,2,3].map(function(n) {
+     *     return n * 3;
+     *   });                                  -> [3,6,9]
+     *   ['one','two','three'].map(function(n) {
+     *     return n.length;
+     *   });                                  -> [3,3,5]
+     *   ['one','two','three'].map('length')  -> [3,3,5]
+     *
+     ***/
+    'map': function(fn, scope) {
+      var scope = arguments[1], length = this.length, index = 0, result = new Array(length);
+      checkFirstArgumentExists(arguments);
+      while(index < length) {
+        if(index in this) {
+          result[index] = fn.call(scope, this[index], index, this);
+        }
+        index++;
+      }
+      return result;
+    },
+
+    /***
+     * @method filter(<f>, [scope])
+     * @returns Array
+     * @short Returns any elements in the array that match <f>.
+     * @extra [scope] is the %this% object. In addition to providing this method for browsers that don't support it natively, this method also implements @array_matching.
+     * @example
+     *
+     +   [1,2,3].filter(function(n) {
+     *     return n > 1;
+     *   });
+     *   [1,2,2,4].filter(2) -> 2
+     *
+     ***/
+    'filter': function(fn) {
+      var scope = arguments[1];
+      var length = this.length, index = 0, result = [];
+      checkFirstArgumentExists(arguments);
+      while(index < length) {
+        if(index in this && fn.call(scope, this[index], index, this)) {
+          result.push(this[index]);
+        }
+        index++;
+      }
+      return result;
+    },
+
+    /***
+     * @method indexOf(<search>, [fromIndex])
+     * @returns Number
+     * @short Searches the array and returns the first index where <search> occurs, or -1 if the element is not found.
+     * @extra [fromIndex] is the index from which to begin the search. This method performs a simple strict equality comparison on <search>. It does not support enhanced functionality such as searching the contents against a regex, callback, or deep comparison of objects. For such functionality, use the %findIndex% method instead.
+     * @example
+     *
+     *   [1,2,3].indexOf(3)           -> 1
+     *   [1,2,3].indexOf(7)           -> -1
+     *
+     ***/
+    'indexOf': function(search) {
+      var fromIndex = arguments[1];
+      if(isString(this)) return this.indexOf(search, fromIndex);
+      return arrayIndexOf(this, search, fromIndex, 1);
+    },
+
+    /***
+     * @method lastIndexOf(<search>, [fromIndex])
+     * @returns Number
+     * @short Searches the array and returns the last index where <search> occurs, or -1 if the element is not found.
+     * @extra [fromIndex] is the index from which to begin the search. This method performs a simple strict equality comparison on <search>.
+     * @example
+     *
+     *   [1,2,1].lastIndexOf(1)                 -> 2
+     *   [1,2,1].lastIndexOf(7)                 -> -1
+     *
+     ***/
+    'lastIndexOf': function(search) {
+      var fromIndex = arguments[1];
+      if(isString(this)) return this.lastIndexOf(search, fromIndex);
+      return arrayIndexOf(this, search, fromIndex, -1);
+    },
+
+    /***
+     * @method forEach([fn], [scope])
+     * @returns Nothing
+     * @short Iterates over the array, calling [fn] on each loop.
+     * @extra This method is only provided for those browsers that do not support it natively. [scope] becomes the %this% object.
+     * @example
+     *
+     *   ['a','b','c'].forEach(function(a) {
+     *     // Called 3 times: 'a','b','c'
+     *   });
+     *
+     ***/
+    'forEach': function(fn) {
+      var length = this.length, index = 0, scope = arguments[1];
+      checkCallback(fn);
+      while(index < length) {
+        if(index in this) {
+          fn.call(scope, this[index], index, this);
+        }
+        index++;
+      }
+    },
+
+    /***
+     * @method reduce(<fn>, [init])
+     * @returns Mixed
+     * @short Reduces the array to a single result.
+     * @extra If [init] is passed as a starting value, that value will be passed as the first argument to the callback. The second argument will be the first element in the array. From that point, the result of the callback will then be used as the first argument of the next iteration. This is often refered to as "accumulation", and [init] is often called an "accumulator". If [init] is not passed, then <fn> will be called n - 1 times, where n is the length of the array. In this case, on the first iteration only, the first argument will be the first element of the array, and the second argument will be the second. After that callbacks work as normal, using the result of the previous callback as the first argument of the next. This method is only provided for those browsers that do not support it natively.
+     *
+     * @example
+     *
+     +   [1,2,3,4].reduce(function(a, b) {
+     *     return a - b;
+     *   });
+     +   [1,2,3,4].reduce(function(a, b) {
+     *     return a - b;
+     *   }, 100);
+     *
+     ***/
+    'reduce': function(fn) {
+      return arrayReduce(this, fn, arguments[1]);
+    },
+
+    /***
+     * @method reduceRight([fn], [init])
+     * @returns Mixed
+     * @short Identical to %Array#reduce%, but operates on the elements in reverse order.
+     * @extra This method is only provided for those browsers that do not support it natively.
+     *
+     *
+     *
+     *
+     * @example
+     *
+     +   [1,2,3,4].reduceRight(function(a, b) {
+     *     return a - b;
+     *   });
+     *
+     ***/
+    'reduceRight': function(fn) {
+      return arrayReduce(this, fn, arguments[1], true);
+    }
+
+
+  });
+
+
+
+
+  /***
+   * String module
+   *
+   ***/
+
+
+  function buildTrim() {
+    var support = getTrimmableCharacters().match(/^\s+$/);
+    try { string.prototype.trim.call([1]); } catch(e) { support = false; }
+    extend(string, true, !support, {
+
+      /***
+       * @method trim[Side]()
+       * @returns String
+       * @short Removes leading and/or trailing whitespace from the string.
+       * @extra Whitespace is defined as line breaks, tabs, and any character in the "Space, Separator" Unicode category, conforming to the the ES5 spec. The standard %trim% method is only added when not fully supported natively.
+       *
+       * @set
+       *   trim
+       *   trimLeft
+       *   trimRight
+       *
+       * @example
+       *
+       *   '   wasabi   '.trim()      -> 'wasabi'
+       *   '   wasabi   '.trimLeft()  -> 'wasabi   '
+       *   '   wasabi   '.trimRight() -> '   wasabi'
+       *
+       ***/
+      'trim': function() {
+        return this.toString().trimLeft().trimRight();
+      },
+
+      'trimLeft': function() {
+        return this.replace(regexp('^['+getTrimmableCharacters()+']+'), '');
+      },
+
+      'trimRight': function() {
+        return this.replace(regexp('['+getTrimmableCharacters()+']+$'), '');
+      }
+    });
+  }
+
+
+
+  /***
+   * Function module
+   *
+   ***/
+
+
+  extend(Function, true, false, {
+
+     /***
+     * @method bind(<scope>, [arg1], ...)
+     * @returns Function
+     * @short Binds <scope> as the %this% object for the function when it is called. Also allows currying an unlimited number of parameters.
+     * @extra "currying" means setting parameters ([arg1], [arg2], etc.) ahead of time so that they are passed when the function is called later. If you pass additional parameters when the function is actually called, they will be added will be added to the end of the curried parameters. This method is provided for browsers that don't support it internally.
+     * @example
+     *
+     +   (function() {
+     *     return this;
+     *   }).bind('woof')(); -> returns 'woof'; function is bound with 'woof' as the this object.
+     *   (function(a) {
+     *     return a;
+     *   }).bind(1, 2)();   -> returns 2; function is bound with 1 as the this object and 2 curried as the first parameter
+     *   (function(a, b) {
+     *     return a + b;
+     *   }).bind(1, 2)(3);  -> returns 5; function is bound with 1 as the this object, 2 curied as the first parameter and 3 passed as the second when calling the function
+     *
+     ***/
+    'bind': function(scope) {
+      var fn = this, args = multiArgs(arguments, null, 1), bound;
+      if(!isFunction(this)) {
+        throw new TypeError('Function.prototype.bind called on a non-function');
+      }
+      bound = function() {
+        return fn.apply(fn.prototype && this instanceof fn ? this : scope, args.concat(multiArgs(arguments)));
+      }
+      bound.prototype = this.prototype;
+      return bound;
+    }
+
+  });
+
+  /***
+   * Date module
+   *
+   ***/
+
+   /***
+   * @method toISOString()
+   * @returns String
+   * @short Formats the string to ISO8601 format.
+   * @extra This will always format as UTC time. Provided for browsers that do not support this method.
+   * @example
+   *
+   *   Date.create().toISOString() -> ex. 2011-07-05 12:24:55.528Z
+   *
+   ***
+   * @method toJSON()
+   * @returns String
+   * @short Returns a JSON representation of the date.
+   * @extra This is effectively an alias for %toISOString%. Will always return the date in UTC time. Provided for browsers that do not support this method.
+   * @example
+   *
+   *   Date.create().toJSON() -> ex. 2011-07-05 12:24:55.528Z
+   *
+   ***/
+
+  extend(date, false, false, {
+
+     /***
+     * @method Date.now()
+     * @returns String
+     * @short Returns the number of milliseconds since January 1st, 1970 00:00:00 (UTC time).
+     * @extra Provided for browsers that do not support this method.
+     * @example
+     *
+     *   Date.now() -> ex. 1311938296231
+     *
+     ***/
+    'now': function() {
+      return new date().getTime();
+    }
+
+  });
+
+   function buildISOString() {
+    var d = new date(date.UTC(1999, 11, 31)), target = '1999-12-31T00:00:00.000Z';
+    var support = d.toISOString && d.toISOString() === target;
+    extendSimilar(date, true, !support, 'toISOString,toJSON', function(methods, name) {
+      methods[name] = function() {
+        return padNumber(this.getUTCFullYear(), 4) + '-' +
+               padNumber(this.getUTCMonth() + 1, 2) + '-' +
+               padNumber(this.getUTCDate(), 2) + 'T' +
+               padNumber(this.getUTCHours(), 2) + ':' +
+               padNumber(this.getUTCMinutes(), 2) + ':' +
+               padNumber(this.getUTCSeconds(), 2) + '.' +
+               padNumber(this.getUTCMilliseconds(), 3) + 'Z';
+      }
+    });
+   }
+
+  // Initialize
+  buildTrim();
+  buildISOString();
+
+
+  /***
+   * @package Array
+   * @dependency core
+   * @description Array manipulation and traversal, "fuzzy matching" against elements, alphanumeric sorting and collation, enumerable methods on Object.
+   *
+   ***/
+
+
+  function regexMatcher(reg) {
+    reg = regexp(reg);
+    return function (el) {
+      return reg.test(el);
+    }
+  }
+
+  function dateMatcher(d) {
+    var ms = d.getTime();
+    return function (el) {
+      return !!(el && el.getTime) && el.getTime() === ms;
+    }
+  }
+
+  function functionMatcher(fn) {
+    return function (el, i, arr) {
+      // Return true up front if match by reference
+      return el === fn || fn.call(this, el, i, arr);
+    }
+  }
+
+  function invertedArgsFunctionMatcher(fn) {
+    return function (value, key, obj) {
+      // Return true up front if match by reference
+      return value === fn || fn.call(obj, key, value, obj);
+    }
+  }
+
+  function fuzzyMatcher(obj, isObject) {
+    var matchers = {};
+    return function (el, i, arr) {
+      var key;
+      if(!isObjectType(el)) {
+        return false;
+      }
+      for(key in obj) {
+        matchers[key] = matchers[key] || getMatcher(obj[key], isObject);
+        if(matchers[key].call(arr, el[key], i, arr) === false) {
+          return false;
+        }
+      }
+      return true;
+    }
+  }
+
+  function defaultMatcher(f) {
+    return function (el) {
+      return el === f || isEqual(el, f);
+    }
+  }
+
+  function getMatcher(f, isObject) {
+    if(isPrimitiveType(f)) {
+      // Do nothing and fall through to the
+      // default matcher below.
+    } else if(isRegExp(f)) {
+      // Match against a regexp
+      return regexMatcher(f);
+    } else if(isDate(f)) {
+      // Match against a date. isEqual below should also
+      // catch this but matching directly up front for speed.
+      return dateMatcher(f);
+    } else if(isFunction(f)) {
+      // Match against a filtering function
+      if(isObject) {
+        return invertedArgsFunctionMatcher(f);
+      } else {
+        return functionMatcher(f);
+      }
+    } else if(isPlainObject(f)) {
+      // Match against a fuzzy hash or array.
+      return fuzzyMatcher(f, isObject);
+    }
+    // Default is standard isEqual
+    return defaultMatcher(f);
+  }
+
+  function transformArgument(el, map, context, mapArgs) {
+    if(!map) {
+      return el;
+    } else if(map.apply) {
+      return map.apply(context, mapArgs || []);
+    } else if(isFunction(el[map])) {
+      return el[map].call(el);
+    } else {
+      return el[map];
+    }
+  }
+
+  // Basic array internal methods
+
+  function arrayEach(arr, fn, startIndex, loop) {
+    var index, i, length = +arr.length;
+    if(startIndex < 0) startIndex = arr.length + startIndex;
+    i = isNaN(startIndex) ? 0 : startIndex;
+    if(loop === true) {
+      length += i;
+    }
+    while(i < length) {
+      index = i % arr.length;
+      if(!(index in arr)) {
+        return iterateOverSparseArray(arr, fn, i, loop);
+      } else if(fn.call(arr, arr[index], index, arr) === false) {
+        break;
+      }
+      i++;
+    }
+  }
+
+  function iterateOverSparseArray(arr, fn, fromIndex, loop) {
+    var indexes = [], i;
+    for(i in arr) {
+      if(isArrayIndex(arr, i) && i >= fromIndex) {
+        indexes.push(parseInt(i));
+      }
+    }
+    indexes.sort().each(function(index) {
+      return fn.call(arr, arr[index], index, arr);
+    });
+    return arr;
+  }
+
+  function isArrayIndex(arr, i) {
+    return i in arr && toUInt32(i) == i && i != 0xffffffff;
+  }
+
+  function toUInt32(i) {
+    return i >>> 0;
+  }
+
+  function arrayFind(arr, f, startIndex, loop, returnIndex, context) {
+    var result, index, matcher;
+    if(arr.length > 0) {
+      matcher = getMatcher(f);
+      arrayEach(arr, function(el, i) {
+        if(matcher.call(context, el, i, arr)) {
+          result = el;
+          index = i;
+          return false;
+        }
+      }, startIndex, loop);
+    }
+    return returnIndex ? index : result;
+  }
+
+  function arrayUnique(arr, map) {
+    var result = [], o = {}, transformed;
+    arrayEach(arr, function(el, i) {
+      transformed = map ? transformArgument(el, map, arr, [el, i, arr]) : el;
+      if(!checkForElementInHashAndSet(o, transformed)) {
+        result.push(el);
+      }
+    })
+    return result;
+  }
+
+  function arrayIntersect(arr1, arr2, subtract) {
+    var result = [], o = {};
+    arr2.each(function(el) {
+      checkForElementInHashAndSet(o, el);
+    });
+    arr1.each(function(el) {
+      var stringified = stringify(el),
+          isReference = !objectIsMatchedByValue(el);
+      // Add the result to the array if:
+      // 1. We're subtracting intersections or it doesn't already exist in the result and
+      // 2. It exists in the compared array and we're adding, or it doesn't exist and we're removing.
+      if(elementExistsInHash(o, stringified, el, isReference) !== subtract) {
+        discardElementFromHash(o, stringified, el, isReference);
+        result.push(el);
+      }
+    });
+    return result;
+  }
+
+  function arrayFlatten(arr, level, current) {
+    level = level || Infinity;
+    current = current || 0;
+    var result = [];
+    arrayEach(arr, function(el) {
+      if(isArray(el) && current < level) {
+        result = result.concat(arrayFlatten(el, level, current + 1));
+      } else {
+        result.push(el);
+      }
+    });
+    return result;
+  }
+
+  function isArrayLike(obj) {
+    return hasProperty(obj, 'length') && !isString(obj) && !isPlainObject(obj);
+  }
+
+  function isArgumentsObject(obj) {
+    // .callee exists on Arguments objects in < IE8
+    return hasProperty(obj, 'length') && (className(obj) === '[object Arguments]' || !!obj.callee);
+  }
+
+  function flatArguments(args) {
+    var result = [];
+    multiArgs(args, function(arg) {
+      result = result.concat(arg);
+    });
+    return result;
+  }
+
+  function elementExistsInHash(hash, key, element, isReference) {
+    var exists = key in hash;
+    if(isReference) {
+      if(!hash[key]) {
+        hash[key] = [];
+      }
+      exists = hash[key].indexOf(element) !== -1;
+    }
+    return exists;
+  }
+
+  function checkForElementInHashAndSet(hash, element) {
+    var stringified = stringify(element),
+        isReference = !objectIsMatchedByValue(element),
+        exists      = elementExistsInHash(hash, stringified, element, isReference);
+    if(isReference) {
+      hash[stringified].push(element);
+    } else {
+      hash[stringified] = element;
+    }
+    return exists;
+  }
+
+  function discardElementFromHash(hash, key, element, isReference) {
+    var arr, i = 0;
+    if(isReference) {
+      arr = hash[key];
+      while(i < arr.length) {
+        if(arr[i] === element) {
+          arr.splice(i, 1);
+        } else {
+          i += 1;
+        }
+      }
+    } else {
+      delete hash[key];
+    }
+  }
+
+  // Support methods
+
+  function getMinOrMax(obj, map, which, all) {
+    var el,
+        key,
+        edge,
+        test,
+        result = [],
+        max = which === 'max',
+        min = which === 'min',
+        isArray = array.isArray(obj);
+    for(key in obj) {
+      if(!obj.hasOwnProperty(key)) continue;
+      el   = obj[key];
+      test = transformArgument(el, map, obj, isArray ? [el, parseInt(key), obj] : []);
+      if(isUndefined(test)) {
+        throw new TypeError('Cannot compare with undefined');
+      }
+      if(test === edge) {
+        result.push(el);
+      } else if(isUndefined(edge) || (max && test > edge) || (min && test < edge)) {
+        result = [el];
+        edge = test;
+      }
+    }
+    if(!isArray) result = arrayFlatten(result, 1);
+    return all ? result : result[0];
+  }
+
+
+  // Alphanumeric collation helpers
+
+  function collateStrings(a, b) {
+    var aValue, bValue, aChar, bChar, aEquiv, bEquiv, index = 0, tiebreaker = 0;
+
+    var sortIgnore      = array[AlphanumericSortIgnore];
+    var sortIgnoreCase  = array[AlphanumericSortIgnoreCase];
+    var sortEquivalents = array[AlphanumericSortEquivalents];
+    var sortOrder       = array[AlphanumericSortOrder];
+    var naturalSort     = array[AlphanumericSortNatural];
+
+    a = getCollationReadyString(a, sortIgnore, sortIgnoreCase);
+    b = getCollationReadyString(b, sortIgnore, sortIgnoreCase);
+
+    do {
+
+      aChar  = getCollationCharacter(a, index, sortEquivalents);
+      bChar  = getCollationCharacter(b, index, sortEquivalents);
+      aValue = getSortOrderIndex(aChar, sortOrder);
+      bValue = getSortOrderIndex(bChar, sortOrder);
+
+      if(aValue === -1 || bValue === -1) {
+        aValue = a.charCodeAt(index) || null;
+        bValue = b.charCodeAt(index) || null;
+        if(naturalSort && codeIsNumeral(aValue) && codeIsNumeral(bValue)) {
+          aValue = stringToNumber(a.slice(index));
+          bValue = stringToNumber(b.slice(index));
+        }
+      } else {
+        aEquiv = aChar !== a.charAt(index);
+        bEquiv = bChar !== b.charAt(index);
+        if(aEquiv !== bEquiv && tiebreaker === 0) {
+          tiebreaker = aEquiv - bEquiv;
+        }
+      }
+      index += 1;
+    } while(aValue != null && bValue != null && aValue === bValue);
+    if(aValue === bValue) return tiebreaker;
+    return aValue - bValue;
+  }
+
+  function getCollationReadyString(str, sortIgnore, sortIgnoreCase) {
+    if(!isString(str)) str = string(str);
+    if(sortIgnoreCase) {
+      str = str.toLowerCase();
+    }
+    if(sortIgnore) {
+      str = str.replace(sortIgnore, '');
+    }
+    return str;
+  }
+
+  function getCollationCharacter(str, index, sortEquivalents) {
+    var chr = str.charAt(index);
+    return sortEquivalents[chr] || chr;
+  }
+
+  function getSortOrderIndex(chr, sortOrder) {
+    if(!chr) {
+      return null;
+    } else {
+      return sortOrder.indexOf(chr);
+    }
+  }
+
+  var AlphanumericSort            = 'AlphanumericSort';
+  var AlphanumericSortOrder       = 'AlphanumericSortOrder';
+  var AlphanumericSortIgnore      = 'AlphanumericSortIgnore';
+  var AlphanumericSortIgnoreCase  = 'AlphanumericSortIgnoreCase';
+  var AlphanumericSortEquivalents = 'AlphanumericSortEquivalents';
+  var AlphanumericSortNatural     = 'AlphanumericSortNatural';
+
+
+
+  function buildEnhancements() {
+    var nativeMap = array.prototype.map;
+    var callbackCheck = function() {
+      var args = arguments;
+      return args.length > 0 && !isFunction(args[0]);
+    };
+    extendSimilar(array, true, callbackCheck, 'every,all,some,filter,any,none,find,findIndex', function(methods, name) {
+      var nativeFn = array.prototype[name]
+      methods[name] = function(f) {
+        var matcher = getMatcher(f);
+        return nativeFn.call(this, function(el, index) {
+          return matcher(el, index, this);
+        });
+      }
+    });
+    extend(array, true, callbackCheck, {
+      'map': function(f) {
+        return nativeMap.call(this, function(el, index) {
+          return transformArgument(el, f, this, [el, index, this]);
+        });
+      }
+    });
+  }
+
+  function buildAlphanumericSort() {
+    var order = 'Aรร€ร‚รƒฤ„BCฤ†ฤŒร‡DฤŽรEร‰รˆฤšรŠร‹ฤ˜FGฤžHฤฑIรรŒฤฐรŽรJKLลMNลƒล‡ร‘Oร“ร’ร”PQRล˜Sลšล ลžTลคUรšร™ลฎร›รœVWXYรZลนลปลฝรžร†ล’ร˜ร•ร…ร„ร–';
+    var equiv = 'Aรร€ร‚รƒร„,Cร‡,Eร‰รˆรŠร‹,IรรŒฤฐรŽร,Oร“ร’ร”ร•ร–,SรŸ,Uรšร™ร›รœ';
+    array[AlphanumericSortOrder] = order.split('').map(function(str) {
+      return str + str.toLowerCase();
+    }).join('');
+    var equivalents = {};
+    arrayEach(equiv.split(','), function(set) {
+      var equivalent = set.charAt(0);
+      arrayEach(set.slice(1).split(''), function(chr) {
+        equivalents[chr] = equivalent;
+        equivalents[chr.toLowerCase()] = equivalent.toLowerCase();
+      });
+    });
+    array[AlphanumericSortNatural] = true;
+    array[AlphanumericSortIgnoreCase] = true;
+    array[AlphanumericSortEquivalents] = equivalents;
+  }
+
+  extend(array, false, true, {
+
+    /***
+     *
+     * @method Array.create(<obj1>, <obj2>, ...)
+     * @returns Array
+     * @short Alternate array constructor.
+     * @extra This method will create a single array by calling %concat% on all arguments passed. In addition to ensuring that an unknown variable is in a single, flat array (the standard constructor will create nested arrays, this one will not), it is also a useful shorthand to convert a function's arguments object into a standard array.
+     * @example
+     *
+     *   Array.create('one', true, 3)   -> ['one', true, 3]
+     *   Array.create(['one', true, 3]) -> ['one', true, 3]
+     +   Array.create(function(n) {
+     *     return arguments;
+     *   }('howdy', 'doody'));
+     *
+     ***/
+    'create': function() {
+      var result = [];
+      multiArgs(arguments, function(a) {
+        if(isArgumentsObject(a) || isArrayLike(a)) {
+          a = array.prototype.slice.call(a, 0);
+        }
+        result = result.concat(a);
+      });
+      return result;
+    }
+
+  });
+
+  extend(array, true, false, {
+
+    /***
+     * @method find(<f>, [context] = undefined)
+     * @returns Mixed
+     * @short Returns the first element that matches <f>.
+     * @extra [context] is the %this% object if passed. When <f> is a function, will use native implementation if it exists. <f> will also match a string, number, array, object, or alternately test against a function or regex. This method implements @array_matching.
+     * @example
+     *
+     +   [{a:1,b:2},{a:1,b:3},{a:1,b:4}].find(function(n) {
+     *     return n['a'] == 1;
+     *   });                                  -> {a:1,b:3}
+     *   ['cuba','japan','canada'].find(/^c/) -> 'cuba'
+     *
+     ***/
+    'find': function(f, context) {
+      checkCallback(f);
+      return arrayFind(this, f, 0, false, false, context);
+    },
+
+    /***
+     * @method findIndex(<f>, [context] = undefined)
+     * @returns Number
+     * @short Returns the index of the first element that matches <f> or -1 if not found.
+     * @extra [context] is the %this% object if passed. When <f> is a function, will use native implementation if it exists. <f> will also match a string, number, array, object, or alternately test against a function or regex. This method implements @array_matching.
+     *
+     * @example
+     *
+     +   [1,2,3,4].findIndex(function(n) {
+     *     return n % 2 == 0;
+     *   }); -> 1
+     +   [1,2,3,4].findIndex(3);               -> 2
+     +   ['one','two','three'].findIndex(/t/); -> 1
+     *
+     ***/
+    'findIndex': function(f, context) {
+      var index;
+      checkCallback(f);
+      index = arrayFind(this, f, 0, false, true, context);
+      return isUndefined(index) ? -1 : index;
+    }
+
+  });
+
+  extend(array, true, true, {
+
+    /***
+     * @method findFrom(<f>, [index] = 0, [loop] = false)
+     * @returns Array
+     * @short Returns any element that matches <f>, beginning from [index].
+     * @extra <f> will match a string, number, array, object, or alternately test against a function or regex. Will continue from index = 0 if [loop] is true. This method implements @array_matching.
+     * @example
+     *
+     *   ['cuba','japan','canada'].findFrom(/^c/, 2) -> 'canada'
+     *
+     ***/
+    'findFrom': function(f, index, loop) {
+      return arrayFind(this, f, index, loop);
+    },
+
+    /***
+     * @method findIndexFrom(<f>, [index] = 0, [loop] = false)
+     * @returns Array
+     * @short Returns the index of any element that matches <f>, beginning from [index].
+     * @extra <f> will match a string, number, array, object, or alternately test against a function or regex. Will continue from index = 0 if [loop] is true. This method implements @array_matching.
+     * @example
+     *
+     *   ['cuba','japan','canada'].findIndexFrom(/^c/, 2) -> 2
+     *
+     ***/
+    'findIndexFrom': function(f, index, loop) {
+      var index = arrayFind(this, f, index, loop, true);
+      return isUndefined(index) ? -1 : index;
+    },
+
+    /***
+     * @method findAll(<f>, [index] = 0, [loop] = false)
+     * @returns Array
+     * @short Returns all elements that match <f>.
+     * @extra <f> will match a string, number, array, object, or alternately test against a function or regex. Starts at [index], and will continue once from index = 0 if [loop] is true. This method implements @array_matching.
+     * @example
+     *
+     +   [{a:1,b:2},{a:1,b:3},{a:2,b:4}].findAll(function(n) {
+     *     return n['a'] == 1;
+     *   });                                        -> [{a:1,b:3},{a:1,b:4}]
+     *   ['cuba','japan','canada'].findAll(/^c/)    -> 'cuba','canada'
+     *   ['cuba','japan','canada'].findAll(/^c/, 2) -> 'canada'
+     *
+     ***/
+    'findAll': function(f, index, loop) {
+      var result = [], matcher;
+      if(this.length > 0) {
+        matcher = getMatcher(f);
+        arrayEach(this, function(el, i, arr) {
+          if(matcher(el, i, arr)) {
+            result.push(el);
+          }
+        }, index, loop);
+      }
+      return result;
+    },
+
+    /***
+     * @method count(<f>)
+     * @returns Number
+     * @short Counts all elements in the array that match <f>.
+     * @extra <f> will match a string, number, array, object, or alternately test against a function or regex. This method implements @array_matching.
+     * @example
+     *
+     *   [1,2,3,1].count(1)       -> 2
+     *   ['a','b','c'].count(/b/) -> 1
+     +   [{a:1},{b:2}].count(function(n) {
+     *     return n['a'] > 1;
+     *   });                      -> 0
+     *
+     ***/
+    'count': function(f) {
+      if(isUndefined(f)) return this.length;
+      return this.findAll(f).length;
+    },
+
+    /***
+     * @method removeAt(<start>, [end])
+     * @returns Array
+     * @short Removes element at <start>. If [end] is specified, removes the range between <start> and [end]. This method will change the array! If you don't intend the array to be changed use %clone% first.
+     * @example
+     *
+     *   ['a','b','c'].removeAt(0) -> ['b','c']
+     *   [1,2,3,4].removeAt(1, 3)  -> [1]
+     *
+     ***/
+    'removeAt': function(start, end) {
+      if(isUndefined(start)) return this;
+      if(isUndefined(end))   end = start;
+      this.splice(start, end - start + 1);
+      return this;
+    },
+
+    /***
+     * @method include(<el>, [index])
+     * @returns Array
+     * @short Adds <el> to the array.
+     * @extra This is a non-destructive alias for %add%. It will not change the original array.
+     * @example
+     *
+     *   [1,2,3,4].include(5)       -> [1,2,3,4,5]
+     *   [1,2,3,4].include(8, 1)    -> [1,8,2,3,4]
+     *   [1,2,3,4].include([5,6,7]) -> [1,2,3,4,5,6,7]
+     *
+     ***/
+    'include': function(el, index) {
+      return this.clone().add(el, index);
+    },
+
+    /***
+     * @method exclude([f1], [f2], ...)
+     * @returns Array
+     * @short Removes any element in the array that matches [f1], [f2], etc.
+     * @extra This is a non-destructive alias for %remove%. It will not change the original array. This method implements @array_matching.
+     * @example
+     *
+     *   [1,2,3].exclude(3)         -> [1,2]
+     *   ['a','b','c'].exclude(/b/) -> ['a','c']
+     +   [{a:1},{b:2}].exclude(function(n) {
+     *     return n['a'] == 1;
+     *   });                       -> [{b:2}]
+     *
+     ***/
+    'exclude': function() {
+      return array.prototype.remove.apply(this.clone(), arguments);
+    },
+
+    /***
+     * @method clone()
+     * @returns Array
+     * @short Makes a shallow clone of the array.
+     * @example
+     *
+     *   [1,2,3].clone() -> [1,2,3]
+     *
+     ***/
+    'clone': function() {
+      return simpleMerge([], this);
+    },
+
+    /***
+     * @method unique([map] = null)
+     * @returns Array
+     * @short Removes all duplicate elements in the array.
+     * @extra [map] may be a function mapping the value to be uniqued on or a string acting as a shortcut. This is most commonly used when you have a key that ensures the object's uniqueness, and don't need to check all fields. This method will also correctly operate on arrays of objects.
+     * @example
+     *
+     *   [1,2,2,3].unique()                 -> [1,2,3]
+     *   [{foo:'bar'},{foo:'bar'}].unique() -> [{foo:'bar'}]
+     +   [{foo:'bar'},{foo:'bar'}].unique(function(obj){
+     *     return obj.foo;
+     *   }); -> [{foo:'bar'}]
+     *   [{foo:'bar'},{foo:'bar'}].unique('foo') -> [{foo:'bar'}]
+     *
+     ***/
+    'unique': function(map) {
+      return arrayUnique(this, map);
+    },
+
+    /***
+     * @method flatten([limit] = Infinity)
+     * @returns Array
+     * @short Returns a flattened, one-dimensional copy of the array.
+     * @extra You can optionally specify a [limit], which will only flatten that depth.
+     * @example
+     *
+     *   [[1], 2, [3]].flatten()      -> [1,2,3]
+     *   [['a'],[],'b','c'].flatten() -> ['a','b','c']
+     *
+     ***/
+    'flatten': function(limit) {
+      return arrayFlatten(this, limit);
+    },
+
+    /***
+     * @method union([a1], [a2], ...)
+     * @returns Array
+     * @short Returns an array containing all elements in all arrays with duplicates removed.
+     * @extra This method will also correctly operate on arrays of objects.
+     * @example
+     *
+     *   [1,3,5].union([5,7,9])     -> [1,3,5,7,9]
+     *   ['a','b'].union(['b','c']) -> ['a','b','c']
+     *
+     ***/
+    'union': function() {
+      return arrayUnique(this.concat(flatArguments(arguments)));
+    },
+
+    /***
+     * @method intersect([a1], [a2], ...)
+     * @returns Array
+     * @short Returns an array containing the elements all arrays have in common.
+     * @extra This method will also correctly operate on arrays of objects.
+     * @example
+     *
+     *   [1,3,5].intersect([5,7,9])   -> [5]
+     *   ['a','b'].intersect('b','c') -> ['b']
+     *
+     ***/
+    'intersect': function() {
+      return arrayIntersect(this, flatArguments(arguments), false);
+    },
+
+    /***
+     * @method subtract([a1], [a2], ...)
+     * @returns Array
+     * @short Subtracts from the array all elements in [a1], [a2], etc.
+     * @extra This method will also correctly operate on arrays of objects.
+     * @example
+     *
+     *   [1,3,5].subtract([5,7,9])   -> [1,3]
+     *   [1,3,5].subtract([3],[5])   -> [1]
+     *   ['a','b'].subtract('b','c') -> ['a']
+     *
+     ***/
+    'subtract': function(a) {
+      return arrayIntersect(this, flatArguments(arguments), true);
+    },
+
+    /***
+     * @method at(<index>, [loop] = true)
+     * @returns Mixed
+     * @short Gets the element(s) at a given index.
+     * @extra When [loop] is true, overshooting the end of the array (or the beginning) will begin counting from the other end. As an alternate syntax, passing multiple indexes will get the elements at those indexes.
+     * @example
+     *
+     *   [1,2,3].at(0)        -> 1
+     *   [1,2,3].at(2)        -> 3
+     *   [1,2,3].at(4)        -> 2
+     *   [1,2,3].at(4, false) -> null
+     *   [1,2,3].at(-1)       -> 3
+     *   [1,2,3].at(0,1)      -> [1,2]
+     *
+     ***/
+    'at': function() {
+      return getEntriesForIndexes(this, arguments);
+    },
+
+    /***
+     * @method first([num] = 1)
+     * @returns Mixed
+     * @short Returns the first element(s) in the array.
+     * @extra When <num> is passed, returns the first <num> elements in the array.
+     * @example
+     *
+     *   [1,2,3].first()        -> 1
+     *   [1,2,3].first(2)       -> [1,2]
+     *
+     ***/
+    'first': function(num) {
+      if(isUndefined(num)) return this[0];
+      if(num < 0) num = 0;
+      return this.slice(0, num);
+    },
+
+    /***
+     * @method last([num] = 1)
+     * @returns Mixed
+     * @short Returns the last element(s) in the array.
+     * @extra When <num> is passed, returns the last <num> elements in the array.
+     * @example
+     *
+     *   [1,2,3].last()        -> 3
+     *   [1,2,3].last(2)       -> [2,3]
+     *
+     ***/
+    'last': function(num) {
+      if(isUndefined(num)) return this[this.length - 1];
+      var start = this.length - num < 0 ? 0 : this.length - num;
+      return this.slice(start);
+    },
+
+    /***
+     * @method from(<index>)
+     * @returns Array
+     * @short Returns a slice of the array from <index>.
+     * @example
+     *
+     *   [1,2,3].from(1)  -> [2,3]
+     *   [1,2,3].from(2)  -> [3]
+     *
+     ***/
+    'from': function(num) {
+      return this.slice(num);
+    },
+
+    /***
+     * @method to(<index>)
+     * @returns Array
+     * @short Returns a slice of the array up to <index>.
+     * @example
+     *
+     *   [1,2,3].to(1)  -> [1]
+     *   [1,2,3].to(2)  -> [1,2]
+     *
+     ***/
+    'to': function(num) {
+      if(isUndefined(num)) num = this.length;
+      return this.slice(0, num);
+    },
+
+    /***
+     * @method min([map], [all] = false)
+     * @returns Mixed
+     * @short Returns the element in the array with the lowest value.
+     * @extra [map] may be a function mapping the value to be checked or a string acting as a shortcut. If [all] is true, will return all min values in an array.
+     * @example
+     *
+     *   [1,2,3].min()                          -> 1
+     *   ['fee','fo','fum'].min('length')       -> 'fo'
+     *   ['fee','fo','fum'].min('length', true) -> ['fo']
+     +   ['fee','fo','fum'].min(function(n) {
+     *     return n.length;
+     *   });                              -> ['fo']
+     +   [{a:3,a:2}].min(function(n) {
+     *     return n['a'];
+     *   });                              -> [{a:2}]
+     *
+     ***/
+    'min': function(map, all) {
+      return getMinOrMax(this, map, 'min', all);
+    },
+
+    /***
+     * @method max([map], [all] = false)
+     * @returns Mixed
+     * @short Returns the element in the array with the greatest value.
+     * @extra [map] may be a function mapping the value to be checked or a string acting as a shortcut. If [all] is true, will return all max values in an array.
+     * @example
+     *
+     *   [1,2,3].max()                          -> 3
+     *   ['fee','fo','fum'].max('length')       -> 'fee'
+     *   ['fee','fo','fum'].max('length', true) -> ['fee']
+     +   [{a:3,a:2}].max(function(n) {
+     *     return n['a'];
+     *   });                              -> {a:3}
+     *
+     ***/
+    'max': function(map, all) {
+      return getMinOrMax(this, map, 'max', all);
+    },
+
+    /***
+     * @method least([map])
+     * @returns Array
+     * @short Returns the elements in the array with the least commonly occuring value.
+     * @extra [map] may be a function mapping the value to be checked or a string acting as a shortcut.
+     * @example
+     *
+     *   [3,2,2].least()                   -> [3]
+     *   ['fe','fo','fum'].least('length') -> ['fum']
+     +   [{age:35,name:'ken'},{age:12,name:'bob'},{age:12,name:'ted'}].least(function(n) {
+     *     return n.age;
+     *   });                               -> [{age:35,name:'ken'}]
+     *
+     ***/
+    'least': function(map, all) {
+      return getMinOrMax(this.groupBy.apply(this, [map]), 'length', 'min', all);
+    },
+
+    /***
+     * @method most([map])
+     * @returns Array
+     * @short Returns the elements in the array with the most commonly occuring value.
+     * @extra [map] may be a function mapping the value to be checked or a string acting as a shortcut.
+     * @example
+     *
+     *   [3,2,2].most()                   -> [2]
+     *   ['fe','fo','fum'].most('length') -> ['fe','fo']
+     +   [{age:35,name:'ken'},{age:12,name:'bob'},{age:12,name:'ted'}].most(function(n) {
+     *     return n.age;
+     *   });                              -> [{age:12,name:'bob'},{age:12,name:'ted'}]
+     *
+     ***/
+    'most': function(map, all) {
+      return getMinOrMax(this.groupBy.apply(this, [map]), 'length', 'max', all);
+    },
+
+    /***
+     * @method sum([map])
+     * @returns Number
+     * @short Sums all values in the array.
+     * @extra [map] may be a function mapping the value to be summed or a string acting as a shortcut.
+     * @example
+     *
+     *   [1,2,2].sum()                           -> 5
+     +   [{age:35},{age:12},{age:12}].sum(function(n) {
+     *     return n.age;
+     *   });                                     -> 59
+     *   [{age:35},{age:12},{age:12}].sum('age') -> 59
+     *
+     ***/
+    'sum': function(map) {
+      var arr = map ? this.map(map) : this;
+      return arr.length > 0 ? arr.reduce(function(a,b) { return a + b; }) : 0;
+    },
+
+    /***
+     * @method average([map])
+     * @returns Number
+     * @short Gets the mean average for all values in the array.
+     * @extra [map] may be a function mapping the value to be averaged or a string acting as a shortcut.
+     * @example
+     *
+     *   [1,2,3].average()                           -> 2
+     +   [{age:35},{age:11},{age:11}].average(function(n) {
+     *     return n.age;
+     *   });                                         -> 19
+     *   [{age:35},{age:11},{age:11}].average('age') -> 19
+     *
+     ***/
+    'average': function(map) {
+      var arr = map ? this.map(map) : this;
+      return arr.length > 0 ? arr.sum() / arr.length : 0;
+    },
+
+    /***
+     * @method inGroups(<num>, [padding])
+     * @returns Array
+     * @short Groups the array into <num> arrays.
+     * @extra [padding] specifies a value with which to pad the last array so that they are all equal length.
+     * @example
+     *
+     *   [1,2,3,4,5,6,7].inGroups(3)         -> [ [1,2,3], [4,5,6], [7] ]
+     *   [1,2,3,4,5,6,7].inGroups(3, 'none') -> [ [1,2,3], [4,5,6], [7,'none','none'] ]
+     *
+     ***/
+    'inGroups': function(num, padding) {
+      var pad = arguments.length > 1;
+      var arr = this;
+      var result = [];
+      var divisor = ceil(this.length / num);
+      simpleRepeat(num, function(i) {
+        var index = i * divisor;
+        var group = arr.slice(index, index + divisor);
+        if(pad && group.length < divisor) {
+          simpleRepeat(divisor - group.length, function() {
+            group = group.add(padding);
+          });
+        }
+        result.push(group);
+      });
+      return result;
+    },
+
+    /***
+     * @method inGroupsOf(<num>, [padding] = null)
+     * @returns Array
+     * @short Groups the array into arrays of <num> elements each.
+     * @extra [padding] specifies a value with which to pad the last array so that they are all equal length.
+     * @example
+     *
+     *   [1,2,3,4,5,6,7].inGroupsOf(4)         -> [ [1,2,3,4], [5,6,7] ]
+     *   [1,2,3,4,5,6,7].inGroupsOf(4, 'none') -> [ [1,2,3,4], [5,6,7,'none'] ]
+     *
+     ***/
+    'inGroupsOf': function(num, padding) {
+      var result = [], len = this.length, arr = this, group;
+      if(len === 0 || num === 0) return arr;
+      if(isUndefined(num)) num = 1;
+      if(isUndefined(padding)) padding = null;
+      simpleRepeat(ceil(len / num), function(i) {
+        group = arr.slice(num * i, num * i + num);
+        while(group.length < num) {
+          group.push(padding);
+        }
+        result.push(group);
+      });
+      return result;
+    },
+
+    /***
+     * @method isEmpty()
+     * @returns Boolean
+     * @short Returns true if the array is empty.
+     * @extra This is true if the array has a length of zero, or contains only %undefined%, %null%, or %NaN%.
+     * @example
+     *
+     *   [].isEmpty()               -> true
+     *   [null,undefined].isEmpty() -> true
+     *
+     ***/
+    'isEmpty': function() {
+      return this.compact().length == 0;
+    },
+
+    /***
+     * @method sortBy(<map>, [desc] = false)
+     * @returns Array
+     * @short Sorts the array by <map>.
+     * @extra <map> may be a function, a string acting as a shortcut, or blank (direct comparison of array values). [desc] will sort the array in descending order. When the field being sorted on is a string, the resulting order will be determined by an internal collation algorithm that is optimized for major Western languages, but can be customized. For more information see @array_sorting.
+     * @example
+     *
+     *   ['world','a','new'].sortBy('length')       -> ['a','new','world']
+     *   ['world','a','new'].sortBy('length', true) -> ['world','new','a']
+     +   [{age:72},{age:13},{age:18}].sortBy(function(n) {
+     *     return n.age;
+     *   });                                        -> [{age:13},{age:18},{age:72}]
+     *
+     ***/
+    'sortBy': function(map, desc) {
+      var arr = this.clone();
+      arr.sort(function(a, b) {
+        var aProperty, bProperty, comp;
+        aProperty = transformArgument(a, map, arr, [a]);
+        bProperty = transformArgument(b, map, arr, [b]);
+        if(isString(aProperty) && isString(bProperty)) {
+          comp = collateStrings(aProperty, bProperty);
+        } else if(aProperty < bProperty) {
+          comp = -1;
+        } else if(aProperty > bProperty) {
+          comp = 1;
+        } else {
+          comp = 0;
+        }
+        return comp * (desc ? -1 : 1);
+      });
+      return arr;
+    },
+
+    /***
+     * @method randomize()
+     * @returns Array
+     * @short Returns a copy of the array with the elements randomized.
+     * @extra Uses Fisher-Yates algorithm.
+     * @example
+     *
+     *   [1,2,3,4].randomize()  -> [?,?,?,?]
+     *
+     ***/
+    'randomize': function() {
+      var arr = this.concat(), i = arr.length, j, x;
+      while(i) {
+        j = (math.random() * i) | 0;
+        x = arr[--i];
+        arr[i] = arr[j];
+        arr[j] = x;
+      }
+      return arr;
+    },
+
+    /***
+     * @method zip([arr1], [arr2], ...)
+     * @returns Array
+     * @short Merges multiple arrays together.
+     * @extra This method "zips up" smaller arrays into one large whose elements are "all elements at index 0", "all elements at index 1", etc. Useful when you have associated data that is split over separated arrays. If the arrays passed have more elements than the original array, they will be discarded. If they have fewer elements, the missing elements will filled with %null%.
+     * @example
+     *
+     *   [1,2,3].zip([4,5,6])                                       -> [[1,2], [3,4], [5,6]]
+     *   ['Martin','John'].zip(['Luther','F.'], ['King','Kennedy']) -> [['Martin','Luther','King'], ['John','F.','Kennedy']]
+     *
+     ***/
+    'zip': function() {
+      var args = multiArgs(arguments);
+      return this.map(function(el, i) {
+        return [el].concat(args.map(function(k) {
+          return (i in k) ? k[i] : null;
+        }));
+      });
+    },
+
+    /***
+     * @method sample([num])
+     * @returns Mixed
+     * @short Returns a random element from the array.
+     * @extra If [num] is passed, will return [num] samples from the array.
+     * @example
+     *
+     *   [1,2,3,4,5].sample()  -> // Random element
+     *   [1,2,3,4,5].sample(3) -> // Array of 3 random elements
+     *
+     ***/
+    'sample': function(num) {
+      var arr = this.randomize();
+      return arguments.length > 0 ? arr.slice(0, num) : arr[0];
+    },
+
+    /***
+     * @method each(<fn>, [index] = 0, [loop] = false)
+     * @returns Array
+     * @short Runs <fn> against each element in the array. Enhanced version of %Array#forEach%.
+     * @extra Parameters passed to <fn> are identical to %forEach%, ie. the first parameter is the current element, second parameter is the current index, and third parameter is the array itself. If <fn> returns %false% at any time it will break out of the loop. Once %each% finishes, it will return the array. If [index] is passed, <fn> will begin at that index and work its way to the end. If [loop] is true, it will then start over from the beginning of the array and continue until it reaches [index] - 1.
+     * @example
+     *
+     *   [1,2,3,4].each(function(n) {
+     *     // Called 4 times: 1, 2, 3, 4
+     *   });
+     *   [1,2,3,4].each(function(n) {
+     *     // Called 4 times: 3, 4, 1, 2
+     *   }, 2, true);
+     *
+     ***/
+    'each': function(fn, index, loop) {
+      arrayEach(this, fn, index, loop);
+      return this;
+    },
+
+    /***
+     * @method add(<el>, [index])
+     * @returns Array
+     * @short Adds <el> to the array.
+     * @extra If [index] is specified, it will add at [index], otherwise adds to the end of the array. %add% behaves like %concat% in that if <el> is an array it will be joined, not inserted. This method will change the array! Use %include% for a non-destructive alias. Also, %insert% is provided as an alias that reads better when using an index.
+     * @example
+     *
+     *   [1,2,3,4].add(5)       -> [1,2,3,4,5]
+     *   [1,2,3,4].add([5,6,7]) -> [1,2,3,4,5,6,7]
+     *   [1,2,3,4].insert(8, 1) -> [1,8,2,3,4]
+     *
+     ***/
+    'add': function(el, index) {
+      if(!isNumber(number(index)) || isNaN(index)) index = this.length;
+      array.prototype.splice.apply(this, [index, 0].concat(el));
+      return this;
+    },
+
+    /***
+     * @method remove([f1], [f2], ...)
+     * @returns Array
+     * @short Removes any element in the array that matches [f1], [f2], etc.
+     * @extra Will match a string, number, array, object, or alternately test against a function or regex. This method will change the array! Use %exclude% for a non-destructive alias. This method implements @array_matching.
+     * @example
+     *
+     *   [1,2,3].remove(3)         -> [1,2]
+     *   ['a','b','c'].remove(/b/) -> ['a','c']
+     +   [{a:1},{b:2}].remove(function(n) {
+     *     return n['a'] == 1;
+     *   });                       -> [{b:2}]
+     *
+     ***/
+    'remove': function() {
+      var arr = this;
+      multiArgs(arguments, function(f) {
+        var i = 0, matcher = getMatcher(f);
+        while(i < arr.length) {
+          if(matcher(arr[i], i, arr)) {
+            arr.splice(i, 1);
+          } else {
+            i++;
+          }
+        }
+      });
+      return arr;
+    },
+
+    /***
+     * @method compact([all] = false)
+     * @returns Array
+     * @short Removes all instances of %undefined%, %null%, and %NaN% from the array.
+     * @extra If [all] is %true%, all "falsy" elements will be removed. This includes empty strings, 0, and false.
+     * @example
+     *
+     *   [1,null,2,undefined,3].compact() -> [1,2,3]
+     *   [1,'',2,false,3].compact()       -> [1,'',2,false,3]
+     *   [1,'',2,false,3].compact(true)   -> [1,2,3]
+     *
+     ***/
+    'compact': function(all) {
+      var result = [];
+      arrayEach(this, function(el, i) {
+        if(isArray(el)) {
+          result.push(el.compact());
+        } else if(all && el) {
+          result.push(el);
+        } else if(!all && el != null && el.valueOf() === el.valueOf()) {
+          result.push(el);
+        }
+      });
+      return result;
+    },
+
+    /***
+     * @method groupBy(<map>, [fn])
+     * @returns Object
+     * @short Groups the array by <map>.
+     * @extra Will return an object with keys equal to the grouped values. <map> may be a mapping function, or a string acting as a shortcut. Optionally calls [fn] for each group.
+     * @example
+     *
+     *   ['fee','fi','fum'].groupBy('length') -> { 2: ['fi'], 3: ['fee','fum'] }
+     +   [{age:35,name:'ken'},{age:15,name:'bob'}].groupBy(function(n) {
+     *     return n.age;
+     *   });                                  -> { 35: [{age:35,name:'ken'}], 15: [{age:15,name:'bob'}] }
+     *
+     ***/
+    'groupBy': function(map, fn) {
+      var arr = this, result = {}, key;
+      arrayEach(arr, function(el, index) {
+        key = transformArgument(el, map, arr, [el, index, arr]);
+        if(!result[key]) result[key] = [];
+        result[key].push(el);
+      });
+      if(fn) {
+        iterateOverObject(result, fn);
+      }
+      return result;
+    },
+
+    /***
+     * @method none(<f>)
+     * @returns Boolean
+     * @short Returns true if none of the elements in the array match <f>.
+     * @extra <f> will match a string, number, array, object, or alternately test against a function or regex. This method implements @array_matching.
+     * @example
+     *
+     *   [1,2,3].none(5)         -> true
+     *   ['a','b','c'].none(/b/) -> false
+     +   [{a:1},{b:2}].none(function(n) {
+     *     return n['a'] > 1;
+     *   });                     -> true
+     *
+     ***/
+    'none': function() {
+      return !this.any.apply(this, arguments);
+    }
+
+
+  });
+
+
+  // Aliases
+
+  extend(array, true, true, {
+
+    /***
+     * @method all()
+     * @alias every
+     *
+     ***/
+    'all': array.prototype.every,
+
+    /*** @method any()
+     * @alias some
+     *
+     ***/
+    'any': array.prototype.some,
+
+    /***
+     * @method insert()
+     * @alias add
+     *
+     ***/
+    'insert': array.prototype.add
+
+  });
+
+
+  /***
+   * Object module
+   * Enumerable methods on objects
+   *
+   ***/
+
+   function keysWithObjectCoercion(obj) {
+     return object.keys(coercePrimitiveToObject(obj));
+   }
+
+  /***
+   * @method [enumerable](<obj>)
+   * @returns Boolean
+   * @short Enumerable methods in the Array package are also available to the Object class. They will perform their normal operations for every property in <obj>.
+   * @extra In cases where a callback is used, instead of %element, index%, the callback will instead be passed %key, value%. Enumerable methods are also available to extended objects as instance methods.
+   *
+   * @set
+   *   each
+   *   map
+   *   any
+   *   all
+   *   none
+   *   count
+   *   find
+   *   findAll
+   *   reduce
+   *   isEmpty
+   *   sum
+   *   average
+   *   min
+   *   max
+   *   least
+   *   most
+   *
+   * @example
+   *
+   *   Object.any({foo:'bar'}, 'bar')            -> true
+   *   Object.extended({foo:'bar'}).any('bar')   -> true
+   *   Object.isEmpty({})                        -> true
+   +   Object.map({ fred: { age: 52 } }, 'age'); -> { fred: 52 }
+   *
+   ***/
+
+  function buildEnumerableMethods(names, mapping) {
+    extendSimilar(object, false, true, names, function(methods, name) {
+      methods[name] = function(obj, arg1, arg2) {
+        var result, coerced = keysWithObjectCoercion(obj), matcher;
+        if(!mapping) {
+          matcher = getMatcher(arg1, true);
+        }
+        result = array.prototype[name].call(coerced, function(key) {
+          var value = obj[key];
+          if(mapping) {
+            return transformArgument(value, arg1, obj, [key, value, obj]);
+          } else {
+            return matcher(value, key, obj);
+          }
+        }, arg2);
+        if(isArray(result)) {
+          // The method has returned an array of keys so use this array
+          // to build up the resulting object in the form we want it in.
+          result = result.reduce(function(o, key, i) {
+            o[key] = obj[key];
+            return o;
+          }, {});
+        }
+        return result;
+      };
+    });
+    buildObjectInstanceMethods(names, Hash);
+  }
+
+  function exportSortAlgorithm() {
+    array[AlphanumericSort] = collateStrings;
+  }
+
+  extend(object, false, true, {
+
+    'map': function(obj, map) {
+      var result = {}, key, value;
+      for(key in obj) {
+        if(!hasOwnProperty(obj, key)) continue;
+        value = obj[key];
+        result[key] = transformArgument(value, map, obj, [key, value, obj]);
+      }
+      return result;
+    },
+
+    'reduce': function(obj) {
+      var values = keysWithObjectCoercion(obj).map(function(key) {
+        return obj[key];
+      });
+      return values.reduce.apply(values, multiArgs(arguments, null, 1));
+    },
+
+    'each': function(obj, fn) {
+      checkCallback(fn);
+      iterateOverObject(obj, fn);
+      return obj;
+    },
+
+    /***
+     * @method size(<obj>)
+     * @returns Number
+     * @short Returns the number of properties in <obj>.
+     * @extra %size% is available as an instance method on extended objects.
+     * @example
+     *
+     *   Object.size({ foo: 'bar' }) -> 1
+     *
+     ***/
+    'size': function (obj) {
+      return keysWithObjectCoercion(obj).length;
+    }
+
+  });
+
+  var EnumerableFindingMethods = 'any,all,none,count,find,findAll,isEmpty'.split(',');
+  var EnumerableMappingMethods = 'sum,average,min,max,least,most'.split(',');
+  var EnumerableOtherMethods   = 'map,reduce,size'.split(',');
+  var EnumerableMethods        = EnumerableFindingMethods.concat(EnumerableMappingMethods).concat(EnumerableOtherMethods);
+
+  buildEnhancements();
+  buildAlphanumericSort();
+  buildEnumerableMethods(EnumerableFindingMethods);
+  buildEnumerableMethods(EnumerableMappingMethods, true);
+  buildObjectInstanceMethods(EnumerableOtherMethods, Hash);
+  exportSortAlgorithm();
+
+
+  /***
+   * @package Date
+   * @dependency core
+   * @description Date parsing and formatting, relative formats like "1 minute ago", Number methods like "daysAgo", localization support with default English locale definition.
+   *
+   ***/
+
+  var English;
+  var CurrentLocalization;
+
+  var TimeFormat = ['ampm','hour','minute','second','ampm','utc','offset_sign','offset_hours','offset_minutes','ampm']
+  var DecimalReg = '(?:[,.]\\d+)?';
+  var HoursReg   = '\\d{1,2}' + DecimalReg;
+  var SixtyReg   = '[0-5]\\d' + DecimalReg;
+  var RequiredTime = '({t})?\\s*('+HoursReg+')(?:{h}('+SixtyReg+')?{m}(?::?('+SixtyReg+'){s})?\\s*(?:({t})|(Z)|(?:([+-])(\\d{2,2})(?::?(\\d{2,2}))?)?)?|\\s*({t}))';
+
+  var KanjiDigits = 'ใ€‡ไธ€ไบŒไธ‰ๅ››ไบ”ๅ…ญไธƒๅ…ซไนๅ็™พๅƒไธ‡';
+  var AsianDigitMap = {};
+  var AsianDigitReg;
+
+  var DateArgumentUnits;
+  var DateUnitsReversed;
+  var CoreDateFormats = [];
+  var CompiledOutputFormats = {};
+
+  var DateFormatTokens = {
+
+    'yyyy': function(d) {
+      return callDateGet(d, 'FullYear');
+    },
+
+    'yy': function(d) {
+      return callDateGet(d, 'FullYear') % 100;
+    },
+
+    'ord': function(d) {
+      var date = callDateGet(d, 'Date');
+      return date + getOrdinalizedSuffix(date);
+    },
+
+    'tz': function(d) {
+      return d.getUTCOffset();
+    },
+
+    'isotz': function(d) {
+      return d.getUTCOffset(true);
+    },
+
+    'Z': function(d) {
+      return d.getUTCOffset();
+    },
+
+    'ZZ': function(d) {
+      return d.getUTCOffset().replace(/(\d{2})$/, ':$1');
+    }
+
+  };
+
+  var DateUnits = [
+    {
+      name: 'year',
+      method: 'FullYear',
+      ambiguous: true,
+      multiplier: function(d) {
+        var adjust = d ? (d.isLeapYear() ? 1 : 0) : 0.25;
+        return (365 + adjust) * 24 * 60 * 60 * 1000;
+      }
+    },
+    {
+      name: 'month',
+      error: 0.919, // Feb 1-28 over 1 month
+      method: 'Month',
+      ambiguous: true,
+      multiplier: function(d, ms) {
+        var days = 30.4375, inMonth;
+        if(d) {
+          inMonth = d.daysInMonth();
+          if(ms <= inMonth.days()) {
+            days = inMonth;
+          }
+        }
+        return days * 24 * 60 * 60 * 1000;
+      }
+    },
+    {
+      name: 'week',
+      method: 'ISOWeek',
+      multiplier: function() {
+        return 7 * 24 * 60 * 60 * 1000;
+      }
+    },
+    {
+      name: 'day',
+      error: 0.958, // DST traversal over 1 day
+      method: 'Date',
+      ambiguous: true,
+      multiplier: function() {
+        return 24 * 60 * 60 * 1000;
+      }
+    },
+    {
+      name: 'hour',
+      method: 'Hours',
+      multiplier: function() {
+        return 60 * 60 * 1000;
+      }
+    },
+    {
+      name: 'minute',
+      method: 'Minutes',
+      multiplier: function() {
+        return 60 * 1000;
+      }
+    },
+    {
+      name: 'second',
+      method: 'Seconds',
+      multiplier: function() {
+        return 1000;
+      }
+    },
+    {
+      name: 'millisecond',
+      method: 'Milliseconds',
+      multiplier: function() {
+        return 1;
+      }
+    }
+  ];
+
+
+
+
+  // Date Localization
+
+  var Localizations = {};
+
+  // Localization object
+
+  function Localization(l) {
+    simpleMerge(this, l);
+    this.compiledFormats = CoreDateFormats.concat();
+  }
+
+  Localization.prototype = {
+
+    getMonth: function(n) {
+      if(isNumber(n)) {
+        return n - 1;
+      } else {
+        return this['months'].indexOf(n) % 12;
+      }
+    },
+
+    getWeekday: function(n) {
+      return this['weekdays'].indexOf(n) % 7;
+    },
+
+    getNumber: function(n) {
+      var i;
+      if(isNumber(n)) {
+        return n;
+      } else if(n && (i = this['numbers'].indexOf(n)) !== -1) {
+        return (i + 1) % 10;
+      } else {
+        return 1;
+      }
+    },
+
+    getNumericDate: function(n) {
+      var self = this;
+      return n.replace(regexp(this['num'], 'g'), function(d) {
+        var num = self.getNumber(d);
+        return num || '';
+      });
+    },
+
+    getUnitIndex: function(n) {
+      return this['units'].indexOf(n) % 8;
+    },
+
+    getRelativeFormat: function(adu) {
+      return this.convertAdjustedToFormat(adu, adu[2] > 0 ? 'future' : 'past');
+    },
+
+    getDuration: function(ms) {
+      return this.convertAdjustedToFormat(getAdjustedUnit(ms), 'duration');
+    },
+
+    hasVariant: function(code) {
+      code = code || this.code;
+      return code === 'en' || code === 'en-US' ? true : this['variant'];
+    },
+
+    matchAM: function(str) {
+      return str === this['ampm'][0];
+    },
+
+    matchPM: function(str) {
+      return str && str === this['ampm'][1];
+    },
+
+    convertAdjustedToFormat: function(adu, mode) {
+      var sign, unit, mult,
+          num    = adu[0],
+          u      = adu[1],
+          ms     = adu[2],
+          format = this[mode] || this['relative'];
+      if(isFunction(format)) {
+        return format.call(this, num, u, ms, mode);
+      }
+      mult = this['plural'] && num > 1 ? 1 : 0;
+      unit = this['units'][mult * 8 + u] || this['units'][u];
+      if(this['capitalizeUnit']) unit = simpleCapitalize(unit);
+      sign = this['modifiers'].filter(function(m) { return m.name == 'sign' && m.value == (ms > 0 ? 1 : -1); })[0];
+      return format.replace(/\{(.*?)\}/g, function(full, match) {
+        switch(match) {
+          case 'num': return num;
+          case 'unit': return unit;
+          case 'sign': return sign.src;
+        }
+      });
+    },
+
+    getFormats: function() {
+      return this.cachedFormat ? [this.cachedFormat].concat(this.compiledFormats) : this.compiledFormats;
+    },
+
+    addFormat: function(src, allowsTime, match, variant, iso) {
+      var to = match || [], loc = this, time, timeMarkers, lastIsNumeral;
+
+      src = src.replace(/\s+/g, '[,. ]*');
+      src = src.replace(/\{([^,]+?)\}/g, function(all, k) {
+        var value, arr, result,
+            opt   = k.match(/\?$/),
+            nc    = k.match(/^(\d+)\??$/),
+            slice = k.match(/(\d)(?:-(\d))?/),
+            key   = k.replace(/[^a-z]+$/, '');
+        if(nc) {
+          value = loc['tokens'][nc[1]];
+        } else if(loc[key]) {
+          value = loc[key];
+        } else if(loc[key + 's']) {
+          value = loc[key + 's'];
+          if(slice) {
+            // Can't use filter here as Prototype hijacks the method and doesn't
+            // pass an index, so use a simple loop instead!
+            arr = [];
+            value.forEach(function(m, i) {
+              var mod = i % (loc['units'] ? 8 : value.length);
+              if(mod >= slice[1] && mod <= (slice[2] || slice[1])) {
+                arr.push(m);
+              }
+            });
+            value = arr;
+          }
+          value = arrayToAlternates(value);
+        }
+        if(nc) {
+          result = '(?:' + value + ')';
+        } else {
+          if(!match) {
+            to.push(key);
+          }
+          result = '(' + value + ')';
+        }
+        if(opt) {
+          result += '?';
+        }
+        return result;
+      });
+      if(allowsTime) {
+        time = prepareTime(RequiredTime, loc, iso);
+        timeMarkers = ['t','[\\s\\u3000]'].concat(loc['timeMarker']);
+        lastIsNumeral = src.match(/\\d\{\d,\d\}\)+\??$/);
+        addDateInputFormat(loc, '(?:' + time + ')[,\\s\\u3000]+?' + src, TimeFormat.concat(to), variant);
+        addDateInputFormat(loc, src + '(?:[,\\s]*(?:' + timeMarkers.join('|') + (lastIsNumeral ? '+' : '*') +')' + time + ')?', to.concat(TimeFormat), variant);
+      } else {
+        addDateInputFormat(loc, src, to, variant);
+      }
+    }
+
+  };
+
+
+  // Localization helpers
+
+  function getLocalization(localeCode, fallback) {
+    var loc;
+    if(!isString(localeCode)) localeCode = '';
+    loc = Localizations[localeCode] || Localizations[localeCode.slice(0,2)];
+    if(fallback === false && !loc) {
+      throw new TypeError('Invalid locale.');
+    }
+    return loc || CurrentLocalization;
+  }
+
+  function setLocalization(localeCode, set) {
+    var loc, canAbbreviate;
+
+    function initializeField(name) {
+      var val = loc[name];
+      if(isString(val)) {
+        loc[name] = val.split(',');
+      } else if(!val) {
+        loc[name] = [];
+      }
+    }
+
+    function eachAlternate(str, fn) {
+      str = str.split('+').map(function(split) {
+        return split.replace(/(.+):(.+)$/, function(full, base, suffixes) {
+          return suffixes.split('|').map(function(suffix) {
+            return base + suffix;
+          }).join('|');
+        });
+      }).join('|');
+      return str.split('|').forEach(fn);
+    }
+
+    function setArray(name, abbreviate, multiple) {
+      var arr = [];
+      loc[name].forEach(function(full, i) {
+        if(abbreviate) {
+          full += '+' + full.slice(0,3);
+        }
+        eachAlternate(full, function(day, j) {
+          arr[j * multiple + i] = day.toLowerCase();
+        });
+      });
+      loc[name] = arr;
+    }
+
+    function getDigit(start, stop, allowNumbers) {
+      var str = '\\d{' + start + ',' + stop + '}';
+      if(allowNumbers) str += '|(?:' + arrayToAlternates(loc['numbers']) + ')+';
+      return str;
+    }
+
+    function getNum() {
+      var arr = ['-?\\d+'].concat(loc['articles']);
+      if(loc['numbers']) arr = arr.concat(loc['numbers']);
+      return arrayToAlternates(arr);
+    }
+
+    function setDefault(name, value) {
+      loc[name] = loc[name] || value;
+    }
+
+    function setModifiers() {
+      var arr = [];
+      loc.modifiersByName = {};
+      loc['modifiers'].push({ 'name': 'day', 'src': 'yesterday', 'value': -1 });
+      loc['modifiers'].push({ 'name': 'day', 'src': 'today', 'value': 0 });
+      loc['modifiers'].push({ 'name': 'day', 'src': 'tomorrow', 'value': 1 });
+      loc['modifiers'].forEach(function(modifier) {
+        var name = modifier.name;
+        eachAlternate(modifier.src, function(t) {
+          var locEntry = loc[name];
+          loc.modifiersByName[t] = modifier;
+          arr.push({ name: name, src: t, value: modifier.value });
+          loc[name] = locEntry ? locEntry + '|' + t : t;
+        });
+      });
+      loc['day'] += '|' + arrayToAlternates(loc['weekdays']);
+      loc['modifiers'] = arr;
+    }
+
+    // Initialize the locale
+    loc = new Localization(set);
+    initializeField('modifiers');
+    'months,weekdays,units,numbers,articles,tokens,timeMarker,ampm,timeSuffixes,dateParse,timeParse'.split(',').forEach(initializeField);
+
+    canAbbreviate = !loc['monthSuffix'];
+
+    setArray('months',   canAbbreviate, 12);
+    setArray('weekdays', canAbbreviate, 7);
+    setArray('units', false, 8);
+    setArray('numbers', false, 10);
+
+    setDefault('code', localeCode);
+    setDefault('date', getDigit(1,2, loc['digitDate']));
+    setDefault('year', "'\\d{2}|" + getDigit(4,4));
+    setDefault('num', getNum());
+
+    setModifiers();
+
+    if(loc['monthSuffix']) {
+      loc['month'] = getDigit(1,2);
+      loc['months'] = '1,2,3,4,5,6,7,8,9,10,11,12'.split(',').map(function(n) { return n + loc['monthSuffix']; });
+    }
+    loc['full_month'] = getDigit(1,2) + '|' + arrayToAlternates(loc['months']);
+
+    // The order of these formats is very important. Order is reversed so formats that come
+    // later will take precedence over formats that come before. This generally means that
+    // more specific formats should come later, however, the {year} format should come before
+    // {day}, as 2011 needs to be parsed as a year (2011) and not date (20) + hours (11)
+
+    // If the locale has time suffixes then add a time only format for that locale
+    // that is separate from the core English-based one.
+    if(loc['timeSuffixes'].length > 0) {
+      loc.addFormat(prepareTime(RequiredTime, loc), false, TimeFormat)
+    }
+
+    loc.addFormat('{day}', true);
+    loc.addFormat('{month}' + (loc['monthSuffix'] || ''));
+    loc.addFormat('{year}' + (loc['yearSuffix'] || ''));
+
+    loc['timeParse'].forEach(function(src) {
+      loc.addFormat(src, true);
+    });
+
+    loc['dateParse'].forEach(function(src) {
+      loc.addFormat(src);
+    });
+
+    return Localizations[localeCode] = loc;
+  }
+
+
+  // General helpers
+
+  function addDateInputFormat(locale, format, match, variant) {
+    locale.compiledFormats.unshift({
+      variant: variant,
+      locale: locale,
+      reg: regexp('^' + format + '$', 'i'),
+      to: match
+    });
+  }
+
+  function simpleCapitalize(str) {
+    return str.slice(0,1).toUpperCase() + str.slice(1);
+  }
+
+  function arrayToAlternates(arr) {
+    return arr.filter(function(el) {
+      return !!el;
+    }).join('|');
+  }
+
+  function getNewDate() {
+    var fn = date.SugarNewDate;
+    return fn ? fn() : new date;
+  }
+
+  // Date argument helpers
+
+  function collectDateArguments(args, allowDuration) {
+    var obj;
+    if(isObjectType(args[0])) {
+      return args;
+    } else if (isNumber(args[0]) && !isNumber(args[1])) {
+      return [args[0]];
+    } else if (isString(args[0]) && allowDuration) {
+      return [getDateParamsFromString(args[0]), args[1]];
+    }
+    obj = {};
+    DateArgumentUnits.forEach(function(u,i) {
+      obj[u.name] = args[i];
+    });
+    return [obj];
+  }
+
+  function getDateParamsFromString(str, num) {
+    var match, params = {};
+    match = str.match(/^(\d+)?\s?(\w+?)s?$/i);
+    if(match) {
+      if(isUndefined(num)) {
+        num = parseInt(match[1]) || 1;
+      }
+      params[match[2].toLowerCase()] = num;
+    }
+    return params;
+  }
+
+  // Date iteration helpers
+
+  function iterateOverDateUnits(fn, from, to) {
+    var i, unit;
+    if(isUndefined(to)) to = DateUnitsReversed.length;
+    for(i = from || 0; i < to; i++) {
+      unit = DateUnitsReversed[i];
+      if(fn(unit.name, unit, i) === false) {
+        break;
+      }
+    }
+  }
+
+  // Date parsing helpers
+
+  function getFormatMatch(match, arr) {
+    var obj = {}, value, num;
+    arr.forEach(function(key, i) {
+      value = match[i + 1];
+      if(isUndefined(value) || value === '') return;
+      if(key === 'year') {
+        obj.yearAsString = value.replace(/'/, '');
+      }
+      num = parseFloat(value.replace(/'/, '').replace(/,/, '.'));
+      obj[key] = !isNaN(num) ? num : value.toLowerCase();
+    });
+    return obj;
+  }
+
+  function cleanDateInput(str) {
+    str = str.trim().replace(/^just (?=now)|\.+$/i, '');
+    return convertAsianDigits(str);
+  }
+
+  function convertAsianDigits(str) {
+    return str.replace(AsianDigitReg, function(full, disallowed, match) {
+      var sum = 0, place = 1, lastWasHolder, lastHolder;
+      if(disallowed) return full;
+      match.split('').reverse().forEach(function(letter) {
+        var value = AsianDigitMap[letter], holder = value > 9;
+        if(holder) {
+          if(lastWasHolder) sum += place;
+          place *= value / (lastHolder || 1);
+          lastHolder = value;
+        } else {
+          if(lastWasHolder === false) {
+            place *= 10;
+          }
+          sum += place * value;
+        }
+        lastWasHolder = holder;
+      });
+      if(lastWasHolder) sum += place;
+      return sum;
+    });
+  }
+
+  function getExtendedDate(f, localeCode, prefer, forceUTC) {
+    var d, relative, baseLocalization, afterCallbacks, loc, set, unit, unitIndex, weekday, num, tmp;
+
+    d = getNewDate();
+    afterCallbacks = [];
+
+    function afterDateSet(fn) {
+      afterCallbacks.push(fn);
+    }
+
+    function fireCallbacks() {
+      afterCallbacks.forEach(function(fn) {
+        fn.call();
+      });
+    }
+
+    function setWeekdayOfMonth() {
+      var w = d.getWeekday();
+      d.setWeekday((7 * (set['num'] - 1)) + (w > weekday ? weekday + 7 : weekday));
+    }
+
+    function setUnitEdge() {
+      var modifier = loc.modifiersByName[set['edge']];
+      iterateOverDateUnits(function(name) {
+        if(isDefined(set[name])) {
+          unit = name;
+          return false;
+        }
+      }, 4);
+      if(unit === 'year') set.specificity = 'month';
+      else if(unit === 'month' || unit === 'week') set.specificity = 'day';
+      d[(modifier.value < 0 ? 'endOf' : 'beginningOf') + simpleCapitalize(unit)]();
+      // This value of -2 is arbitrary but it's a nice clean way to hook into this system.
+      if(modifier.value === -2) d.reset();
+    }
+
+    function separateAbsoluteUnits() {
+      var params;
+      iterateOverDateUnits(function(name, u, i) {
+        if(name === 'day') name = 'date';
+        if(isDefined(set[name])) {
+          // If there is a time unit set that is more specific than
+          // the matched unit we have a string like "5:30am in 2 minutes",
+          // which is meaningless, so invalidate the date...
+          if(i >= unitIndex) {
+            invalidateDate(d);
+            return false;
+          }
+          // ...otherwise set the params to set the absolute date
+          // as a callback after the relative date has been set.
+          params = params || {};
+          params[name] = set[name];
+          delete set[name];
+        }
+      });
+      if(params) {
+        afterDateSet(function() {
+          d.set(params, true);
+        });
+      }
+    }
+
+    d.utc(forceUTC);
+
+    if(isDate(f)) {
+      // If the source here is already a date object, then the operation
+      // is the same as cloning the date, which preserves the UTC flag.
+      d.utc(f.isUTC()).setTime(f.getTime());
+    } else if(isNumber(f)) {
+      d.setTime(f);
+    } else if(isObjectType(f)) {
+      d.set(f, true);
+      set = f;
+    } else if(isString(f)) {
+
+      // The act of getting the localization will pre-initialize
+      // if it is missing and add the required formats.
+      baseLocalization = getLocalization(localeCode);
+
+      // Clean the input and convert Kanji based numerals if they exist.
+      f = cleanDateInput(f);
+
+      if(baseLocalization) {
+        iterateOverObject(baseLocalization.getFormats(), function(i, dif) {
+          var match = f.match(dif.reg);
+          if(match) {
+
+            loc = dif.locale;
+            set = getFormatMatch(match, dif.to, loc);
+            loc.cachedFormat = dif;
+
+
+            if(set['utc']) {
+              d.utc();
+            }
+
+            if(set.timestamp) {
+              set = set.timestamp;
+              return false;
+            }
+
+            // If there's a variant (crazy Endian American format), swap the month and day.
+            if(dif.variant && !isString(set['month']) && (isString(set['date']) || baseLocalization.hasVariant(localeCode))) {
+              tmp = set['month'];
+              set['month'] = set['date'];
+              set['date']  = tmp;
+            }
+
+            // If the year is 2 digits then get the implied century.
+            if(set['year'] && set.yearAsString.length === 2) {
+              set['year'] = getYearFromAbbreviation(set['year']);
+            }
+
+            // Set the month which may be localized.
+            if(set['month']) {
+              set['month'] = loc.getMonth(set['month']);
+              if(set['shift'] && !set['unit']) set['unit'] = loc['units'][7];
+            }
+
+            // If there is both a weekday and a date, the date takes precedence.
+            if(set['weekday'] && set['date']) {
+              delete set['weekday'];
+            // Otherwise set a localized weekday.
+            } else if(set['weekday']) {
+              set['weekday'] = loc.getWeekday(set['weekday']);
+              if(set['shift'] && !set['unit']) set['unit'] = loc['units'][5];
+            }
+
+            // Relative day localizations such as "today" and "tomorrow".
+            if(set['day'] && (tmp = loc.modifiersByName[set['day']])) {
+              set['day'] = tmp.value;
+              d.reset();
+              relative = true;
+            // If the day is a weekday, then set that instead.
+            } else if(set['day'] && (weekday = loc.getWeekday(set['day'])) > -1) {
+              delete set['day'];
+              if(set['num'] && set['month']) {
+                // If we have "the 2nd tuesday of June", set the day to the beginning of the month, then
+                // set the weekday after all other properties have been set. The weekday needs to be set
+                // after the actual set because it requires overriding the "prefer" argument which
+                // could unintentionally send the year into the future, past, etc.
+                afterDateSet(setWeekdayOfMonth);
+                set['day'] = 1;
+              } else {
+                set['weekday'] = weekday;
+              }
+            }
+
+            if(set['date'] && !isNumber(set['date'])) {
+              set['date'] = loc.getNumericDate(set['date']);
+            }
+
+            // If the time is 1pm-11pm advance the time by 12 hours.
+            if(loc.matchPM(set['ampm']) && set['hour'] < 12) {
+              set['hour'] += 12;
+            } else if(loc.matchAM(set['ampm']) && set['hour'] === 12) {
+              set['hour'] = 0;
+            }
+
+            // Adjust for timezone offset
+            if('offset_hours' in set || 'offset_minutes' in set) {
+              d.utc();
+              set['offset_minutes'] = set['offset_minutes'] || 0;
+              set['offset_minutes'] += set['offset_hours'] * 60;
+              if(set['offset_sign'] === '-') {
+                set['offset_minutes'] *= -1;
+              }
+              set['minute'] -= set['offset_minutes'];
+            }
+
+            // Date has a unit like "days", "months", etc. are all relative to the current date.
+            if(set['unit']) {
+              relative  = true;
+              num       = loc.getNumber(set['num']);
+              unitIndex = loc.getUnitIndex(set['unit']);
+              unit      = English['units'][unitIndex];
+
+              // Formats like "the 15th of last month" or "6:30pm of next week"
+              // contain absolute units in addition to relative ones, so separate
+              // them here, remove them from the params, and set up a callback to
+              // set them after the relative ones have been set.
+              separateAbsoluteUnits();
+
+              // Shift and unit, ie "next month", "last week", etc.
+              if(set['shift']) {
+                num *= (tmp = loc.modifiersByName[set['shift']]) ? tmp.value : 0;
+              }
+
+              // Unit and sign, ie "months ago", "weeks from now", etc.
+              if(set['sign'] && (tmp = loc.modifiersByName[set['sign']])) {
+                num *= tmp.value;
+              }
+
+              // Units can be with non-relative dates, set here. ie "the day after monday"
+              if(isDefined(set['weekday'])) {
+                d.set({'weekday': set['weekday'] }, true);
+                delete set['weekday'];
+              }
+
+              // Finally shift the unit.
+              set[unit] = (set[unit] || 0) + num;
+            }
+
+            // If there is an "edge" it needs to be set after the
+            // other fields are set. ie "the end of February"
+            if(set['edge']) {
+              afterDateSet(setUnitEdge);
+            }
+
+            if(set['year_sign'] === '-') {
+              set['year'] *= -1;
+            }
+
+            iterateOverDateUnits(function(name, unit, i) {
+              var value = set[name], fraction = value % 1;
+              if(fraction) {
+                set[DateUnitsReversed[i - 1].name] = round(fraction * (name === 'second' ? 1000 : 60));
+                set[name] = floor(value);
+              }
+            }, 1, 4);
+            return false;
+          }
+        });
+      }
+      if(!set) {
+        // The Date constructor does something tricky like checking the number
+        // of arguments so simply passing in undefined won't work.
+        if(f !== 'now') {
+          d = new date(f);
+        }
+        if(forceUTC) {
+          // Falling back to system date here which cannot be parsed as UTC,
+          // so if we're forcing UTC then simply add the offset.
+          d.addMinutes(-d.getTimezoneOffset());
+        }
+      } else if(relative) {
+        d.advance(set);
+      } else {
+        if(d._utc) {
+          // UTC times can traverse into other days or even months,
+          // so preemtively reset the time here to prevent this.
+          d.reset();
+        }
+        updateDate(d, set, true, false, prefer);
+      }
+      fireCallbacks();
+      // A date created by parsing a string presumes that the format *itself* is UTC, but
+      // not that the date, once created, should be manipulated as such. In other words,
+      // if you are creating a date object from a server time "2012-11-15T12:00:00Z",
+      // in the majority of cases you are using it to create a date that will, after creation,
+      // be manipulated as local, so reset the utc flag here.
+      d.utc(false);
+    }
+    return {
+      date: d,
+      set: set
+    }
+  }
+
+  // If the year is two digits, add the most appropriate century prefix.
+  function getYearFromAbbreviation(year) {
+    return round(callDateGet(getNewDate(), 'FullYear') / 100) * 100 - round(year / 100) * 100 + year;
+  }
+
+  function getShortHour(d) {
+    var hours = callDateGet(d, 'Hours');
+    return hours === 0 ? 12 : hours - (floor(hours / 13) * 12);
+  }
+
+  // weeksSince won't work here as the result needs to be floored, not rounded.
+  function getWeekNumber(date) {
+    date = date.clone();
+    var dow = callDateGet(date, 'Day') || 7;
+    date.addDays(4 - dow).reset();
+    return 1 + floor(date.daysSince(date.clone().beginningOfYear()) / 7);
+  }
+
+  function getAdjustedUnit(ms) {
+    var next, ams = abs(ms), value = ams, unitIndex = 0;
+    iterateOverDateUnits(function(name, unit, i) {
+      next = floor(withPrecision(ams / unit.multiplier(), 1));
+      if(next >= 1) {
+        value = next;
+        unitIndex = i;
+      }
+    }, 1);
+    return [value, unitIndex, ms];
+  }
+
+  function getRelativeWithMonthFallback(date) {
+    var adu = getAdjustedUnit(date.millisecondsFromNow());
+    if(allowMonthFallback(date, adu)) {
+      // If the adjusted unit is in months, then better to use
+      // the "monthsfromNow" which applies a special error margin
+      // for edge cases such as Jan-09 - Mar-09 being less than
+      // 2 months apart (when using a strict numeric definition).
+      // The third "ms" element in the array will handle the sign
+      // (past or future), so simply take the absolute value here.
+      adu[0] = abs(date.monthsFromNow());
+      adu[1] = 6;
+    }
+    return adu;
+  }
+
+  function allowMonthFallback(date, adu) {
+    // Allow falling back to monthsFromNow if the unit is in months...
+    return adu[1] === 6 ||
+    // ...or if it's === 4 weeks and there are more days than in the given month
+    (adu[1] === 5 && adu[0] === 4 && date.daysFromNow() >= getNewDate().daysInMonth());
+  }
+
+
+  // Date format token helpers
+
+  function createMeridianTokens(slice, caps) {
+    var fn = function(d, localeCode) {
+      var hours = callDateGet(d, 'Hours');
+      return getLocalization(localeCode)['ampm'][floor(hours / 12)] || '';
+    }
+    createFormatToken('t', fn, 1);
+    createFormatToken('tt', fn);
+    createFormatToken('T', fn, 1, 1);
+    createFormatToken('TT', fn, null, 2);
+  }
+
+  function createWeekdayTokens(slice, caps) {
+    var fn = function(d, localeCode) {
+      var dow = callDateGet(d, 'Day');
+      return getLocalization(localeCode)['weekdays'][dow];
+    }
+    createFormatToken('dow', fn, 3);
+    createFormatToken('Dow', fn, 3, 1);
+    createFormatToken('weekday', fn);
+    createFormatToken('Weekday', fn, null, 1);
+  }
+
+  function createMonthTokens(slice, caps) {
+    createMonthToken('mon', 0, 3);
+    createMonthToken('month', 0);
+
+    // For inflected month forms, namely Russian.
+    createMonthToken('month2', 1);
+    createMonthToken('month3', 2);
+  }
+
+  function createMonthToken(token, multiplier, slice) {
+    var fn = function(d, localeCode) {
+      var month = callDateGet(d, 'Month');
+      return getLocalization(localeCode)['months'][month + (multiplier * 12)];
+    };
+    createFormatToken(token, fn, slice);
+    createFormatToken(simpleCapitalize(token), fn, slice, 1);
+  }
+
+  function createFormatToken(t, fn, slice, caps) {
+    DateFormatTokens[t] = function(d, localeCode) {
+      var str = fn(d, localeCode);
+      if(slice) str = str.slice(0, slice);
+      if(caps)  str = str.slice(0, caps).toUpperCase() + str.slice(caps);
+      return str;
+    }
+  }
+
+  function createPaddedToken(t, fn, ms) {
+    DateFormatTokens[t] = fn;
+    DateFormatTokens[t + t] = function (d, localeCode) {
+      return padNumber(fn(d, localeCode), 2);
+    };
+    if(ms) {
+      DateFormatTokens[t + t + t] = function (d, localeCode) {
+        return padNumber(fn(d, localeCode), 3);
+      };
+      DateFormatTokens[t + t + t + t] = function (d, localeCode) {
+        return padNumber(fn(d, localeCode), 4);
+      };
+    }
+  }
+
+
+  // Date formatting helpers
+
+  function buildCompiledOutputFormat(format) {
+    var match = format.match(/(\{\w+\})|[^{}]+/g);
+    CompiledOutputFormats[format] = match.map(function(p) {
+      p.replace(/\{(\w+)\}/, function(full, token) {
+        p = DateFormatTokens[token] || token;
+        return token;
+      });
+      return p;
+    });
+  }
+
+  function executeCompiledOutputFormat(date, format, localeCode) {
+    var compiledFormat, length, i, t, result = '';
+    compiledFormat = CompiledOutputFormats[format];
+    for(i = 0, length = compiledFormat.length; i < length; i++) {
+      t = compiledFormat[i];
+      result += isFunction(t) ? t(date, localeCode) : t;
+    }
+    return result;
+  }
+
+  function formatDate(date, format, relative, localeCode) {
+    var adu;
+    if(!date.isValid()) {
+      return 'Invalid Date';
+    } else if(Date[format]) {
+      format = Date[format];
+    } else if(isFunction(format)) {
+      adu = getRelativeWithMonthFallback(date);
+      format = format.apply(date, adu.concat(getLocalization(localeCode)));
+    }
+    if(!format && relative) {
+      adu = adu || getRelativeWithMonthFallback(date);
+      // Adjust up if time is in ms, as this doesn't
+      // look very good for a standard relative date.
+      if(adu[1] === 0) {
+        adu[1] = 1;
+        adu[0] = 1;
+      }
+      return getLocalization(localeCode).getRelativeFormat(adu);
+    }
+    format = format || 'long';
+    if(format === 'short' || format === 'long' || format === 'full') {
+      format = getLocalization(localeCode)[format];
+    }
+
+    if(!CompiledOutputFormats[format]) {
+      buildCompiledOutputFormat(format);
+    }
+
+    return executeCompiledOutputFormat(date, format, localeCode);
+  }
+
+  // Date comparison helpers
+
+  function compareDate(d, find, localeCode, buffer, forceUTC) {
+    var p, t, min, max, override, capitalized, accuracy = 0, loBuffer = 0, hiBuffer = 0;
+    p = getExtendedDate(find, localeCode, null, forceUTC);
+    if(buffer > 0) {
+      loBuffer = hiBuffer = buffer;
+      override = true;
+    }
+    if(!p.date.isValid()) return false;
+    if(p.set && p.set.specificity) {
+      DateUnits.forEach(function(u, i) {
+        if(u.name === p.set.specificity) {
+          accuracy = u.multiplier(p.date, d - p.date) - 1;
+        }
+      });
+      capitalized = simpleCapitalize(p.set.specificity);
+      if(p.set['edge'] || p.set['shift']) {
+        p.date['beginningOf' + capitalized]();
+      }
+      if(p.set.specificity === 'month') {
+        max = p.date.clone()['endOf' + capitalized]().getTime();
+      }
+      if(!override && p.set['sign'] && p.set.specificity != 'millisecond') {
+        // If the time is relative, there can occasionally be an disparity between the relative date
+        // and "now", which it is being compared to, so set an extra buffer to account for this.
+        loBuffer = 50;
+        hiBuffer = -50;
+      }
+    }
+    t   = d.getTime();
+    min = p.date.getTime();
+    max = max || (min + accuracy);
+    max = compensateForTimezoneTraversal(d, min, max);
+    return t >= (min - loBuffer) && t <= (max + hiBuffer);
+  }
+
+  function compensateForTimezoneTraversal(d, min, max) {
+    var dMin, dMax, minOffset, maxOffset;
+    dMin = new date(min);
+    dMax = new date(max).utc(d.isUTC());
+    if(callDateGet(dMax, 'Hours') !== 23) {
+      minOffset = dMin.getTimezoneOffset();
+      maxOffset = dMax.getTimezoneOffset();
+      if(minOffset !== maxOffset) {
+        max += (maxOffset - minOffset).minutes();
+      }
+    }
+    return max;
+  }
+
+  function updateDate(d, params, reset, advance, prefer) {
+    var weekday, specificityIndex;
+
+    function getParam(key) {
+      return isDefined(params[key]) ? params[key] : params[key + 's'];
+    }
+
+    function paramExists(key) {
+      return isDefined(getParam(key));
+    }
+
+    function uniqueParamExists(key, isDay) {
+      return paramExists(key) || (isDay && paramExists('weekday'));
+    }
+
+    function canDisambiguate() {
+      switch(prefer) {
+        case -1: return d > getNewDate();
+        case  1: return d < getNewDate();
+      }
+    }
+
+    if(isNumber(params) && advance) {
+      // If param is a number and we're advancing, the number is presumed to be milliseconds.
+      params = { 'milliseconds': params };
+    } else if(isNumber(params)) {
+      // Otherwise just set the timestamp and return.
+      d.setTime(params);
+      return d;
+    }
+
+    // "date" can also be passed for the day
+    if(isDefined(params['date'])) {
+      params['day'] = params['date'];
+    }
+
+    // Reset any unit lower than the least specific unit set. Do not do this for weeks
+    // or for years. This needs to be performed before the acutal setting of the date
+    // because the order needs to be reversed in order to get the lowest specificity,
+    // also because higher order units can be overwritten by lower order units, such
+    // as setting hour: 3, minute: 345, etc.
+    iterateOverDateUnits(function(name, unit, i) {
+      var isDay = name === 'day';
+      if(uniqueParamExists(name, isDay)) {
+        params.specificity = name;
+        specificityIndex = +i;
+        return false;
+      } else if(reset && name !== 'week' && (!isDay || !paramExists('week'))) {
+        // Days are relative to months, not weeks, so don't reset if a week exists.
+        callDateSet(d, unit.method, (isDay ? 1 : 0));
+      }
+    });
+
+    // Now actually set or advance the date in order, higher units first.
+    DateUnits.forEach(function(u, i) {
+      var name = u.name, method = u.method, higherUnit = DateUnits[i - 1], value;
+      value = getParam(name)
+      if(isUndefined(value)) return;
+      if(advance) {
+        if(name === 'week') {
+          value  = (params['day'] || 0) + (value * 7);
+          method = 'Date';
+        }
+        value = (value * advance) + callDateGet(d, method);
+      } else if(name === 'month' && paramExists('day')) {
+        // When setting the month, there is a chance that we will traverse into a new month.
+        // This happens in DST shifts, for example June 1st DST jumping to January 1st
+        // (non-DST) will have a shift of -1:00 which will traverse into the previous year.
+        // Prevent this by proactively setting the day when we know it will be set again anyway.
+        // It can also happen when there are not enough days in the target month. This second
+        // situation is identical to checkMonthTraversal below, however when we are advancing
+        // we want to reset the date to "the last date in the target month". In the case of
+        // DST shifts, however, we want to avoid the "edges" of months as that is where this
+        // unintended traversal can happen. This is the reason for the different handling of
+        // two similar but slightly different situations.
+        //
+        // TL;DR This method avoids the edges of a month IF not advancing and the date is going
+        // to be set anyway, while checkMonthTraversal resets the date to the last day if advancing.
+        //
+        callDateSet(d, 'Date', 15);
+      }
+      callDateSet(d, method, value);
+      if(advance && name === 'month') {
+        checkMonthTraversal(d, value);
+      }
+    });
+
+
+    // If a weekday is included in the params, set it ahead of time and set the params
+    // to reflect the updated date so that resetting works properly.
+    if(!advance && !paramExists('day') && paramExists('weekday')) {
+      var weekday = getParam('weekday'), isAhead, futurePreferred;
+      d.setWeekday(weekday);
+    }
+
+    // If past or future is preferred, then the process of "disambiguation" will ensure that an
+    // ambiguous time/date ("4pm", "thursday", "June", etc.) will be in the past or future.
+    if(canDisambiguate()) {
+      iterateOverDateUnits(function(name, unit) {
+        var ambiguous = unit.ambiguous || (name === 'week' && paramExists('weekday'));
+        if(ambiguous && !uniqueParamExists(name, name === 'day')) {
+          d[unit.addMethod](prefer);
+          return false;
+        }
+      }, specificityIndex + 1);
+    }
+    return d;
+  }
+
+  // The ISO format allows times strung together without a demarcating ":", so make sure
+  // that these markers are now optional.
+  function prepareTime(format, loc, iso) {
+    var timeSuffixMapping = {'h':0,'m':1,'s':2}, add;
+    loc = loc || English;
+    return format.replace(/{([a-z])}/g, function(full, token) {
+      var separators = [],
+          isHours = token === 'h',
+          tokenIsRequired = isHours && !iso;
+      if(token === 't') {
+        return loc['ampm'].join('|');
+      } else {
+        if(isHours) {
+          separators.push(':');
+        }
+        if(add = loc['timeSuffixes'][timeSuffixMapping[token]]) {
+          separators.push(add + '\\s*');
+        }
+        return separators.length === 0 ? '' : '(?:' + separators.join('|') + ')' + (tokenIsRequired ? '' : '?');
+      }
+    });
+  }
+
+
+  // If the month is being set, then we don't want to accidentally
+  // traverse into a new month just because the target month doesn't have enough
+  // days. In other words, "5 months ago" from July 30th is still February, even
+  // though there is no February 30th, so it will of necessity be February 28th
+  // (or 29th in the case of a leap year).
+
+  function checkMonthTraversal(date, targetMonth) {
+    if(targetMonth < 0) {
+      targetMonth = targetMonth % 12 + 12;
+    }
+    if(targetMonth % 12 != callDateGet(date, 'Month')) {
+      callDateSet(date, 'Date', 0);
+    }
+  }
+
+  function createDate(args, prefer, forceUTC) {
+    var f, localeCode;
+    if(isNumber(args[1])) {
+      // If the second argument is a number, then we have an enumerated constructor type as in "new Date(2003, 2, 12);"
+      f = collectDateArguments(args)[0];
+    } else {
+      f          = args[0];
+      localeCode = args[1];
+    }
+    return getExtendedDate(f, localeCode, prefer, forceUTC).date;
+  }
+
+  function invalidateDate(d) {
+    d.setTime(NaN);
+  }
+
+  function buildDateUnits() {
+    DateUnitsReversed = DateUnits.concat().reverse();
+    DateArgumentUnits = DateUnits.concat();
+    DateArgumentUnits.splice(2,1);
+  }
+
+
+  /***
+   * @method [units]Since([d], [locale] = currentLocale)
+   * @returns Number
+   * @short Returns the time since [d] in the appropriate unit.
+   * @extra [d] will accept a date object, timestamp, or text format. If not specified, [d] is assumed to be now. [locale] can be passed to specify the locale that the date is in. %[unit]Ago% is provided as an alias to make this more readable when [d] is assumed to be the current date. For more see @date_format.
+   *
+   * @set
+   *   millisecondsSince
+   *   secondsSince
+   *   minutesSince
+   *   hoursSince
+   *   daysSince
+   *   weeksSince
+   *   monthsSince
+   *   yearsSince
+   *
+   * @example
+   *
+   *   Date.create().millisecondsSince('1 hour ago') -> 3,600,000
+   *   Date.create().daysSince('1 week ago')         -> 7
+   *   Date.create().yearsSince('15 years ago')      -> 15
+   *   Date.create('15 years ago').yearsAgo()        -> 15
+   *
+   ***
+   * @method [units]Ago()
+   * @returns Number
+   * @short Returns the time ago in the appropriate unit.
+   *
+   * @set
+   *   millisecondsAgo
+   *   secondsAgo
+   *   minutesAgo
+   *   hoursAgo
+   *   daysAgo
+   *   weeksAgo
+   *   monthsAgo
+   *   yearsAgo
+   *
+   * @example
+   *
+   *   Date.create('last year').millisecondsAgo() -> 3,600,000
+   *   Date.create('last year').daysAgo()         -> 7
+   *   Date.create('last year').yearsAgo()        -> 15
+   *
+   ***
+   * @method [units]Until([d], [locale] = currentLocale)
+   * @returns Number
+   * @short Returns the time until [d] in the appropriate unit.
+   * @extra [d] will accept a date object, timestamp, or text format. If not specified, [d] is assumed to be now. [locale] can be passed to specify the locale that the date is in. %[unit]FromNow% is provided as an alias to make this more readable when [d] is assumed to be the current date. For more see @date_format.
+   *
+   * @set
+   *   millisecondsUntil
+   *   secondsUntil
+   *   minutesUntil
+   *   hoursUntil
+   *   daysUntil
+   *   weeksUntil
+   *   monthsUntil
+   *   yearsUntil
+   *
+   * @example
+   *
+   *   Date.create().millisecondsUntil('1 hour from now') -> 3,600,000
+   *   Date.create().daysUntil('1 week from now')         -> 7
+   *   Date.create().yearsUntil('15 years from now')      -> 15
+   *   Date.create('15 years from now').yearsFromNow()    -> 15
+   *
+   ***
+   * @method [units]FromNow()
+   * @returns Number
+   * @short Returns the time from now in the appropriate unit.
+   *
+   * @set
+   *   millisecondsFromNow
+   *   secondsFromNow
+   *   minutesFromNow
+   *   hoursFromNow
+   *   daysFromNow
+   *   weeksFromNow
+   *   monthsFromNow
+   *   yearsFromNow
+   *
+   * @example
+   *
+   *   Date.create('next year').millisecondsFromNow() -> 3,600,000
+   *   Date.create('next year').daysFromNow()         -> 7
+   *   Date.create('next year').yearsFromNow()        -> 15
+   *
+   ***
+   * @method add[Units](<num>, [reset] = false)
+   * @returns Date
+   * @short Adds <num> of the unit to the date. If [reset] is true, all lower units will be reset.
+   * @extra Note that "months" is ambiguous as a unit of time. If the target date falls on a day that does not exist (ie. August 31 -> February 31), the date will be shifted to the last day of the month. Don't use %addMonths% if you need precision.
+   *
+   * @set
+   *   addMilliseconds
+   *   addSeconds
+   *   addMinutes
+   *   addHours
+   *   addDays
+   *   addWeeks
+   *   addMonths
+   *   addYears
+   *
+   * @example
+   *
+   *   Date.create().addMilliseconds(5) -> current time + 5 milliseconds
+   *   Date.create().addDays(5)         -> current time + 5 days
+   *   Date.create().addYears(5)        -> current time + 5 years
+   *
+   ***
+   * @method isLast[Unit]()
+   * @returns Boolean
+   * @short Returns true if the date is last week/month/year.
+   *
+   * @set
+   *   isLastWeek
+   *   isLastMonth
+   *   isLastYear
+   *
+   * @example
+   *
+   *   Date.create('yesterday').isLastWeek()  -> true or false?
+   *   Date.create('yesterday').isLastMonth() -> probably not...
+   *   Date.create('yesterday').isLastYear()  -> even less likely...
+   *
+   ***
+   * @method isThis[Unit]()
+   * @returns Boolean
+   * @short Returns true if the date is this week/month/year.
+   *
+   * @set
+   *   isThisWeek
+   *   isThisMonth
+   *   isThisYear
+   *
+   * @example
+   *
+   *   Date.create('tomorrow').isThisWeek()  -> true or false?
+   *   Date.create('tomorrow').isThisMonth() -> probably...
+   *   Date.create('tomorrow').isThisYear()  -> signs point to yes...
+   *
+   ***
+   * @method isNext[Unit]()
+   * @returns Boolean
+   * @short Returns true if the date is next week/month/year.
+   *
+   * @set
+   *   isNextWeek
+   *   isNextMonth
+   *   isNextYear
+   *
+   * @example
+   *
+   *   Date.create('tomorrow').isNextWeek()  -> true or false?
+   *   Date.create('tomorrow').isNextMonth() -> probably not...
+   *   Date.create('tomorrow').isNextYear()  -> even less likely...
+   *
+   ***
+   * @method beginningOf[Unit]()
+   * @returns Date
+   * @short Sets the date to the beginning of the appropriate unit.
+   *
+   * @set
+   *   beginningOfDay
+   *   beginningOfWeek
+   *   beginningOfMonth
+   *   beginningOfYear
+   *
+   * @example
+   *
+   *   Date.create().beginningOfDay()   -> the beginning of today (resets the time)
+   *   Date.create().beginningOfWeek()  -> the beginning of the week
+   *   Date.create().beginningOfMonth() -> the beginning of the month
+   *   Date.create().beginningOfYear()  -> the beginning of the year
+   *
+   ***
+   * @method endOf[Unit]()
+   * @returns Date
+   * @short Sets the date to the end of the appropriate unit.
+   *
+   * @set
+   *   endOfDay
+   *   endOfWeek
+   *   endOfMonth
+   *   endOfYear
+   *
+   * @example
+   *
+   *   Date.create().endOfDay()   -> the end of today (sets the time to 23:59:59.999)
+   *   Date.create().endOfWeek()  -> the end of the week
+   *   Date.create().endOfMonth() -> the end of the month
+   *   Date.create().endOfYear()  -> the end of the year
+   *
+   ***/
+
+  function buildDateMethods() {
+    extendSimilar(date, true, true, DateUnits, function(methods, u, i) {
+      var name = u.name, caps = simpleCapitalize(name), multiplier = u.multiplier(), since, until;
+      u.addMethod = 'add' + caps + 's';
+      // "since/until now" only count "past" an integer, i.e. "2 days ago" is
+      // anything between 2 - 2.999 days. The default margin of error is 0.999,
+      // but "months" have an inherently larger margin, as the number of days
+      // in a given month may be significantly less than the number of days in
+      // the average month, so for example "30 days" before March 15 may in fact
+      // be 1 month ago. Years also have a margin of error due to leap years,
+      // but this is roughly 0.999 anyway (365 / 365.25). Other units do not
+      // technically need the error margin applied to them but this accounts
+      // for discrepancies like (15).hoursAgo() which technically creates the
+      // current date first, then creates a date 15 hours before and compares
+      // them, the discrepancy between the creation of the 2 dates means that
+      // they may actually be 15.0001 hours apart. Milliseconds don't have
+      // fractions, so they won't be subject to this error margin.
+      function applyErrorMargin(ms) {
+        var num      = ms / multiplier,
+            fraction = num % 1,
+            error    = u.error || 0.999;
+        if(fraction && abs(fraction % 1) > error) {
+          num = round(num);
+        }
+        return num < 0 ? ceil(num) : floor(num);
+      }
+      since = function(f, localeCode) {
+        return applyErrorMargin(this.getTime() - date.create(f, localeCode).getTime());
+      };
+      until = function(f, localeCode) {
+        return applyErrorMargin(date.create(f, localeCode).getTime() - this.getTime());
+      };
+      methods[name+'sAgo']     = until;
+      methods[name+'sUntil']   = until;
+      methods[name+'sSince']   = since;
+      methods[name+'sFromNow'] = since;
+      methods[u.addMethod] = function(num, reset) {
+        var set = {};
+        set[name] = num;
+        return this.advance(set, reset);
+      };
+      buildNumberToDateAlias(u, multiplier);
+      if(i < 3) {
+        ['Last','This','Next'].forEach(function(shift) {
+          methods['is' + shift + caps] = function() {
+            return compareDate(this, shift + ' ' + name, 'en');
+          };
+        });
+      }
+      if(i < 4) {
+        methods['beginningOf' + caps] = function() {
+          var set = {};
+          switch(name) {
+            case 'year':  set['year']    = callDateGet(this, 'FullYear'); break;
+            case 'month': set['month']   = callDateGet(this, 'Month');    break;
+            case 'day':   set['day']     = callDateGet(this, 'Date');     break;
+            case 'week':  set['weekday'] = 0; break;
+          }
+          return this.set(set, true);
+        };
+        methods['endOf' + caps] = function() {
+          var set = { 'hours': 23, 'minutes': 59, 'seconds': 59, 'milliseconds': 999 };
+          switch(name) {
+            case 'year':  set['month']   = 11; set['day'] = 31; break;
+            case 'month': set['day']     = this.daysInMonth();  break;
+            case 'week':  set['weekday'] = 6;                   break;
+          }
+          return this.set(set, true);
+        };
+      }
+    });
+  }
+
+  function buildCoreInputFormats() {
+    English.addFormat('([+-])?(\\d{4,4})[-.]?{full_month}[-.]?(\\d{1,2})?', true, ['year_sign','year','month','date'], false, true);
+    English.addFormat('(\\d{1,2})[-.\\/]{full_month}(?:[-.\\/](\\d{2,4}))?', true, ['date','month','year'], true);
+    English.addFormat('{full_month}[-.](\\d{4,4})', false, ['month','year']);
+    English.addFormat('\\/Date\\((\\d+(?:[+-]\\d{4,4})?)\\)\\/', false, ['timestamp'])
+    English.addFormat(prepareTime(RequiredTime, English), false, TimeFormat)
+
+    // When a new locale is initialized it will have the CoreDateFormats initialized by default.
+    // From there, adding new formats will push them in front of the previous ones, so the core
+    // formats will be the last to be reached. However, the core formats themselves have English
+    // months in them, which means that English needs to first be initialized and creates a race
+    // condition. I'm getting around this here by adding these generalized formats in the order
+    // specific -> general, which will mean they will be added to the English localization in
+    // general -> specific order, then chopping them off the front and reversing to get the correct
+    // order. Note that there are 7 formats as 2 have times which adds a front and a back format.
+    CoreDateFormats = English.compiledFormats.slice(0,7).reverse();
+    English.compiledFormats = English.compiledFormats.slice(7).concat(CoreDateFormats);
+  }
+
+  function buildFormatTokens() {
+
+    createPaddedToken('f', function(d) {
+      return callDateGet(d, 'Milliseconds');
+    }, true);
+
+    createPaddedToken('s', function(d) {
+      return callDateGet(d, 'Seconds');
+    });
+
+    createPaddedToken('m', function(d) {
+      return callDateGet(d, 'Minutes');
+    });
+
+    createPaddedToken('h', function(d) {
+      return callDateGet(d, 'Hours') % 12 || 12;
+    });
+
+    createPaddedToken('H', function(d) {
+      return callDateGet(d, 'Hours');
+    });
+
+    createPaddedToken('d', function(d) {
+      return callDateGet(d, 'Date');
+    });
+
+    createPaddedToken('M', function(d) {
+      return callDateGet(d, 'Month') + 1;
+    });
+
+    createMeridianTokens();
+    createWeekdayTokens();
+    createMonthTokens();
+
+    // Aliases
+    DateFormatTokens['ms']           = DateFormatTokens['f'];
+    DateFormatTokens['milliseconds'] = DateFormatTokens['f'];
+    DateFormatTokens['seconds']      = DateFormatTokens['s'];
+    DateFormatTokens['minutes']      = DateFormatTokens['m'];
+    DateFormatTokens['hours']        = DateFormatTokens['h'];
+    DateFormatTokens['24hr']         = DateFormatTokens['H'];
+    DateFormatTokens['12hr']         = DateFormatTokens['h'];
+    DateFormatTokens['date']         = DateFormatTokens['d'];
+    DateFormatTokens['day']          = DateFormatTokens['d'];
+    DateFormatTokens['year']         = DateFormatTokens['yyyy'];
+
+  }
+
+  function buildFormatShortcuts() {
+    extendSimilar(date, true, true, 'short,long,full', function(methods, name) {
+      methods[name] = function(localeCode) {
+        return formatDate(this, name, false, localeCode);
+      }
+    });
+  }
+
+  function buildAsianDigits() {
+    KanjiDigits.split('').forEach(function(digit, value) {
+      var holder;
+      if(value > 9) {
+        value = pow(10, value - 9);
+      }
+      AsianDigitMap[digit] = value;
+    });
+    simpleMerge(AsianDigitMap, NumberNormalizeMap);
+    // Kanji numerals may also be included in phrases which are text-based rather
+    // than actual numbers such as Chinese weekdays (ไธŠๅ‘จไธ‰), and "the day before
+    // yesterday" (ไธ€ๆ˜จๆ—ฅ) in Japanese, so don't match these.
+    AsianDigitReg = regexp('([ๆœŸ้€ฑๅ‘จ])?([' + KanjiDigits + FullWidthDigits + ']+)(?!ๆ˜จ)', 'g');
+  }
+
+   /***
+   * @method is[Day]()
+   * @returns Boolean
+   * @short Returns true if the date falls on that day.
+   * @extra Also available: %isYesterday%, %isToday%, %isTomorrow%, %isWeekday%, and %isWeekend%.
+   *
+   * @set
+   *   isToday
+   *   isYesterday
+   *   isTomorrow
+   *   isWeekday
+   *   isWeekend
+   *   isSunday
+   *   isMonday
+   *   isTuesday
+   *   isWednesday
+   *   isThursday
+   *   isFriday
+   *   isSaturday
+   *
+   * @example
+   *
+   *   Date.create('tomorrow').isToday() -> false
+   *   Date.create('thursday').isTomorrow() -> ?
+   *   Date.create('yesterday').isWednesday() -> ?
+   *   Date.create('today').isWeekend() -> ?
+   *
+   ***
+   * @method isFuture()
+   * @returns Boolean
+   * @short Returns true if the date is in the future.
+   * @example
+   *
+   *   Date.create('next week').isFuture() -> true
+   *   Date.create('last week').isFuture() -> false
+   *
+   ***
+   * @method isPast()
+   * @returns Boolean
+   * @short Returns true if the date is in the past.
+   * @example
+   *
+   *   Date.create('last week').isPast() -> true
+   *   Date.create('next week').isPast() -> false
+   *
+   ***/
+  function buildRelativeAliases() {
+    var special  = 'today,yesterday,tomorrow,weekday,weekend,future,past'.split(',');
+    var weekdays = English['weekdays'].slice(0,7);
+    var months   = English['months'].slice(0,12);
+    extendSimilar(date, true, true, special.concat(weekdays).concat(months), function(methods, name) {
+      methods['is'+ simpleCapitalize(name)] = function(utc) {
+       return this.is(name, 0, utc);
+      };
+    });
+  }
+
+  function buildUTCAliases() {
+    // Don't want to use extend here as it will override
+    // the actual "utc" method on the prototype.
+    if(date['utc']) return;
+    date['utc'] = {
+
+        'create': function() {
+          return createDate(arguments, 0, true);
+        },
+
+        'past': function() {
+          return createDate(arguments, -1, true);
+        },
+
+        'future': function() {
+          return createDate(arguments, 1, true);
+        }
+    };
+  }
+
+  function setDateProperties() {
+    extend(date, false , true, {
+      'RFC1123': '{Dow}, {dd} {Mon} {yyyy} {HH}:{mm}:{ss} {tz}',
+      'RFC1036': '{Weekday}, {dd}-{Mon}-{yy} {HH}:{mm}:{ss} {tz}',
+      'ISO8601_DATE': '{yyyy}-{MM}-{dd}',
+      'ISO8601_DATETIME': '{yyyy}-{MM}-{dd}T{HH}:{mm}:{ss}.{fff}{isotz}'
+    });
+  }
+
+
+  extend(date, false, true, {
+
+     /***
+     * @method Date.create(<d>, [locale] = currentLocale)
+     * @returns Date
+     * @short Alternate Date constructor which understands many different text formats, a timestamp, or another date.
+     * @extra If no argument is given, date is assumed to be now. %Date.create% additionally can accept enumerated parameters as with the standard date constructor. [locale] can be passed to specify the locale that the date is in. When unspecified, the current locale (default is English) is assumed. UTC-based dates can be created through the %utc% object. For more see @date_format.
+     * @set
+     *   Date.utc.create
+     *
+     * @example
+     *
+     *   Date.create('July')          -> July of this year
+     *   Date.create('1776')          -> 1776
+     *   Date.create('today')         -> today
+     *   Date.create('wednesday')     -> This wednesday
+     *   Date.create('next friday')   -> Next friday
+     *   Date.create('July 4, 1776')  -> July 4, 1776
+     *   Date.create(-446806800000)   -> November 5, 1955
+     *   Date.create(1776, 6, 4)      -> July 4, 1776
+     *   Date.create('1776ๅนด07ๆœˆ04ๆ—ฅ', 'ja') -> July 4, 1776
+     *   Date.utc.create('July 4, 1776', 'en')  -> July 4, 1776
+     *
+     ***/
+    'create': function() {
+      return createDate(arguments);
+    },
+
+     /***
+     * @method Date.past(<d>, [locale] = currentLocale)
+     * @returns Date
+     * @short Alternate form of %Date.create% with any ambiguity assumed to be the past.
+     * @extra For example %"Sunday"% can be either "the Sunday coming up" or "the Sunday last" depending on context. Note that dates explicitly in the future ("next Sunday") will remain in the future. This method simply provides a hint when ambiguity exists. UTC-based dates can be created through the %utc% object. For more, see @date_format.
+     * @set
+     *   Date.utc.past
+     *
+     * @example
+     *
+     *   Date.past('July')          -> July of this year or last depending on the current month
+     *   Date.past('Wednesday')     -> This wednesday or last depending on the current weekday
+     *
+     ***/
+    'past': function() {
+      return createDate(arguments, -1);
+    },
+
+     /***
+     * @method Date.future(<d>, [locale] = currentLocale)
+     * @returns Date
+     * @short Alternate form of %Date.create% with any ambiguity assumed to be the future.
+     * @extra For example %"Sunday"% can be either "the Sunday coming up" or "the Sunday last" depending on context. Note that dates explicitly in the past ("last Sunday") will remain in the past. This method simply provides a hint when ambiguity exists. UTC-based dates can be created through the %utc% object. For more, see @date_format.
+     * @set
+     *   Date.utc.future
+     *
+     * @example
+     *
+     *   Date.future('July')          -> July of this year or next depending on the current month
+     *   Date.future('Wednesday')     -> This wednesday or next depending on the current weekday
+     *
+     ***/
+    'future': function() {
+      return createDate(arguments, 1);
+    },
+
+     /***
+     * @method Date.addLocale(<code>, <set>)
+     * @returns Locale
+     * @short Adds a locale <set> to the locales understood by Sugar.
+     * @extra For more see @date_format.
+     *
+     ***/
+    'addLocale': function(localeCode, set) {
+      return setLocalization(localeCode, set);
+    },
+
+     /***
+     * @method Date.setLocale(<code>)
+     * @returns Locale
+     * @short Sets the current locale to be used with dates.
+     * @extra Sugar has support for 13 locales that are available through the "Date Locales" package. In addition you can define a new locale with %Date.addLocale%. For more see @date_format.
+     *
+     ***/
+    'setLocale': function(localeCode, set) {
+      var loc = getLocalization(localeCode, false);
+      CurrentLocalization = loc;
+      // The code is allowed to be more specific than the codes which are required:
+      // i.e. zh-CN or en-US. Currently this only affects US date variants such as 8/10/2000.
+      if(localeCode && localeCode != loc['code']) {
+        loc['code'] = localeCode;
+      }
+      return loc;
+    },
+
+     /***
+     * @method Date.getLocale([code] = current)
+     * @returns Locale
+     * @short Gets the locale for the given code, or the current locale.
+     * @extra The resulting locale object can be manipulated to provide more control over date localizations. For more about locales, see @date_format.
+     *
+     ***/
+    'getLocale': function(localeCode) {
+      return !localeCode ? CurrentLocalization : getLocalization(localeCode, false);
+    },
+
+     /**
+     * @method Date.addFormat(<format>, <match>, [code] = null)
+     * @returns Nothing
+     * @short Manually adds a new date input format.
+     * @extra This method allows fine grained control for alternate formats. <format> is a string that can have regex tokens inside. <match> is an array of the tokens that each regex capturing group will map to, for example %year%, %date%, etc. For more, see @date_format.
+     *
+     **/
+    'addFormat': function(format, match, localeCode) {
+      addDateInputFormat(getLocalization(localeCode), format, match);
+    }
+
+  });
+
+  extend(date, true, true, {
+
+     /***
+     * @method set(<set>, [reset] = false)
+     * @returns Date
+     * @short Sets the date object.
+     * @extra This method can accept multiple formats including a single number as a timestamp, an object, or enumerated parameters (as with the Date constructor). If [reset] is %true%, any units more specific than those passed will be reset.
+     *
+     * @example
+     *
+     *   new Date().set({ year: 2011, month: 11, day: 31 }) -> December 31, 2011
+     *   new Date().set(2011, 11, 31)                       -> December 31, 2011
+     *   new Date().set(86400000)                           -> 1 day after Jan 1, 1970
+     *   new Date().set({ year: 2004, month: 6 }, true)     -> June 1, 2004, 00:00:00.000
+     *
+     ***/
+    'set': function() {
+      var args = collectDateArguments(arguments);
+      return updateDate(this, args[0], args[1])
+    },
+
+     /***
+     * @method setWeekday()
+     * @returns Nothing
+     * @short Sets the weekday of the date.
+     * @extra In order to maintain a parallel with %getWeekday% (which itself is an alias for Javascript native %getDay%), Sunday is considered day %0%. This contrasts with ISO-8601 standard (used in %getISOWeek% and %setISOWeek%) which places Sunday at the end of the week (day 7). This effectively means that passing %0% to this method while in the middle of a week will rewind the date, where passing %7% will advance it.
+     *
+     * @example
+     *
+     *   d = new Date(); d.setWeekday(1); d; -> Monday of this week
+     *   d = new Date(); d.setWeekday(6); d; -> Saturday of this week
+     *
+     ***/
+    'setWeekday': function(dow) {
+      if(isUndefined(dow)) return;
+      return callDateSet(this, 'Date', callDateGet(this, 'Date') + dow - callDateGet(this, 'Day'));
+    },
+
+     /***
+     * @method setISOWeek()
+     * @returns Nothing
+     * @short Sets the week (of the year) as defined by the ISO-8601 standard.
+     * @extra Note that this standard places Sunday at the end of the week (day 7).
+     *
+     * @example
+     *
+     *   d = new Date(); d.setISOWeek(15); d; -> 15th week of the year
+     *
+     ***/
+    'setISOWeek': function(week) {
+      var weekday = callDateGet(this, 'Day') || 7;
+      if(isUndefined(week)) return;
+      this.set({ 'month': 0, 'date': 4 });
+      this.set({ 'weekday': 1 });
+      if(week > 1) {
+        this.addWeeks(week - 1);
+      }
+      if(weekday !== 1) {
+        this.advance({ 'days': weekday - 1 });
+      }
+      return this.getTime();
+    },
+
+     /***
+     * @method getISOWeek()
+     * @returns Number
+     * @short Gets the date's week (of the year) as defined by the ISO-8601 standard.
+     * @extra Note that this standard places Sunday at the end of the week (day 7). If %utc% is set on the date, the week will be according to UTC time.
+     *
+     * @example
+     *
+     *   new Date().getISOWeek()    -> today's week of the year
+     *
+     ***/
+    'getISOWeek': function() {
+      return getWeekNumber(this);
+    },
+
+     /***
+     * @method beginningOfISOWeek()
+     * @returns Date
+     * @short Set the date to the beginning of week as defined by this ISO-8601 standard.
+     * @extra Note that this standard places Monday at the start of the week.
+     * @example
+     *
+     *   Date.create().beginningOfISOWeek() -> Monday
+     *
+     ***/
+    'beginningOfISOWeek': function() {
+      var day = this.getDay();
+      if(day === 0) {
+        day = -6;
+      } else if(day !== 1) {
+        day = 1;
+      }
+      this.setWeekday(day);
+      return this.reset();
+    },
+
+     /***
+     * @method endOfISOWeek()
+     * @returns Date
+     * @short Set the date to the end of week as defined by this ISO-8601 standard.
+     * @extra Note that this standard places Sunday at the end of the week.
+     * @example
+     *
+     *   Date.create().endOfISOWeek() -> Sunday
+     *
+     ***/
+    'endOfISOWeek': function() {
+      if(this.getDay() !== 0) {
+        this.setWeekday(7);
+      }
+      return this.endOfDay()
+    },
+
+     /***
+     * @method getUTCOffset([iso])
+     * @returns String
+     * @short Returns a string representation of the offset from UTC time. If [iso] is true the offset will be in ISO8601 format.
+     * @example
+     *
+     *   new Date().getUTCOffset()     -> "+0900"
+     *   new Date().getUTCOffset(true) -> "+09:00"
+     *
+     ***/
+    'getUTCOffset': function(iso) {
+      var offset = this._utc ? 0 : this.getTimezoneOffset();
+      var colon  = iso === true ? ':' : '';
+      if(!offset && iso) return 'Z';
+      return padNumber(floor(-offset / 60), 2, true) + colon + padNumber(abs(offset % 60), 2);
+    },
+
+     /***
+     * @method utc([on] = true)
+     * @returns Date
+     * @short Sets the internal utc flag for the date. When on, UTC-based methods will be called internally.
+     * @extra For more see @date_format.
+     * @example
+     *
+     *   new Date().utc(true)
+     *   new Date().utc(false)
+     *
+     ***/
+    'utc': function(set) {
+      defineProperty(this, '_utc', set === true || arguments.length === 0);
+      return this;
+    },
+
+     /***
+     * @method isUTC()
+     * @returns Boolean
+     * @short Returns true if the date has no timezone offset.
+     * @extra This will also return true for utc-based dates (dates that have the %utc% method set true). Note that even if the utc flag is set, %getTimezoneOffset% will always report the same thing as Javascript always reports that based on the environment's locale.
+     * @example
+     *
+     *   new Date().isUTC()           -> true or false?
+     *   new Date().utc(true).isUTC() -> true
+     *
+     ***/
+    'isUTC': function() {
+      return !!this._utc || this.getTimezoneOffset() === 0;
+    },
+
+     /***
+     * @method advance(<set>, [reset] = false)
+     * @returns Date
+     * @short Sets the date forward.
+     * @extra This method can accept multiple formats including an object, a string in the format %3 days%, a single number as milliseconds, or enumerated parameters (as with the Date constructor). If [reset] is %true%, any units more specific than those passed will be reset. For more see @date_format.
+     * @example
+     *
+     *   new Date().advance({ year: 2 }) -> 2 years in the future
+     *   new Date().advance('2 days')    -> 2 days in the future
+     *   new Date().advance(0, 2, 3)     -> 2 months 3 days in the future
+     *   new Date().advance(86400000)    -> 1 day in the future
+     *
+     ***/
+    'advance': function() {
+      var args = collectDateArguments(arguments, true);
+      return updateDate(this, args[0], args[1], 1);
+    },
+
+     /***
+     * @method rewind(<set>, [reset] = false)
+     * @returns Date
+     * @short Sets the date back.
+     * @extra This method can accept multiple formats including a single number as a timestamp, an object, or enumerated parameters (as with the Date constructor). If [reset] is %true%, any units more specific than those passed will be reset. For more see @date_format.
+     * @example
+     *
+     *   new Date().rewind({ year: 2 }) -> 2 years in the past
+     *   new Date().rewind(0, 2, 3)     -> 2 months 3 days in the past
+     *   new Date().rewind(86400000)    -> 1 day in the past
+     *
+     ***/
+    'rewind': function() {
+      var args = collectDateArguments(arguments, true);
+      return updateDate(this, args[0], args[1], -1);
+    },
+
+     /***
+     * @method isValid()
+     * @returns Boolean
+     * @short Returns true if the date is valid.
+     * @example
+     *
+     *   new Date().isValid()         -> true
+     *   new Date('flexor').isValid() -> false
+     *
+     ***/
+    'isValid': function() {
+      return !isNaN(this.getTime());
+    },
+
+     /***
+     * @method isAfter(<d>, [margin] = 0)
+     * @returns Boolean
+     * @short Returns true if the date is after the <d>.
+     * @extra [margin] is to allow extra margin of error (in ms). <d> will accept a date object, timestamp, or text format. If not specified, <d> is assumed to be now. See @date_format for more.
+     * @example
+     *
+     *   new Date().isAfter('tomorrow')  -> false
+     *   new Date().isAfter('yesterday') -> true
+     *
+     ***/
+    'isAfter': function(d, margin, utc) {
+      return this.getTime() > date.create(d).getTime() - (margin || 0);
+    },
+
+     /***
+     * @method isBefore(<d>, [margin] = 0)
+     * @returns Boolean
+     * @short Returns true if the date is before <d>.
+     * @extra [margin] is to allow extra margin of error (in ms). <d> will accept a date object, timestamp, or text format. If not specified, <d> is assumed to be now. See @date_format for more.
+     * @example
+     *
+     *   new Date().isBefore('tomorrow')  -> true
+     *   new Date().isBefore('yesterday') -> false
+     *
+     ***/
+    'isBefore': function(d, margin) {
+      return this.getTime() < date.create(d).getTime() + (margin || 0);
+    },
+
+     /***
+     * @method isBetween(<d1>, <d2>, [margin] = 0)
+     * @returns Boolean
+     * @short Returns true if the date falls between <d1> and <d2>.
+     * @extra [margin] is to allow extra margin of error (in ms). <d1> and <d2> will accept a date object, timestamp, or text format. If not specified, they are assumed to be now. See @date_format for more.
+     * @example
+     *
+     *   new Date().isBetween('yesterday', 'tomorrow')    -> true
+     *   new Date().isBetween('last year', '2 years ago') -> false
+     *
+     ***/
+    'isBetween': function(d1, d2, margin) {
+      var t  = this.getTime();
+      var t1 = date.create(d1).getTime();
+      var t2 = date.create(d2).getTime();
+      var lo = min(t1, t2);
+      var hi = max(t1, t2);
+      margin = margin || 0;
+      return (lo - margin < t) && (hi + margin > t);
+    },
+
+     /***
+     * @method isLeapYear()
+     * @returns Boolean
+     * @short Returns true if the date is a leap year.
+     * @example
+     *
+     *   Date.create('2000').isLeapYear() -> true
+     *
+     ***/
+    'isLeapYear': function() {
+      var year = callDateGet(this, 'FullYear');
+      return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
+    },
+
+     /***
+     * @method daysInMonth()
+     * @returns Number
+     * @short Returns the number of days in the date's month.
+     * @example
+     *
+     *   Date.create('May').daysInMonth()            -> 31
+     *   Date.create('February, 2000').daysInMonth() -> 29
+     *
+     ***/
+    'daysInMonth': function() {
+      return 32 - callDateGet(new date(callDateGet(this, 'FullYear'), callDateGet(this, 'Month'), 32), 'Date');
+    },
+
+     /***
+     * @method format(<format>, [locale] = currentLocale)
+     * @returns String
+     * @short Formats and outputs the date.
+     * @extra <format> can be a number of pre-determined formats or a string of tokens. Locale-specific formats are %short%, %long%, and %full% which have their own aliases and can be called with %date.short()%, etc. If <format> is not specified the %long% format is assumed. [locale] specifies a locale code to use (if not specified the current locale is used). See @date_format for more details.
+     *
+     * @set
+     *   short
+     *   long
+     *   full
+     *
+     * @example
+     *
+     *   Date.create().format()                                   -> ex. July 4, 2003
+     *   Date.create().format('{Weekday} {d} {Month}, {yyyy}')    -> ex. Monday July 4, 2003
+     *   Date.create().format('{hh}:{mm}')                        -> ex. 15:57
+     *   Date.create().format('{12hr}:{mm}{tt}')                  -> ex. 3:57pm
+     *   Date.create().format(Date.ISO8601_DATETIME)              -> ex. 2011-07-05 12:24:55.528Z
+     *   Date.create('last week').format('short', 'ja')                -> ex. ๅ…ˆ้€ฑ
+     *   Date.create('yesterday').format(function(value,unit,ms,loc) {
+     *     // value = 1, unit = 3, ms = -86400000, loc = [current locale object]
+     *   });                                                      -> ex. 1 day ago
+     *
+     ***/
+    'format': function(f, localeCode) {
+      return formatDate(this, f, false, localeCode);
+    },
+
+     /***
+     * @method relative([fn], [locale] = currentLocale)
+     * @returns String
+     * @short Returns a relative date string offset to the current time.
+     * @extra [fn] can be passed to provide for more granular control over the resulting string. [fn] is passed 4 arguments: the adjusted value, unit, offset in milliseconds, and a localization object. As an alternate syntax, [locale] can also be passed as the first (and only) parameter. For more, see @date_format.
+     * @example
+     *
+     *   Date.create('90 seconds ago').relative() -> 1 minute ago
+     *   Date.create('January').relative()        -> ex. 5 months ago
+     *   Date.create('January').relative('ja')    -> 3ใƒถๆœˆๅ‰
+     *   Date.create('120 minutes ago').relative(function(val,unit,ms,loc) {
+     *     // value = 2, unit = 3, ms = -7200, loc = [current locale object]
+     *   });                                      -> ex. 5 months ago
+     *
+     ***/
+    'relative': function(fn, localeCode) {
+      if(isString(fn)) {
+        localeCode = fn;
+        fn = null;
+      }
+      return formatDate(this, fn, true, localeCode);
+    },
+
+     /***
+     * @method is(<d>, [margin] = 0)
+     * @returns Boolean
+     * @short Returns true if the date is <d>.
+     * @extra <d> will accept a date object, timestamp, or text format. %is% additionally understands more generalized expressions like month/weekday names, 'today', etc, and compares to the precision implied in <d>. [margin] allows an extra margin of error in milliseconds.  For more, see @date_format.
+     * @example
+     *
+     *   Date.create().is('July')               -> true or false?
+     *   Date.create().is('1776')               -> false
+     *   Date.create().is('today')              -> true
+     *   Date.create().is('weekday')            -> true or false?
+     *   Date.create().is('July 4, 1776')       -> false
+     *   Date.create().is(-6106093200000)       -> false
+     *   Date.create().is(new Date(1776, 6, 4)) -> false
+     *
+     ***/
+    'is': function(d, margin, utc) {
+      var tmp, comp;
+      if(!this.isValid()) return;
+      if(isString(d)) {
+        d = d.trim().toLowerCase();
+        comp = this.clone().utc(utc);
+        switch(true) {
+          case d === 'future':  return this.getTime() > getNewDate().getTime();
+          case d === 'past':    return this.getTime() < getNewDate().getTime();
+          case d === 'weekday': return callDateGet(comp, 'Day') > 0 && callDateGet(comp, 'Day') < 6;
+          case d === 'weekend': return callDateGet(comp, 'Day') === 0 || callDateGet(comp, 'Day') === 6;
+          case (tmp = English['weekdays'].indexOf(d) % 7) > -1: return callDateGet(comp, 'Day') === tmp;
+          case (tmp = English['months'].indexOf(d) % 12) > -1:  return callDateGet(comp, 'Month') === tmp;
+        }
+      }
+      return compareDate(this, d, null, margin, utc);
+    },
+
+     /***
+     * @method reset([unit] = 'hours')
+     * @returns Date
+     * @short Resets the unit passed and all smaller units. Default is "hours", effectively resetting the time.
+     * @example
+     *
+     *   Date.create().reset('day')   -> Beginning of today
+     *   Date.create().reset('month') -> 1st of the month
+     *
+     ***/
+    'reset': function(unit) {
+      var params = {}, recognized;
+      unit = unit || 'hours';
+      if(unit === 'date') unit = 'days';
+      recognized = DateUnits.some(function(u) {
+        return unit === u.name || unit === u.name + 's';
+      });
+      params[unit] = unit.match(/^days?/) ? 1 : 0;
+      return recognized ? this.set(params, true) : this;
+    },
+
+     /***
+     * @method clone()
+     * @returns Date
+     * @short Clones the date.
+     * @example
+     *
+     *   Date.create().clone() -> Copy of now
+     *
+     ***/
+    'clone': function() {
+      var d = new date(this.getTime());
+      d.utc(!!this._utc);
+      return d;
+    }
+
+  });
+
+
+  // Instance aliases
+  extend(date, true, true, {
+
+     /***
+     * @method iso()
+     * @alias toISOString
+     *
+     ***/
+    'iso': function() {
+      return this.toISOString();
+    },
+
+     /***
+     * @method getWeekday()
+     * @returns Number
+     * @short Alias for %getDay%.
+     * @set
+     *   getUTCWeekday
+     *
+     * @example
+     *
+     +   Date.create().getWeekday();    -> (ex.) 3
+     +   Date.create().getUTCWeekday();    -> (ex.) 3
+     *
+     ***/
+    'getWeekday':    date.prototype.getDay,
+    'getUTCWeekday':    date.prototype.getUTCDay
+
+  });
+
+
+
+  /***
+   * Number module
+   *
+   ***/
+
+  /***
+   * @method [unit]()
+   * @returns Number
+   * @short Takes the number as a corresponding unit of time and converts to milliseconds.
+   * @extra Method names can be singular or plural.  Note that as "a month" is ambiguous as a unit of time, %months% will be equivalent to 30.4375 days, the average number in a month. Be careful using %months% if you need exact precision.
+   *
+   * @set
+   *   millisecond
+   *   milliseconds
+   *   second
+   *   seconds
+   *   minute
+   *   minutes
+   *   hour
+   *   hours
+   *   day
+   *   days
+   *   week
+   *   weeks
+   *   month
+   *   months
+   *   year
+   *   years
+   *
+   * @example
+   *
+   *   (5).milliseconds() -> 5
+   *   (10).hours()       -> 36000000
+   *   (1).day()          -> 86400000
+   *
+   ***
+   * @method [unit]Before([d], [locale] = currentLocale)
+   * @returns Date
+   * @short Returns a date that is <n> units before [d], where <n> is the number.
+   * @extra [d] will accept a date object, timestamp, or text format. Note that "months" is ambiguous as a unit of time. If the target date falls on a day that does not exist (ie. August 31 -> February 31), the date will be shifted to the last day of the month. Be careful using %monthsBefore% if you need exact precision. See @date_format for more.
+   *
+   * @set
+   *   millisecondBefore
+   *   millisecondsBefore
+   *   secondBefore
+   *   secondsBefore
+   *   minuteBefore
+   *   minutesBefore
+   *   hourBefore
+   *   hoursBefore
+   *   dayBefore
+   *   daysBefore
+   *   weekBefore
+   *   weeksBefore
+   *   monthBefore
+   *   monthsBefore
+   *   yearBefore
+   *   yearsBefore
+   *
+   * @example
+   *
+   *   (5).daysBefore('tuesday')          -> 5 days before tuesday of this week
+   *   (1).yearBefore('January 23, 1997') -> January 23, 1996
+   *
+   ***
+   * @method [unit]Ago()
+   * @returns Date
+   * @short Returns a date that is <n> units ago.
+   * @extra Note that "months" is ambiguous as a unit of time. If the target date falls on a day that does not exist (ie. August 31 -> February 31), the date will be shifted to the last day of the month. Be careful using %monthsAgo% if you need exact precision.
+   *
+   * @set
+   *   millisecondAgo
+   *   millisecondsAgo
+   *   secondAgo
+   *   secondsAgo
+   *   minuteAgo
+   *   minutesAgo
+   *   hourAgo
+   *   hoursAgo
+   *   dayAgo
+   *   daysAgo
+   *   weekAgo
+   *   weeksAgo
+   *   monthAgo
+   *   monthsAgo
+   *   yearAgo
+   *   yearsAgo
+   *
+   * @example
+   *
+   *   (5).weeksAgo() -> 5 weeks ago
+   *   (1).yearAgo()  -> January 23, 1996
+   *
+   ***
+   * @method [unit]After([d], [locale] = currentLocale)
+   * @returns Date
+   * @short Returns a date <n> units after [d], where <n> is the number.
+   * @extra [d] will accept a date object, timestamp, or text format. Note that "months" is ambiguous as a unit of time. If the target date falls on a day that does not exist (ie. August 31 -> February 31), the date will be shifted to the last day of the month. Be careful using %monthsAfter% if you need exact precision. See @date_format for more.
+   *
+   * @set
+   *   millisecondAfter
+   *   millisecondsAfter
+   *   secondAfter
+   *   secondsAfter
+   *   minuteAfter
+   *   minutesAfter
+   *   hourAfter
+   *   hoursAfter
+   *   dayAfter
+   *   daysAfter
+   *   weekAfter
+   *   weeksAfter
+   *   monthAfter
+   *   monthsAfter
+   *   yearAfter
+   *   yearsAfter
+   *
+   * @example
+   *
+   *   (5).daysAfter('tuesday')          -> 5 days after tuesday of this week
+   *   (1).yearAfter('January 23, 1997') -> January 23, 1998
+   *
+   ***
+   * @method [unit]FromNow()
+   * @returns Date
+   * @short Returns a date <n> units from now.
+   * @extra Note that "months" is ambiguous as a unit of time. If the target date falls on a day that does not exist (ie. August 31 -> February 31), the date will be shifted to the last day of the month. Be careful using %monthsFromNow% if you need exact precision.
+   *
+   * @set
+   *   millisecondFromNow
+   *   millisecondsFromNow
+   *   secondFromNow
+   *   secondsFromNow
+   *   minuteFromNow
+   *   minutesFromNow
+   *   hourFromNow
+   *   hoursFromNow
+   *   dayFromNow
+   *   daysFromNow
+   *   weekFromNow
+   *   weeksFromNow
+   *   monthFromNow
+   *   monthsFromNow
+   *   yearFromNow
+   *   yearsFromNow
+   *
+   * @example
+   *
+   *   (5).weeksFromNow() -> 5 weeks ago
+   *   (1).yearFromNow()  -> January 23, 1998
+   *
+   ***/
+  function buildNumberToDateAlias(u, multiplier) {
+    var name = u.name, methods = {};
+    function base() { return round(this * multiplier); }
+    function after() { return createDate(arguments)[u.addMethod](this);  }
+    function before() { return createDate(arguments)[u.addMethod](-this); }
+    methods[name] = base;
+    methods[name + 's'] = base;
+    methods[name + 'Before'] = before;
+    methods[name + 'sBefore'] = before;
+    methods[name + 'Ago'] = before;
+    methods[name + 'sAgo'] = before;
+    methods[name + 'After'] = after;
+    methods[name + 'sAfter'] = after;
+    methods[name + 'FromNow'] = after;
+    methods[name + 'sFromNow'] = after;
+    number.extend(methods);
+  }
+
+  extend(number, true, true, {
+
+     /***
+     * @method duration([locale] = currentLocale)
+     * @returns String
+     * @short Takes the number as milliseconds and returns a unit-adjusted localized string.
+     * @extra This method is the same as %Date#relative% without the localized equivalent of "from now" or "ago". [locale] can be passed as the first (and only) parameter. Note that this method is only available when the dates package is included.
+     * @example
+     *
+     *   (500).duration() -> '500 milliseconds'
+     *   (1200).duration() -> '1 second'
+     *   (75).minutes().duration() -> '1 hour'
+     *   (75).minutes().duration('es') -> '1 hora'
+     *
+     ***/
+    'duration': function(localeCode) {
+      return getLocalization(localeCode).getDuration(this);
+    }
+
+  });
+
+
+  English = CurrentLocalization = date.addLocale('en', {
+    'plural':     true,
+    'timeMarker': 'at',
+    'ampm':       'am,pm',
+    'months':     'January,February,March,April,May,June,July,August,September,October,November,December',
+    'weekdays':   'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday',
+    'units':      'millisecond:|s,second:|s,minute:|s,hour:|s,day:|s,week:|s,month:|s,year:|s',
+    'numbers':    'one,two,three,four,five,six,seven,eight,nine,ten',
+    'articles':   'a,an,the',
+    'tokens':     'the,st|nd|rd|th,of',
+    'short':      '{Month} {d}, {yyyy}',
+    'long':       '{Month} {d}, {yyyy} {h}:{mm}{tt}',
+    'full':       '{Weekday} {Month} {d}, {yyyy} {h}:{mm}:{ss}{tt}',
+    'past':       '{num} {unit} {sign}',
+    'future':     '{num} {unit} {sign}',
+    'duration':   '{num} {unit}',
+    'modifiers': [
+      { 'name': 'sign',  'src': 'ago|before', 'value': -1 },
+      { 'name': 'sign',  'src': 'from now|after|from|in|later', 'value': 1 },
+      { 'name': 'edge',  'src': 'last day', 'value': -2 },
+      { 'name': 'edge',  'src': 'end', 'value': -1 },
+      { 'name': 'edge',  'src': 'first day|beginning', 'value': 1 },
+      { 'name': 'shift', 'src': 'last', 'value': -1 },
+      { 'name': 'shift', 'src': 'the|this', 'value': 0 },
+      { 'name': 'shift', 'src': 'next', 'value': 1 }
+    ],
+    'dateParse': [
+      '{month} {year}',
+      '{shift} {unit=5-7}',
+      '{0?} {date}{1}',
+      '{0?} {edge} of {shift?} {unit=4-7?}{month?}{year?}'
+    ],
+    'timeParse': [
+      '{num} {unit} {sign}',
+      '{sign} {num} {unit}',
+      '{0} {num}{1} {day} of {month} {year?}',
+      '{weekday?} {month} {date}{1?} {year?}',
+      '{date} {month} {year}',
+      '{date} {month}',
+      '{shift} {weekday}',
+      '{shift} week {weekday}',
+      '{weekday} {2?} {shift} week',
+      '{num} {unit=4-5} {sign} {day}',
+      '{0?} {date}{1} of {month}',
+      '{0?}{month?} {date?}{1?} of {shift} {unit=6-7}'
+    ]
+  });
+
+  buildDateUnits();
+  buildDateMethods();
+  buildCoreInputFormats();
+  buildFormatTokens();
+  buildFormatShortcuts();
+  buildAsianDigits();
+  buildRelativeAliases();
+  buildUTCAliases();
+  setDateProperties();
+
+
+  /***
+   * @package Range
+   * @dependency core
+   * @description Ranges allow creating spans of numbers, strings, or dates. They can enumerate over specific points within that range, and be manipulated and compared.
+   *
+   ***/
+
+  function Range(start, end) {
+    this.start = cloneRangeMember(start);
+    this.end   = cloneRangeMember(end);
+  };
+
+  function getRangeMemberNumericValue(m) {
+    return isString(m) ? m.charCodeAt(0) : m;
+  }
+
+  function getRangeMemberPrimitiveValue(m) {
+    if(m == null) return m;
+    return isDate(m) ? m.getTime() : m.valueOf();
+  }
+
+  function cloneRangeMember(m) {
+    if(isDate(m)) {
+      return new date(m.getTime());
+    } else {
+      return getRangeMemberPrimitiveValue(m);
+    }
+  }
+
+  function isValidRangeMember(m) {
+    var val = getRangeMemberPrimitiveValue(m);
+    return !!val || val === 0;
+  }
+
+  function getDuration(amt) {
+    var match, val, unit;
+    if(isNumber(amt)) {
+      return amt;
+    }
+    match = amt.toLowerCase().match(/^(\d+)?\s?(\w+?)s?$/i);
+    val = parseInt(match[1]) || 1;
+    unit = match[2].slice(0,1).toUpperCase() + match[2].slice(1);
+    if(unit.match(/hour|minute|second/i)) {
+      unit += 's';
+    } else if(unit === 'Year') {
+      unit = 'FullYear';
+    } else if(unit === 'Day') {
+      unit = 'Date';
+    }
+    return [val, unit];
+  }
+
+  function incrementDate(current, amount) {
+    var num, unit, val, d;
+    if(isNumber(amount)) {
+      return new date(current.getTime() + amount);
+    }
+    num  = amount[0];
+    unit = amount[1];
+    val  = callDateGet(current, unit);
+    d    = new date(current.getTime());
+    callDateSet(d, unit, val + num);
+    return d;
+  }
+
+  function incrementString(current, amount) {
+    return string.fromCharCode(current.charCodeAt(0) + amount);
+  }
+
+  function incrementNumber(current, amount) {
+    return current + amount;
+  }
+
+  /***
+   * @method toString()
+   * @returns String
+   * @short Returns a string representation of the range.
+   * @example
+   *
+   *   Number.range(1, 5).toString()                               -> 1..5
+   *   Date.range(new Date(2003, 0), new Date(2005, 0)).toString() -> January 1, 2003..January 1, 2005
+   *
+   ***/
+
+  // Note: 'toString' doesn't appear in a for..in loop in IE even though
+  // hasOwnProperty reports true, so extend() can't be used here.
+  // Also tried simply setting the prototype = {} up front for all
+  // methods but GCC very oddly started dropping properties in the
+  // object randomly (maybe because of the global scope?) hence
+  // the need for the split logic here.
+  Range.prototype.toString = function() {
+    return this.isValid() ? this.start + ".." + this.end : 'Invalid Range';
+  };
+
+  extend(Range, true, true, {
+
+    /***
+     * @method isValid()
+     * @returns Boolean
+     * @short Returns true if the range is valid, false otherwise.
+     * @example
+     *
+     *   Date.range(new Date(2003, 0), new Date(2005, 0)).isValid() -> true
+     *   Number.range(NaN, NaN).isValid()                           -> false
+     *
+     ***/
+    'isValid': function() {
+      return isValidRangeMember(this.start) && isValidRangeMember(this.end) && typeof this.start === typeof this.end;
+    },
+
+    /***
+     * @method span()
+     * @returns Number
+     * @short Returns the span of the range. If the range is a date range, the value is in milliseconds.
+     * @extra The span includes both the start and the end.
+     * @example
+     *
+     *   Number.range(5, 10).span()                              -> 6
+     *   Date.range(new Date(2003, 0), new Date(2005, 0)).span() -> 94694400000
+     *
+     ***/
+    'span': function() {
+      return this.isValid() ? abs(
+        getRangeMemberNumericValue(this.end) - getRangeMemberNumericValue(this.start)
+      ) + 1 : NaN;
+    },
+
+    /***
+     * @method contains(<obj>)
+     * @returns Boolean
+     * @short Returns true if <obj> is contained inside the range. <obj> may be a value or another range.
+     * @example
+     *
+     *   Number.range(5, 10).contains(7)                                              -> true
+     *   Date.range(new Date(2003, 0), new Date(2005, 0)).contains(new Date(2004, 0)) -> true
+     *
+     ***/
+    'contains': function(obj) {
+      var self = this, arr;
+      if(obj == null) return false;
+      if(obj.start && obj.end) {
+        return obj.start >= this.start && obj.start <= this.end &&
+               obj.end   >= this.start && obj.end   <= this.end;
+      } else {
+        return obj >= this.start && obj <= this.end;
+      }
+    },
+
+    /***
+     * @method every(<amount>, [fn])
+     * @returns Array
+     * @short Iterates through the range for every <amount>, calling [fn] if it is passed. Returns an array of each increment visited.
+     * @extra In the case of date ranges, <amount> can also be a string, in which case it will increment a number of  units. Note that %(2).months()% first resolves to a number, which will be interpreted as milliseconds and is an approximation, so stepping through the actual months by passing %"2 months"% is usually preferable.
+     * @example
+     *
+     *   Number.range(2, 8).every(2)                                       -> [2,4,6,8]
+     *   Date.range(new Date(2003, 1), new Date(2003,3)).every("2 months") -> [...]
+     *
+     ***/
+    'every': function(amount, fn) {
+      var increment,
+          start   = this.start,
+          end     = this.end,
+          inverse = end < start,
+          current = start,
+          index   = 0,
+          result  = [];
+
+      if(isFunction(amount)) {
+        fn = amount;
+        amount = null;
+      }
+      amount = amount || 1;
+      if(isNumber(start)) {
+        increment = incrementNumber;
+      } else if(isString(start)) {
+        increment = incrementString;
+      } else if(isDate(start)) {
+        amount    = getDuration(amount);
+        increment = incrementDate;
+      }
+      // Avoiding infinite loops
+      if(inverse && amount > 0) {
+        amount *= -1;
+      }
+      while(inverse ? current >= end : current <= end) {
+        result.push(current);
+        if(fn) {
+          fn(current, index);
+        }
+        current = increment(current, amount);
+        index++;
+      }
+      return result;
+    },
+
+    /***
+     * @method union(<range>)
+     * @returns Range
+     * @short Returns a new range with the earliest starting point as its start, and the latest ending point as its end. If the two ranges do not intersect this will effectively remove the "gap" between them.
+     * @example
+     *
+     *   Number.range(1, 3).union(Number.range(2, 5)) -> 1..5
+     *   Date.range(new Date(2003, 1), new Date(2005, 1)).union(Date.range(new Date(2004, 1), new Date(2006, 1))) -> Jan 1, 2003..Jan 1, 2006
+     *
+     ***/
+    'union': function(range) {
+      return new Range(
+        this.start < range.start ? this.start : range.start,
+        this.end   > range.end   ? this.end   : range.end
+      );
+    },
+
+    /***
+     * @method intersect(<range>)
+     * @returns Range
+     * @short Returns a new range with the latest starting point as its start, and the earliest ending point as its end. If the two ranges do not intersect this will effectively produce an invalid range.
+     * @example
+     *
+     *   Number.range(1, 5).intersect(Number.range(4, 8)) -> 4..5
+     *   Date.range(new Date(2003, 1), new Date(2005, 1)).intersect(Date.range(new Date(2004, 1), new Date(2006, 1))) -> Jan 1, 2004..Jan 1, 2005
+     *
+     ***/
+    'intersect': function(range) {
+      if(range.start > this.end || range.end < this.start) {
+        return new Range(NaN, NaN);
+      }
+      return new Range(
+        this.start > range.start ? this.start : range.start,
+        this.end   < range.end   ? this.end   : range.end
+      );
+    },
+
+    /***
+     * @method clone()
+     * @returns Range
+     * @short Clones the range.
+     * @extra Members of the range will also be cloned.
+     * @example
+     *
+     *   Number.range(1, 5).clone() -> Returns a copy of the range.
+     *
+     ***/
+    'clone': function(range) {
+      return new Range(this.start, this.end);
+    },
+
+    /***
+     * @method clamp(<obj>)
+     * @returns Mixed
+     * @short Clamps <obj> to be within the range if it falls outside.
+     * @example
+     *
+     *   Number.range(1, 5).clamp(8) -> 5
+     *   Date.range(new Date(2010, 0), new Date(2012, 0)).clamp(new Date(2013, 0)) -> 2012-01
+     *
+     ***/
+    'clamp': function(obj) {
+      var clamped,
+          start = this.start,
+          end = this.end,
+          min = end < start ? end : start,
+          max = start > end ? start : end;
+      if(obj < min) {
+        clamped = min;
+      } else if(obj > max) {
+        clamped = max;
+      } else {
+        clamped = obj;
+      }
+      return cloneRangeMember(clamped);
+    }
+
+  });
+
+
+  /***
+   * Number module
+   ***
+   * @method Number.range([start], [end])
+   * @returns Range
+   * @short Creates a new range between [start] and [end]. See @ranges for more.
+   * @example
+   *
+   *   Number.range(5, 10)
+   *
+   ***
+   * String module
+   ***
+   * @method String.range([start], [end])
+   * @returns Range
+   * @short Creates a new range between [start] and [end]. See @ranges for more.
+   * @example
+   *
+   *   String.range('a', 'z')
+   *
+   ***
+   * Date module
+   ***
+   * @method Date.range([start], [end])
+   * @returns Range
+   * @short Creates a new range between [start] and [end].
+   * @extra If either [start] or [end] are null, they will default to the current date. See @ranges for more.
+   * @example
+   *
+   *   Date.range('today', 'tomorrow')
+   *
+   ***/
+  [number, string, date].forEach(function(klass) {
+     extend(klass, false, true, {
+
+      'range': function(start, end) {
+        if(klass.create) {
+          start = klass.create(start);
+          end   = klass.create(end);
+        }
+        return new Range(start, end);
+      }
+
+    });
+
+  });
+
+  /***
+   * Number module
+   *
+   ***/
+
+  extend(number, true, true, {
+
+    /***
+     * @method upto(<num>, [fn], [step] = 1)
+     * @returns Array
+     * @short Returns an array containing numbers from the number up to <num>.
+     * @extra Optionally calls [fn] callback for each number in that array. [step] allows multiples greater than 1.
+     * @example
+     *
+     *   (2).upto(6) -> [2, 3, 4, 5, 6]
+     *   (2).upto(6, function(n) {
+     *     // This function is called 5 times receiving n as the value.
+     *   });
+     *   (2).upto(8, null, 2) -> [2, 4, 6, 8]
+     *
+     ***/
+    'upto': function(num, fn, step) {
+      return number.range(this, num).every(step, fn);
+    },
+
+     /***
+     * @method clamp([start] = Infinity, [end] = Infinity)
+     * @returns Number
+     * @short Constrains the number so that it is between [start] and [end].
+     * @extra This will build a range object that has an equivalent %clamp% method.
+     * @example
+     *
+     *   (3).clamp(50, 100)  -> 50
+     *   (85).clamp(50, 100) -> 85
+     *
+     ***/
+    'clamp': function(start, end) {
+      return new Range(start, end).clamp(this);
+    },
+
+     /***
+     * @method cap([max] = Infinity)
+     * @returns Number
+     * @short Constrains the number so that it is no greater than [max].
+     * @extra This will build a range object that has an equivalent %cap% method.
+     * @example
+     *
+     *   (100).cap(80) -> 80
+     *
+     ***/
+    'cap': function(max) {
+      return this.clamp(Undefined, max);
+    }
+
+  });
+
+  extend(number, true, true, {
+
+    /***
+     * @method downto(<num>, [fn], [step] = 1)
+     * @returns Array
+     * @short Returns an array containing numbers from the number down to <num>.
+     * @extra Optionally calls [fn] callback for each number in that array. [step] allows multiples greater than 1.
+     * @example
+     *
+     *   (8).downto(3) -> [8, 7, 6, 5, 4, 3]
+     *   (8).downto(3, function(n) {
+     *     // This function is called 6 times receiving n as the value.
+     *   });
+     *   (8).downto(2, null, 2) -> [8, 6, 4, 2]
+     *
+     ***/
+    'downto': number.prototype.upto
+
+  });
+
+
+  /***
+   * Array module
+   *
+   ***/
+
+  extend(array, false, function(a) { return a instanceof Range; }, {
+
+    'create': function(range) {
+      return range.every();
+    }
+
+  });
+
+
+  /***
+   * @package Function
+   * @dependency core
+   * @description Lazy, throttled, and memoized functions, delayed functions and handling of timers, argument currying.
+   *
+   ***/
+
+  function setDelay(fn, ms, after, scope, args) {
+    // Delay of infinity is never called of course...
+    if(ms === Infinity) return;
+    if(!fn.timers) fn.timers = [];
+    if(!isNumber(ms)) ms = 1;
+    // This is a workaround for <= IE8, which apparently has the
+    // ability to call timeouts in the queue on the same tick (ms?)
+    // even if functionally they have already been cleared.
+    fn._canceled = false;
+    fn.timers.push(setTimeout(function(){
+      if(!fn._canceled) {
+        after.apply(scope, args || []);
+      }
+    }, ms));
+  }
+
+  extend(Function, true, true, {
+
+     /***
+     * @method lazy([ms] = 1, [immediate] = false, [limit] = Infinity)
+     * @returns Function
+     * @short Creates a lazy function that, when called repeatedly, will queue execution and wait [ms] milliseconds to execute.
+     * @extra If [immediate] is %true%, first execution will happen immediately, then lock. If [limit] is a fininte number, calls past [limit] will be ignored while execution is locked. Compare this to %throttle%, which will execute only once per [ms] milliseconds. Note that [ms] can also be a fraction. Calling %cancel% on a lazy function will clear the entire queue. For more see @functions.
+     * @example
+     *
+     *   (function() {
+     *     // Executes immediately.
+     *   }).lazy()();
+     *   (3).times(function() {
+     *     // Executes 3 times, with each execution 20ms later than the last.
+     *   }.lazy(20));
+     *   (100).times(function() {
+     *     // Executes 50 times, with each execution 20ms later than the last.
+     *   }.lazy(20, false, 50));
+     *
+     ***/
+    'lazy': function(ms, immediate, limit) {
+      var fn = this, queue = [], locked = false, execute, rounded, perExecution, result;
+      ms = ms || 1;
+      limit = limit || Infinity;
+      rounded = ceil(ms);
+      perExecution = round(rounded / ms) || 1;
+      execute = function() {
+        var queueLength = queue.length, maxPerRound;
+        if(queueLength == 0) return;
+        // Allow fractions of a millisecond by calling
+        // multiple times per actual timeout execution
+        maxPerRound = max(queueLength - perExecution, 0);
+        while(queueLength > maxPerRound) {
+          // Getting uber-meta here...
+          result = Function.prototype.apply.apply(fn, queue.shift());
+          queueLength--;
+        }
+        setDelay(lazy, rounded, function() {
+          locked = false;
+          execute();
+        });
+      }
+      function lazy() {
+        // If the execution has locked and it's immediate, then
+        // allow 1 less in the queue as 1 call has already taken place.
+        if(queue.length < limit - (locked && immediate ? 1 : 0)) {
+          queue.push([this, arguments]);
+        }
+        if(!locked) {
+          locked = true;
+          if(immediate) {
+            execute();
+          } else {
+            setDelay(lazy, rounded, execute);
+          }
+        }
+        // Return the memoized result
+        return result;
+      }
+      return lazy;
+    },
+
+     /***
+     * @method throttle([ms] = 1)
+     * @returns Function
+     * @short Creates a "throttled" version of the function that will only be executed once per <ms> milliseconds.
+     * @extra This is functionally equivalent to calling %lazy% with a [limit] of %1% and [immediate] as %true%. %throttle% is appropriate when you want to make sure a function is only executed at most once for a given duration. For more see @functions.
+     * @example
+     *
+     *   (3).times(function() {
+     *     // called only once. will wait 50ms until it responds again
+     *   }.throttle(50));
+     *
+     ***/
+    'throttle': function(ms) {
+      return this.lazy(ms, true, 1);
+    },
+
+     /***
+     * @method debounce([ms] = 1)
+     * @returns Function
+     * @short Creates a "debounced" function that postpones its execution until after <ms> milliseconds have passed.
+     * @extra This method is useful to execute a function after things have "settled down". A good example of this is when a user tabs quickly through form fields, execution of a heavy operation should happen after a few milliseconds when they have "settled" on a field. For more see @functions.
+     * @example
+     *
+     *   var fn = (function(arg1) {
+     *     // called once 50ms later
+     *   }).debounce(50); fn() fn() fn();
+     *
+     ***/
+    'debounce': function(ms) {
+      var fn = this;
+      function debounced() {
+        debounced.cancel();
+        setDelay(debounced, ms, fn, this, arguments);
+      };
+      return debounced;
+    },
+
+     /***
+     * @method delay([ms] = 1, [arg1], ...)
+     * @returns Function
+     * @short Executes the function after <ms> milliseconds.
+     * @extra Returns a reference to itself. %delay% is also a way to execute non-blocking operations that will wait until the CPU is free. Delayed functions can be canceled using the %cancel% method. Can also curry arguments passed in after <ms>.
+     * @example
+     *
+     *   (function(arg1) {
+     *     // called 1s later
+     *   }).delay(1000, 'arg1');
+     *
+     ***/
+    'delay': function(ms) {
+      var fn = this;
+      var args = multiArgs(arguments, null, 1);
+      setDelay(fn, ms, fn, fn, args);
+      return fn;
+    },
+
+     /***
+     * @method every([ms] = 1, [arg1], ...)
+     * @returns Function
+     * @short Executes the function every <ms> milliseconds.
+     * @extra Returns a reference to itself. Repeating functions with %every% can be canceled using the %cancel% method. Can also curry arguments passed in after <ms>.
+     * @example
+     *
+     *   (function(arg1) {
+     *     // called every 1s
+     *   }).every(1000, 'arg1');
+     *
+     ***/
+    'every': function(ms) {
+      var fn = this, args = arguments;
+      args = args.length > 1 ? multiArgs(args, null, 1) : [];
+      function execute () {
+        fn.apply(fn, args);
+        setDelay(fn, ms, execute);
+      }
+      setDelay(fn, ms, execute);
+      return fn;
+    },
+
+     /***
+     * @method cancel()
+     * @returns Function
+     * @short Cancels a delayed function scheduled to be run.
+     * @extra %delay%, %lazy%, %throttle%, and %debounce% can all set delays.
+     * @example
+     *
+     *   (function() {
+     *     alert('hay'); // Never called
+     *   }).delay(500).cancel();
+     *
+     ***/
+    'cancel': function() {
+      var timers = this.timers, timer;
+      if(isArray(timers)) {
+        while(timer = timers.shift()) {
+          clearTimeout(timer);
+        }
+      }
+      this._canceled = true;
+      return this;
+    },
+
+     /***
+     * @method after([num] = 1)
+     * @returns Function
+     * @short Creates a function that will execute after [num] calls.
+     * @extra %after% is useful for running a final callback after a series of asynchronous operations, when the order in which the operations will complete is unknown.
+     * @example
+     *
+     *   var fn = (function() {
+     *     // Will be executed once only
+     *   }).after(3); fn(); fn(); fn();
+     *
+     ***/
+    'after': function(num) {
+      var fn = this, counter = 0, storedArguments = [];
+      if(!isNumber(num)) {
+        num = 1;
+      } else if(num === 0) {
+        fn.call();
+        return fn;
+      }
+      return function() {
+        var ret;
+        storedArguments.push(multiArgs(arguments));
+        counter++;
+        if(counter == num) {
+          ret = fn.call(this, storedArguments);
+          counter = 0;
+          storedArguments = [];
+          return ret;
+        }
+      }
+    },
+
+     /***
+     * @method once()
+     * @returns Function
+     * @short Creates a function that will execute only once and store the result.
+     * @extra %once% is useful for creating functions that will cache the result of an expensive operation and use it on subsequent calls. Also it can be useful for creating initialization functions that only need to be run once.
+     * @example
+     *
+     *   var fn = (function() {
+     *     // Will be executed once only
+     *   }).once(); fn(); fn(); fn();
+     *
+     ***/
+    'once': function() {
+      return this.throttle(Infinity, true);
+    },
+
+     /***
+     * @method fill(<arg1>, <arg2>, ...)
+     * @returns Function
+     * @short Returns a new version of the function which when called will have some of its arguments pre-emptively filled in, also known as "currying".
+     * @extra Arguments passed to a "filled" function are generally appended to the curried arguments. However, if %undefined% is passed as any of the arguments to %fill%, it will be replaced, when the "filled" function is executed. This allows currying of arguments even when they occur toward the end of an argument list (the example demonstrates this much more clearly).
+     * @example
+     *
+     *   var delayOneSecond = setTimeout.fill(undefined, 1000);
+     *   delayOneSecond(function() {
+     *     // Will be executed 1s later
+     *   });
+     *
+     ***/
+    'fill': function() {
+      var fn = this, curried = multiArgs(arguments);
+      return function() {
+        var args = multiArgs(arguments);
+        curried.forEach(function(arg, index) {
+          if(arg != null || index >= args.length) args.splice(index, 0, arg);
+        });
+        return fn.apply(this, args);
+      }
+    }
+
+
+  });
+
+
+  /***
+   * @package Number
+   * @dependency core
+   * @description Number formatting, rounding (with precision), and ranges. Aliases to Math methods.
+   *
+   ***/
+
+
+  function abbreviateNumber(num, roundTo, str, mid, limit, bytes) {
+    var fixed        = num.toFixed(20),
+        decimalPlace = fixed.search(/\./),
+        numeralPlace = fixed.search(/[1-9]/),
+        significant  = decimalPlace - numeralPlace,
+        unit, i, divisor;
+    if(significant > 0) {
+      significant -= 1;
+    }
+    i = max(min(floor(significant / 3), limit === false ? str.length : limit), -mid);
+    unit = str.charAt(i + mid - 1);
+    if(significant < -9) {
+      i = -3;
+      roundTo = abs(significant) - 9;
+      unit = str.slice(0,1);
+    }
+    divisor = bytes ? pow(2, 10 * i) : pow(10, i * 3);
+    return withPrecision(num / divisor, roundTo || 0).format() + unit.trim();
+  }
+
+
+  extend(number, false, true, {
+
+    /***
+     * @method Number.random([n1], [n2])
+     * @returns Number
+     * @short Returns a random integer between [n1] and [n2].
+     * @extra If only 1 number is passed, the other will be 0. If none are passed, the number will be either 0 or 1.
+     * @example
+     *
+     *   Number.random(50, 100) -> ex. 85
+     *   Number.random(50)      -> ex. 27
+     *   Number.random()        -> ex. 0
+     *
+     ***/
+    'random': function(n1, n2) {
+      var minNum, maxNum;
+      if(arguments.length == 1) n2 = n1, n1 = 0;
+      minNum = min(n1 || 0, isUndefined(n2) ? 1 : n2);
+      maxNum = max(n1 || 0, isUndefined(n2) ? 1 : n2) + 1;
+      return floor((math.random() * (maxNum - minNum)) + minNum);
+    }
+
+  });
+
+  extend(number, true, true, {
+
+    /***
+     * @method log(<base> = Math.E)
+     * @returns Number
+     * @short Returns the logarithm of the number with base <base>, or natural logarithm of the number if <base> is undefined.
+     * @example
+     *
+     *   (64).log(2) -> 6
+     *   (9).log(3)  -> 2
+     *   (5).log()   -> 1.6094379124341003
+     *
+     ***/
+
+    'log': function(base) {
+       return math.log(this) / (base ? math.log(base) : 1);
+     },
+
+    /***
+     * @method abbr([precision] = 0)
+     * @returns String
+     * @short Returns an abbreviated form of the number.
+     * @extra [precision] will round to the given precision.
+     * @example
+     *
+     *   (1000).abbr()    -> "1k"
+     *   (1000000).abbr() -> "1m"
+     *   (1280).abbr(1)   -> "1.3k"
+     *
+     ***/
+    'abbr': function(precision) {
+      return abbreviateNumber(this, precision, 'kmbt', 0, 4);
+    },
+
+    /***
+     * @method metric([precision] = 0, [limit] = 1)
+     * @returns String
+     * @short Returns the number as a string in metric notation.
+     * @extra [precision] will round to the given precision. Both very large numbers and very small numbers are supported. [limit] is the upper limit for the units. The default is %1%, which is "kilo". If [limit] is %false%, the upper limit will be "exa". The lower limit is "nano", and cannot be changed.
+     * @example
+     *
+     *   (1000).metric()            -> "1k"
+     *   (1000000).metric()         -> "1,000k"
+     *   (1000000).metric(0, false) -> "1M"
+     *   (1249).metric(2) + 'g'     -> "1.25kg"
+     *   (0.025).metric() + 'm'     -> "25mm"
+     *
+     ***/
+    'metric': function(precision, limit) {
+      return abbreviateNumber(this, precision, 'nฮผm kMGTPE', 4, isUndefined(limit) ? 1 : limit);
+    },
+
+    /***
+     * @method bytes([precision] = 0, [limit] = 4)
+     * @returns String
+     * @short Returns an abbreviated form of the number, considered to be "Bytes".
+     * @extra [precision] will round to the given precision. [limit] is the upper limit for the units. The default is %4%, which is "terabytes" (TB). If [limit] is %false%, the upper limit will be "exa".
+     * @example
+     *
+     *   (1000).bytes()                 -> "1kB"
+     *   (1000).bytes(2)                -> "0.98kB"
+     *   ((10).pow(20)).bytes()         -> "90,949,470TB"
+     *   ((10).pow(20)).bytes(0, false) -> "87EB"
+     *
+     ***/
+    'bytes': function(precision, limit) {
+      return abbreviateNumber(this, precision, 'kMGTPE', 0, isUndefined(limit) ? 4 : limit, true) + 'B';
+    },
+
+    /***
+     * @method isInteger()
+     * @returns Boolean
+     * @short Returns true if the number has no trailing decimal.
+     * @example
+     *
+     *   (420).isInteger() -> true
+     *   (4.5).isInteger() -> false
+     *
+     ***/
+    'isInteger': function() {
+      return this % 1 == 0;
+    },
+
+    /***
+     * @method isOdd()
+     * @returns Boolean
+     * @short Returns true if the number is odd.
+     * @example
+     *
+     *   (3).isOdd()  -> true
+     *   (18).isOdd() -> false
+     *
+     ***/
+    'isOdd': function() {
+      return !isNaN(this) && !this.isMultipleOf(2);
+    },
+
+    /***
+     * @method isEven()
+     * @returns Boolean
+     * @short Returns true if the number is even.
+     * @example
+     *
+     *   (6).isEven()  -> true
+     *   (17).isEven() -> false
+     *
+     ***/
+    'isEven': function() {
+      return this.isMultipleOf(2);
+    },
+
+    /***
+     * @method isMultipleOf(<num>)
+     * @returns Boolean
+     * @short Returns true if the number is a multiple of <num>.
+     * @example
+     *
+     *   (6).isMultipleOf(2)  -> true
+     *   (17).isMultipleOf(2) -> false
+     *   (32).isMultipleOf(4) -> true
+     *   (34).isMultipleOf(4) -> false
+     *
+     ***/
+    'isMultipleOf': function(num) {
+      return this % num === 0;
+    },
+
+
+    /***
+     * @method format([place] = 0, [thousands] = ',', [decimal] = '.')
+     * @returns String
+     * @short Formats the number to a readable string.
+     * @extra If [place] is %undefined%, will automatically determine the place. [thousands] is the character used for the thousands separator. [decimal] is the character used for the decimal point.
+     * @example
+     *
+     *   (56782).format()           -> '56,782'
+     *   (56782).format(2)          -> '56,782.00'
+     *   (4388.43).format(2, ' ')      -> '4 388.43'
+     *   (4388.43).format(2, '.', ',') -> '4.388,43'
+     *
+     ***/
+    'format': function(place, thousands, decimal) {
+      var i, str, split, integer, fraction, result = '';
+      if(isUndefined(thousands)) {
+        thousands = ',';
+      }
+      if(isUndefined(decimal)) {
+        decimal = '.';
+      }
+      str      = (isNumber(place) ? withPrecision(this, place || 0).toFixed(max(place, 0)) : this.toString()).replace(/^-/, '');
+      split    = str.split('.');
+      integer  = split[0];
+      fraction = split[1];
+      for(i = integer.length; i > 0; i -= 3) {
+        if(i < integer.length) {
+          result = thousands + result;
+        }
+        result = integer.slice(max(0, i - 3), i) + result;
+      }
+      if(fraction) {
+        result += decimal + repeatString('0', (place || 0) - fraction.length) + fraction;
+      }
+      return (this < 0 ? '-' : '') + result;
+    },
+
+    /***
+     * @method hex([pad] = 1)
+     * @returns String
+     * @short Converts the number to hexidecimal.
+     * @extra [pad] will pad the resulting string to that many places.
+     * @example
+     *
+     *   (255).hex()   -> 'ff';
+     *   (255).hex(4)  -> '00ff';
+     *   (23654).hex() -> '5c66';
+     *
+     ***/
+    'hex': function(pad) {
+      return this.pad(pad || 1, false, 16);
+    },
+
+    /***
+     * @method times(<fn>)
+     * @returns Number
+     * @short Calls <fn> a number of times equivalent to the number.
+     * @example
+     *
+     *   (8).times(function(i) {
+     *     // This function is called 8 times.
+     *   });
+     *
+     ***/
+    'times': function(fn) {
+      if(fn) {
+        for(var i = 0; i < this; i++) {
+          fn.call(this, i);
+        }
+      }
+      return this.toNumber();
+    },
+
+    /***
+     * @method chr()
+     * @returns String
+     * @short Returns a string at the code point of the number.
+     * @example
+     *
+     *   (65).chr() -> "A"
+     *   (75).chr() -> "K"
+     *
+     ***/
+    'chr': function() {
+      return string.fromCharCode(this);
+    },
+
+    /***
+     * @method pad(<place> = 0, [sign] = false, [base] = 10)
+     * @returns String
+     * @short Pads a number with "0" to <place>.
+     * @extra [sign] allows you to force the sign as well (+05, etc). [base] can change the base for numeral conversion.
+     * @example
+     *
+     *   (5).pad(2)        -> '05'
+     *   (-5).pad(4)       -> '-0005'
+     *   (82).pad(3, true) -> '+082'
+     *
+     ***/
+    'pad': function(place, sign, base) {
+      return padNumber(this, place, sign, base);
+    },
+
+    /***
+     * @method ordinalize()
+     * @returns String
+     * @short Returns an ordinalized (English) string, i.e. "1st", "2nd", etc.
+     * @example
+     *
+     *   (1).ordinalize() -> '1st';
+     *   (2).ordinalize() -> '2nd';
+     *   (8).ordinalize() -> '8th';
+     *
+     ***/
+    'ordinalize': function() {
+      var suffix, num = abs(this), last = parseInt(num.toString().slice(-2));
+      return this + getOrdinalizedSuffix(last);
+    },
+
+    /***
+     * @method toNumber()
+     * @returns Number
+     * @short Returns a number. This is mostly for compatibility reasons.
+     * @example
+     *
+     *   (420).toNumber() -> 420
+     *
+     ***/
+    'toNumber': function() {
+      return parseFloat(this, 10);
+    }
+
+  });
+
+  /***
+   * @method round(<precision> = 0)
+   * @returns Number
+   * @short Shortcut for %Math.round% that also allows a <precision>.
+   *
+   * @example
+   *
+   *   (3.241).round()  -> 3
+   *   (-3.841).round() -> -4
+   *   (3.241).round(2) -> 3.24
+   *   (3748).round(-2) -> 3800
+   *
+   ***
+   * @method ceil(<precision> = 0)
+   * @returns Number
+   * @short Shortcut for %Math.ceil% that also allows a <precision>.
+   *
+   * @example
+   *
+   *   (3.241).ceil()  -> 4
+   *   (-3.241).ceil() -> -3
+   *   (3.241).ceil(2) -> 3.25
+   *   (3748).ceil(-2) -> 3800
+   *
+   ***
+   * @method floor(<precision> = 0)
+   * @returns Number
+   * @short Shortcut for %Math.floor% that also allows a <precision>.
+   *
+   * @example
+   *
+   *   (3.241).floor()  -> 3
+   *   (-3.841).floor() -> -4
+   *   (3.241).floor(2) -> 3.24
+   *   (3748).floor(-2) -> 3700
+   *
+   ***
+   * @method [math]()
+   * @returns Number
+   * @short Math related functions are mapped as shortcuts to numbers and are identical. Note that %Number#log% provides some special defaults.
+   *
+   * @set
+   *   abs
+   *   sin
+   *   asin
+   *   cos
+   *   acos
+   *   tan
+   *   atan
+   *   sqrt
+   *   exp
+   *   pow
+   *
+   * @example
+   *
+   *   (3).pow(3) -> 27
+   *   (-3).abs() -> 3
+   *   (1024).sqrt() -> 32
+   *
+   ***/
+
+  function buildNumber() {
+    function createRoundingFunction(fn) {
+      return function (precision) {
+        return precision ? withPrecision(this, precision, fn) : fn(this);
+      }
+    }
+    extend(number, true, true, {
+      'ceil':   createRoundingFunction(ceil),
+      'round':  createRoundingFunction(round),
+      'floor':  createRoundingFunction(floor)
+    });
+    extendSimilar(number, true, true, 'abs,pow,sin,asin,cos,acos,tan,atan,exp,pow,sqrt', function(methods, name) {
+      methods[name] = function(a, b) {
+        return math[name](this, a, b);
+      }
+    });
+  }
+
+  buildNumber();
+
+
+  /***
+   * @package Object
+   * @dependency core
+   * @description Object manipulation, type checking (isNumber, isString, ...), extended objects with hash-like methods available as instance methods.
+   *
+   * Much thanks to kangax for his informative aricle about how problems with instanceof and constructor
+   * http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
+   *
+   ***/
+
+  var ObjectTypeMethods = 'isObject,isNaN'.split(',');
+  var ObjectHashMethods = 'keys,values,select,reject,each,merge,clone,equal,watch,tap,has,toQueryString'.split(',');
+
+  function setParamsObject(obj, param, value, castBoolean) {
+    var reg = /^(.+?)(\[.*\])$/, paramIsArray, match, allKeys, key;
+    if(match = param.match(reg)) {
+      key = match[1];
+      allKeys = match[2].replace(/^\[|\]$/g, '').split('][');
+      allKeys.forEach(function(k) {
+        paramIsArray = !k || k.match(/^\d+$/);
+        if(!key && isArray(obj)) key = obj.length;
+        if(!hasOwnProperty(obj, key)) {
+          obj[key] = paramIsArray ? [] : {};
+        }
+        obj = obj[key];
+        key = k;
+      });
+      if(!key && paramIsArray) key = obj.length.toString();
+      setParamsObject(obj, key, value, castBoolean);
+    } else if(castBoolean && value === 'true') {
+      obj[param] = true;
+    } else if(castBoolean && value === 'false') {
+      obj[param] = false;
+    } else {
+      obj[param] = value;
+    }
+  }
+
+  function objectToQueryString(base, obj) {
+    var tmp;
+    // If a custom toString exists bail here and use that instead
+    if(isArray(obj) || (isObjectType(obj) && obj.toString === internalToString)) {
+      tmp = [];
+      iterateOverObject(obj, function(key, value) {
+        if(base) {
+          key = base + '[' + key + ']';
+        }
+        tmp.push(objectToQueryString(key, value));
+      });
+      return tmp.join('&');
+    } else {
+      if(!base) return '';
+      return sanitizeURIComponent(base) + '=' + (isDate(obj) ? obj.getTime() : sanitizeURIComponent(obj));
+    }
+  }
+
+  function sanitizeURIComponent(obj) {
+    // undefined, null, and NaN are represented as a blank string,
+    // while false and 0 are stringified. "+" is allowed in query string
+    return !obj && obj !== false && obj !== 0 ? '' : encodeURIComponent(obj).replace(/%20/g, '+');
+  }
+
+  function matchInObject(match, key, value) {
+    if(isRegExp(match)) {
+      return match.test(key);
+    } else if(isObjectType(match)) {
+      return match[key] === value;
+    } else {
+      return key === string(match);
+    }
+  }
+
+  function selectFromObject(obj, args, select) {
+    var match, result = obj instanceof Hash ? new Hash : {};
+    iterateOverObject(obj, function(key, value) {
+      match = false;
+      flattenedArgs(args, function(arg) {
+        if(matchInObject(arg, key, value)) {
+          match = true;
+        }
+      }, 1);
+      if(match === select) {
+        result[key] = value;
+      }
+    });
+    return result;
+  }
+
+
+  /***
+   * @method Object.is[Type](<obj>)
+   * @returns Boolean
+   * @short Returns true if <obj> is an object of that type.
+   * @extra %isObject% will return false on anything that is not an object literal, including instances of inherited classes. Note also that %isNaN% will ONLY return true if the object IS %NaN%. It does not mean the same as browser native %isNaN%, which returns true for anything that is "not a number".
+   *
+   * @set
+   *   isArray
+   *   isObject
+   *   isBoolean
+   *   isDate
+   *   isFunction
+   *   isNaN
+   *   isNumber
+   *   isString
+   *   isRegExp
+   *
+   * @example
+   *
+   *   Object.isArray([1,2,3])            -> true
+   *   Object.isDate(3)                   -> false
+   *   Object.isRegExp(/wasabi/)          -> true
+   *   Object.isObject({ broken:'wear' }) -> true
+   *
+   ***/
+  function buildTypeMethods() {
+    extendSimilar(object, false, true, ClassNames, function(methods, name) {
+      var method = 'is' + name;
+      ObjectTypeMethods.push(method);
+      methods[method] = typeChecks[name];
+    });
+  }
+
+  function buildObjectExtend() {
+    extend(object, false, function(){ return arguments.length === 0; }, {
+      'extend': function() {
+        var methods = ObjectTypeMethods.concat(ObjectHashMethods)
+        if(typeof EnumerableMethods !== 'undefined') {
+          methods = methods.concat(EnumerableMethods);
+        }
+        buildObjectInstanceMethods(methods, object);
+      }
+    });
+  }
+
+  extend(object, false, true, {
+      /***
+       * @method watch(<obj>, <prop>, <fn>)
+       * @returns Nothing
+       * @short Watches a property of <obj> and runs <fn> when it changes.
+       * @extra <fn> is passed three arguments: the property <prop>, the old value, and the new value. The return value of [fn] will be set as the new value. This method is useful for things such as validating or cleaning the value when it is set. Warning: this method WILL NOT work in browsers that don't support %Object.defineProperty% (IE 8 and below). This is the only method in Sugar that is not fully compatible with all browsers. %watch% is available as an instance method on extended objects.
+       * @example
+       *
+       *   Object.watch({ foo: 'bar' }, 'foo', function(prop, oldVal, newVal) {
+       *     // Will be run when the property 'foo' is set on the object.
+       *   });
+       *   Object.extended().watch({ foo: 'bar' }, 'foo', function(prop, oldVal, newVal) {
+       *     // Will be run when the property 'foo' is set on the object.
+       *   });
+       *
+       ***/
+    'watch': function(obj, prop, fn) {
+      if(!definePropertySupport) return;
+      var value = obj[prop];
+      object.defineProperty(obj, prop, {
+        'enumerable'  : true,
+        'configurable': true,
+        'get': function() {
+          return value;
+        },
+        'set': function(to) {
+          value = fn.call(obj, prop, value, to);
+        }
+      });
+    }
+  });
+
+  extend(object, false, function() { return arguments.length > 1; }, {
+
+    /***
+     * @method keys(<obj>, [fn])
+     * @returns Array
+     * @short Returns an array containing the keys in <obj>. Optionally calls [fn] for each key.
+     * @extra This method is provided for browsers that don't support it natively, and additionally is enhanced to accept the callback [fn]. Returned keys are in no particular order. %keys% is available as an instance method on extended objects.
+     * @example
+     *
+     *   Object.keys({ broken: 'wear' }) -> ['broken']
+     *   Object.keys({ broken: 'wear' }, function(key, value) {
+     *     // Called once for each key.
+     *   });
+     *   Object.extended({ broken: 'wear' }).keys() -> ['broken']
+     *
+     ***/
+    'keys': function(obj, fn) {
+      var keys = object.keys(obj);
+      keys.forEach(function(key) {
+        fn.call(obj, key, obj[key]);
+      });
+      return keys;
+    }
+
+  });
+
+  extend(object, false, true, {
+
+    'isObject': function(obj) {
+      return isPlainObject(obj);
+    },
+
+    'isNaN': function(obj) {
+      // This is only true of NaN
+      return isNumber(obj) && obj.valueOf() !== obj.valueOf();
+    },
+
+    /***
+     * @method equal(<a>, <b>)
+     * @returns Boolean
+     * @short Returns true if <a> and <b> are equal.
+     * @extra %equal% in Sugar is "egal", meaning the values are equal if they are "not observably distinguishable". Note that on extended objects the name is %equals% for readability.
+     * @example
+     *
+     *   Object.equal({a:2}, {a:2}) -> true
+     *   Object.equal({a:2}, {a:3}) -> false
+     *   Object.extended({a:2}).equals({a:3}) -> false
+     *
+     ***/
+    'equal': function(a, b) {
+      return isEqual(a, b);
+    },
+
+    /***
+     * @method Object.extended(<obj> = {})
+     * @returns Extended object
+     * @short Creates a new object, equivalent to %new Object()% or %{}%, but with extended methods.
+     * @extra See extended objects for more.
+     * @example
+     *
+     *   Object.extended()
+     *   Object.extended({ happy:true, pappy:false }).keys() -> ['happy','pappy']
+     *   Object.extended({ happy:true, pappy:false }).values() -> [true, false]
+     *
+     ***/
+    'extended': function(obj) {
+      return new Hash(obj);
+    },
+
+    /***
+     * @method merge(<target>, <source>, [deep] = false, [resolve] = true)
+     * @returns Merged object
+     * @short Merges all the properties of <source> into <target>.
+     * @extra Merges are shallow unless [deep] is %true%. Properties of <source> will win in the case of conflicts, unless [resolve] is %false%. [resolve] can also be a function that resolves the conflict. In this case it will be passed 3 arguments, %key%, %targetVal%, and %sourceVal%, with the context set to <source>. This will allow you to solve conflict any way you want, ie. adding two numbers together, etc. %merge% is available as an instance method on extended objects.
+     * @example
+     *
+     *   Object.merge({a:1},{b:2}) -> { a:1, b:2 }
+     *   Object.merge({a:1},{a:2}, false, false) -> { a:1 }
+     +   Object.merge({a:1},{a:2}, false, function(key, a, b) {
+     *     return a + b;
+     *   }); -> { a:3 }
+     *   Object.extended({a:1}).merge({b:2}) -> { a:1, b:2 }
+     *
+     ***/
+    'merge': function(target, source, deep, resolve) {
+      var key, sourceIsObject, targetIsObject, sourceVal, targetVal, conflict, result;
+      // Strings cannot be reliably merged thanks to
+      // their properties not being enumerable in < IE8.
+      if(target && typeof source !== 'string') {
+        for(key in source) {
+          if(!hasOwnProperty(source, key) || !target) continue;
+          sourceVal      = source[key];
+          targetVal      = target[key];
+          conflict       = isDefined(targetVal);
+          sourceIsObject = isObjectType(sourceVal);
+          targetIsObject = isObjectType(targetVal);
+          result         = conflict && resolve === false ? targetVal : sourceVal;
+
+          if(conflict) {
+            if(isFunction(resolve)) {
+              // Use the result of the callback as the result.
+              result = resolve.call(source, key, targetVal, sourceVal)
+            }
+          }
+
+          // Going deep
+          if(deep && (sourceIsObject || targetIsObject)) {
+            if(isDate(sourceVal)) {
+              result = new date(sourceVal.getTime());
+            } else if(isRegExp(sourceVal)) {
+              result = new regexp(sourceVal.source, getRegExpFlags(sourceVal));
+            } else {
+              if(!targetIsObject) target[key] = array.isArray(sourceVal) ? [] : {};
+              object.merge(target[key], sourceVal, deep, resolve);
+              continue;
+            }
+          }
+          target[key] = result;
+        }
+      }
+      return target;
+    },
+
+    /***
+     * @method values(<obj>, [fn])
+     * @returns Array
+     * @short Returns an array containing the values in <obj>. Optionally calls [fn] for each value.
+     * @extra Returned values are in no particular order. %values% is available as an instance method on extended objects.
+     * @example
+     *
+     *   Object.values({ broken: 'wear' }) -> ['wear']
+     *   Object.values({ broken: 'wear' }, function(value) {
+     *     // Called once for each value.
+     *   });
+     *   Object.extended({ broken: 'wear' }).values() -> ['wear']
+     *
+     ***/
+    'values': function(obj, fn) {
+      var values = [];
+      iterateOverObject(obj, function(k,v) {
+        values.push(v);
+        if(fn) fn.call(obj,v);
+      });
+      return values;
+    },
+
+    /***
+     * @method clone(<obj> = {}, [deep] = false)
+     * @returns Cloned object
+     * @short Creates a clone (copy) of <obj>.
+     * @extra Default is a shallow clone, unless [deep] is true. %clone% is available as an instance method on extended objects.
+     * @example
+     *
+     *   Object.clone({foo:'bar'})            -> { foo: 'bar' }
+     *   Object.clone()                       -> {}
+     *   Object.extended({foo:'bar'}).clone() -> { foo: 'bar' }
+     *
+     ***/
+    'clone': function(obj, deep) {
+      var target, klass;
+      if(!isObjectType(obj)) {
+        return obj;
+      }
+      klass = className(obj);
+      if(isDate(obj, klass) && obj.clone) {
+        // Preserve internal UTC flag when applicable.
+        return obj.clone();
+      } else if(isDate(obj, klass) || isRegExp(obj, klass)) {
+        return new obj.constructor(obj);
+      } else if(obj instanceof Hash) {
+        target = new Hash;
+      } else if(isArray(obj, klass)) {
+        target = [];
+      } else if(isPlainObject(obj, klass)) {
+        target = {};
+      } else {
+        throw new TypeError('Clone must be a basic data type.');
+      }
+      return object.merge(target, obj, deep);
+    },
+
+    /***
+     * @method Object.fromQueryString(<str>, [booleans] = false)
+     * @returns Object
+     * @short Converts the query string of a URL into an object.
+     * @extra If [booleans] is true, then %"true"% and %"false"% will be cast into booleans. All other values, including numbers will remain their string values.
+     * @example
+     *
+     *   Object.fromQueryString('foo=bar&broken=wear') -> { foo: 'bar', broken: 'wear' }
+     *   Object.fromQueryString('foo[]=1&foo[]=2')     -> { foo: ['1','2'] }
+     *   Object.fromQueryString('foo=true', true)      -> { foo: true }
+     *
+     ***/
+    'fromQueryString': function(str, castBoolean) {
+      var result = object.extended(), split;
+      str = str && str.toString ? str.toString() : '';
+      str.replace(/^.*?\?/, '').split('&').forEach(function(p) {
+        var split = p.split('=');
+        if(split.length !== 2) return;
+        setParamsObject(result, split[0], decodeURIComponent(split[1]), castBoolean);
+      });
+      return result;
+    },
+
+    /***
+     * @method Object.toQueryString(<obj>, [namespace] = null)
+     * @returns Object
+     * @short Converts the object into a query string.
+     * @extra Accepts deep nested objects and arrays. If [namespace] is passed, it will be prefixed to all param names.
+     * @example
+     *
+     *   Object.toQueryString({foo:'bar'})          -> 'foo=bar'
+     *   Object.toQueryString({foo:['a','b','c']})  -> 'foo[0]=a&foo[1]=b&foo[2]=c'
+     *   Object.toQueryString({name:'Bob'}, 'user') -> 'user[name]=Bob'
+     *
+     ***/
+    'toQueryString': function(obj, namespace) {
+      return objectToQueryString(namespace, obj);
+    },
+
+    /***
+     * @method tap(<obj>, <fn>)
+     * @returns Object
+     * @short Runs <fn> and returns <obj>.
+     * @extra  A string can also be used as a shortcut to a method. This method is used to run an intermediary function in the middle of method chaining. As a standalone method on the Object class it doesn't have too much use. The power of %tap% comes when using extended objects or modifying the Object prototype with Object.extend().
+     * @example
+     *
+     *   Object.extend();
+     *   [2,4,6].map(Math.exp).tap(function(arr) {
+     *     arr.pop()
+     *   });
+     *   [2,4,6].map(Math.exp).tap('pop').map(Math.round); ->  [7,55]
+     *
+     ***/
+    'tap': function(obj, arg) {
+      var fn = arg;
+      if(!isFunction(arg)) {
+        fn = function() {
+          if(arg) obj[arg]();
+        }
+      }
+      fn.call(obj, obj);
+      return obj;
+    },
+
+    /***
+     * @method has(<obj>, <key>)
+     * @returns Boolean
+     * @short Checks if <obj> has <key> using hasOwnProperty from Object.prototype.
+     * @extra This method is considered safer than %Object#hasOwnProperty% when using objects as hashes. See http://www.devthought.com/2012/01/18/an-object-is-not-a-hash/ for more.
+     * @example
+     *
+     *   Object.has({ foo: 'bar' }, 'foo') -> true
+     *   Object.has({ foo: 'bar' }, 'baz') -> false
+     *   Object.has({ hasOwnProperty: true }, 'foo') -> false
+     *
+     ***/
+    'has': function (obj, key) {
+      return hasOwnProperty(obj, key);
+    },
+
+    /***
+     * @method select(<obj>, <find>, ...)
+     * @returns Object
+     * @short Builds a new object containing the values specified in <find>.
+     * @extra When <find> is a string, that single key will be selected. It can also be a regex, selecting any key that matches, or an object which will match if the key also exists in that object, effectively doing an "intersect" operation on that object. Multiple selections may also be passed as an array or directly as enumerated arguments. %select% is available as an instance method on extended objects.
+     * @example
+     *
+     *   Object.select({a:1,b:2}, 'a')        -> {a:1}
+     *   Object.select({a:1,b:2}, /[a-z]/)    -> {a:1,ba:2}
+     *   Object.select({a:1,b:2}, {a:1})      -> {a:1}
+     *   Object.select({a:1,b:2}, 'a', 'b')   -> {a:1,b:2}
+     *   Object.select({a:1,b:2}, ['a', 'b']) -> {a:1,b:2}
+     *
+     ***/
+    'select': function (obj) {
+      return selectFromObject(obj, arguments, true);
+    },
+
+    /***
+     * @method reject(<obj>, <find>, ...)
+     * @returns Object
+     * @short Builds a new object containing all values except those specified in <find>.
+     * @extra When <find> is a string, that single key will be rejected. It can also be a regex, rejecting any key that matches, or an object which will match if the key also exists in that object, effectively "subtracting" that object. Multiple selections may also be passed as an array or directly as enumerated arguments. %reject% is available as an instance method on extended objects.
+     * @example
+     *
+     *   Object.reject({a:1,b:2}, 'a')        -> {b:2}
+     *   Object.reject({a:1,b:2}, /[a-z]/)    -> {}
+     *   Object.reject({a:1,b:2}, {a:1})      -> {b:2}
+     *   Object.reject({a:1,b:2}, 'a', 'b')   -> {}
+     *   Object.reject({a:1,b:2}, ['a', 'b']) -> {}
+     *
+     ***/
+    'reject': function (obj) {
+      return selectFromObject(obj, arguments, false);
+    }
+
+  });
+
+
+  buildTypeMethods();
+  buildObjectExtend();
+  buildObjectInstanceMethods(ObjectHashMethods, Hash);
+
+  /***
+   * @package RegExp
+   * @dependency core
+   * @description Escaping regexes and manipulating their flags.
+   *
+   * Note here that methods on the RegExp class like .exec and .test will fail in the current version of SpiderMonkey being
+   * used by CouchDB when using shorthand regex notation like /foo/. This is the reason for the intermixed use of shorthand
+   * and compiled regexes here. If you're using JS in CouchDB, it is safer to ALWAYS compile your regexes from a string.
+   *
+   ***/
+
+  extend(regexp, false, true, {
+
+   /***
+    * @method RegExp.escape(<str> = '')
+    * @returns String
+    * @short Escapes all RegExp tokens in a string.
+    * @example
+    *
+    *   RegExp.escape('really?')      -> 'really\?'
+    *   RegExp.escape('yes.')         -> 'yes\.'
+    *   RegExp.escape('(not really)') -> '\(not really\)'
+    *
+    ***/
+    'escape': function(str) {
+      return escapeRegExp(str);
+    }
+
+  });
+
+  extend(regexp, true, true, {
+
+   /***
+    * @method getFlags()
+    * @returns String
+    * @short Returns the flags of the regex as a string.
+    * @example
+    *
+    *   /texty/gim.getFlags('testy') -> 'gim'
+    *
+    ***/
+    'getFlags': function() {
+      return getRegExpFlags(this);
+    },
+
+   /***
+    * @method setFlags(<flags>)
+    * @returns RegExp
+    * @short Sets the flags on a regex and retuns a copy.
+    * @example
+    *
+    *   /texty/.setFlags('gim') -> now has global, ignoreCase, and multiline set
+    *
+    ***/
+    'setFlags': function(flags) {
+      return regexp(this.source, flags);
+    },
+
+   /***
+    * @method addFlag(<flag>)
+    * @returns RegExp
+    * @short Adds <flag> to the regex.
+    * @example
+    *
+    *   /texty/.addFlag('g') -> now has global flag set
+    *
+    ***/
+    'addFlag': function(flag) {
+      return this.setFlags(getRegExpFlags(this, flag));
+    },
+
+   /***
+    * @method removeFlag(<flag>)
+    * @returns RegExp
+    * @short Removes <flag> from the regex.
+    * @example
+    *
+    *   /texty/g.removeFlag('g') -> now has global flag removed
+    *
+    ***/
+    'removeFlag': function(flag) {
+      return this.setFlags(getRegExpFlags(this).replace(flag, ''));
+    }
+
+  });
+
+
+
+  /***
+   * @package String
+   * @dependency core
+   * @description String manupulation, escaping, encoding, truncation, and:conversion.
+   *
+   ***/
+
+  function getAcronym(word) {
+    var inflector = string.Inflector;
+    var word = inflector && inflector.acronyms[word];
+    if(isString(word)) {
+      return word;
+    }
+  }
+
+  function checkRepeatRange(num) {
+    num = +num;
+    if(num < 0 || num === Infinity) {
+      throw new RangeError('Invalid number');
+    }
+    return num;
+  }
+
+  function padString(num, padding) {
+    return repeatString(isDefined(padding) ? padding : ' ', num);
+  }
+
+  function truncateString(str, length, from, ellipsis, split) {
+    var str1, str2, len1, len2;
+    if(str.length <= length) {
+      return str.toString();
+    }
+    ellipsis = isUndefined(ellipsis) ? '...' : ellipsis;
+    switch(from) {
+      case 'left':
+        str2 = split ? truncateOnWord(str, length, true) : str.slice(str.length - length);
+        return ellipsis + str2;
+      case 'middle':
+        len1 = ceil(length / 2);
+        len2 = floor(length / 2);
+        str1 = split ? truncateOnWord(str, len1) : str.slice(0, len1);
+        str2 = split ? truncateOnWord(str, len2, true) : str.slice(str.length - len2);
+        return str1 + ellipsis + str2;
+      default:
+        str1 = split ? truncateOnWord(str, length) : str.slice(0, length);
+        return str1 + ellipsis;
+    }
+  }
+
+  function truncateOnWord(str, limit, fromLeft) {
+    if(fromLeft) {
+      return truncateOnWord(str.reverse(), limit).reverse();
+    }
+    var reg = regexp('(?=[' + getTrimmableCharacters() + '])');
+    var words = str.split(reg);
+    var count = 0;
+    return words.filter(function(word) {
+      count += word.length;
+      return count <= limit;
+    }).join('');
+  }
+
+  function numberOrIndex(str, n, from) {
+    if(isString(n)) {
+      n = str.indexOf(n);
+      if(n === -1) {
+        n = from ? str.length : 0;
+      }
+    }
+    return n;
+  }
+
+  var btoa, atob;
+
+  function buildBase64(key) {
+    if(globalContext.btoa) {
+      btoa = globalContext.btoa;
+      atob = globalContext.atob;
+      return;
+    }
+    var base64reg = /[^A-Za-z0-9\+\/\=]/g;
+    btoa = function(str) {
+      var output = '';
+      var chr1, chr2, chr3;
+      var enc1, enc2, enc3, enc4;
+      var i = 0;
+      do {
+        chr1 = str.charCodeAt(i++);
+        chr2 = str.charCodeAt(i++);
+        chr3 = str.charCodeAt(i++);
+        enc1 = chr1 >> 2;
+        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+        enc4 = chr3 & 63;
+        if (isNaN(chr2)) {
+          enc3 = enc4 = 64;
+        } else if (isNaN(chr3)) {
+          enc4 = 64;
+        }
+        output = output + key.charAt(enc1) + key.charAt(enc2) + key.charAt(enc3) + key.charAt(enc4);
+        chr1 = chr2 = chr3 = '';
+        enc1 = enc2 = enc3 = enc4 = '';
+      } while (i < str.length);
+      return output;
+    }
+    atob = function(input) {
+      var output = '';
+      var chr1, chr2, chr3;
+      var enc1, enc2, enc3, enc4;
+      var i = 0;
+      if(input.match(base64reg)) {
+        throw new Error('String contains invalid base64 characters');
+      }
+      input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
+      do {
+        enc1 = key.indexOf(input.charAt(i++));
+        enc2 = key.indexOf(input.charAt(i++));
+        enc3 = key.indexOf(input.charAt(i++));
+        enc4 = key.indexOf(input.charAt(i++));
+        chr1 = (enc1 << 2) | (enc2 >> 4);
+        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+        chr3 = ((enc3 & 3) << 6) | enc4;
+        output = output + chr(chr1);
+        if (enc3 != 64) {
+          output = output + chr(chr2);
+        }
+        if (enc4 != 64) {
+          output = output + chr(chr3);
+        }
+        chr1 = chr2 = chr3 = '';
+        enc1 = enc2 = enc3 = enc4 = '';
+      } while (i < input.length);
+      return output;
+    }
+  }
+
+  extend(string, true, false, {
+    /***
+     * @method repeat([num] = 0)
+     * @returns String
+     * @short Returns the string repeated [num] times.
+     * @example
+     *
+     *   'jumpy'.repeat(2) -> 'jumpyjumpy'
+     *   'a'.repeat(5)     -> 'aaaaa'
+     *   'a'.repeat(0)     -> ''
+     *
+     ***/
+    'repeat': function(num) {
+      num = checkRepeatRange(num);
+      return repeatString(this, num);
+    }
+
+  });
+
+  extend(string, true, function(reg) { return isRegExp(reg) || arguments.length > 2; }, {
+
+    /***
+     * @method startsWith(<find>, [pos] = 0, [case] = true)
+     * @returns Boolean
+     * @short Returns true if the string starts with <find>.
+     * @extra <find> may be either a string or regex. Search begins at [pos], which defaults to the entire string. Case sensitive if [case] is true.
+     * @example
+     *
+     *   'hello'.startsWith('hell')           -> true
+     *   'hello'.startsWith(/[a-h]/)          -> true
+     *   'hello'.startsWith('HELL')           -> false
+     *   'hello'.startsWith('ell', 1)         -> true
+     *   'hello'.startsWith('HELL', 0, false) -> true
+     *
+     ***/
+    'startsWith': function(reg) {
+      var args = arguments, pos = args[1], c = args[2], str = this, source;
+      if(pos) str = str.slice(pos);
+      if(isUndefined(c)) c = true;
+      source = isRegExp(reg) ? reg.source.replace('^', '') : escapeRegExp(reg);
+      return regexp('^' + source, c ? '' : 'i').test(str);
+    },
+
+    /***
+     * @method endsWith(<find>, [pos] = length, [case] = true)
+     * @returns Boolean
+     * @short Returns true if the string ends with <find>.
+     * @extra <find> may be either a string or regex. Search ends at [pos], which defaults to the entire string. Case sensitive if [case] is true.
+     * @example
+     *
+     *   'jumpy'.endsWith('py')            -> true
+     *   'jumpy'.endsWith(/[q-z]/)         -> true
+     *   'jumpy'.endsWith('MPY')           -> false
+     *   'jumpy'.endsWith('mp', 4)         -> false
+     *   'jumpy'.endsWith('MPY', 5, false) -> true
+     *
+     ***/
+    'endsWith': function(reg) {
+      var args = arguments, pos = args[1], c = args[2], str = this, source;
+      if(isDefined(pos)) str = str.slice(0, pos);
+      if(isUndefined(c)) c = true;
+      source = isRegExp(reg) ? reg.source.replace('$', '') : escapeRegExp(reg);
+      return regexp(source + '$', c ? '' : 'i').test(str);
+    }
+
+  });
+
+  extend(string, true, true, {
+
+     /***
+      * @method escapeRegExp()
+      * @returns String
+      * @short Escapes all RegExp tokens in the string.
+      * @example
+      *
+      *   'really?'.escapeRegExp()       -> 'really\?'
+      *   'yes.'.escapeRegExp()         -> 'yes\.'
+      *   '(not really)'.escapeRegExp() -> '\(not really\)'
+      *
+      ***/
+    'escapeRegExp': function() {
+      return escapeRegExp(this);
+    },
+
+     /***
+      * @method escapeURL([param] = false)
+      * @returns String
+      * @short Escapes characters in a string to make a valid URL.
+      * @extra If [param] is true, it will also escape valid URL characters for use as a URL parameter.
+      * @example
+      *
+      *   'http://foo.com/"bar"'.escapeURL()     -> 'http://foo.com/%22bar%22'
+      *   'http://foo.com/"bar"'.escapeURL(true) -> 'http%3A%2F%2Ffoo.com%2F%22bar%22'
+      *
+      ***/
+    'escapeURL': function(param) {
+      return param ? encodeURIComponent(this) : encodeURI(this);
+    },
+
+     /***
+      * @method unescapeURL([partial] = false)
+      * @returns String
+      * @short Restores escaped characters in a URL escaped string.
+      * @extra If [partial] is true, it will only unescape non-valid URL characters. [partial] is included here for completeness, but should very rarely be needed.
+      * @example
+      *
+      *   'http%3A%2F%2Ffoo.com%2Fthe%20bar'.unescapeURL()     -> 'http://foo.com/the bar'
+      *   'http%3A%2F%2Ffoo.com%2Fthe%20bar'.unescapeURL(true) -> 'http%3A%2F%2Ffoo.com%2Fthe bar'
+      *
+      ***/
+    'unescapeURL': function(param) {
+      return param ? decodeURI(this) : decodeURIComponent(this);
+    },
+
+     /***
+      * @method escapeHTML()
+      * @returns String
+      * @short Converts HTML characters to their entity equivalents.
+      * @example
+      *
+      *   '<p>some text</p>'.escapeHTML() -> '&lt;p&gt;some text&lt;/p&gt;'
+      *   'one & two'.escapeHTML()        -> 'one &amp; two'
+      *
+      ***/
+    'escapeHTML': function() {
+      return this.replace(/&/g,  '&amp;' )
+                 .replace(/</g,  '&lt;'  )
+                 .replace(/>/g,  '&gt;'  )
+                 .replace(/"/g,  '&quot;')
+                 .replace(/'/g,  '&apos;')
+                 .replace(/\//g, '&#x2f;');
+    },
+
+     /***
+      * @method unescapeHTML([partial] = false)
+      * @returns String
+      * @short Restores escaped HTML characters.
+      * @example
+      *
+      *   '&lt;p&gt;some text&lt;/p&gt;'.unescapeHTML() -> '<p>some text</p>'
+      *   'one &amp; two'.unescapeHTML()                -> 'one & two'
+      *
+      ***/
+    'unescapeHTML': function() {
+      return this.replace(/&lt;/g,   '<')
+                 .replace(/&gt;/g,   '>')
+                 .replace(/&quot;/g, '"')
+                 .replace(/&apos;/g, "'")
+                 .replace(/&#x2f;/g, '/')
+                 .replace(/&amp;/g,  '&');
+    },
+
+     /***
+      * @method encodeBase64()
+      * @returns String
+      * @short Encodes the string into base64 encoding.
+      * @extra This method wraps the browser native %btoa% when available, and uses a custom implementation when not available. It can also handle Unicode string encodings.
+      * @example
+      *
+      *   'gonna get encoded!'.encodeBase64()  -> 'Z29ubmEgZ2V0IGVuY29kZWQh'
+      *   'http://twitter.com/'.encodeBase64() -> 'aHR0cDovL3R3aXR0ZXIuY29tLw=='
+      *
+      ***/
+    'encodeBase64': function() {
+      return btoa(unescape(encodeURIComponent(this)));
+    },
+
+     /***
+      * @method decodeBase64()
+      * @returns String
+      * @short Decodes the string from base64 encoding.
+      * @extra This method wraps the browser native %atob% when available, and uses a custom implementation when not available. It can also handle Unicode string encodings.
+      * @example
+      *
+      *   'aHR0cDovL3R3aXR0ZXIuY29tLw=='.decodeBase64() -> 'http://twitter.com/'
+      *   'anVzdCBnb3QgZGVjb2RlZA=='.decodeBase64()     -> 'just got decoded!'
+      *
+      ***/
+    'decodeBase64': function() {
+      return decodeURIComponent(escape(atob(this)));
+    },
+
+    /***
+     * @method each([search] = single character, [fn])
+     * @returns Array
+     * @short Runs callback [fn] against each occurence of [search].
+     * @extra Returns an array of matches. [search] may be either a string or regex, and defaults to every character in the string.
+     * @example
+     *
+     *   'jumpy'.each() -> ['j','u','m','p','y']
+     *   'jumpy'.each(/[r-z]/) -> ['u','y']
+     *   'jumpy'.each(/[r-z]/, function(m) {
+     *     // Called twice: "u", "y"
+     *   });
+     *
+     ***/
+    'each': function(search, fn) {
+      var match, i, len;
+      if(isFunction(search)) {
+        fn = search;
+        search = /[\s\S]/g;
+      } else if(!search) {
+        search = /[\s\S]/g
+      } else if(isString(search)) {
+        search = regexp(escapeRegExp(search), 'gi');
+      } else if(isRegExp(search)) {
+        search = regexp(search.source, getRegExpFlags(search, 'g'));
+      }
+      match = this.match(search) || [];
+      if(fn) {
+        for(i = 0, len = match.length; i < len; i++) {
+          match[i] = fn.call(this, match[i], i, match) || match[i];
+        }
+      }
+      return match;
+    },
+
+    /***
+     * @method shift(<n>)
+     * @returns Array
+     * @short Shifts each character in the string <n> places in the character map.
+     * @example
+     *
+     *   'a'.shift(1)  -> 'b'
+     *   'ใ‚ฏ'.shift(1) -> 'ใ‚ฐ'
+     *
+     ***/
+    'shift': function(n) {
+      var result = '';
+      n = n || 0;
+      this.codes(function(c) {
+        result += chr(c + n);
+      });
+      return result;
+    },
+
+    /***
+     * @method codes([fn])
+     * @returns Array
+     * @short Runs callback [fn] against each character code in the string. Returns an array of character codes.
+     * @example
+     *
+     *   'jumpy'.codes() -> [106,117,109,112,121]
+     *   'jumpy'.codes(function(c) {
+     *     // Called 5 times: 106, 117, 109, 112, 121
+     *   });
+     *
+     ***/
+    'codes': function(fn) {
+      var codes = [], i, len;
+      for(i = 0, len = this.length; i < len; i++) {
+        var code = this.charCodeAt(i);
+        codes.push(code);
+        if(fn) fn.call(this, code, i);
+      }
+      return codes;
+    },
+
+    /***
+     * @method chars([fn])
+     * @returns Array
+     * @short Runs callback [fn] against each character in the string. Returns an array of characters.
+     * @example
+     *
+     *   'jumpy'.chars() -> ['j','u','m','p','y']
+     *   'jumpy'.chars(function(c) {
+     *     // Called 5 times: "j","u","m","p","y"
+     *   });
+     *
+     ***/
+    'chars': function(fn) {
+      return this.each(fn);
+    },
+
+    /***
+     * @method words([fn])
+     * @returns Array
+     * @short Runs callback [fn] against each word in the string. Returns an array of words.
+     * @extra A "word" here is defined as any sequence of non-whitespace characters.
+     * @example
+     *
+     *   'broken wear'.words() -> ['broken','wear']
+     *   'broken wear'.words(function(w) {
+     *     // Called twice: "broken", "wear"
+     *   });
+     *
+     ***/
+    'words': function(fn) {
+      return this.trim().each(/\S+/g, fn);
+    },
+
+    /***
+     * @method lines([fn])
+     * @returns Array
+     * @short Runs callback [fn] against each line in the string. Returns an array of lines.
+     * @example
+     *
+     *   'broken wear\nand\njumpy jump'.lines() -> ['broken wear','and','jumpy jump']
+     *   'broken wear\nand\njumpy jump'.lines(function(l) {
+     *     // Called three times: "broken wear", "and", "jumpy jump"
+     *   });
+     *
+     ***/
+    'lines': function(fn) {
+      return this.trim().each(/^.*$/gm, fn);
+    },
+
+    /***
+     * @method paragraphs([fn])
+     * @returns Array
+     * @short Runs callback [fn] against each paragraph in the string. Returns an array of paragraphs.
+     * @extra A paragraph here is defined as a block of text bounded by two or more line breaks.
+     * @example
+     *
+     *   'Once upon a time.\n\nIn the land of oz...'.paragraphs() -> ['Once upon a time.','In the land of oz...']
+     *   'Once upon a time.\n\nIn the land of oz...'.paragraphs(function(p) {
+     *     // Called twice: "Once upon a time.", "In teh land of oz..."
+     *   });
+     *
+     ***/
+    'paragraphs': function(fn) {
+      var paragraphs = this.trim().split(/[\r\n]{2,}/);
+      paragraphs = paragraphs.map(function(p) {
+        if(fn) var s = fn.call(p);
+        return s ? s : p;
+      });
+      return paragraphs;
+    },
+
+    /***
+     * @method isBlank()
+     * @returns Boolean
+     * @short Returns true if the string has a length of 0 or contains only whitespace.
+     * @example
+     *
+     *   ''.isBlank()      -> true
+     *   '   '.isBlank()   -> true
+     *   'noway'.isBlank() -> false
+     *
+     ***/
+    'isBlank': function() {
+      return this.trim().length === 0;
+    },
+
+    /***
+     * @method has(<find>)
+     * @returns Boolean
+     * @short Returns true if the string matches <find>.
+     * @extra <find> may be a string or regex.
+     * @example
+     *
+     *   'jumpy'.has('py')     -> true
+     *   'broken'.has(/[a-n]/) -> true
+     *   'broken'.has(/[s-z]/) -> false
+     *
+     ***/
+    'has': function(find) {
+      return this.search(isRegExp(find) ? find : escapeRegExp(find)) !== -1;
+    },
+
+
+    /***
+     * @method add(<str>, [index] = length)
+     * @returns String
+     * @short Adds <str> at [index]. Negative values are also allowed.
+     * @extra %insert% is provided as an alias, and is generally more readable when using an index.
+     * @example
+     *
+     *   'schfifty'.add(' five')      -> schfifty five
+     *   'dopamine'.insert('e', 3)       -> dopeamine
+     *   'spelling eror'.insert('r', -3) -> spelling error
+     *
+     ***/
+    'add': function(str, index) {
+      index = isUndefined(index) ? this.length : index;
+      return this.slice(0, index) + str + this.slice(index);
+    },
+
+    /***
+     * @method remove(<f>)
+     * @returns String
+     * @short Removes any part of the string that matches <f>.
+     * @extra <f> can be a string or a regex.
+     * @example
+     *
+     *   'schfifty five'.remove('f')     -> 'schity ive'
+     *   'schfifty five'.remove(/[a-f]/g) -> 'shity iv'
+     *
+     ***/
+    'remove': function(f) {
+      return this.replace(f, '');
+    },
+
+    /***
+     * @method reverse()
+     * @returns String
+     * @short Reverses the string.
+     * @example
+     *
+     *   'jumpy'.reverse()        -> 'ypmuj'
+     *   'lucky charms'.reverse() -> 'smrahc ykcul'
+     *
+     ***/
+    'reverse': function() {
+      return this.split('').reverse().join('');
+    },
+
+    /***
+     * @method compact()
+     * @returns String
+     * @short Compacts all white space in the string to a single space and trims the ends.
+     * @example
+     *
+     *   'too \n much \n space'.compact() -> 'too much space'
+     *   'enough \n '.compact()           -> 'enought'
+     *
+     ***/
+    'compact': function() {
+      return this.trim().replace(/([\r\n\sใ€€])+/g, function(match, whitespace){
+        return whitespace === 'ใ€€' ? whitespace : ' ';
+      });
+    },
+
+    /***
+     * @method at(<index>, [loop] = true)
+     * @returns String or Array
+     * @short Gets the character(s) at a given index.
+     * @extra When [loop] is true, overshooting the end of the string (or the beginning) will begin counting from the other end. As an alternate syntax, passing multiple indexes will get the characters at those indexes.
+     * @example
+     *
+     *   'jumpy'.at(0)               -> 'j'
+     *   'jumpy'.at(2)               -> 'm'
+     *   'jumpy'.at(5)               -> 'j'
+     *   'jumpy'.at(5, false)        -> ''
+     *   'jumpy'.at(-1)              -> 'y'
+     *   'lucky charms'.at(2,4,6,8) -> ['u','k','y',c']
+     *
+     ***/
+    'at': function() {
+      return getEntriesForIndexes(this, arguments, true);
+    },
+
+    /***
+     * @method from([index] = 0)
+     * @returns String
+     * @short Returns a section of the string starting from [index].
+     * @example
+     *
+     *   'lucky charms'.from()   -> 'lucky charms'
+     *   'lucky charms'.from(7)  -> 'harms'
+     *
+     ***/
+    'from': function(from) {
+      return this.slice(numberOrIndex(this, from, true));
+    },
+
+    /***
+     * @method to([index] = end)
+     * @returns String
+     * @short Returns a section of the string ending at [index].
+     * @example
+     *
+     *   'lucky charms'.to()   -> 'lucky charms'
+     *   'lucky charms'.to(7)  -> 'lucky ch'
+     *
+     ***/
+    'to': function(to) {
+      if(isUndefined(to)) to = this.length;
+      return this.slice(0, numberOrIndex(this, to));
+    },
+
+    /***
+     * @method dasherize()
+     * @returns String
+     * @short Converts underscores and camel casing to hypens.
+     * @example
+     *
+     *   'a_farewell_to_arms'.dasherize() -> 'a-farewell-to-arms'
+     *   'capsLock'.dasherize()           -> 'caps-lock'
+     *
+     ***/
+    'dasherize': function() {
+      return this.underscore().replace(/_/g, '-');
+    },
+
+    /***
+     * @method underscore()
+     * @returns String
+     * @short Converts hyphens and camel casing to underscores.
+     * @example
+     *
+     *   'a-farewell-to-arms'.underscore() -> 'a_farewell_to_arms'
+     *   'capsLock'.underscore()           -> 'caps_lock'
+     *
+     ***/
+    'underscore': function() {
+      return this
+        .replace(/[-\s]+/g, '_')
+        .replace(string.Inflector && string.Inflector.acronymRegExp, function(acronym, index) {
+          return (index > 0 ? '_' : '') + acronym.toLowerCase();
+        })
+        .replace(/([A-Z\d]+)([A-Z][a-z])/g,'$1_$2')
+        .replace(/([a-z\d])([A-Z])/g,'$1_$2')
+        .toLowerCase();
+    },
+
+    /***
+     * @method camelize([first] = true)
+     * @returns String
+     * @short Converts underscores and hyphens to camel case. If [first] is true the first letter will also be capitalized.
+     * @extra If the Inflections package is included acryonyms can also be defined that will be used when camelizing.
+     * @example
+     *
+     *   'caps_lock'.camelize()              -> 'CapsLock'
+     *   'moz-border-radius'.camelize()      -> 'MozBorderRadius'
+     *   'moz-border-radius'.camelize(false) -> 'mozBorderRadius'
+     *
+     ***/
+    'camelize': function(first) {
+      return this.underscore().replace(/(^|_)([^_]+)/g, function(match, pre, word, index) {
+        var acronym = getAcronym(word), capitalize = first !== false || index > 0;
+        if(acronym) return capitalize ? acronym : acronym.toLowerCase();
+        return capitalize ? word.capitalize() : word;
+      });
+    },
+
+    /***
+     * @method spacify()
+     * @returns String
+     * @short Converts camel case, underscores, and hyphens to a properly spaced string.
+     * @example
+     *
+     *   'camelCase'.spacify()                         -> 'camel case'
+     *   'an-ugly-string'.spacify()                    -> 'an ugly string'
+     *   'oh-no_youDid-not'.spacify().capitalize(true) -> 'something else'
+     *
+     ***/
+    'spacify': function() {
+      return this.underscore().replace(/_/g, ' ');
+    },
+
+    /***
+     * @method stripTags([tag1], [tag2], ...)
+     * @returns String
+     * @short Strips all HTML tags from the string.
+     * @extra Tags to strip may be enumerated in the parameters, otherwise will strip all.
+     * @example
+     *
+     *   '<p>just <b>some</b> text</p>'.stripTags()    -> 'just some text'
+     *   '<p>just <b>some</b> text</p>'.stripTags('p') -> 'just <b>some</b> text'
+     *
+     ***/
+    'stripTags': function() {
+      var str = this, args = arguments.length > 0 ? arguments : [''];
+      flattenedArgs(args, function(tag) {
+        str = str.replace(regexp('<\/?' + escapeRegExp(tag) + '[^<>]*>', 'gi'), '');
+      });
+      return str;
+    },
+
+    /***
+     * @method removeTags([tag1], [tag2], ...)
+     * @returns String
+     * @short Removes all HTML tags and their contents from the string.
+     * @extra Tags to remove may be enumerated in the parameters, otherwise will remove all.
+     * @example
+     *
+     *   '<p>just <b>some</b> text</p>'.removeTags()    -> ''
+     *   '<p>just <b>some</b> text</p>'.removeTags('b') -> '<p>just text</p>'
+     *
+     ***/
+    'removeTags': function() {
+      var str = this, args = arguments.length > 0 ? arguments : ['\\S+'];
+      flattenedArgs(args, function(t) {
+        var reg = regexp('<(' + t + ')[^<>]*(?:\\/>|>.*?<\\/\\1>)', 'gi');
+        str = str.replace(reg, '');
+      });
+      return str;
+    },
+
+    /***
+     * @method truncate(<length>, [from] = 'right', [ellipsis] = '...')
+     * @returns String
+     * @short Truncates a string.
+     * @extra [from] can be %'right'%, %'left'%, or %'middle'%. If the string is shorter than <length>, [ellipsis] will not be added.
+     * @example
+     *
+     *   'sittin on the dock of the bay'.truncate(18)           -> 'just sittin on the do...'
+     *   'sittin on the dock of the bay'.truncate(18, 'left')   -> '...the dock of the bay'
+     *   'sittin on the dock of the bay'.truncate(18, 'middle') -> 'just sitt...of the bay'
+     *
+     ***/
+    'truncate': function(length, from, ellipsis) {
+      return truncateString(this, length, from, ellipsis);
+    },
+
+    /***
+     * @method truncateOnWord(<length>, [from] = 'right', [ellipsis] = '...')
+     * @returns String
+     * @short Truncates a string without splitting up words.
+     * @extra [from] can be %'right'%, %'left'%, or %'middle'%. If the string is shorter than <length>, [ellipsis] will not be added.
+     * @example
+     *
+     *   'here we go'.truncateOnWord(5)               -> 'here...'
+     *   'here we go'.truncateOnWord(5, 'left')       -> '...we go'
+     *
+     ***/
+    'truncateOnWord': function(length, from, ellipsis) {
+      return truncateString(this, length, from, ellipsis, true);
+    },
+
+    /***
+     * @method pad[Side](<num> = null, [padding] = ' ')
+     * @returns String
+     * @short Pads the string out with [padding] to be exactly <num> characters.
+     *
+     * @set
+     *   pad
+     *   padLeft
+     *   padRight
+     *
+     * @example
+     *
+     *   'wasabi'.pad(8)           -> ' wasabi '
+     *   'wasabi'.padLeft(8)       -> '  wasabi'
+     *   'wasabi'.padRight(8)      -> 'wasabi  '
+     *   'wasabi'.padRight(8, '-') -> 'wasabi--'
+     *
+     ***/
+    'pad': function(num, padding) {
+      var half, front, back;
+      num   = checkRepeatRange(num);
+      half  = max(0, num - this.length) / 2;
+      front = floor(half);
+      back  = ceil(half);
+      return padString(front, padding) + this + padString(back, padding);
+    },
+
+    'padLeft': function(num, padding) {
+      num = checkRepeatRange(num);
+      return padString(max(0, num - this.length), padding) + this;
+    },
+
+    'padRight': function(num, padding) {
+      num = checkRepeatRange(num);
+      return this + padString(max(0, num - this.length), padding);
+    },
+
+    /***
+     * @method first([n] = 1)
+     * @returns String
+     * @short Returns the first [n] characters of the string.
+     * @example
+     *
+     *   'lucky charms'.first()   -> 'l'
+     *   'lucky charms'.first(3)  -> 'luc'
+     *
+     ***/
+    'first': function(num) {
+      if(isUndefined(num)) num = 1;
+      return this.substr(0, num);
+    },
+
+    /***
+     * @method last([n] = 1)
+     * @returns String
+     * @short Returns the last [n] characters of the string.
+     * @example
+     *
+     *   'lucky charms'.last()   -> 's'
+     *   'lucky charms'.last(3)  -> 'rms'
+     *
+     ***/
+    'last': function(num) {
+      if(isUndefined(num)) num = 1;
+      var start = this.length - num < 0 ? 0 : this.length - num;
+      return this.substr(start);
+    },
+
+    /***
+     * @method toNumber([base] = 10)
+     * @returns Number
+     * @short Converts the string into a number.
+     * @extra Any value with a "." fill be converted to a floating point value, otherwise an integer.
+     * @example
+     *
+     *   '153'.toNumber()    -> 153
+     *   '12,000'.toNumber() -> 12000
+     *   '10px'.toNumber()   -> 10
+     *   'ff'.toNumber(16)   -> 255
+     *
+     ***/
+    'toNumber': function(base) {
+      return stringToNumber(this, base);
+    },
+
+    /***
+     * @method capitalize([all] = false)
+     * @returns String
+     * @short Capitalizes the first character in the string and downcases all other letters.
+     * @extra If [all] is true, all words in the string will be capitalized.
+     * @example
+     *
+     *   'hello'.capitalize()           -> 'Hello'
+     *   'hello kitty'.capitalize()     -> 'Hello kitty'
+     *   'hello kitty'.capitalize(true) -> 'Hello Kitty'
+     *
+     *
+     ***/
+    'capitalize': function(all) {
+      var lastResponded;
+      return this.toLowerCase().replace(all ? /[^']/g : /^\S/, function(lower) {
+        var upper = lower.toUpperCase(), result;
+        result = lastResponded ? lower : upper;
+        lastResponded = upper !== lower;
+        return result;
+      });
+    },
+
+    /***
+     * @method assign(<obj1>, <obj2>, ...)
+     * @returns String
+     * @short Assigns variables to tokens in a string, demarcated with `{}`.
+     * @extra If an object is passed, it's properties can be assigned using the object's keys (i.e. {name}). If a non-object (string, number, etc.) is passed it can be accessed by the argument number beginning with {1} (as with regex tokens). Multiple objects can be passed and will be merged together (original objects are unaffected).
+     * @example
+     *
+     *   'Welcome, Mr. {name}.'.assign({ name: 'Franklin' })   -> 'Welcome, Mr. Franklin.'
+     *   'You are {1} years old today.'.assign(14)             -> 'You are 14 years old today.'
+     *   '{n} and {r}'.assign({ n: 'Cheech' }, { r: 'Chong' }) -> 'Cheech and Chong'
+     *
+     ***/
+    'assign': function() {
+      var assign = {};
+      flattenedArgs(arguments, function(a, i) {
+        if(isObjectType(a)) {
+          simpleMerge(assign, a);
+        } else {
+          assign[i + 1] = a;
+        }
+      });
+      return this.replace(/\{([^{]+?)\}/g, function(m, key) {
+        return hasOwnProperty(assign, key) ? assign[key] : m;
+      });
+    }
+
+  });
+
+
+  // Aliases
+
+  extend(string, true, true, {
+
+    /***
+     * @method insert()
+     * @alias add
+     *
+     ***/
+    'insert': string.prototype.add
+  });
+
+  buildBase64('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=');
+
+
+  /***
+   *
+   * @package Inflections
+   * @dependency string
+   * @description Pluralization similar to ActiveSupport including uncountable words and acronyms. Humanized and URL-friendly strings.
+   *
+   ***/
+
+  /***
+   * String module
+   *
+   ***/
+
+
+  var plurals      = [],
+      singulars    = [],
+      uncountables = [],
+      humans       = [],
+      acronyms     = {},
+      Downcased,
+      Inflector;
+
+  function removeFromArray(arr, find) {
+    var index = arr.indexOf(find);
+    if(index > -1) {
+      arr.splice(index, 1);
+    }
+  }
+
+  function removeFromUncountablesAndAddTo(arr, rule, replacement) {
+    if(isString(rule)) {
+      removeFromArray(uncountables, rule);
+    }
+    removeFromArray(uncountables, replacement);
+    arr.unshift({ rule: rule, replacement: replacement })
+  }
+
+  function paramMatchesType(param, type) {
+    return param == type || param == 'all' || !param;
+  }
+
+  function isUncountable(word) {
+    return uncountables.some(function(uncountable) {
+      return new regexp('\\b' + uncountable + '$', 'i').test(word);
+    });
+  }
+
+  function inflect(word, pluralize) {
+    word = isString(word) ? word.toString() : '';
+    if(word.isBlank() || isUncountable(word)) {
+      return word;
+    } else {
+      return runReplacements(word, pluralize ? plurals : singulars);
+    }
+  }
+
+  function runReplacements(word, table) {
+    iterateOverObject(table, function(i, inflection) {
+      if(word.match(inflection.rule)) {
+        word = word.replace(inflection.rule, inflection.replacement);
+        return false;
+      }
+    });
+    return word;
+  }
+
+  function capitalize(word) {
+    return word.replace(/^\W*[a-z]/, function(w){
+      return w.toUpperCase();
+    });
+  }
+
+  Inflector = {
+
+    /*
+     * Specifies a new acronym. An acronym must be specified as it will appear in a camelized string.  An underscore
+     * string that contains the acronym will retain the acronym when passed to %camelize%, %humanize%, or %titleize%.
+     * A camelized string that contains the acronym will maintain the acronym when titleized or humanized, and will
+     * convert the acronym into a non-delimited single lowercase word when passed to String#underscore.
+     *
+     * Examples:
+     *   String.Inflector.acronym('HTML')
+     *   'html'.titleize()     -> 'HTML'
+     *   'html'.camelize()     -> 'HTML'
+     *   'MyHTML'.underscore() -> 'my_html'
+     *
+     * The acronym, however, must occur as a delimited unit and not be part of another word for conversions to recognize it:
+     *
+     *   String.Inflector.acronym('HTTP')
+     *   'my_http_delimited'.camelize() -> 'MyHTTPDelimited'
+     *   'https'.camelize()             -> 'Https', not 'HTTPs'
+     *   'HTTPS'.underscore()           -> 'http_s', not 'https'
+     *
+     *   String.Inflector.acronym('HTTPS')
+     *   'https'.camelize()   -> 'HTTPS'
+     *   'HTTPS'.underscore() -> 'https'
+     *
+     * Note: Acronyms that are passed to %pluralize% will no longer be recognized, since the acronym will not occur as
+     * a delimited unit in the pluralized result. To work around this, you must specify the pluralized form as an
+     * acronym as well:
+     *
+     *    String.Inflector.acronym('API')
+     *    'api'.pluralize().camelize() -> 'Apis'
+     *
+     *    String.Inflector.acronym('APIs')
+     *    'api'.pluralize().camelize() -> 'APIs'
+     *
+     * %acronym% may be used to specify any word that contains an acronym or otherwise needs to maintain a non-standard
+     * capitalization. The only restriction is that the word must begin with a capital letter.
+     *
+     * Examples:
+     *   String.Inflector.acronym('RESTful')
+     *   'RESTful'.underscore()           -> 'restful'
+     *   'RESTfulController'.underscore() -> 'restful_controller'
+     *   'RESTfulController'.titleize()   -> 'RESTful Controller'
+     *   'restful'.camelize()             -> 'RESTful'
+     *   'restful_controller'.camelize()  -> 'RESTfulController'
+     *
+     *   String.Inflector.acronym('McDonald')
+     *   'McDonald'.underscore() -> 'mcdonald'
+     *   'mcdonald'.camelize()   -> 'McDonald'
+     */
+    'acronym': function(word) {
+      acronyms[word.toLowerCase()] = word;
+      var all = object.keys(acronyms).map(function(key) {
+        return acronyms[key];
+      });
+      Inflector.acronymRegExp = regexp(all.join('|'), 'g');
+    },
+
+    /*
+     * Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
+     * The replacement should always be a string that may include references to the matched data from the rule.
+     */
+    'plural': function(rule, replacement) {
+      removeFromUncountablesAndAddTo(plurals, rule, replacement);
+    },
+
+    /*
+     * Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
+     * The replacement should always be a string that may include references to the matched data from the rule.
+     */
+    'singular': function(rule, replacement) {
+      removeFromUncountablesAndAddTo(singulars, rule, replacement);
+    },
+
+    /*
+     * Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
+     * for strings, not regular expressions. You simply pass the irregular in singular and plural form.
+     *
+     * Examples:
+     *   String.Inflector.irregular('octopus', 'octopi')
+     *   String.Inflector.irregular('person', 'people')
+     */
+    'irregular': function(singular, plural) {
+      var singularFirst      = singular.first(),
+          singularRest       = singular.from(1),
+          pluralFirst        = plural.first(),
+          pluralRest         = plural.from(1),
+          pluralFirstUpper   = pluralFirst.toUpperCase(),
+          pluralFirstLower   = pluralFirst.toLowerCase(),
+          singularFirstUpper = singularFirst.toUpperCase(),
+          singularFirstLower = singularFirst.toLowerCase();
+      removeFromArray(uncountables, singular);
+      removeFromArray(uncountables, plural);
+      if(singularFirstUpper == pluralFirstUpper) {
+        Inflector.plural(new regexp('({1}){2}$'.assign(singularFirst, singularRest), 'i'), '$1' + pluralRest);
+        Inflector.plural(new regexp('({1}){2}$'.assign(pluralFirst, pluralRest), 'i'), '$1' + pluralRest);
+        Inflector.singular(new regexp('({1}){2}$'.assign(pluralFirst, pluralRest), 'i'), '$1' + singularRest);
+      } else {
+        Inflector.plural(new regexp('{1}{2}$'.assign(singularFirstUpper, singularRest)), pluralFirstUpper + pluralRest);
+        Inflector.plural(new regexp('{1}{2}$'.assign(singularFirstLower, singularRest)), pluralFirstLower + pluralRest);
+        Inflector.plural(new regexp('{1}{2}$'.assign(pluralFirstUpper, pluralRest)), pluralFirstUpper + pluralRest);
+        Inflector.plural(new regexp('{1}{2}$'.assign(pluralFirstLower, pluralRest)), pluralFirstLower + pluralRest);
+        Inflector.singular(new regexp('{1}{2}$'.assign(pluralFirstUpper, pluralRest)), singularFirstUpper + singularRest);
+        Inflector.singular(new regexp('{1}{2}$'.assign(pluralFirstLower, pluralRest)), singularFirstLower + singularRest);
+      }
+    },
+
+    /*
+     * Add uncountable words that shouldn't be attempted inflected.
+     *
+     * Examples:
+     *   String.Inflector.uncountable('money')
+     *   String.Inflector.uncountable('money', 'information')
+     *   String.Inflector.uncountable(['money', 'information', 'rice'])
+     */
+    'uncountable': function(first) {
+      var add = array.isArray(first) ? first : multiArgs(arguments);
+      uncountables = uncountables.concat(add);
+    },
+
+    /*
+     * Specifies a humanized form of a string by a regular expression rule or by a string mapping.
+     * When using a regular expression based replacement, the normal humanize formatting is called after the replacement.
+     * When a string is used, the human form should be specified as desired (example: 'The name', not 'the_name')
+     *
+     * Examples:
+     *   String.Inflector.human(/_cnt$/i, '_count')
+     *   String.Inflector.human('legacy_col_person_name', 'Name')
+     */
+    'human': function(rule, replacement) {
+      humans.unshift({ rule: rule, replacement: replacement })
+    },
+
+
+    /*
+     * Clears the loaded inflections within a given scope (default is 'all').
+     * Options are: 'all', 'plurals', 'singulars', 'uncountables', 'humans'.
+     *
+     * Examples:
+     *   String.Inflector.clear('all')
+     *   String.Inflector.clear('plurals')
+     */
+    'clear': function(type) {
+      if(paramMatchesType(type, 'singulars'))    singulars    = [];
+      if(paramMatchesType(type, 'plurals'))      plurals      = [];
+      if(paramMatchesType(type, 'uncountables')) uncountables = [];
+      if(paramMatchesType(type, 'humans'))       humans       = [];
+      if(paramMatchesType(type, 'acronyms'))     acronyms     = {};
+    }
+
+  };
+
+  Downcased = [
+    'and', 'or', 'nor', 'a', 'an', 'the', 'so', 'but', 'to', 'of', 'at',
+    'by', 'from', 'into', 'on', 'onto', 'off', 'out', 'in', 'over',
+    'with', 'for'
+  ];
+
+  Inflector.plural(/$/, 's');
+  Inflector.plural(/s$/gi, 's');
+  Inflector.plural(/(ax|test)is$/gi, '$1es');
+  Inflector.plural(/(octop|vir|fung|foc|radi|alumn)(i|us)$/gi, '$1i');
+  Inflector.plural(/(census|alias|status)$/gi, '$1es');
+  Inflector.plural(/(bu)s$/gi, '$1ses');
+  Inflector.plural(/(buffal|tomat)o$/gi, '$1oes');
+  Inflector.plural(/([ti])um$/gi, '$1a');
+  Inflector.plural(/([ti])a$/gi, '$1a');
+  Inflector.plural(/sis$/gi, 'ses');
+  Inflector.plural(/f+e?$/gi, 'ves');
+  Inflector.plural(/(cuff|roof)$/gi, '$1s');
+  Inflector.plural(/([ht]ive)$/gi, '$1s');
+  Inflector.plural(/([^aeiouy]o)$/gi, '$1es');
+  Inflector.plural(/([^aeiouy]|qu)y$/gi, '$1ies');
+  Inflector.plural(/(x|ch|ss|sh)$/gi, '$1es');
+  Inflector.plural(/(matr|vert|ind)(?:ix|ex)$/gi, '$1ices');
+  Inflector.plural(/([ml])ouse$/gi, '$1ice');
+  Inflector.plural(/([ml])ice$/gi, '$1ice');
+  Inflector.plural(/^(ox)$/gi, '$1en');
+  Inflector.plural(/^(oxen)$/gi, '$1');
+  Inflector.plural(/(quiz)$/gi, '$1zes');
+  Inflector.plural(/(phot|cant|hom|zer|pian|portic|pr|quart|kimon)o$/gi, '$1os');
+  Inflector.plural(/(craft)$/gi, '$1');
+  Inflector.plural(/([ft])[eo]{2}(th?)$/gi, '$1ee$2');
+
+  Inflector.singular(/s$/gi, '');
+  Inflector.singular(/([pst][aiu]s)$/gi, '$1');
+  Inflector.singular(/([aeiouy])ss$/gi, '$1ss');
+  Inflector.singular(/(n)ews$/gi, '$1ews');
+  Inflector.singular(/([ti])a$/gi, '$1um');
+  Inflector.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/gi, '$1$2sis');
+  Inflector.singular(/(^analy)ses$/gi, '$1sis');
+  Inflector.singular(/(i)(f|ves)$/i, '$1fe');
+  Inflector.singular(/([aeolr]f?)(f|ves)$/i, '$1f');
+  Inflector.singular(/([ht]ive)s$/gi, '$1');
+  Inflector.singular(/([^aeiouy]|qu)ies$/gi, '$1y');
+  Inflector.singular(/(s)eries$/gi, '$1eries');
+  Inflector.singular(/(m)ovies$/gi, '$1ovie');
+  Inflector.singular(/(x|ch|ss|sh)es$/gi, '$1');
+  Inflector.singular(/([ml])(ous|ic)e$/gi, '$1ouse');
+  Inflector.singular(/(bus)(es)?$/gi, '$1');
+  Inflector.singular(/(o)es$/gi, '$1');
+  Inflector.singular(/(shoe)s?$/gi, '$1');
+  Inflector.singular(/(cris|ax|test)[ie]s$/gi, '$1is');
+  Inflector.singular(/(octop|vir|fung|foc|radi|alumn)(i|us)$/gi, '$1us');
+  Inflector.singular(/(census|alias|status)(es)?$/gi, '$1');
+  Inflector.singular(/^(ox)(en)?/gi, '$1');
+  Inflector.singular(/(vert|ind)(ex|ices)$/gi, '$1ex');
+  Inflector.singular(/(matr)(ix|ices)$/gi, '$1ix');
+  Inflector.singular(/(quiz)(zes)?$/gi, '$1');
+  Inflector.singular(/(database)s?$/gi, '$1');
+  Inflector.singular(/ee(th?)$/gi, 'oo$1');
+
+  Inflector.irregular('person', 'people');
+  Inflector.irregular('man', 'men');
+  Inflector.irregular('child', 'children');
+  Inflector.irregular('sex', 'sexes');
+  Inflector.irregular('move', 'moves');
+  Inflector.irregular('save', 'saves');
+  Inflector.irregular('cow', 'kine');
+  Inflector.irregular('goose', 'geese');
+  Inflector.irregular('zombie', 'zombies');
+
+  Inflector.uncountable('equipment,information,rice,money,species,series,fish,sheep,jeans'.split(','));
+
+
+  extend(string, true, true, {
+
+    /***
+     * @method pluralize()
+     * @returns String
+     * @short Returns the plural form of the word in the string.
+     * @example
+     *
+     *   'post'.pluralize()         -> 'posts'
+     *   'octopus'.pluralize()      -> 'octopi'
+     *   'sheep'.pluralize()        -> 'sheep'
+     *   'words'.pluralize()        -> 'words'
+     *   'CamelOctopus'.pluralize() -> 'CamelOctopi'
+     *
+     ***/
+    'pluralize': function() {
+      return inflect(this, true);
+    },
+
+    /***
+     * @method singularize()
+     * @returns String
+     * @short The reverse of String#pluralize. Returns the singular form of a word in a string.
+     * @example
+     *
+     *   'posts'.singularize()       -> 'post'
+     *   'octopi'.singularize()      -> 'octopus'
+     *   'sheep'.singularize()       -> 'sheep'
+     *   'word'.singularize()        -> 'word'
+     *   'CamelOctopi'.singularize() -> 'CamelOctopus'
+     *
+     ***/
+    'singularize': function() {
+      return inflect(this, false);
+    },
+
+    /***
+     * @method humanize()
+     * @returns String
+     * @short Creates a human readable string.
+     * @extra Capitalizes the first word and turns underscores into spaces and strips a trailing '_id', if any. Like String#titleize, this is meant for creating pretty output.
+     * @example
+     *
+     *   'employee_salary'.humanize() -> 'Employee salary'
+     *   'author_id'.humanize()       -> 'Author'
+     *
+     ***/
+    'humanize': function() {
+      var str = runReplacements(this, humans), acronym;
+      str = str.replace(/_id$/g, '');
+      str = str.replace(/(_)?([a-z\d]*)/gi, function(match, _, word){
+        acronym = hasOwnProperty(acronyms, word) ? acronyms[word] : null;
+        return (_ ? ' ' : '') + (acronym || word.toLowerCase());
+      });
+      return capitalize(str);
+    },
+
+    /***
+     * @method titleize()
+     * @returns String
+     * @short Creates a title version of the string.
+     * @extra Capitalizes all the words and replaces some characters in the string to create a nicer looking title. String#titleize is meant for creating pretty output.
+     * @example
+     *
+     *   'man from the boondocks'.titleize() -> 'Man from the Boondocks'
+     *   'x-men: the last stand'.titleize() -> 'X Men: The Last Stand'
+     *   'TheManWithoutAPast'.titleize() -> 'The Man Without a Past'
+     *   'raiders_of_the_lost_ark'.titleize() -> 'Raiders of the Lost Ark'
+     *
+     ***/
+    'titleize': function() {
+      var fullStopPunctuation = /[.:;!]$/, hasPunctuation, lastHadPunctuation, isFirstOrLast;
+      return this.spacify().humanize().words(function(word, index, words) {
+        hasPunctuation = fullStopPunctuation.test(word);
+        isFirstOrLast = index == 0 || index == words.length - 1 || hasPunctuation || lastHadPunctuation;
+        lastHadPunctuation = hasPunctuation;
+        if(isFirstOrLast || Downcased.indexOf(word) === -1) {
+          return capitalize(word);
+        } else {
+          return word;
+        }
+      }).join(' ');
+    },
+
+    /***
+     * @method parameterize()
+     * @returns String
+     * @short Replaces special characters in a string so that it may be used as part of a pretty URL.
+     * @example
+     *
+     *   'hell, no!'.parameterize() -> 'hell-no'
+     *
+     ***/
+    'parameterize': function(separator) {
+      var str = this;
+      if(separator === undefined) separator = '-';
+      if(str.normalize) {
+        str = str.normalize();
+      }
+      str = str.replace(/[^a-z0-9\-_]+/gi, separator)
+      if(separator) {
+        str = str.replace(new regexp('^{sep}+|{sep}+$|({sep}){sep}+'.assign({ 'sep': escapeRegExp(separator) }), 'g'), '$1');
+      }
+      return encodeURI(str.toLowerCase());
+    }
+
+  });
+
+  string.Inflector = Inflector;
+  string.Inflector.acronyms = acronyms;
+
+
+  /***
+   *
+   * @package Language
+   * @dependency string
+   * @description Detecting language by character block. Full-width <-> half-width character conversion. Hiragana and Katakana conversions.
+   *
+   ***/
+
+  /***
+   * String module
+   *
+   ***/
+
+
+  /***
+   * @method has[Script]()
+   * @returns Boolean
+   * @short Returns true if the string contains any characters in that script.
+   *
+   * @set
+   *   hasArabic
+   *   hasCyrillic
+   *   hasGreek
+   *   hasHangul
+   *   hasHan
+   *   hasKanji
+   *   hasHebrew
+   *   hasHiragana
+   *   hasKana
+   *   hasKatakana
+   *   hasLatin
+   *   hasThai
+   *   hasDevanagari
+   *
+   * @example
+   *
+   *   'ุฃุชูƒู„ู…'.hasArabic()          -> true
+   *   'ะฒะธะทะธั‚'.hasCyrillic()        -> true
+   *   '์ž˜ ๋จน๊ฒ ์Šต๋‹ˆ๋‹ค!'.hasHangul() -> true
+   *   'ใƒŸใƒƒใ‚ฏใ‚นใงใ™'.hasKatakana() -> true
+   *   "l'annรฉe".hasLatin()         -> true
+   *
+   ***
+   * @method is[Script]()
+   * @returns Boolean
+   * @short Returns true if the string contains only characters in that script. Whitespace is ignored.
+   *
+   * @set
+   *   isArabic
+   *   isCyrillic
+   *   isGreek
+   *   isHangul
+   *   isHan
+   *   isKanji
+   *   isHebrew
+   *   isHiragana
+   *   isKana
+   *   isKatakana
+   *   isKatakana
+   *   isThai
+   *   isDevanagari
+   *
+   * @example
+   *
+   *   'ุฃุชูƒู„ู…'.isArabic()          -> true
+   *   'ะฒะธะทะธั‚'.isCyrillic()        -> true
+   *   '์ž˜ ๋จน๊ฒ ์Šต๋‹ˆ๋‹ค!'.isHangul() -> true
+   *   'ใƒŸใƒƒใ‚ฏใ‚นใงใ™'.isKatakana() -> false
+   *   "l'annรฉe".isLatin()         -> true
+   *
+   ***/
+  var unicodeScripts = [
+    { names: ['Arabic'],      source: '\u0600-\u06FF' },
+    { names: ['Cyrillic'],    source: '\u0400-\u04FF' },
+    { names: ['Devanagari'],  source: '\u0900-\u097F' },
+    { names: ['Greek'],       source: '\u0370-\u03FF' },
+    { names: ['Hangul'],      source: '\uAC00-\uD7AF\u1100-\u11FF' },
+    { names: ['Han','Kanji'], source: '\u4E00-\u9FFF\uF900-\uFAFF' },
+    { names: ['Hebrew'],      source: '\u0590-\u05FF' },
+    { names: ['Hiragana'],    source: '\u3040-\u309F\u30FB-\u30FC' },
+    { names: ['Kana'],        source: '\u3040-\u30FF\uFF61-\uFF9F' },
+    { names: ['Katakana'],    source: '\u30A0-\u30FF\uFF61-\uFF9F' },
+    { names: ['Latin'],       source: '\u0001-\u007F\u0080-\u00FF\u0100-\u017F\u0180-\u024F' },
+    { names: ['Thai'],        source: '\u0E00-\u0E7F' }
+  ];
+
+  function buildUnicodeScripts() {
+    unicodeScripts.forEach(function(s) {
+      var is = regexp('^['+s.source+'\\s]+$');
+      var has = regexp('['+s.source+']');
+      s.names.forEach(function(name) {
+        defineProperty(string.prototype, 'is' + name, function() { return is.test(this.trim()); });
+        defineProperty(string.prototype, 'has' + name, function() { return has.test(this); });
+      });
+    });
+  }
+
+  // Support for converting character widths and katakana to hiragana.
+
+  var HALF_WIDTH_TO_FULL_WIDTH_TRAVERSAL = 65248;
+
+  var widthConversionRanges = [
+    { type: 'a', start: 65,  end: 90  },
+    { type: 'a', start: 97,  end: 122 },
+    { type: 'n', start: 48,  end: 57  },
+    { type: 'p', start: 33,  end: 47  },
+    { type: 'p', start: 58,  end: 64  },
+    { type: 'p', start: 91,  end: 96  },
+    { type: 'p', start: 123, end: 126 }
+  ];
+
+  var WidthConversionTable;
+  var allHankaku   = /[\u0020-\u00A5]|[\uFF61-\uFF9F][๏พž๏พŸ]?/g;
+  var allZenkaku   = /[\u3000-\u301C]|[\u301A-\u30FC]|[\uFF01-\uFF60]|[\uFFE0-\uFFE6]/g;
+  var hankakuPunctuation  = '๏ฝก๏ฝค๏ฝข๏ฝฃยฅยขยฃ';
+  var zenkakuPunctuation  = 'ใ€‚ใ€ใ€Œใ€๏ฟฅ๏ฟ ๏ฟก';
+  var voicedKatakana      = /[ใ‚ซใ‚ญใ‚ฏใ‚ฑใ‚ณใ‚ตใ‚ทใ‚นใ‚ปใ‚ฝใ‚ฟใƒใƒ„ใƒ†ใƒˆใƒใƒ’ใƒ•ใƒ˜ใƒ›]/;
+  var semiVoicedKatakana  = /[ใƒใƒ’ใƒ•ใƒ˜ใƒ›ใƒฒ]/;
+  var hankakuKatakana     = '๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝถ๏ฝท๏ฝธ๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏ฝฏ๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏ฝฌ๏พ•๏ฝญ๏พ–๏ฝฎ๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏ฝฆ๏พ๏ฝฐ๏ฝฅ';
+  var zenkakuKatakana     = 'ใ‚ขใ‚คใ‚ฆใ‚จใ‚ชใ‚กใ‚ฃใ‚ฅใ‚งใ‚ฉใ‚ซใ‚ญใ‚ฏใ‚ฑใ‚ณใ‚ตใ‚ทใ‚นใ‚ปใ‚ฝใ‚ฟใƒใƒ„ใƒƒใƒ†ใƒˆใƒŠใƒ‹ใƒŒใƒใƒŽใƒใƒ’ใƒ•ใƒ˜ใƒ›ใƒžใƒŸใƒ ใƒกใƒขใƒคใƒฃใƒฆใƒฅใƒจใƒงใƒฉใƒชใƒซใƒฌใƒญใƒฏใƒฒใƒณใƒผใƒป';
+
+  function convertCharacterWidth(str, args, reg, type) {
+    if(!WidthConversionTable) {
+      buildWidthConversionTables();
+    }
+    var mode = multiArgs(args).join(''), table = WidthConversionTable[type];
+    mode = mode.replace(/all/, '').replace(/(\w)lphabet|umbers?|atakana|paces?|unctuation/g, '$1');
+    return str.replace(reg, function(c) {
+      if(table[c] && (!mode || mode.has(table[c].type))) {
+        return table[c].to;
+      } else {
+        return c;
+      }
+    });
+  }
+
+  function buildWidthConversionTables() {
+    var hankaku;
+    WidthConversionTable = {
+      'zenkaku': {},
+      'hankaku': {}
+    };
+    widthConversionRanges.forEach(function(r) {
+      simpleRepeat(r.end - r.start + 1, function(n) {
+        n += r.start;
+        setWidthConversion(r.type, chr(n), chr(n + HALF_WIDTH_TO_FULL_WIDTH_TRAVERSAL));
+      });
+    });
+    zenkakuKatakana.each(function(c, i) {
+      hankaku = hankakuKatakana.charAt(i);
+      setWidthConversion('k', hankaku, c);
+      if(c.match(voicedKatakana)) {
+        setWidthConversion('k', hankaku + '๏พž', c.shift(1));
+      }
+      if(c.match(semiVoicedKatakana)) {
+        setWidthConversion('k', hankaku + '๏พŸ', c.shift(2));
+      }
+    });
+    zenkakuPunctuation.each(function(c, i) {
+      setWidthConversion('p', hankakuPunctuation.charAt(i), c);
+    });
+    setWidthConversion('k', '๏ฝณ๏พž', 'ใƒด');
+    setWidthConversion('k', '๏ฝฆ๏พž', 'ใƒบ');
+    setWidthConversion('s', ' ', 'ใ€€');
+  }
+
+  function setWidthConversion(type, half, full) {
+    WidthConversionTable['zenkaku'][half] = { type: type, to: full };
+    WidthConversionTable['hankaku'][full] = { type: type, to: half };
+  }
+
+
+  extend(string, true, true, {
+
+    /***
+     * @method hankaku([mode] = 'all')
+     * @returns String
+     * @short Converts full-width characters (zenkaku) to half-width (hankaku).
+     * @extra [mode] accepts any combination of "a" (alphabet), "n" (numbers), "k" (katakana), "s" (spaces), "p" (punctuation), or "all".
+     * @example
+     *
+     *   'ใ‚ฟใƒญใ‚ฆใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™๏ผ'.hankaku()                      -> '๏พ€๏พ›๏ฝณ YAMADAใงใ™!'
+     *   'ใ‚ฟใƒญใ‚ฆใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™๏ผ'.hankaku('a')                   -> 'ใ‚ฟใƒญใ‚ฆใ€€YAMADAใงใ™๏ผ'
+     *   'ใ‚ฟใƒญใ‚ฆใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™๏ผ'.hankaku('alphabet')            -> 'ใ‚ฟใƒญใ‚ฆใ€€YAMADAใงใ™๏ผ'
+     *   'ใ‚ฟใƒญใ‚ฆใงใ™๏ผใ€€๏ผ’๏ผ•ๆญณใงใ™๏ผ'.hankaku('katakana', 'numbers') -> '๏พ€๏พ›๏ฝณใงใ™๏ผใ€€25ๆญณใงใ™๏ผ'
+     *   'ใ‚ฟใƒญใ‚ฆใงใ™๏ผใ€€๏ผ’๏ผ•ๆญณใงใ™๏ผ'.hankaku('k', 'n')              -> '๏พ€๏พ›๏ฝณใงใ™๏ผใ€€25ๆญณใงใ™๏ผ'
+     *   'ใ‚ฟใƒญใ‚ฆใงใ™๏ผใ€€๏ผ’๏ผ•ๆญณใงใ™๏ผ'.hankaku('kn')                  -> '๏พ€๏พ›๏ฝณใงใ™๏ผใ€€25ๆญณใงใ™๏ผ'
+     *   'ใ‚ฟใƒญใ‚ฆใงใ™๏ผใ€€๏ผ’๏ผ•ๆญณใงใ™๏ผ'.hankaku('sp')                  -> 'ใ‚ฟใƒญใ‚ฆใงใ™! ๏ผ’๏ผ•ๆญณใงใ™!'
+     *
+     ***/
+    'hankaku': function() {
+      return convertCharacterWidth(this, arguments, allZenkaku, 'hankaku');
+    },
+
+    /***
+     * @method zenkaku([mode] = 'all')
+     * @returns String
+     * @short Converts half-width characters (hankaku) to full-width (zenkaku).
+     * @extra [mode] accepts any combination of "a" (alphabet), "n" (numbers), "k" (katakana), "s" (spaces), "p" (punctuation), or "all".
+     * @example
+     *
+     *   '๏พ€๏พ›๏ฝณ YAMADAใงใ™!'.zenkaku()                         -> 'ใ‚ฟใƒญใ‚ฆใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™๏ผ'
+     *   '๏พ€๏พ›๏ฝณ YAMADAใงใ™!'.zenkaku('a')                      -> '๏พ€๏พ›๏ฝณ ๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™!'
+     *   '๏พ€๏พ›๏ฝณ YAMADAใงใ™!'.zenkaku('alphabet')               -> '๏พ€๏พ›๏ฝณ ๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™!'
+     *   '๏พ€๏พ›๏ฝณใงใ™! 25ๆญณใงใ™!'.zenkaku('katakana', 'numbers') -> 'ใ‚ฟใƒญใ‚ฆใงใ™! ๏ผ’๏ผ•ๆญณใงใ™!'
+     *   '๏พ€๏พ›๏ฝณใงใ™! 25ๆญณใงใ™!'.zenkaku('k', 'n')              -> 'ใ‚ฟใƒญใ‚ฆใงใ™! ๏ผ’๏ผ•ๆญณใงใ™!'
+     *   '๏พ€๏พ›๏ฝณใงใ™! 25ๆญณใงใ™!'.zenkaku('kn')                  -> 'ใ‚ฟใƒญใ‚ฆใงใ™! ๏ผ’๏ผ•ๆญณใงใ™!'
+     *   '๏พ€๏พ›๏ฝณใงใ™! 25ๆญณใงใ™!'.zenkaku('sp')                  -> '๏พ€๏พ›๏ฝณใงใ™๏ผใ€€25ๆญณใงใ™๏ผ'
+     *
+     ***/
+    'zenkaku': function() {
+      return convertCharacterWidth(this, arguments, allHankaku, 'zenkaku');
+    },
+
+    /***
+     * @method hiragana([all] = true)
+     * @returns String
+     * @short Converts katakana into hiragana.
+     * @extra If [all] is false, only full-width katakana will be converted.
+     * @example
+     *
+     *   'ใ‚ซใ‚ฟใ‚ซใƒŠ'.hiragana()   -> 'ใ‹ใŸใ‹ใช'
+     *   'ใ‚ณใƒณใƒ‹ใƒใƒ'.hiragana() -> 'ใ“ใ‚“ใซใกใฏ'
+     *   '๏ฝถ๏พ€๏ฝถ๏พ…'.hiragana()       -> 'ใ‹ใŸใ‹ใช'
+     *   '๏ฝถ๏พ€๏ฝถ๏พ…'.hiragana(false)  -> '๏ฝถ๏พ€๏ฝถ๏พ…'
+     *
+     ***/
+    'hiragana': function(all) {
+      var str = this;
+      if(all !== false) {
+        str = str.zenkaku('k');
+      }
+      return str.replace(/[\u30A1-\u30F6]/g, function(c) {
+        return c.shift(-96);
+      });
+    },
+
+    /***
+     * @method katakana()
+     * @returns String
+     * @short Converts hiragana into katakana.
+     * @example
+     *
+     *   'ใ‹ใŸใ‹ใช'.katakana()   -> 'ใ‚ซใ‚ฟใ‚ซใƒŠ'
+     *   'ใ“ใ‚“ใซใกใฏ'.katakana() -> 'ใ‚ณใƒณใƒ‹ใƒใƒ'
+     *
+     ***/
+    'katakana': function() {
+      return this.replace(/[\u3041-\u3096]/g, function(c) {
+        return c.shift(96);
+      });
+    }
+
+
+  });
+
+  buildUnicodeScripts();
+
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('da');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('da', {
+  'plural': true,
+  'months': 'januar,februar,marts,april,maj,juni,juli,august,september,oktober,november,december',
+  'weekdays': 'sรธndag|sondag,mandag,tirsdag,onsdag,torsdag,fredag,lรธrdag|lordag',
+  'units': 'millisekund:|er,sekund:|er,minut:|ter,tim:e|er,dag:|e,ug:e|er|en,mรฅned:|er|en+maaned:|er|en,รฅr:||et+aar:||et',
+  'numbers': 'en|et,to,tre,fire,fem,seks,syv,otte,ni,ti',
+  'tokens': 'den,for',
+  'articles': 'den',
+  'short':'d. {d}. {month} {yyyy}',
+  'long': 'den {d}. {month} {yyyy} {H}:{mm}',
+  'full': '{Weekday} den {d}. {month} {yyyy} {H}:{mm}:{ss}',
+  'past': '{num} {unit} {sign}',
+  'future': '{sign} {num} {unit}',
+  'duration': '{num} {unit}',
+  'ampm': 'am,pm',
+  'modifiers': [
+    { 'name': 'day', 'src': 'forgรฅrs|i forgรฅrs|forgaars|i forgaars', 'value': -2 },
+    { 'name': 'day', 'src': 'i gรฅr|igรฅr|i gaar|igaar', 'value': -1 },
+    { 'name': 'day', 'src': 'i dag|idag', 'value': 0 },
+    { 'name': 'day', 'src': 'i morgen|imorgen', 'value': 1 },
+    { 'name': 'day', 'src': 'over morgon|overmorgen|i over morgen|i overmorgen|iovermorgen', 'value': 2 },
+    { 'name': 'sign', 'src': 'siden', 'value': -1 },
+    { 'name': 'sign', 'src': 'om', 'value':  1 },
+    { 'name': 'shift', 'src': 'i sidste|sidste', 'value': -1 },
+    { 'name': 'shift', 'src': 'denne', 'value': 0 },
+    { 'name': 'shift', 'src': 'nรฆste|naeste', 'value': 1 }
+  ],
+  'dateParse': [
+    '{num} {unit} {sign}',
+    '{sign} {num} {unit}',
+    '{1?} {num} {unit} {sign}',
+    '{shift} {unit=5-7}'
+  ],
+  'timeParse': [
+    '{0?} {weekday?} {date?} {month} {year}',
+    '{date} {month}',
+    '{shift} {weekday}'
+  ]
+});
+
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('de');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('de', {
+  'plural': true,
+   'capitalizeUnit': true,
+  'months': 'Januar,Februar,Mรคrz|Marz,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember',
+  'weekdays': 'Sonntag,Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag',
+  'units': 'Millisekunde:|n,Sekunde:|n,Minute:|n,Stunde:|n,Tag:|en,Woche:|n,Monat:|en,Jahr:|en',
+  'numbers': 'ein:|e|er|en|em,zwei,drei,vier,fuenf,sechs,sieben,acht,neun,zehn',
+  'tokens': 'der',
+  'short':'{d}. {Month} {yyyy}',
+  'long': '{d}. {Month} {yyyy} {H}:{mm}',
+  'full': '{Weekday} {d}. {Month} {yyyy} {H}:{mm}:{ss}',
+  'past': '{sign} {num} {unit}',
+  'future': '{sign} {num} {unit}',
+  'duration': '{num} {unit}',
+  'timeMarker': 'um',
+  'ampm': 'am,pm',
+  'modifiers': [
+    { 'name': 'day', 'src': 'vorgestern', 'value': -2 },
+    { 'name': 'day', 'src': 'gestern', 'value': -1 },
+    { 'name': 'day', 'src': 'heute', 'value': 0 },
+    { 'name': 'day', 'src': 'morgen', 'value': 1 },
+    { 'name': 'day', 'src': 'รผbermorgen|ubermorgen|uebermorgen', 'value': 2 },
+    { 'name': 'sign', 'src': 'vor:|her', 'value': -1 },
+    { 'name': 'sign', 'src': 'in', 'value': 1 },
+    { 'name': 'shift', 'src': 'letzte:|r|n|s', 'value': -1 },
+    { 'name': 'shift', 'src': 'nรคchste:|r|n|s+nachste:|r|n|s+naechste:|r|n|s+kommende:n|r', 'value': 1 }
+  ],
+  'dateParse': [
+    '{sign} {num} {unit}',
+    '{num} {unit} {sign}',
+    '{shift} {unit=5-7}'
+  ],
+  'timeParse': [
+    '{weekday?} {date?} {month} {year?}',
+    '{shift} {weekday}'
+  ]
+});
+
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('es');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('es', {
+  'plural': true,
+  'months': 'enero,febrero,marzo,abril,mayo,junio,julio,agosto,septiembre,octubre,noviembre,diciembre',
+  'weekdays': 'domingo,lunes,martes,miรฉrcoles|miercoles,jueves,viernes,sรกbado|sabado',
+  'units': 'milisegundo:|s,segundo:|s,minuto:|s,hora:|s,dรญa|dรญas|dia|dias,semana:|s,mes:|es,aรฑo|aรฑos|ano|anos',
+  'numbers': 'uno,dos,tres,cuatro,cinco,seis,siete,ocho,nueve,diez',
+  'tokens': 'el,la,de',
+  'short':'{d} {month} {yyyy}',
+  'long': '{d} {month} {yyyy} {H}:{mm}',
+  'full': '{Weekday} {d} {month} {yyyy} {H}:{mm}:{ss}',
+  'past': '{sign} {num} {unit}',
+  'future': '{sign} {num} {unit}',
+  'duration': '{num} {unit}',
+  'timeMarker': 'a las',
+  'ampm': 'am,pm',
+  'modifiers': [
+    { 'name': 'day', 'src': 'anteayer', 'value': -2 },
+    { 'name': 'day', 'src': 'ayer', 'value': -1 },
+    { 'name': 'day', 'src': 'hoy', 'value': 0 },
+    { 'name': 'day', 'src': 'maรฑana|manana', 'value': 1 },
+    { 'name': 'sign', 'src': 'hace', 'value': -1 },
+    { 'name': 'sign', 'src': 'dentro de', 'value': 1 },
+    { 'name': 'shift', 'src': 'pasad:o|a', 'value': -1 },
+    { 'name': 'shift', 'src': 'prรณximo|prรณxima|proximo|proxima', 'value': 1 }
+  ],
+  'dateParse': [
+    '{sign} {num} {unit}',
+    '{num} {unit} {sign}',
+    '{0?}{1?} {unit=5-7} {shift}',
+    '{0?}{1?} {shift} {unit=5-7}'
+  ],
+  'timeParse': [
+    '{shift} {weekday}',
+    '{weekday} {shift}',
+    '{date?} {2?} {month} {2?} {year?}'
+  ]
+});
+Date.addLocale('fi', {
+    'plural':     true,
+    'timeMarker': 'kello',
+    'ampm':       ',',
+    'months':     'tammikuu,helmikuu,maaliskuu,huhtikuu,toukokuu,kesรคkuu,heinรคkuu,elokuu,syyskuu,lokakuu,marraskuu,joulukuu',
+    'weekdays':   'sunnuntai,maanantai,tiistai,keskiviikko,torstai,perjantai,lauantai',
+    'units':      'millisekun:ti|tia|teja|tina|nin,sekun:ti|tia|teja|tina|nin,minuut:ti|tia|teja|tina|in,tun:ti|tia|teja|tina|nin,pรคiv:รค|รครค|iรค|รคnรค|รคn,viik:ko|koa|koja|on|kona,kuukau:si|sia|tta|den|tena,vuo:si|sia|tta|den|tena',
+    'numbers':    'yksi|ensimmรคinen,kaksi|toinen,kolm:e|as,neljรค:s,vii:si|des,kuu:si|des,seitsemรค:n|s,kahdeksa:n|s,yhdeksรค:n|s,kymmene:n|s',
+    'articles':   '',
+    'optionals':  '',
+    'short':      '{d}. {month}ta {yyyy}',
+    'long':       '{d}. {month}ta {yyyy} kello {H}.{mm}',
+    'full':       '{Weekday}na {d}. {month}ta {yyyy} kello {H}.{mm}',
+    'relative':       function(num, unit, ms, format) {
+      var units = this['units'];
+      function numberWithUnit(mult) {
+        return (num === 1 ? '' : num + ' ') + units[(8 * mult) + unit];
+      }
+      switch(format) {
+        case 'duration':  return numberWithUnit(0);
+        case 'past':      return numberWithUnit(num > 1 ? 1 : 0) + ' sitten';
+        case 'future':    return numberWithUnit(4) + ' pรครคstรค';
+      }
+    },
+    'modifiers': [
+        { 'name': 'day',   'src': 'toissa pรคivรคnรค|toissa pรคivรคistรค', 'value': -2 },
+        { 'name': 'day',   'src': 'eilen|eilistรค', 'value': -1 },
+        { 'name': 'day',   'src': 'tรคnรครคn', 'value': 0 },
+        { 'name': 'day',   'src': 'huomenna|huomista', 'value': 1 },
+        { 'name': 'day',   'src': 'ylihuomenna|ylihuomista', 'value': 2 },
+        { 'name': 'sign',  'src': 'sitten|aiemmin', 'value': -1 },
+        { 'name': 'sign',  'src': 'pรครคstรค|kuluttua|myรถhemmin', 'value': 1 },
+        { 'name': 'edge',  'src': 'viimeinen|viimeisenรค', 'value': -2 },
+        { 'name': 'edge',  'src': 'lopussa', 'value': -1 },
+        { 'name': 'edge',  'src': 'ensimmรคinen|ensimmรคisenรค', 'value': 1 },
+        { 'name': 'shift', 'src': 'edellinen|edellisenรค|edeltรคvรค|edeltรคvรคnรค|viime|toissa', 'value': -1 },
+        { 'name': 'shift', 'src': 'tรคnรค|tรคmรคn', 'value': 0 },
+        { 'name': 'shift', 'src': 'seuraava|seuraavana|tuleva|tulevana|ensi', 'value': 1 }
+    ],
+    'dateParse': [
+        '{num} {unit} {sign}',
+        '{sign} {num} {unit}',
+        '{num} {unit=4-5} {sign} {day}',
+        '{month} {year}',
+        '{shift} {unit=5-7}'
+    ],
+    'timeParse': [
+        '{0} {num}{1} {day} of {month} {year?}',
+        '{weekday?} {month} {date}{1} {year?}',
+        '{date} {month} {year}',
+        '{shift} {weekday}',
+        '{shift} week {weekday}',
+        '{weekday} {2} {shift} week',
+        '{0} {date}{1} of {month}',
+        '{0}{month?} {date?}{1} of {shift} {unit=6-7}'
+    ]
+});
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('fr');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('fr', {
+  'plural': true,
+  'months': 'janvier,fรฉvrier|fevrier,mars,avril,mai,juin,juillet,aoรปt,septembre,octobre,novembre,dรฉcembre|decembre',
+  'weekdays': 'dimanche,lundi,mardi,mercredi,jeudi,vendredi,samedi',
+  'units': 'milliseconde:|s,seconde:|s,minute:|s,heure:|s,jour:|s,semaine:|s,mois,an:|s|nรฉe|nee',
+  'numbers': 'un:|e,deux,trois,quatre,cinq,six,sept,huit,neuf,dix',
+  'tokens': "l'|la|le",
+  'short':'{d} {month} {yyyy}',
+  'long': '{d} {month} {yyyy} {H}:{mm}',
+  'full': '{Weekday} {d} {month} {yyyy} {H}:{mm}:{ss}',
+  'past': '{sign} {num} {unit}',
+  'future': '{sign} {num} {unit}',
+  'duration': '{num} {unit}',
+  'timeMarker': 'ร ',
+  'ampm': 'am,pm',
+  'modifiers': [
+    { 'name': 'day', 'src': 'hier', 'value': -1 },
+    { 'name': 'day', 'src': "aujourd'hui", 'value': 0 },
+    { 'name': 'day', 'src': 'demain', 'value': 1 },
+    { 'name': 'sign', 'src': 'il y a', 'value': -1 },
+    { 'name': 'sign', 'src': "dans|d'ici", 'value': 1 },
+    { 'name': 'shift', 'src': 'derni:รจr|er|รจre|ere', 'value': -1 },
+    { 'name': 'shift', 'src': 'prochain:|e', 'value': 1 }
+  ],
+  'dateParse': [
+    '{sign} {num} {unit}',
+    '{sign} {num} {unit}',
+    '{0?} {unit=5-7} {shift}'
+  ],
+  'timeParse': [
+    '{weekday?} {0?} {date?} {month} {year?}',
+    '{0?} {weekday} {shift}'
+  ]
+});
+
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('it');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('it', {
+  'plural': true,
+  'months': 'Gennaio,Febbraio,Marzo,Aprile,Maggio,Giugno,Luglio,Agosto,Settembre,Ottobre,Novembre,Dicembre',
+  'weekdays': 'Domenica,Luned:รฌ|i,Marted:รฌ|i,Mercoled:รฌ|i,Gioved:รฌ|i,Venerd:รฌ|i,Sabato',
+  'units': 'millisecond:o|i,second:o|i,minut:o|i,or:a|e,giorn:o|i,settiman:a|e,mes:e|i,ann:o|i',
+  'numbers': "un:|a|o|',due,tre,quattro,cinque,sei,sette,otto,nove,dieci",
+  'tokens': "l'|la|il",
+  'short':'{d} {Month} {yyyy}',
+  'long': '{d} {Month} {yyyy} {H}:{mm}',
+  'full': '{Weekday} {d} {Month} {yyyy} {H}:{mm}:{ss}',
+  'past': '{num} {unit} {sign}',
+  'future': '{num} {unit} {sign}',
+  'duration': '{num} {unit}',
+  'timeMarker': 'alle',
+  'ampm': 'am,pm',
+  'modifiers': [
+    { 'name': 'day', 'src': 'ieri', 'value': -1 },
+    { 'name': 'day', 'src': 'oggi', 'value': 0 },
+    { 'name': 'day', 'src': 'domani', 'value': 1 },
+    { 'name': 'day', 'src': 'dopodomani', 'value': 2 },
+    { 'name': 'sign', 'src': 'fa', 'value': -1 },
+    { 'name': 'sign', 'src': 'da adesso', 'value': 1 },
+    { 'name': 'shift', 'src': 'scors:o|a', 'value': -1 },
+    { 'name': 'shift', 'src': 'prossim:o|a', 'value': 1 }
+  ],
+  'dateParse': [
+    '{num} {unit} {sign}',
+    '{0?} {unit=5-7} {shift}',
+    '{0?} {shift} {unit=5-7}'
+  ],
+  'timeParse': [
+    '{weekday?} {date?} {month} {year?}',
+    '{shift} {weekday}'
+  ]
+});
+
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('ja');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('ja', {
+  'monthSuffix': 'ๆœˆ',
+  'weekdays': 'ๆ—ฅๆ›œๆ—ฅ,ๆœˆๆ›œๆ—ฅ,็ซๆ›œๆ—ฅ,ๆฐดๆ›œๆ—ฅ,ๆœจๆ›œๆ—ฅ,้‡‘ๆ›œๆ—ฅ,ๅœŸๆ›œๆ—ฅ',
+  'units': 'ใƒŸใƒช็ง’,็ง’,ๅˆ†,ๆ™‚้–“,ๆ—ฅ,้€ฑ้–“|้€ฑ,ใƒถๆœˆ|ใƒตๆœˆ|ๆœˆ,ๅนด',
+  'short': '{yyyy}ๅนด{M}ๆœˆ{d}ๆ—ฅ',
+  'long': '{yyyy}ๅนด{M}ๆœˆ{d}ๆ—ฅ {H}ๆ™‚{mm}ๅˆ†',
+  'full': '{yyyy}ๅนด{M}ๆœˆ{d}ๆ—ฅ {Weekday} {H}ๆ™‚{mm}ๅˆ†{ss}็ง’',
+  'past': '{num}{unit}{sign}',
+  'future': '{num}{unit}{sign}',
+  'duration': '{num}{unit}',
+  'timeSuffixes': 'ๆ™‚,ๅˆ†,็ง’',
+  'ampm': 'ๅˆๅ‰,ๅˆๅพŒ',
+  'modifiers': [
+    { 'name': 'day', 'src': 'ไธ€ๆ˜จๆ—ฅ', 'value': -2 },
+    { 'name': 'day', 'src': 'ๆ˜จๆ—ฅ', 'value': -1 },
+    { 'name': 'day', 'src': 'ไปŠๆ—ฅ', 'value': 0 },
+    { 'name': 'day', 'src': 'ๆ˜Žๆ—ฅ', 'value': 1 },
+    { 'name': 'day', 'src': 'ๆ˜ŽๅพŒๆ—ฅ', 'value': 2 },
+    { 'name': 'sign', 'src': 'ๅ‰', 'value': -1 },
+    { 'name': 'sign', 'src': 'ๅพŒ', 'value':  1 },
+    { 'name': 'shift', 'src': 'ๅŽป|ๅ…ˆ', 'value': -1 },
+    { 'name': 'shift', 'src': 'ๆฅ', 'value':  1 }
+  ],
+  'dateParse': [
+    '{num}{unit}{sign}'
+  ],
+  'timeParse': [
+    '{shift}{unit=5-7}{weekday?}',
+    '{year}ๅนด{month?}ๆœˆ?{date?}ๆ—ฅ?',
+    '{month}ๆœˆ{date?}ๆ—ฅ?',
+    '{date}ๆ—ฅ'
+  ]
+});
+
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('ko');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('ko', {
+  'digitDate': true,
+  'monthSuffix': '์›”',
+  'weekdays': '์ผ์š”์ผ,์›”์š”์ผ,ํ™”์š”์ผ,์ˆ˜์š”์ผ,๋ชฉ์š”์ผ,๊ธˆ์š”์ผ,ํ† ์š”์ผ',
+  'units': '๋ฐ€๋ฆฌ์ดˆ,์ดˆ,๋ถ„,์‹œ๊ฐ„,์ผ,์ฃผ,๊ฐœ์›”|๋‹ฌ,๋…„',
+  'numbers': '์ผ|ํ•œ,์ด,์‚ผ,์‚ฌ,์˜ค,์œก,์น ,ํŒ”,๊ตฌ,์‹ญ',
+  'short': '{yyyy}๋…„{M}์›”{d}์ผ',
+  'long': '{yyyy}๋…„{M}์›”{d}์ผ {H}์‹œ{mm}๋ถ„',
+  'full': '{yyyy}๋…„{M}์›”{d}์ผ {Weekday} {H}์‹œ{mm}๋ถ„{ss}์ดˆ',
+  'past': '{num}{unit} {sign}',
+  'future': '{num}{unit} {sign}',
+  'duration': '{num}{unit}',
+  'timeSuffixes': '์‹œ,๋ถ„,์ดˆ',
+  'ampm': '์˜ค์ „,์˜คํ›„',
+  'modifiers': [
+    { 'name': 'day', 'src': '๊ทธ์ €๊ป˜', 'value': -2 },
+    { 'name': 'day', 'src': '์–ด์ œ', 'value': -1 },
+    { 'name': 'day', 'src': '์˜ค๋Š˜', 'value': 0 },
+    { 'name': 'day', 'src': '๋‚ด์ผ', 'value': 1 },
+    { 'name': 'day', 'src': '๋ชจ๋ ˆ', 'value': 2 },
+    { 'name': 'sign', 'src': '์ „', 'value': -1 },
+    { 'name': 'sign', 'src': 'ํ›„', 'value':  1 },
+    { 'name': 'shift', 'src': '์ง€๋‚œ|์ž‘', 'value': -1 },
+    { 'name': 'shift', 'src': '์ด๋ฒˆ', 'value': 0 },
+    { 'name': 'shift', 'src': '๋‹ค์Œ|๋‚ด', 'value': 1 }
+  ],
+  'dateParse': [
+    '{num}{unit} {sign}',
+    '{shift?} {unit=5-7}'
+  ],
+  'timeParse': [
+    '{shift} {unit=5?} {weekday}',
+    '{year}๋…„{month?}์›”?{date?}์ผ?',
+    '{month}์›”{date?}์ผ?',
+    '{date}์ผ'
+  ]
+});
+
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('nl');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('nl', {
+  'plural': true,
+  'months': 'januari,februari,maart,april,mei,juni,juli,augustus,september,oktober,november,december',
+  'weekdays': 'zondag|zo,maandag|ma,dinsdag|di,woensdag|woe|wo,donderdag|do,vrijdag|vrij|vr,zaterdag|za',
+  'units': 'milliseconde:|n,seconde:|n,minu:ut|ten,uur,dag:|en,we:ek|ken,maand:|en,jaar',
+  'numbers': 'een,twee,drie,vier,vijf,zes,zeven,acht,negen',
+  'tokens': '',
+  'short':'{d} {Month} {yyyy}',
+  'long': '{d} {Month} {yyyy} {H}:{mm}',
+  'full': '{Weekday} {d} {Month} {yyyy} {H}:{mm}:{ss}',
+  'past': '{num} {unit} {sign}',
+  'future': '{num} {unit} {sign}',
+  'duration': '{num} {unit}',
+  'timeMarker': "'s|om",
+  'modifiers': [
+    { 'name': 'day', 'src': 'gisteren', 'value': -1 },
+    { 'name': 'day', 'src': 'vandaag', 'value': 0 },
+    { 'name': 'day', 'src': 'morgen', 'value': 1 },
+    { 'name': 'day', 'src': 'overmorgen', 'value': 2 },
+    { 'name': 'sign', 'src': 'geleden', 'value': -1 },
+    { 'name': 'sign', 'src': 'vanaf nu', 'value': 1 },
+    { 'name': 'shift', 'src': 'laatste|vorige|afgelopen', 'value': -1 },
+    { 'name': 'shift', 'src': 'volgend:|e', 'value': 1 }
+  ],
+  'dateParse': [
+    '{num} {unit} {sign}',
+    '{0?} {unit=5-7} {shift}',
+    '{0?} {shift} {unit=5-7}'
+  ],
+  'timeParse': [
+    '{weekday?} {date?} {month} {year?}',
+    '{shift} {weekday}'
+  ]
+});
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('pl');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.optionals. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('pl', {
+  'plural':    true,
+  'months':    'Styczeล„|Stycznia,Luty|Lutego,Marzec|Marca,Kwiecieล„|Kwietnia,Maj|Maja,Czerwiec|Czerwca,Lipiec|Lipca,Sierpieล„|Sierpnia,Wrzesieล„|Wrzeล›nia,Paลบdziernik|Paลบdziernika,Listopad|Listopada,Grudzieล„|Grudnia',
+  'weekdays':  'Niedziela|Niedzielฤ™,Poniedziaล‚ek,Wtorek,ลšrod:a|ฤ™,Czwartek,Piฤ…tek,Sobota|Sobotฤ™',
+  'units':     'milisekund:a|y|,sekund:a|y|,minut:a|y|,godzin:a|y|,dzieล„|dni,tydzieล„|tygodnie|tygodni,miesiฤ…ce|miesiฤ…ce|miesiฤ™cy,rok|lata|lat',
+  'numbers':   'jeden|jednฤ…,dwa|dwie,trzy,cztery,piฤ™ฤ‡,szeล›ฤ‡,siedem,osiem,dziewiฤ™ฤ‡,dziesiฤ™ฤ‡',
+  'optionals': 'w|we,roku',
+  'short':     '{d} {Month} {yyyy}',
+  'long':      '{d} {Month} {yyyy} {H}:{mm}',
+  'full' :     '{Weekday}, {d} {Month} {yyyy} {H}:{mm}:{ss}',
+  'past':      '{num} {unit} {sign}',
+  'future':    '{sign} {num} {unit}',
+  'duration':  '{num} {unit}',
+  'timeMarker':'o',
+  'ampm':      'am,pm',
+  'modifiers': [
+    { 'name': 'day', 'src': 'przedwczoraj', 'value': -2 },
+    { 'name': 'day', 'src': 'wczoraj', 'value': -1 },
+    { 'name': 'day', 'src': 'dzisiaj|dziล›', 'value': 0 },
+    { 'name': 'day', 'src': 'jutro', 'value': 1 },
+    { 'name': 'day', 'src': 'pojutrze', 'value': 2 },
+    { 'name': 'sign', 'src': 'temu|przed', 'value': -1 },
+    { 'name': 'sign', 'src': 'za', 'value': 1 },
+    { 'name': 'shift', 'src': 'zeszล‚y|zeszล‚a|ostatni|ostatnia', 'value': -1 },
+    { 'name': 'shift', 'src': 'nastฤ™pny|nastฤ™pna|nastฤ™pnego|przyszล‚y|przyszล‚a|przyszล‚ego', 'value': 1 }
+  ],
+  'dateParse': [
+    '{num} {unit} {sign}',
+    '{sign} {num} {unit}',
+    '{month} {year}',
+    '{shift} {unit=5-7}',
+    '{0} {shift?} {weekday}'
+  ],
+  'timeParse': [
+    '{date} {month} {year?} {1}',
+    '{0} {shift?} {weekday}'
+  ]
+});
+
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('pt');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('pt', {
+  'plural': true,
+  'months': 'janeiro,fevereiro,marรงo,abril,maio,junho,julho,agosto,setembro,outubro,novembro,dezembro',
+  'weekdays': 'domingo,segunda-feira,terรงa-feira,quarta-feira,quinta-feira,sexta-feira,sรกbado|sabado',
+  'units': 'milisegundo:|s,segundo:|s,minuto:|s,hora:|s,dia:|s,semana:|s,mรชs|mรชses|mes|meses,ano:|s',
+  'numbers': 'um,dois,trรชs|tres,quatro,cinco,seis,sete,oito,nove,dez,uma,duas',
+  'tokens': 'a,de',
+  'short':'{d} de {month} de {yyyy}',
+  'long': '{d} de {month} de {yyyy} {H}:{mm}',
+  'full': '{Weekday}, {d} de {month} de {yyyy} {H}:{mm}:{ss}',
+  'past': '{num} {unit} {sign}',
+  'future': '{sign} {num} {unit}',
+  'duration': '{num} {unit}',
+  'timeMarker': 'ร s',
+  'ampm': 'am,pm',
+  'modifiers': [
+    { 'name': 'day', 'src': 'anteontem', 'value': -2 },
+    { 'name': 'day', 'src': 'ontem', 'value': -1 },
+    { 'name': 'day', 'src': 'hoje', 'value': 0 },
+    { 'name': 'day', 'src': 'amanh:รฃ|a', 'value': 1 },
+    { 'name': 'sign', 'src': 'atrรกs|atras|hรก|ha', 'value': -1 },
+    { 'name': 'sign', 'src': 'daqui a', 'value': 1 },
+    { 'name': 'shift', 'src': 'passad:o|a', 'value': -1 },
+    { 'name': 'shift', 'src': 'prรณximo|prรณxima|proximo|proxima', 'value': 1 }
+  ],
+  'dateParse': [
+    '{num} {unit} {sign}',
+    '{sign} {num} {unit}',
+    '{0?} {unit=5-7} {shift}',
+    '{0?} {shift} {unit=5-7}'
+  ],
+  'timeParse': [
+    '{date?} {1?} {month} {1?} {year?}',
+    '{0?} {shift} {weekday}'
+  ]
+});
+
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('ru');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('ru', {
+  'months': 'ะฏะฝะฒะฐั€:ั|ัŒ,ะคะตะฒั€ะฐะป:ั|ัŒ,ะœะฐั€ั‚:ะฐ|,ะะฟั€ะตะป:ั|ัŒ,ะœะฐ:ั|ะน,ะ˜ัŽะฝ:ั|ัŒ,ะ˜ัŽะป:ั|ัŒ,ะะฒะณัƒัั‚:ะฐ|,ะกะตะฝั‚ัะฑั€:ั|ัŒ,ะžะบั‚ัะฑั€:ั|ัŒ,ะะพัะฑั€:ั|ัŒ,ะ”ะตะบะฐะฑั€:ั|ัŒ',
+  'weekdays': 'ะ’ะพัะบั€ะตัะตะฝัŒะต,ะŸะพะฝะตะดะตะปัŒะฝะธะบ,ะ’ั‚ะพั€ะฝะธะบ,ะกั€ะตะดะฐ,ะงะตั‚ะฒะตั€ะณ,ะŸัั‚ะฝะธั†ะฐ,ะกัƒะฑะฑะพั‚ะฐ',
+  'units': 'ะผะธะปะปะธัะตะบัƒะฝะด:ะฐ|ัƒ|ั‹|,ัะตะบัƒะฝะด:ะฐ|ัƒ|ั‹|,ะผะธะฝัƒั‚:ะฐ|ัƒ|ั‹|,ั‡ะฐั:||ะฐ|ะพะฒ,ะดะตะฝัŒ|ะดะตะฝัŒ|ะดะฝั|ะดะฝะตะน,ะฝะตะดะตะป:ั|ัŽ|ะธ|ัŒ|ะต,ะผะตััั†:||ะฐ|ะตะฒ|ะต,ะณะพะด|ะณะพะด|ะณะพะดะฐ|ะปะตั‚|ะณะพะดัƒ',
+  'numbers': 'ะพะด:ะธะฝ|ะฝัƒ,ะดะฒ:ะฐ|ะต,ั‚ั€ะธ,ั‡ะตั‚ั‹ั€ะต,ะฟัั‚ัŒ,ัˆะตัั‚ัŒ,ัะตะผัŒ,ะฒะพัะตะผัŒ,ะดะตะฒัั‚ัŒ,ะดะตััั‚ัŒ',
+  'tokens': 'ะฒ|ะฝะฐ,ะณะพะดะฐ',
+  'short':'{d} {month} {yyyy} ะณะพะดะฐ',
+  'long': '{d} {month} {yyyy} ะณะพะดะฐ {H}:{mm}',
+  'full': '{Weekday} {d} {month} {yyyy} ะณะพะดะฐ {H}:{mm}:{ss}',
+  'relative': function(num, unit, ms, format) {
+    var numberWithUnit, last = num.toString().slice(-1), mult;
+    switch(true) {
+      case num >= 11 && num <= 15: mult = 3; break;
+      case last == 1: mult = 1; break;
+      case last >= 2 && last <= 4: mult = 2; break;
+      default: mult = 3;
+    }
+    numberWithUnit = num + ' ' + this['units'][(mult * 8) + unit];
+    switch(format) {
+      case 'duration':  return numberWithUnit;
+      case 'past':      return numberWithUnit + ' ะฝะฐะทะฐะด';
+      case 'future':    return 'ั‡ะตั€ะตะท ' + numberWithUnit;
+    }
+  },
+  'timeMarker': 'ะฒ',
+  'ampm': ' ัƒั‚ั€ะฐ, ะฒะตั‡ะตั€ะฐ',
+  'modifiers': [
+    { 'name': 'day', 'src': 'ะฟะพะทะฐะฒั‡ะตั€ะฐ', 'value': -2 },
+    { 'name': 'day', 'src': 'ะฒั‡ะตั€ะฐ', 'value': -1 },
+    { 'name': 'day', 'src': 'ัะตะณะพะดะฝั', 'value': 0 },
+    { 'name': 'day', 'src': 'ะทะฐะฒั‚ั€ะฐ', 'value': 1 },
+    { 'name': 'day', 'src': 'ะฟะพัะปะตะทะฐะฒั‚ั€ะฐ', 'value': 2 },
+    { 'name': 'sign', 'src': 'ะฝะฐะทะฐะด', 'value': -1 },
+    { 'name': 'sign', 'src': 'ั‡ะตั€ะตะท', 'value': 1 },
+    { 'name': 'shift', 'src': 'ะฟั€ะพัˆะป:ั‹ะน|ะพะน|ะพะผ', 'value': -1 },
+    { 'name': 'shift', 'src': 'ัะปะตะดัƒัŽั‰:ะธะน|ะตะน|ะตะผ', 'value': 1 }
+  ],
+  'dateParse': [
+    '{num} {unit} {sign}',
+    '{sign} {num} {unit}',
+    '{month} {year}',
+    '{0?} {shift} {unit=5-7}'
+  ],
+  'timeParse': [
+    '{date} {month} {year?} {1?}',
+    '{0?} {shift} {weekday}'
+  ]
+});
+
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('sv');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('sv', {
+  'plural': true,
+  'months': 'januari,februari,mars,april,maj,juni,juli,augusti,september,oktober,november,december',
+  'weekdays': 'sรถndag|sondag,mรฅndag:|en+mandag:|en,tisdag,onsdag,torsdag,fredag,lรถrdag|lordag',
+  'units': 'millisekund:|er,sekund:|er,minut:|er,timm:e|ar,dag:|ar,veck:a|or|an,mรฅnad:|er|en+manad:|er|en,รฅr:||et+ar:||et',
+  'numbers': 'en|ett,tvรฅ|tva,tre,fyra,fem,sex,sju,รฅtta|atta,nio,tio',
+  'tokens': 'den,fรถr|for',
+  'articles': 'den',
+  'short':'den {d} {month} {yyyy}',
+  'long': 'den {d} {month} {yyyy} {H}:{mm}',
+  'full': '{Weekday} den {d} {month} {yyyy} {H}:{mm}:{ss}',
+  'past': '{num} {unit} {sign}',
+  'future': '{sign} {num} {unit}',
+  'duration': '{num} {unit}',
+  'ampm': 'am,pm',
+  'modifiers': [
+    { 'name': 'day', 'src': 'fรถrrgรฅr|i fรถrrgรฅr|ifรถrrgรฅr|forrgar|i forrgar|iforrgar', 'value': -2 },
+    { 'name': 'day', 'src': 'gรฅr|i gรฅr|igรฅr|gar|i gar|igar', 'value': -1 },
+    { 'name': 'day', 'src': 'dag|i dag|idag', 'value': 0 },
+    { 'name': 'day', 'src': 'morgon|i morgon|imorgon', 'value': 1 },
+    { 'name': 'day', 'src': 'รถver morgon|รถvermorgon|i รถver morgon|i รถvermorgon|iรถvermorgon|over morgon|overmorgon|i over morgon|i overmorgon|iovermorgon', 'value': 2 },
+    { 'name': 'sign', 'src': 'sedan|sen', 'value': -1 },
+    { 'name': 'sign', 'src': 'om', 'value':  1 },
+    { 'name': 'shift', 'src': 'i fรถrra|fรถrra|i forra|forra', 'value': -1 },
+    { 'name': 'shift', 'src': 'denna', 'value': 0 },
+    { 'name': 'shift', 'src': 'nรคsta|nasta', 'value': 1 }
+  ],
+  'dateParse': [
+    '{num} {unit} {sign}',
+    '{sign} {num} {unit}',
+    '{1?} {num} {unit} {sign}',
+    '{shift} {unit=5-7}'
+  ],
+  'timeParse': [
+    '{0?} {weekday?} {date?} {month} {year}',
+    '{date} {month}',
+    '{shift} {weekday}'
+  ]
+});
+
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('zh-CN');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+Date.addLocale('zh-CN', {
+  'variant': true,
+  'monthSuffix': 'ๆœˆ',
+  'weekdays': 'ๆ˜ŸๆœŸๆ—ฅ|ๅ‘จๆ—ฅ,ๆ˜ŸๆœŸไธ€|ๅ‘จไธ€,ๆ˜ŸๆœŸไบŒ|ๅ‘จไบŒ,ๆ˜ŸๆœŸไธ‰|ๅ‘จไธ‰,ๆ˜ŸๆœŸๅ››|ๅ‘จๅ››,ๆ˜ŸๆœŸไบ”|ๅ‘จไบ”,ๆ˜ŸๆœŸๅ…ญ|ๅ‘จๅ…ญ',
+  'units': 'ๆฏซ็ง’,็ง’้’Ÿ,ๅˆ†้’Ÿ,ๅฐๆ—ถ,ๅคฉ,ไธชๆ˜ŸๆœŸ|ๅ‘จ,ไธชๆœˆ,ๅนด',
+  'tokens': 'ๆ—ฅ|ๅท',
+  'short':'{yyyy}ๅนด{M}ๆœˆ{d}ๆ—ฅ',
+  'long': '{yyyy}ๅนด{M}ๆœˆ{d}ๆ—ฅ {tt}{h}:{mm}',
+  'full': '{yyyy}ๅนด{M}ๆœˆ{d}ๆ—ฅ {weekday} {tt}{h}:{mm}:{ss}',
+  'past': '{num}{unit}{sign}',
+  'future': '{num}{unit}{sign}',
+  'duration': '{num}{unit}',
+  'timeSuffixes': '็‚น|ๆ—ถ,ๅˆ†้’Ÿ?,็ง’',
+  'ampm': 'ไธŠๅˆ,ไธ‹ๅˆ',
+  'modifiers': [
+    { 'name': 'day', 'src': 'ๅ‰ๅคฉ', 'value': -2 },
+    { 'name': 'day', 'src': 'ๆ˜จๅคฉ', 'value': -1 },
+    { 'name': 'day', 'src': 'ไปŠๅคฉ', 'value': 0 },
+    { 'name': 'day', 'src': 'ๆ˜Žๅคฉ', 'value': 1 },
+    { 'name': 'day', 'src': 'ๅŽๅคฉ', 'value': 2 },
+    { 'name': 'sign', 'src': 'ๅ‰', 'value': -1 },
+    { 'name': 'sign', 'src': 'ๅŽ', 'value':  1 },
+    { 'name': 'shift', 'src': 'ไธŠ|ๅŽป', 'value': -1 },
+    { 'name': 'shift', 'src': '่ฟ™', 'value':  0 },
+    { 'name': 'shift', 'src': 'ไธ‹|ๆ˜Ž', 'value':  1 }
+  ],
+  'dateParse': [
+    '{num}{unit}{sign}',
+    '{shift}{unit=5-7}'
+  ],
+  'timeParse': [
+    '{shift}{weekday}',
+    '{year}ๅนด{month?}ๆœˆ?{date?}{0?}',
+    '{month}ๆœˆ{date?}{0?}',
+    '{date}[ๆ—ฅๅท]'
+  ]
+});
+
+/*
+ *
+ * Date.addLocale(<code>) adds this locale to Sugar.
+ * To set the locale globally, simply call:
+ *
+ * Date.setLocale('zh-TW');
+ *
+ * var locale = Date.getLocale(<code>) will return this object, which
+ * can be tweaked to change the behavior of parsing/formatting in the locales.
+ *
+ * locale.addFormat adds a date format (see this file for examples).
+ * Special tokens in the date format will be parsed out into regex tokens:
+ *
+ * {0} is a reference to an entry in locale.tokens. Output: (?:the)?
+ * {unit} is a reference to all units. Output: (day|week|month|...)
+ * {unit3} is a reference to a specific unit. Output: (hour)
+ * {unit3-5} is a reference to a subset of the units array. Output: (hour|day|week)
+ * {unit?} "?" makes that token optional. Output: (day|week|month)?
+ *
+ * {day} Any reference to tokens in the modifiers array will include all with the same name. Output: (yesterday|today|tomorrow)
+ *
+ * All spaces are optional and will be converted to "\s*"
+ *
+ * Locale arrays months, weekdays, units, numbers, as well as the "src" field for
+ * all entries in the modifiers array follow a special format indicated by a colon:
+ *
+ * minute:|s  = minute|minutes
+ * thicke:n|r = thicken|thicker
+ *
+ * Additionally in the months, weekdays, units, and numbers array these will be added at indexes that are multiples
+ * of the relevant number for retrieval. For example having "sunday:|s" in the units array will result in:
+ *
+ * units: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sundays']
+ *
+ * When matched, the index will be found using:
+ *
+ * units.indexOf(match) % 7;
+ *
+ * Resulting in the correct index with any number of alternates for that entry.
+ *
+ */
+
+  //'zh-TW': '1;ๆœˆ;ๅนด;;ๆ˜ŸๆœŸๆ—ฅ|้€ฑๆ—ฅ,ๆ˜ŸๆœŸไธ€|้€ฑไธ€,ๆ˜ŸๆœŸไบŒ|้€ฑไบŒ,ๆ˜ŸๆœŸไธ‰|้€ฑไธ‰,ๆ˜ŸๆœŸๅ››|้€ฑๅ››,ๆ˜ŸๆœŸไบ”|้€ฑไบ”,ๆ˜ŸๆœŸๅ…ญ|้€ฑๅ…ญ;ๆฏซ็ง’,็ง’้˜,ๅˆ†้˜,ๅฐๆ™‚,ๅคฉ,ๅ€‹ๆ˜ŸๆœŸ|้€ฑ,ๅ€‹ๆœˆ,ๅนด;;;ๆ—ฅ|่™Ÿ;;ไธŠๅˆ,ไธ‹ๅˆ;้ปž|ๆ™‚,ๅˆ†้˜?,็ง’;{num}{unit}{sign},{shift}{unit=5-7};{shift}{weekday},{year}ๅนด{month?}ๆœˆ?{date?}{0},{month}ๆœˆ{date?}{0},{date}{0};{yyyy}ๅนด{M}ๆœˆ{d}ๆ—ฅ {Weekday};{tt}{h}:{mm}:{ss};ๅ‰ๅคฉ,ๆ˜จๅคฉ,ไปŠๅคฉ,ๆ˜Žๅคฉ,ๅพŒๅคฉ;,ๅ‰,,ๅพŒ;,ไธŠ|ๅŽป,้€™,ไธ‹|ๆ˜Ž',
+
+Date.addLocale('zh-TW', {
+  'monthSuffix': 'ๆœˆ',
+  'weekdays': 'ๆ˜ŸๆœŸๆ—ฅ|้€ฑๆ—ฅ,ๆ˜ŸๆœŸไธ€|้€ฑไธ€,ๆ˜ŸๆœŸไบŒ|้€ฑไบŒ,ๆ˜ŸๆœŸไธ‰|้€ฑไธ‰,ๆ˜ŸๆœŸๅ››|้€ฑๅ››,ๆ˜ŸๆœŸไบ”|้€ฑไบ”,ๆ˜ŸๆœŸๅ…ญ|้€ฑๅ…ญ',
+  'units': 'ๆฏซ็ง’,็ง’้˜,ๅˆ†้˜,ๅฐๆ™‚,ๅคฉ,ๅ€‹ๆ˜ŸๆœŸ|้€ฑ,ๅ€‹ๆœˆ,ๅนด',
+  'tokens': 'ๆ—ฅ|่™Ÿ',
+  'short':'{yyyy}ๅนด{M}ๆœˆ{d}ๆ—ฅ',
+  'long': '{yyyy}ๅนด{M}ๆœˆ{d}ๆ—ฅ {tt}{h}:{mm}',
+  'full': '{yyyy}ๅนด{M}ๆœˆ{d}ๆ—ฅ {Weekday} {tt}{h}:{mm}:{ss}',
+  'past': '{num}{unit}{sign}',
+  'future': '{num}{unit}{sign}',
+  'duration': '{num}{unit}',
+  'timeSuffixes': '้ปž|ๆ™‚,ๅˆ†้˜?,็ง’',
+  'ampm': 'ไธŠๅˆ,ไธ‹ๅˆ',
+  'modifiers': [
+    { 'name': 'day', 'src': 'ๅ‰ๅคฉ', 'value': -2 },
+    { 'name': 'day', 'src': 'ๆ˜จๅคฉ', 'value': -1 },
+    { 'name': 'day', 'src': 'ไปŠๅคฉ', 'value': 0 },
+    { 'name': 'day', 'src': 'ๆ˜Žๅคฉ', 'value': 1 },
+    { 'name': 'day', 'src': 'ๅพŒๅคฉ', 'value': 2 },
+    { 'name': 'sign', 'src': 'ๅ‰', 'value': -1 },
+    { 'name': 'sign', 'src': 'ๅพŒ', 'value': 1 },
+    { 'name': 'shift', 'src': 'ไธŠ|ๅŽป', 'value': -1 },
+    { 'name': 'shift', 'src': '้€™', 'value':  0 },
+    { 'name': 'shift', 'src': 'ไธ‹|ๆ˜Ž', 'value':  1 }
+  ],
+  'dateParse': [
+    '{num}{unit}{sign}',
+    '{shift}{unit=5-7}'
+  ],
+  'timeParse': [
+    '{shift}{weekday}',
+    '{year}ๅนด{month?}ๆœˆ?{date?}{0?}',
+    '{month}ๆœˆ{date?}{0?}',
+    '{date}[ๆ—ฅ่™Ÿ]'
+  ]
+});
+
+
+}).call(this);
level2/node_modules/sugar/test/environments/node/test.js
@@ -0,0 +1,38 @@
+
+environment = 'node';
+
+var Sugar = require('../../../release/sugar-full.development');
+
+// Test suite
+require('../../javascripts/setup.js');
+require('../../javascripts/date_helper.js');
+require('../../javascripts/object_helper.js');
+
+// Tests
+require('../sugar/object.js');
+require('../sugar/string.js');
+require('../sugar/string_range.js');
+require('../sugar/array.js');
+require('../sugar/number.js');
+require('../sugar/number_range.js');
+require('../sugar/regexp.js');
+require('../sugar/function.js');
+require('../sugar/es5.js');
+require('../sugar/equals.js');
+require('../sugar/date.js');
+require('../sugar/date_range.js');
+require('../sugar/date_zh_cn.js')
+require('../sugar/date_zh_tw.js')
+require('../sugar/date_ko.js')
+require('../sugar/date_ru.js')
+require('../sugar/date_es.js')
+require('../sugar/date_pt.js')
+require('../sugar/date_fr.js')
+require('../sugar/date_it.js')
+require('../sugar/date_de.js')
+require('../sugar/date_ja.js')
+require('../sugar/date_sv.js')
+require('../sugar/inflections.js');
+require('../sugar/language.js');
+
+syncTestsFinished();
level2/node_modules/sugar/test/environments/sugar/array.js
@@ -0,0 +1,2927 @@
+
+test('Array', function () {
+
+  var arr, expected, expectedIndexes, count, f1 = function(){}, f2 = function(){};
+  var sparseArraySupport = 0 in [undefined];
+  var stringObj = new String('foo');
+
+  // Using [] or the constructor "new Array" will cause this test to fail in IE7/8. Evidently passing undefined to the
+  // constructor will not push undefined as expected, however the length property will still appear as if it was pushed.
+  // arr = [undefined, undefined, undefined];
+  //
+  // However we can do it this way, which is a much more likely user scenario in any case:
+  var arrayOfUndefined = [];
+  arrayOfUndefined.push(undefined);
+  arrayOfUndefined.push(undefined);
+  arrayOfUndefined.push(undefined);
+
+  var arrayOfUndefinedWith1 = [1];
+  arrayOfUndefinedWith1.push(undefined);
+
+
+  arr = [1,2,3];
+  count = 0;
+
+  for(var key in arr){
+    count++;
+  }
+
+  equalWithWarning(count, 3, 'for..in loops will break on arrays.');
+
+
+  equal(['a','b','c'].indexOf('b'), 1, 'Array#indexOf | b in a,b,c');
+  equal(['a','b','c'].indexOf('b', 0), 1, 'Array#indexOf | b in a,b,c from 0');
+  equal(['a','b','c'].indexOf('a'), 0, 'Array#indexOf | a in a,b,c');
+  equal(['a','b','c'].indexOf('f'), -1, 'Array#indexOf | f in a,b,c');
+
+  equal(['a','b','c','b'].indexOf('b'), 1, 'Array#indexOf | finds first instance');
+  equal(['a','b','c','b'].indexOf('b', 2), 3, 'Array#indexOf | finds first instance from index');
+
+  equal([5,2,4].indexOf(5), 0, 'Array#indexOf | 5 in 5,2,4');
+  equal([5,2,4].indexOf(2), 1, 'Array#indexOf | 2 in 5,2,4');
+  equal([5,2,4].indexOf(4), 2, 'Array#indexOf | 4 in 5,2,4');
+  equal([5,2,4,4].indexOf(4, 3), 3, 'Array#indexOf | 4 in 5,2,4,4 from index 3');
+
+  equal([5,2,4,4].indexOf(4, 10), -1, 'Array#indexOf | 4 in 5,2,4,4 from index 10');
+  equal([5,2,4,4].indexOf(4, -10), 2, 'Array#indexOf | 4 in 5,2,4,4 from index -10');
+  equal([5,2,4,4].indexOf(4, -1), 3, 'Array#indexOf | 4 in 5,2,4,4 from index -1');
+
+  equal([{ foo: 'bar' }].indexOf({ foo: 'bar' }), -1, 'Array#indexOf | will not find deep objects (use findIndex)');
+  equal([{ foo: 'bar' }].indexOf(function(a) { return a.foo === 'bar'; }), -1, 'Array#indexOf | will not run against a function (use findIndex)');
+
+  equal(['a','b','c','d','a','b'].lastIndexOf('b'), 5, 'Array#lastIndexOf | b');
+  equal(['a','b','c','d','a','b'].lastIndexOf('b', 4), 1, 'Array#lastIndexOf | b from index 4');
+  equal(['a','b','c','d','a','b'].lastIndexOf('z'), -1, 'Array#lastIndexOf | z');
+
+  equal([1,5,6,8,8,2,5,3].lastIndexOf(3), 7, 'Array#lastIndexOf | 1,5,6,8,8,2,5,3 | 3');
+  equal([1,5,6,8,8,2,5,3].lastIndexOf(3, 0), -1, 'Array#lastIndexOf | 1,5,6,8,8,2,5,3 | 3 from index 0');
+  equal([1,5,6,8,8,2,5,3].lastIndexOf(8), 4, 'Array#lastIndexOf | 1,5,6,8,8,2,5,3 | 8');
+  equal([1,5,6,8,8,2,5,3].lastIndexOf(8, 3), 3, 'Array#lastIndexOf | 1,5,6,8,8,2,5,3 | 8 from index 3');
+  equal([1,5,6,8,8,2,5,3].lastIndexOf(1), 0, 'Array#lastIndexOf | 1,5,6,8,8,2,5,3 | 1');
+  equal([1,5,6,8,8,2,5,3].lastIndexOf(42), -1, 'Array#lastIndexOf | 1,5,6,8,8,2,5,3 | 42');
+
+  equal([2,5,9,2].lastIndexOf(2), 3, 'Array#lastIndexOf | 2,5,9,2 | 2');
+  equal([2,5,9,2].lastIndexOf(7), -1, 'Array#lastIndexOf | 2,5,9,2 | 7');
+  equal([2,5,9,2].lastIndexOf(2, 3), 3, 'Array#lastIndexOf | 2,5,9,2 | 2 from index 3');
+  equal([2,5,9,2].lastIndexOf(2, 2), 0, 'Array#lastIndexOf | 2,5,9,2 | 2 from index 2');
+  equal([2,5,9,2].lastIndexOf(2, -2), 0, 'Array#lastIndexOf | 2,5,9,2 | 2 from index -2');
+  equal([2,5,9,2].lastIndexOf(2, -1), 3, 'Array#lastIndexOf | 2,5,9,2 | 2 from index -1');
+  equal([2,5,9,2].lastIndexOf(2, -10), -1, 'Array#lastIndexOf | 2,5,9,2 | 2 from index -10');
+
+  // Prototype's "lastIndexOf" apparently doesn't pass this particular test.
+  //equal([2,5,9,2].lastIndexOf(2, 10), 3, 'Array#lastIndexOf | 2,5,9,2 | 2 from index 10', { prototype: (jQuery.browser.msie ? 10 : 3) });
+
+  equal([{ foo: 'bar' }].lastIndexOf({ foo: 'bar' }), -1, 'Array#lastIndexOf | will not find deep objects (use findIndex)');
+  equal([{ foo: 'bar' }].lastIndexOf(function(a) { return a.foo === 'bar'; }), -1, 'Array#lastIndexOf | will not run against a function (use findIndex)');
+
+
+
+  equal([1,1,1].every(1), true, 'Array#every | accepts a number shortcut match');
+  equal([1,1,2].every(1), false, 'Array#every | accepts a number shortcut no match');
+  equal(['a','a','a'].every('a'), true, 'Array#every | accepts a string shortcut match');
+  equal(['a','b','a'].every('a'), false, 'Array#every | accepts a string shortcut no match');
+  equal(['a','b','c'].every(/[a-f]/), true, 'Array#every | accepts a regex shortcut match');
+  equal(['a','b','c'].every(/[m-z]/), false, 'Array#every | accepts a regex shortcut no match');
+  equal([{a:1},{a:1}].every({a:1}), true, 'Array#every | checks objects match');
+  equal([{a:1},{a:2}].every({a:1}), false, 'Array#every | checks object no match');
+
+  equal([12,5,8,130,44].every(function(el, i, a) { return el >= 10; }), false, 'Array#every | not every element is greater than 10');
+  equal([12,54,18,130,44].every(function(el, i, a) { return el >= 10; }), true, 'Array#every | every element is greater than 10');
+
+  equal(arrayOfUndefined.every(undefined), true, 'Array#every | all undefined');
+  equal(arrayOfUndefined.clone().add('a').every(undefined), false, 'Array#every | every undefined');
+  equal(['a', 'b'].every(undefined), false, 'Array#every | none undefined');
+
+  ['a'].every(function(el, i, a) {
+    equal(el, 'a', 'Array#every | First parameter is the element');
+    equal(i, 0, 'Array#every | Second parameter is the index');
+    equal(a, ['a'], 'Array#every | Third parameter is the array', { prototype: undefined });
+    equal(this.toString(), 'this', 'Array#every | Scope is passed properly');
+  }, 'this');
+
+
+  equal([{name:'john',age:25}].all({name:'john',age:25}), true, 'Array#all | handles complex objects', { prototype: false });
+  equal([{name:'john',age:25},{name:'fred',age:85}].all('age'), false, 'Array#all | simple string mistakenly passed for complex objects');
+  equal([{name:'john',age:25},{name:'fred',age:85}].all({name:'john',age:25}), false, "Array#all | john isn't all");
+
+
+
+  equal([1,2,3].some(1), true, 'Array#some | accepts a number shortcut match');
+  equal([2,3,4].some(1), false, 'Array#some | accepts a number shortcut no match');
+  equal(['a','b','c'].some('a'), true, 'Array#some | accepts a string shortcut match');
+  equal(['b','c','d'].some('a'), false, 'Array#some | accepts a string shortcut no match');
+  equal(['a','b','c'].some(/[a-f]/), true, 'Array#some | accepts a regex shortcut match');
+  equal(['a','b','c'].some(/[m-z]/), false, 'Array#some | accepts a regex shortcut no match');
+  equal([{a:1},{a:2}].some({a:1}), true, 'Array#some | checks objects match');
+  equal([{a:2},{a:3}].some({a:1}), false, 'Array#some | checks object no match');
+
+  equal([12,5,8,130,44].some(function(el, i, a) { return el > 10 }), true, 'Array#some | some elements are greater than 10');
+  equal([12,5,8,130,44].some(function(el, i, a) { return el < 10 }), true, 'Array#some | some elements are less than 10');
+  equal([12,54,18,130,44].some(function(el, i, a) { return el >= 10 }), true, 'Array#some | all elements are greater than 10');
+  equal([12,5,8,130,44].some(function(el, i, a) { return el < 4 }), false, 'Array#some | no elements are less than 4');
+
+
+  equal(arrayOfUndefined.some(undefined), true, 'Array#some | all undefined');
+  equal(arrayOfUndefined.clone().add('a').some(undefined), true, 'Array#some | some undefined');
+  equal(['a', 'b'].some(undefined), false, 'Array#some | none undefined');
+
+
+
+  equal([].some(function(el, i, a) { return el > 10 }), false, 'Array#some | no elements are greater than 10 in an empty array');
+  ['a'].some(function(el, i, a) {
+    equal(el, 'a', 'Array#some | first parameter is the element');
+    equal(i, 0, 'Array#some | second parameter is the index');
+    equal(a, ['a'], 'Array#some | third parameter is the array', { prototype: undefined });
+    equal(this.toString(), 'this', 'Array#some | scope is passed properly');
+  }, 'this');
+
+  equal([{name:'john',age:25}].some({name:'john',age:25}), true, 'Array#every | handles complex objects');
+  equal([{name:'john',age:25},{name:'fred',age:85}].some('age'), false, 'Array#some | simple string mistakenly passed for complex objects');
+  equal([{name:'john',age:25},{name:'fred',age:85}].some({name:'john',age:25}), true, 'Array#some | john can be found ');
+
+
+
+
+  equal([1,2,3].filter(1), [1], 'Array#filter | accepts a number shortcut match');
+  equal([2,3,4].filter(1), [], 'Array#filter | accepts a number shortcut no match');
+  equal(['a','b','c'].filter('a'), ['a'], 'Array#filter | accepts a string shortcut match');
+  equal(['b','c','d'].filter('a'), [], 'Array#filter | accepts a string shortcut no match');
+  equal(['a','b','c'].filter(/[a-f]/), ['a','b','c'], 'Array#filter | accepts a regex shortcut match');
+  equal(['a','b','c'].filter(/[m-z]/), [], 'Array#filter | accepts a regex shortcut no match');
+  equal([{a:1},{a:2}].filter({a:1}), [{a:1}], 'Array#filter | checks objects match');
+  equal([{a:2},{a:3}].filter({a:1}), [], 'Array#filter | checks object no match');
+
+  equal([12,4,8,130,44].filter(function(el, i, a) { return el > 10 }), [12,130,44], 'Array#filter | numbers above 10');
+  equal([12,4,8,130,44].filter(function(el, i, a) { return el < 10 }), [4,8], 'Array#filter | numbers below 10');
+  ['a'].filter(function(el, i, a) {
+    equal(el, 'a', 'Array#filter | first parameter is the element');
+    equal(i, 0, 'Array#filter | second parameter is the index');
+    equal(a, ['a'], 'Array#filter | third parameter is the array', { prototype: undefined });
+    equal(this.toString(), 'this', 'Array#filter | scope is passed properly');
+  }, 'this');
+
+
+  equal([{name:'john',age:25},{name:'fred',age:85}].filter('age'), [], 'Array#filter | simple string mistakenly passed for complex objects');
+  equal([{name:'john',age:25},{name:'fred',age:85}].filter({name:'john',age:25}), [{name:'john',age:25}], 'Array#filter | filtering john');
+  equal([{name:'john',age:25},{name:'fred',age:85}].filter({name:'fred',age:85}), [{name:'fred',age:85}], 'Array#filter | filtering fred');
+
+
+  arr = [2, 5, 9];
+  arr.forEach(function(el, i, a) {
+    equal(el, a[i], 'Array#forEach | looping successfully');
+  });
+
+  arr = ['a', [1], { foo: 'bar' }, 352];
+  count = 0;
+  arr.forEach(function(el, i, a) {
+      count++;
+  });
+  equal(count, 4, 'Array#forEach | complex array | should have looped 4 times');
+
+  ['a'].forEach(function(el, i, a) {
+    equal(el, 'a', 'Array#forEach | first parameter is the element');
+    equal(i, 0, 'Array#forEach | second parameter is the index');
+    equal(this.toString(), 'this', 'Array#forEach | scope is passed properly');
+  }, 'this');
+
+
+
+
+  // Array#each now splits functionality from forEach
+
+  arr = [2, 5, 9];
+  arr.each(function(el, i, a) {
+    equal(el, arr[i], 'Array#each | looping successfully');
+  });
+
+  arr = ['a', [1], { foo: 'bar' }, 352];
+  count = 0;
+  arr.each(function() {
+      count++;
+  });
+  equal(count, 4, 'Array#each | complex array | should have looped 4 times');
+
+  ['a'].each(function(el, i, a) {
+    equal(el, 'a', 'Array#each | first parameter is the element');
+    equal(i, 0, 'Array#each | second parameter is the index');
+    equal(a, ['a'], 'Array#each | third parameter is the array', { prototype: undefined });
+    // Note: psychotic syntax here because equal() is now strictly equal, and the this object is actually an "object" string
+    // as opposed to a primitive string, but only in Prototype. Calling .toString() in a non-prototype environment would effectively
+    // try to convert the array to a string, which is also not what we want.
+    equal(this, a, 'Array#each | scope is also the array', { prototype: (function(){ return this; }).call('this'), mootools: 'this' });
+  }, 'this');
+
+  count = 0;
+  Array.prototype.each.call({'0':'a','length':'1'}, function() { count++; }, 0, true);
+  equal(count, 1, 'Array#each | looping over array-like objects with string lengths');
+
+  equal(['foot','goose','moose'].map(function(el) { return el.replace(/o/g, 'e'); }), ['feet', 'geese', 'meese'], 'Array#map | with regexp');
+  // cool!
+  equal([1,4,9].map(Math.sqrt), [1,2,3], 'Array#map | passing Math.sqrt directly');
+  equal([{ foo: 'bar' }].map(function(el) { return el['foo']; }), ['bar'], 'Array#map | with key "foo"');
+
+  ['a'].map(function(el, i, a) {
+    equal(el, 'a', 'Array#map | first parameter is the element');
+    equal(i, 0, 'Array#map | second parameter is the index');
+    equal(a, ['a'], 'Array#map | third parameter is the array', { prototype: undefined });
+    equal(this.toString(), 'this', 'Array#map | scope is passed properly');
+  }, 'this');
+
+
+  equal(['foot','goose','moose'].map('length'), [4,5,5], 'Array#map | length');
+  equal([{name:'john',age:25},{name:'fred',age:85}].map('age'), [25,85], 'Array#map | age');
+  equal([{name:'john',age:25},{name:'fred',age:85}].map('name'), ['john','fred'], 'Array#map | name');
+  equal([{name:'john',age:25},{name:'fred',age:85}].map('cupsize'), [undefined, undefined], 'Array#map | (nonexistent) cupsize');
+  equal([].map('name'), [], 'Array#map');
+
+  equal([1,2,3].map('toString'), ['1','2','3'], 'Array#map | calls a function on a shortcut string');
+
+  raisesError(function(){ [1,2,3].map() }, 'Array#map | raises an error if no argument', { prototype: false });
+
+  equal([1,2,3].map(undefined), [1,2,3], 'Array#map | undefined');
+  equal([1,2,3].map(null), [1,2,3], 'Array#map | null');
+  equal([1,2,3].map(4), [undefined, undefined, undefined], 'Array#map | number');
+
+
+
+  equal([0,1,2,3,4].reduce(function(a,b) { return a + b; }), 10, 'Array#reduce | a + b');
+  equal([[0,1],[2,3],[4,5]].reduce(function(a,b) { return a.concat(b); }, []), [0,1,2,3,4,5], 'Array#reduce | concat');
+  ['a'].reduce(function(p, c, i, a) {
+    equal(p, 'c', 'Array#reduce | a | first parameter is the lhs');
+    equal(c, 'a', 'Array#reduce | a | second parameter is the rhs');
+    equal(i, 0, 'Array#reduce | a | third parameter is the index');
+    equal(a, ['a'], 'Array#reduce | a | fourth parameter is the array');
+  }, 'c');
+  [55,66].reduce(function(p, c, i, a) {
+    equal(p, 55, 'Array#reduce | 55,66 | first parameter is the lhs');
+    equal(c, 66, 'Array#reduce | 55,66 | second parameter is the rhs');
+    equal(i, 1, 'Array#reduce | 55,66 | third parameter is the index');
+    equal(a, [55,66], 'Array#reduce | 55,66 | fourth parameter is the array');
+  });
+  [1].reduce(function(p, c, i, a) {
+    // This assertion should never be called.
+    equal(true, false, 'Array#reduce | one element array with no rhs passed in does not iterate');
+  });
+  equal([1].reduce(function() {}), 1, 'Array#reduce | [1] reduces to 1');
+
+
+  equal([0,1,2,3,4].reduceRight(function(a,b) { return a + b; }), 10, 'Array#reduceRight | a + b');
+  equal([[0,1],[2,3],[4,5]].reduceRight(function(a,b) { return a.concat(b); }, []), [4,5,2,3,0,1], 'Array#reduceRight | concat');
+  ['a'].reduceRight(function(p, c, i, a) {
+    equal(p, 'c', 'Array#reduceRight | a | first parameter is the lhs');
+    equal(c, 'a', 'Array#reduceRight | a | second parameter is the rhs');
+    equal(i, 0, 'Array#reduceRight | a | third parameter is the index');
+    equal(a, ['a'], 'Array#reduceRight | a | fourth parameter is the array');
+  }, 'c');
+  [55,66].reduceRight(function(p, c, i, a) {
+    equal(p, 66, 'Array#reduceRight | 55,66 | first parameter is the lhs');
+    equal(c, 55, 'Array#reduceRight | 55,66 | second parameter is the rhs');
+    equal(i, 0, 'Array#reduceRight | 55,66 | third parameter is the index');
+    equal(a, [55,66], 'Array#reduceRight | 55,66 | fourth parameter is the array');
+  });
+  [1].reduceRight(function(p, c, i, a) {
+    // This assertion should never be called.
+    equal(true, false, 'Array#reduceRight | one element array with no rhs passed in does not iterate');
+  });
+  equal([1].reduceRight(function() {}), 1, 'Array#reduceRight | [1] reduces to 1');
+
+
+  var result = [];
+  var count = 0;
+  ['a','b','c'].each(function(s, i) {
+    result.push(s);
+    equal(i, count + 1, 'Array#each | index should be correct', { prototype: count, mootools: count });
+    count++;
+  }, 1);
+
+  equal(count, 2, 'Array#each | should have run 2 times', { prototype: 3, mootools: 3 });
+  equal(result, ['b','c'], 'Array#each | result', { prototype: ['a','b','c'], mootools: ['a','b','c'] });
+
+
+  result = [];
+  indexes = [1,2,0];
+  count = 0;
+  ['a','b','c'].each(function(s, i) {
+    result.push(s);
+    equal(i, indexes[count], 'Array#each | looping from index 1 | index should be correct', { prototype: indexes.at(count - 1), mootools: indexes.at(count - 1) })
+    count++;
+  }, 1, true);
+
+  equal(count, 3, 'Array#each | looping from index 1 | should have run 3 times')
+  equal(result, ['b','c','a'], 'Array#each | looping from index 1 | result', { prototype: ['a','b','c'], mootools: ['a','b','c'] });
+
+
+  result = [];
+  indexes = [0,1,2];
+  count = 0;
+  ['a','b','c'].each(function(s, i) {
+    result.push(s);
+    equal(i, indexes[count], 'Array#each | looping from index 0 | index should be correct')
+    count++;
+  }, 0, true);
+
+  equal(count, 3, 'Array#each | looping from index 0 | should have run 3 times')
+  equal(result, ['a','b','c'], 'Array#each | looping from index 0 | result');
+
+
+
+  result = [];
+  indexes = [2,0,1];
+  count = 0;
+  ['a','b','c'].each(function(s, i) {
+    result.push(s);
+    equal(i, indexes[count], 'Array#each | looping from index 2 | index should be correct', { prototype: indexes.at(count + 1), mootools: indexes.at(count + 1) })
+    count++;
+  }, 2, true);
+
+  equal(count, 3, 'Array#each | looping from index 2 | should have run 3 times')
+  equal(result, ['c','a','b'], 'Array#each | looping from index 2 | result', { prototype: ['a','b','c'], mootools: ['a','b','c'] });
+
+
+
+  result = [];
+  count = 0;
+  ['a','b','c'].each(function(s, i) {
+    result.push(s);
+    count++;
+  }, 3, true);
+
+  equal(count, 3, 'Array#each | looping from index 3 | should have run 3 times')
+  equal(result, ['a','b','c'], 'Array#each | looping from index 3 | result');
+
+
+
+  result = [];
+  count = 0;
+  ['a','b','c'].each(function(s, i) {
+    result.push(s);
+    count++;
+  }, 4, true);
+
+  equal(count, 3, 'Array#each | looping from index 4 | should have run 3 times')
+  equal(result, ['b','c','a'], 'Array#each | looping from index 4 | result', { prototype: ['a','b','c'], mootools: ['a','b','c'] });
+
+
+
+  result = [];
+  count = 0;
+  ['a','b','c'].each(function(s, i) {
+    result.push(s);
+    count++;
+  }, 49, true);
+
+  equal(count, 3, 'Array#each | looping from index 49 | should have run 3 times')
+  equal(result, ['b','c','a'], 'Array#each | looping from index 49 | result', { prototype: ['a','b','c'], mootools: ['a','b','c'] });
+
+
+
+  result = [];
+  count = 0;
+  ['a','b','c'].each(function(s, i) {
+    result.push(s);
+    count++;
+  }, 'hoofa');
+
+  equal(count, 3, 'Array#each | string index should default to 0 | should have run 3 times')
+  equal(result, ['a','b','c'], 'Array#each | string index should default to 0 | result');
+
+
+  equal(['a','b','c'].each(function(){}), ['a','b','c'], 'Array#each | null function returns the array');
+  raisesError(function(){ [1].each() }, 'Array#each | raises an error if no callback');
+
+  count = 0;
+  ['a','b','c'].each(function() {
+    count++;
+    return false;
+  });
+  equal(count, 1, 'Array#each | returning false will break the loop', { prototype: 3, mootools: 3 });
+
+  count = 0;
+  ['a','b','c'].each(function() {
+    count++;
+    return true;
+  });
+  equal(count, 3, 'Array#each | returning true will not break the loop');
+
+  count = 0;
+  ['a','b','c'].each(function() {
+    count++;
+    return;
+  });
+  equal(count, 3, 'Array#each | returning undefined will not break the loop');
+
+
+  // Sparse array handling with Array#each
+  // These tests cannot be run with Prototype/Mootools, as they will lock the browser
+
+  skipEnvironments(['prototype','mootools'], function() {
+
+    arr = ['a'];
+    arr[Math.pow(2,32) - 2] = 'b';
+    expected = ['a','b'];
+    expectedIndexes = [0, Math.pow(2,32) - 2];
+    count = 0;
+    arr.each(function(el, i, a) {
+      equal(this, arr, 'Array#each | sparse arrays | this object should be the array');
+      equal(el, expected[count], 'Array#each | sparse arrays | first argument should be the current element');
+      equal(i, expectedIndexes[count], 'Array#each | sparse arrays | second argument should be the current index');
+      equal(a, arr, 'Array#each | sparse arrays | third argument should be the array');
+      count++;
+    });
+    equal(count, 2, 'Array#each | sparse arrays | count should match');
+
+
+    arr = [];
+    arr[-2] = 'd';
+    arr[2]  = 'f';
+    arr[Math.pow(2,32)] = 'c';
+    count = 0;
+    arr.each(function(el, i) {
+      equal(el, 'f', 'Array#each | sparse arrays | values outside range are not iterated over | el');
+      equal(i, 2, 'Array#each | sparse arrays | values outside range are not iterated over | index');
+      count++;
+    });
+    equal(count, 1, 'Array#each | sparse arrays | values outside range are not iterated over | count');
+
+  });
+
+
+
+  arr = [];
+  arr[9] = 'd';
+  arr[2] = 'f';
+  arr[5] = 'c';
+  count = 0;
+  expected = ['f','c','d'];
+  expectedIndexes = [2,5,9];
+  arr.each(function(el, i) {
+    equal(el, expected[count], 'Array#each | sparse arrays | elements are in expected order');
+    equal(i, expectedIndexes[count], 'Array#each | sparse arrays | index is in expected order', { prototype: count });
+    count++;
+  });
+  equal(count, 3, 'Array#each | sparse arrays | unordered array should match');
+
+
+  count = 0;
+  arrayOfUndefined.each(function() {
+    count++;
+  });
+  equal(count, 3, 'Array#each | however, simply having an undefined in an array does not qualify it as sparse');
+
+
+
+  equal(['a','b','c'].find('a'), 'a', 'Array#find | a');
+  equal(['a','a','c'].find('a'), 'a', 'Array#find | first a');
+  equal(['a','b','c'].find('q'), undefined, 'Array#find | q');
+  equal([1,2,3].find(1), 1, 'Array#find | 1');
+  equal([2,2,3].find(2), 2, 'Array#find | 2');
+  equal([1,2,3].find(4), undefined, 'Array#find | 4');
+  equal([{a:1},{b:2},{c:3}].find({a:1}), {a:1}, 'Array#find | a:1', { prototype: undefined });
+  equal([{a:1},{a:1},{c:3}].find({a:1}), {a:1}, 'Array#find | first a:1', { prototype: undefined });
+  equal([{a:1},{b:2},{c:3}].find({d:4}), undefined, 'Array#find | d:4');
+  equal([{a:1},{b:2},{c:3}].find({c:4}), undefined, 'Array#find | c:4');
+  equal([[1,2],[2,3],[4,5]].find([2,3]), [2,3], 'Array#find | 2,3', { prototype: undefined });
+  equal([[1,2],[2,3],[4,5]].find([2,4]), undefined, 'Array#find | 2,4');
+  equal([[1,2],[2,3],[2,3]].find([2,3]), [2,3], 'Array#find | first 2,3', { prototype: undefined });
+  equal(['foo','bar'].find(/f+/), 'foo', 'Array#find | /f+/', { prototype: undefined });
+  equal(['foo','bar'].find(/[a-f]/), 'foo', 'Array#find | /a-f/', { prototype: undefined });
+  equal(['foo','bar'].find(/q+/), undefined, 'Array#find | /q+/');
+  equal([function() {}].find(function(e) {}, 0), undefined, 'Array#find | undefined function');
+  equal([null, null].find(null, 0), null, 'Array#find | null');
+  equal([undefined, undefined].find(undefined, 0), undefined, 'Array#find | undefined');
+  equal([undefined, 'a'].find(undefined, 1), undefined, 'Array#find | undefined can be found');
+
+
+  count = 0;
+  [1,2,3].find(function(n) {
+    count++;
+    return n == 1;
+  });
+  equal(count, 1, 'Array#find | should immediately finish when it finds a match');
+
+
+
+
+
+  equal(['a','b','c'].findAll('a'), ['a'], 'Array#findAll | a');
+  equal(['a','a','c'].findAll('a'), ['a','a'], 'Array#findAll | a,a');
+  equal(['a','b','c'].findAll('q'), [], 'Array#findAll | q');
+  equal([1,2,3].findAll(1), [1], 'Array#findAll | 1');
+  equal([2,2,3].findAll(2), [2,2], 'Array#findAll | 2,2');
+  equal([1,2,3].findAll(4), [], 'Array#findAll | 4');
+  equal([{a:1},{b:2},{c:3}].findAll({a:1}), [{a:1}], 'Array#findAll | a:1', { prototype: [] });
+  equal([{a:1},{a:1},{c:3}].findAll({a:1}), [{a:1},{a:1}], 'Array#findAll | a:1,a:1', { prototype: [] });
+  equal([{a:1},{b:2},{c:3}].findAll({d:4}), [], 'Array#findAll | d:4');
+  equal([{a:1},{b:2},{c:3}].findAll({c:4}), [], 'Array#findAll | c:4');
+  equal([[1,2],[2,3],[4,5]].findAll([2,3]), [[2,3]], 'Array#findAll | 2,3', { prototype: [] });
+  equal([[1,2],[2,3],[4,5]].findAll([2,4]), [], 'Array#findAll | 2,4');
+  equal([[1,2],[2,3],[2,3]].findAll([2,3]), [[2,3],[2,3]], 'Array#findAll | [2,3],[2,3]', { prototype: [] });
+  equal(['foo','bar'].findAll(/f+/), ['foo'], 'Array#findAll | /f+/', { prototype: [] });
+  equal(['foo','bar'].findAll(/[a-f]/), ['foo','bar'], 'Array#findAll | /[a-f]/', { prototype: [] });
+  equal(['foo','bar'].findAll(/[a-f]/, 1), ['bar'], 'Array#findAll | /[a-f]/ from index 1', { prototype: [] });
+  equal(['foo','bar'].findAll(/[a-f]/, 1, true), ['bar','foo'], 'Array#findAll | /[a-f]/ from index 1', { prototype: [] });
+  equal(['foo','bar'].findAll( /q+/), [], 'Array#findAll | /q+/');
+  equal([1,2,3].findAll(function(e) { return e > 0; }, 0), [1,2,3], 'Array#findAll | greater than 0 from index 0');
+  equal([1,2,3].findAll(function(e) { return e > 0; }, 1), [2,3], 'Array#findAll | greater than 0 from index 1', { prototype: [1,2,3] });
+  equal([1,2,3].findAll(function(e) { return e > 0; }, 2), [3], 'Array#findAll | greater than 0 from index 2', { prototype: [1,2,3] });
+  equal([1,2,3].findAll(function(e) { return e > 0; }, 3), [], 'Array#findAll | greater than 0 from index 3', { prototype: [1,2,3] });
+  equal([1,2,3].findAll(function(e) { return e > 0; }, 4), [], 'Array#findAll | greater than 0 from index 4', { prototype: [1,2,3] });
+  equal([1,2,3].findAll(function(e) { return e > 1; }, 0), [2,3], 'Array#findAll | greater than 1 from index 0');
+  equal([1,2,3].findAll(function(e) { return e > 1; }, 1), [2,3], 'Array#findAll | greater than 1 from index 1');
+  equal([1,2,3].findAll(function(e) { return e > 1; }, 2), [3], 'Array#findAll | greater than 1 from index 2', { prototype: [2,3] });
+  equal([1,2,3].findAll(function(e) { return e > 2; }, 0), [3], 'Array#findAll | greater than 2 from index 0');
+  equal([1,2,3].findAll(function(e) { return e > 3; }, 0), [], 'Array#findAll | greater than 3 from index 0');
+
+  equal([1,2,3].findAll(function(e) { return e > 0; }, 0, true), [1,2,3], 'Array#findAll | looping | greater than 0 from index 0');
+  equal([1,2,3].findAll(function(e) { return e > 0; }, 1, true), [2,3,1], 'Array#findAll | looping | greater than 0 from index 1', { prototype: [1,2,3] });
+  equal([1,2,3].findAll(function(e) { return e > 0; }, 2, true), [3,1,2], 'Array#findAll | looping | greater than 0 from index 2', { prototype: [1,2,3] });
+  equal([1,2,3].findAll(function(e) { return e > 0; }, 3, true), [1,2,3], 'Array#findAll | looping | greater than 0 from index 3', { prototype: [1,2,3] });
+  equal([1,2,3].findAll(function(e) { return e > 1; }, 0, true), [2,3], 'Array#findAll | looping | greater than 1 from index 0');
+  equal([1,2,3].findAll(function(e) { return e > 1; }, 1, true), [2,3], 'Array#findAll | looping | greater than 1 from index 1', { prototype: [2,3] });
+  equal([1,2,3].findAll(function(e) { return e > 1; }, 2, true), [3,2], 'Array#findAll | looping | greater than 1 from index 2', { prototype: [2,3] });
+  equal([1,2,3].findAll(function(e) { return e > 2; }, 0, true), [3], 'Array#findAll | looping | greater than 2 from index 0');
+  equal([1,2,3].findAll(function(e) { return e > 3; }, 0, true), [], 'Array#findAll | looping | greater than 3 from index 0');
+
+  equal([{a:10},{a:8},{a:3}].findAll(function(e) { return e['a'] > 5; }, 0), [{a:10},{a:8}], 'Array#findAll | key "a" is greater than 5');
+  equal([{a:10},{a:8},{a:3}].findAll(function(e) { return e['a'] > 5; }, 1), [{a:8}], 'Array#findAll | key "a" is greater than 5 from index 1', { prototype: [{a:10},{a:8}] });
+  equal([{a:10},{a:8},{a:3}].findAll(function(e) { return e['a'] > 5; }, 2), [], 'Array#findAll | key "a" is greater than 5 from index 2', { prototype: [{a:10},{a:8}] });
+
+  equal([{a:10},{a:8},{a:3}].findAll(function(e) { return e['a'] > 5; }, 0, true), [{a:10},{a:8}], 'Array#findAll | looping | key "a" is greater than 5');
+  equal([{a:10},{a:8},{a:3}].findAll(function(e) { return e['a'] > 5; }, 1, true), [{a:8},{a:10}], 'Array#findAll | looping | key "a" is greater than 5 from index 1', { prototype: [{a:10},{a:8}] });
+  equal([{a:10},{a:8},{a:3}].findAll(function(e) { return e['a'] > 5; }, 2, true), [{a:10},{a:8}], 'Array#findAll | looping | key "a" is greater than 5 from index 2', { prototype: [{a:10},{a:8}] });
+
+  equal([function() {}].findAll(function(e) {}, 0), [], 'Array#findAll | null function');
+  equal([function() {}].findAll(function(e) {}, 1), [], 'Array#findAll | null function from index 1');
+  equal([null, null].findAll(null, 0), [null, null], 'Array#findAll | null');
+  equal([null, null].findAll(null, 1), [null], 'Array#findAll | null from index 1', { prototype: [null,null] });
+
+  equal([function() {}].findAll(function(e) {}, 0, true), [], 'Array#findAll | looping | null function');
+  equal([function() {}].findAll(function(e) {}, 1, true), [], 'Array#findAll | looping | null function from index 1');
+  equal([null, null].findAll(null, 0, true), [null, null], 'Array#findAll | looping | null');
+  equal([null, null].findAll(null, 1, true), [null, null], 'Array#findAll | looping | null from index 1');
+
+  // Example: finding last from an index. (reverse order). This means we don't need a findAllFromLastIndex
+  arr = [{name:'john',age:10,food:'sushi'},{name:'randy',age:23,food:'natto'},{name:'karen',age:32,food:'salad'}];
+  arr = [1,2,3,4,5,6,7,8,9];
+  equal(arr.findAll(function(n) { return n % 3 == 0; }, 4), [6,9], 'Array#findAll | n % 3 from index 4', { prototype: [3,6,9] });
+  equal(arr.reverse().findAll(function(n) { return n % 3 == 0; }, 4), [3], 'Array#findAll | reversed | n % 3 from index 4 reversed', { prototype: [9,6,3] });
+
+  arr.reverse(); // Array#reverse is destructive, dammit!
+  equal(arr.findAll(function(n) { return n % 3 == 0; }, 4, true), [6,9,3], 'Array#findAll | looping | n % 3 from index 4', { prototype: [3,6,9] });
+  equal(arr.reverse().findAll(function(n) { return n % 3 == 0; }, 4, true), [3,9,6], 'Array#findAll | looping | reversed | n % 3 from index 4 reversed', { prototype: [9,6,3] });
+
+  var fn = function() {
+    return false;
+  }
+
+  equal([fn].findAll(fn), [fn], 'Array#findAll | should find functions by reference');
+
+  var undefinedContextObj = (function(){ return this; }).call(undefined);
+  [1].findAll(function() {
+    equal(this, undefinedContextObj, 'Array#findAll | this argument should be undefined context');
+  });
+
+  equal([1,1,3].unique(), [1,3], 'Array#unique | 1,1,3');
+  equal([0,0,0].unique(), [0], 'Array#unique | 0,0,0');
+  equal(['a','b','c'].unique(), ['a','b','c'], 'Array#unique | a,b,c');
+  equal(['a','a','c'].unique(), ['a','c'], 'Array#unique | a,a,c');
+
+
+  equal([{ foo:'bar' }, { foo:'bar' }].unique(), [{foo:'bar'}], 'Array#unique | objects uniqued as well', { prototype: [{foo:'bar'},{foo:'bar'}] });
+  equal([{ first: 'John', last: 'Woo' }, { first: 'Reynold', last: 'Woo' }].unique(function(n){ return n.last; }), [{ first: 'John', last: 'Woo' }], 'Array#unique | can be uniqued via a mapping function');
+  equal([{ first: 'John', last: 'Woo' }, { first: 'Reynold', last: 'Woo' }].unique('last'), [{ first: 'John', last: 'Woo' }], 'Array#unique | can be uniqued via a mapping shortcut');
+
+  [1].unique(function(el,i,a) {
+    equal(this, [1], 'Array#unique | scope should be the array');
+    equal(i, 0, 'Array#unique | second param should be the index');
+    equal(a, [1], 'Array#unique | third param should also be the array');
+  });
+
+  equal([function(){ return 'a' }, function() { return 'a'; }, function() { return 'b'; }].unique().length, 3, 'Array#unique | Functions are always unique');
+
+  equal([1,2,3].union([3,4,5]), [1,2,3,4,5], 'Array#union | 1,2,3 + 3,4,5');
+  equal([1,1,1].union([1,2,3]), [1,2,3], 'Array#union | 1,1,1 + 1,2,3');
+  equal([0,0,0].union([1,2,3]), [0,1,2,3], 'Array#union | 0,0,0 + 1,2,3');
+  equal([0,0,0].union([0,0,0]), [0], 'Array#union | 0,0,0 + 0,0,0');
+  equal([].union([]), [], 'Array#union | 2 empty arrays');
+  equal([-1,-2,-3].union([-2,-4,-5]), [-1,-2,-3,-4,-5], 'Array#union | -1,-2,-3 + -2,-4,-5');
+  equal([-1,-2,-3].union([3,4,5]), [-1,-2,-3,3,4,5], 'Array#union | -1,-2,-3 + 3,4,5');
+  equal([{a:1},{b:2}].union([{b:2},{c:3}]), [{a:1},{b:2},{c:3}], 'Array#union | a:1,b:2 + b:2,c:3', { prototype: [{a:1},{b:2},{b:2},{c:3}] });
+  equal([1,2,3].union(4), [1,2,3,4], 'Array#union | 1,2,3 + 4');
+
+  equal([1,2,3].union(4,8,10), [1,2,3,4,8,10], 'Array#union | 1,2,3 + 4 8 10');
+  equal([1,2,3].union([4],[8],[10]), [1,2,3,4,8,10], 'Array#union | 1,2,3 + [4] [8] [10]');
+
+  arr = [1,2,3];
+  arr.union([4,5,6]);
+  equal(arr, [1,2,3], 'Array#union | is non-destructive');
+
+
+
+  equal([1,2,3].intersect([3,4,5]), [3], 'Array#intersect | 1,2,3 & 3,4,5');
+  equal(['a','b','c'].intersect(['c','d','e']), ['c'], 'Array#intersect | a,b,c & c,d,e');
+  equal([1,2,3].intersect([1,2,3]), [1,2,3], 'Array#intersect | 1,2,3 & 1,2,3');
+  equal([1,2,3].intersect([3,2,1]), [1,2,3], 'Array#intersect | 1,2,3 & 3,2,1');
+  equal([].intersect([3]), [], 'Array#intersect | empty array & 3');
+  equal([3].intersect([]), [], 'Array#intersect | 3 & empty array');
+  equal([].intersect([]), [], 'Array#intersect | 2 empty arrays');
+  equal([null].intersect([]), [], 'Array#intersect | [null] & empty array');
+  equal([null].intersect([null]), [null], 'Array#intersect | [null] & [null]', { prototype: [], mootools: [] });
+  equal([false].intersect([false]), [false], 'Array#intersect | [false] & [false]', { prototype: [] });
+  equal([false].intersect([0]), [], 'Array#intersect | [false] & [0]');
+  equal([false].intersect([null]), [], 'Array#intersect | [false] & [null]');
+  equal([false].intersect([undefined]), [], 'Array#intersect | [false] & [undefined]');
+  equal([{a:1},{b:2}].intersect([{b:2},{c:3}]), [{b:2}], 'Array#intersect | a:1,b:2 & b:2,c:3', { prototype: [] });
+  equal([1,1,3].intersect([1,5,6]), [1], 'Array#intersect | 1,1,3 & 1,5,6');
+  equal([1,2,3].intersect([4,5,6]), [], 'Array#intersect | 1,1,3 & 4,5,6');
+
+  equal([1,2,3].intersect([3,4,5],[0,1]), [1,3], 'Array#intersect | handles multiple arguments', { prototype: [3] });
+
+  arr = [1,2,3];
+  arr.intersect([3,4,5]);
+  equal(arr, [1,2,3], 'Array#intersect | is non-destructive');
+
+
+  // Prototype will blow up here
+  skipEnvironments(['prototype'], function(){
+    equal([1,1].intersect(1,1,[1,1]), [1], 'Array#intersect | assure uniqueness');
+    equal([1,2,3].intersect(1), [1], 'Array#intersect | 1,2,3 + 1');
+  });
+
+
+
+
+
+  equal([1,2,3].subtract([3,4,5]), [1,2], 'Array#subtract | 1,2,3 + 3,4,5');
+  equal([1,1,2,2,3,3,4,4,5,5].subtract([2,3,4]), [1,1,5,5], 'Array#subtract | 1,1,2,2,3,3,4,4,5,5 + 2,3,4');
+  equal(['a','b','c'].subtract(['c','d','e']), ['a','b'], 'Array#subtract | a,b,c + c,d,e');
+  equal([1,2,3].subtract([1,2,3]), [], 'Array#subtract | 1,2,3 + 1,2,3');
+  equal([1,2,3].subtract([3,2,1]), [], 'Array#subtract | 1,2,3 + 3,2,1');
+  equal([].subtract([3]), [], 'Array#subtract | empty array + [3]');
+  equal([3].subtract([]), [3], 'Array#subtract | [3] + empty array');
+  equal([].subtract([]), [], 'Array#subtract | 2 empty arrays');
+  equal([null].subtract([]), [null], 'Array#subtract | [null] + empty array');
+  equal([null].subtract([null]), [], 'Array#subtract | [null] + [null]', { mootools: [null] });
+  equal([false].subtract([false]), [], 'Array#subtract | [false] + [false]');
+  equal([false].subtract([0]), [false], 'Array#subtract | [false] + [0]');
+  equal([false].subtract([null]), [false], 'Array#subtract | [false] + [null]');
+  equal([false].subtract([undefined]), [false], 'Array#subtract | [false] + [undefined]');
+  equal([{a:1},{b:2}].subtract([{b:2},{c:3}]), [{a:1}], 'Array#subtract | a:1,b:2 + b:2,c:3');
+  equal([1,1,3].subtract([1,5,6]), [3], 'Array#subtract | 1,1,3 + 1,5,6');
+  equal([1,2,3].subtract([4,5,6]), [1,2,3], 'Array#subtract | 1,2,3 + 4,5,6');
+  equal([1,2,3].subtract(1), [2,3], 'Array#subtract | 1,2,3 + 1');
+
+  equal([1,2,3,4,5].subtract([1],[3],[5]), [2,4], 'Array#subtract | handles multiple arguments');
+
+  arr = [1,2,3];
+  arr.subtract([3]);
+  equal(arr, [1,2,3], 'Array#subtract | is non-destructive');
+
+
+
+
+
+  equal(['a','b','c'].at(0), 'a', 'Array#at | a,b,c | 0');
+  equal(['a','b','c'].at(1), 'b', 'Array#at | a,b,c | 1');
+  equal(['a','b','c'].at(2), 'c', 'Array#at | a,b,c | 2');
+  equal(['a','b','c'].at(3), 'a', 'Array#at | a,b,c | 3');
+  equal(['a','b','c'].at(-1), 'c', 'Array#at | a,b,c | -1');
+  equal(['a','b','c'].at(-2), 'b', 'Array#at | a,b,c | -2');
+  equal(['a','b','c'].at(-3), 'a', 'Array#at | a,b,c | -3');
+  equal(['a','b','c'].at(-4), 'c', 'Array#at | a,b,c | -3');
+
+  equal(['a','b','c'].at(0, false), 'a', 'Array#at | a,b,c | loop off | 0');
+  equal(['a','b','c'].at(1, false), 'b', 'Array#at | a,b,c | loop off | 1');
+  equal(['a','b','c'].at(2, false), 'c', 'Array#at | a,b,c | loop off | 2');
+  equal(['a','b','c'].at(3, false), undefined, 'Array#at | a,b,c | loop off | 3');
+  equal(['a','b','c'].at(-1, false), undefined, 'Array#at | a,b,c | loop off | -1');
+  equal(['a','b','c'].at(-2, false), undefined, 'Array#at | a,b,c | loop off | -2');
+  equal(['a','b','c'].at(-3, false), undefined, 'Array#at | a,b,c | loop off | -3');
+  equal(['a','b','c'].at(-4, false), undefined, 'Array#at | a,b,c | loop off | -4');
+  equal(['a','b','c'].at(), undefined, 'Array#at | a,b,c | no argument');
+  equal([false].at(0), false, 'Array#at | false | loop off | 0');
+  equal(['a'].at(0), 'a', 'Array#at | a | 0');
+  equal(['a'].at(1), 'a', 'Array#at | a | 1');
+  equal(['a'].at(1, false), undefined, 'Array#at | a | loop off | 1');
+  equal(['a'].at(-1), 'a', 'Array#at | a | -1');
+  equal(['a','b','c','d','e','f'].at(0,2,4), ['a','c','e'], 'Array#at | a,b,c,d,e,f | 0,2,4');
+  equal(['a','b','c','d','e','f'].at(1,3,5), ['b','d','f'], 'Array#at | a,b,c,d,e,f | 1,3,5');
+  equal(['a','b','c','d','e','f'].at(0,2,4,6), ['a','c','e','a'], 'Array#at | a,b,c,d,e,f | 0,2,4,6');
+  equal(['a','b','c','d','e','f'].at(0,2,4,6,18), ['a','c','e','a','a'], 'Array#at | a,b,c,d,e,f | 0,2,4,6,18');
+  equal(['a','b','c','d','e','f'].at(0,2,4,6, false), ['a','c','e', undefined], 'Array#at | a,b,c,d,e,f | 0,2,4,6,false | false');
+
+
+  equal(['a','b','c'].from(), ['a','b','c'], 'Array#from | no argument');
+  equal(['a','b','c'].from(1), ['b','c'], 'Array#from| 1');
+  equal(['a','b','c'].from(2), ['c'], 'Array#from | 2');
+  equal(['a','b','c'].from(3), [], 'Array#from | 3');
+  equal(['a','b','c'].from(4), [], 'Array#from | 4');
+  equal(['a','b','c'].from(-1), ['c'], 'Array#from | -1');
+  equal(['a','b','c'].from(-2), ['b','c'], 'Array#from | -2');
+  equal(['a','b','c'].from(-3), ['a','b','c'], 'Array#from | -3');
+  equal(['a','b','c'].from(-4), ['a','b','c'], 'Array#from | -4');
+
+
+  equal(['a','b','c'].to(), ['a','b','c'], 'Array#to | no argument');
+  equal(['a','b','c'].to(0), [], 'Array#to | no argument');
+  equal(['a','b','c'].to(1), ['a'], 'Array#to | 1');
+  equal(['a','b','c'].to(2), ['a','b'], 'Array#to | 2');
+  equal(['a','b','c'].to(3), ['a','b','c'], 'Array#to | 3');
+  equal(['a','b','c'].to(4), ['a','b','c'], 'Array#to | 4');
+  equal(['a','b','c'].to(-1), ['a','b'], 'Array#to | -1');
+  equal(['a','b','c'].to(-2), ['a'], 'Array#to | -2');
+  equal(['a','b','c'].to(-3), [], 'Array#to | -3');
+  equal(['a','b','c'].to(-4), [], 'Array#to | -4');
+
+
+
+  equal(['a','b','c'].first(), 'a', 'Array#first | no argument');
+  equal(['a','b','c'].first(1), ['a'], 'Array#first | 1', { prototype: 'a' });
+  equal(['a','b','c'].first(2), ['a','b'], 'Array#first | 2', { prototype: 'a' });
+  equal(['a','b','c'].first(3), ['a','b','c'], 'Array#first | 3', { prototype: 'a' });
+  equal(['a','b','c'].first(4), ['a','b','c'], 'Array#first | 4', { prototype: 'a' });
+  equal(['a','b','c'].first(-1), [], 'Array#first | -1', { prototype: 'a' });
+  equal(['a','b','c'].first(-2), [], 'Array#first | -2', { prototype: 'a' });
+  equal(['a','b','c'].first(-3), [], 'Array#first | -3', { prototype: 'a' });
+
+
+  equal(['a','b','c'].last(), 'c', 'Array#last | no argument');
+  equal(['a','b','c'].last(1), ['c'], 'Array#last | 1', { prototype: 'c' });
+  equal(['a','b','c'].last(2), ['b','c'], 'Array#last | 2', { prototype: 'c' });
+  equal(['a','b','c'].last(3), ['a','b','c'], 'Array#last | 3', { prototype: 'c' });
+  equal(['a','b','c'].last(4), ['a','b','c'], 'Array#last | 4', { prototype: 'c' });
+  equal(['a','b','c'].last(-1), [], 'Array#last | -1', { prototype: 'c' });
+  equal(['a','b','c'].last(-2), [], 'Array#last | -2', { prototype: 'c' });
+  equal(['a','b','c'].last(-3), [], 'Array#last | -3', { prototype: 'c' });
+  equal(['a','b','c'].last(-4), [], 'Array#last | -4', { prototype: 'c' });
+
+
+
+
+
+  equal([12,87,55].min(), 12, 'Array#min | no argument', { prototype: 12 });
+  equal([12,87,55].min(undefined), 12, 'Array#min | undefined', { prototype: 12 });
+  equal([12,87,55].min(null), 12, 'Array#min | null', { prototype: 12 });
+  equal([-12,-87,-55].min(), -87, 'Array#min | -87', { prototype: -87 });
+  equal([5,5,5].min(), 5, 'Array#min | 5 is uniqued', { prototype: 5 });
+  equal(['a','b','c'].min(), 'a', 'Array#min | strings are not counted', { prototype: 'a' });
+  equal([].min(), undefined, 'Array#min | empty array', { prototype: undefined });
+  equal([null].min(), null, 'Array#min | [null]', { prototype: null });
+  equal([{a:1,b:5},{a:2,b:5},{a:3,b:5}].min(function(el) { return el['a']; }), {a:1,b:5}, 'Array#min | key "a"', { prototype: 1 });
+  equal([{a:1,b:5},{a:2,b:4},{a:3,b:3}].min(function(el) { return el['b']; }), {a:3,b:3}, 'Array#min | key "b", 1 found', { prototype: 3 });
+  equal([{a:1,b:5},{a:3,b:3},{a:3,b:3}].min(function(el) { return el['b']; }), {a:3,b:3}, 'Array#min | key "b", 1 found', { prototype: 3 });
+  equal([{a:1,b:3},{a:2,b:4},{a:3,b:3}].min(function(el) { return el['b']; }), {a:1,b:3}, 'Array#min | key "b", first found', { prototype: 3 });
+  equal([{a:1,b:3},{a:2,b:4},{a:3,b:3}].min(function(el) { return el['b']; }, true), [{a:1,b:3},{a:3,b:3}], 'Array#min | key "b", 2 found', { prototype: 3 });
+  equal([{a:-1,b:-5},{a:-2,b:-4},{a:-3,b:-3}].min(function(el) { return el['b']; }), {a:-1,b:-5}, 'Array#min | key "b", 1 found', { prototype: -5 });
+  equal(['short','and','mort'].min(function(el) { return el.length; }), 'and', 'Array#min | length', { prototype: 3 });
+  equal(['short','and','mort','fat'].min(function(el) { return el.length; }, true), ['and','fat'], 'Array#min | and,fat', { prototype: 3 });
+  equal(['short','and','mort'].min('length'), 'and', 'Array#min | length with shortcut', { prototype: 3 });
+  equal(['short','and','mort'].min('length', true), ['and'], 'Array#min | length with shortcut', { prototype: 3 });
+
+  skipEnvironments(['prototype'], function() {
+    [1].min(function(el,i,a) {
+      equal(this, [1], 'Array#min | scope should be the array');
+      equal(i, 0, 'Array#min | second param should be the index');
+      equal(a, [1], 'Array#min | third param should also be the array');
+      return el;
+    });
+  });
+
+  equal([12,12,12].min(function(n) { return n; }, true), [12,12,12], 'Array#min | should not unique', { prototype: 12 });
+
+  raisesError(function() { arrayOfUndefined.min(); }, 'Array#min | should raise an error when comparing undefined');
+  raisesError(function() { arrayOfUndefinedWith1.min(); }, 'Array#min | should raise an error when comparing 1 to undefined');
+  raisesError(function() { [87,12,55].min(4); }, 'Array#min | number not found in number, so undefined');
+
+
+  equal([12,87,55].max(), 87, 'Array#max | no argument', { prototype: 87 });
+  equal([12,87,55].max(undefined), 87, 'Array#max | undefined', { prototype: 87 });
+  equal([12,87,55].max(null), 87, 'Array#max | null', { prototype: 87 });
+  equal([-12,-87,-55].max(), -12, 'Array#max | -12', { prototype: -12 });
+  equal([5,5,128].max(), 128, 'Array#max | 128', { prototype: 128 });
+  equal([128,128,128].max(), 128, 'Array#max | 128 is uniqued', { prototype: 128 });
+  equal(['a','b','c'].max(), 'c', 'Array#max | strings are not counted', { prototype: 'c' });
+  equal([].max(), undefined, 'Array#max | empty array', { prototype: undefined });
+  equal([null].max(), null, 'Array#max | [null]', { prototype: null });
+  equal([{a:1,b:5},{a:2,b:5},{a:3,b:5}].max(function(el) { return el['a']; }), {a:3,b:5}, 'Array#max | key "a"', { prototype: 3 });
+  equal([{a:1,b:5},{a:2,b:4},{a:3,b:3}].max(function(el) { return el['b']; }), {a:1,b:5}, 'Array#max | key "b" returns b:5', { prototype: 5 });
+  equal([{a:1,b:3},{a:2,b:4},{a:3,b:3}].max(function(el) { return el['b']; }), {a:2,b:4}, 'Array#max | key "b" returns b:4', { prototype: 4 });
+  equal([{a:1,b:3},{a:2,b:4},{a:2,b:4}].max(function(el) { return el['b']; }), {a:2,b:4}, 'Array#max | key "b" returns b:4 uniqued', { prototype: 4 });
+  equal([{a:1,b:3},{a:2,b:1},{a:3,b:3}].max(function(el) { return el['b']; }), {a:1,b:3}, 'Array#max | key "b", first found', { prototype: 3 });
+  equal([{a:1,b:3},{a:2,b:1},{a:3,b:3}].max(function(el) { return el['b']; }, true), [{a:1,b:3},{a:3,b:3}], 'Array#max | key "b", 2 found', { prototype: 3 });
+  equal([{a:-1,b:-5},{a:-2,b:-4},{a:-3,b:-3}].max(function(el) { return el['b']; }), {a:-3,b:-3}, 'Array#max | key "b" returns b:-3', { prototype: -3 });
+  equal(['short','and', 'mort'].max(function(el) { return el.length; }), 'short', 'Array#max | length', { prototype: 5 });
+  equal(['short','and', 'morts', 'fat'].max(function(el) { return el.length; }, true), ['short','morts'], 'Array#max | short,morts', { prototype: 5 });
+
+  skipEnvironments(['prototype'], function() {
+    [1].max(function(el,i,a) {
+      equal(this, [1], 'Array#max | scope should be the array');
+      equal(i, 0, 'Array#max | second param should be the index');
+      equal(a, [1], 'Array#max | third param should also be the array');
+      return el;
+    });
+  });
+
+  equal([12,12,12].max(function(n){ return n; }, true), [12,12,12], 'Array#max | should not unique', { prototype: 12 });
+
+  raisesError(function() { arrayOfUndefined.max(); }, 'Array#max | should raise an error when comparing undefined');
+  raisesError(function() { arrayOfUndefinedWith1.max(); }, 'Array#max | should raise an error when comparing 1 to undefined');
+  raisesError(function() { [87,12,55].max(4); }, 'Array#max | number not found in number, so undefined');
+
+
+
+  var people = [
+    { name: 'jim',    age: 27, hair: 'brown'  },
+    { name: 'mary',   age: 52, hair: 'blonde' },
+    { name: 'ronnie', age: 13, hair: 'brown'  },
+    { name: 'edmund', age: 27, hair: 'blonde' }
+  ];
+
+  equal([1,2,3].most(null), 1, 'Array#most | null | returns first');
+  equal([1,2,3].most(undefined), 1, 'Array#most | undefined | returns first');
+  equal([1,2,3].most(4), 1, 'Array#most | number | returns first');
+
+  equal(people.most(function(person) { return person.age; }).age, 27, 'Array#most | age | age is 27');
+  equal(people.most(function(person) { return person.age; }, true), [{name:'jim',age:27,hair:'brown'},{name:'edmund',age:27,hair:'blonde'}], 'Array#most | age | returns all');
+  equal(people.most(function(person) { return person.hair; }), {name:'jim',age:27,hair:'brown'}, 'Array#most | hair');
+
+  equal([].most(), undefined, 'Array#most | empty array');
+  equal([1,2,3].most(), 1, 'Array#most | 1,2,3');
+  equal([1,2,3,3].most(), 3, 'Array#most | 1,2,3,3');
+  equal([1,1,2,3,3].most(), 1, 'Array#most | 1,1,2,3,3 | first');
+  equal([1,1,2,3,3].most(function(n) { return n; }, true), [1,1,3,3], 'Array#most | 1,1,2,3,3 | all');
+  equal(['a','b','c'].most(), 'a', 'Array#most | a,b,c');
+  equal(['a','b','c','c'].most(), 'c', 'Array#most | a,b,c,c');
+  equal(['a','a','b','c','c'].most(), 'a', 'Array#most | a,a,b,c,c | first');
+  equal(['a','a','b','c','c'].most(function(s){ return s; }, true), ['a','a','c','c'], 'Array#most | a,a,b,c,c | all');
+
+  // Leaving this here as a reference for how to collect the actual number of occurences.
+  equal(people.most(function(person) { return person.age; }, true).length, 2, 'Array#most | collect actual number of occurrences');
+
+  [1].most(function(el,i,a) {
+    equal(this, [1], 'Array#most | scope should be the array');
+    equal(i, 0, 'Array#most | second param should be the index');
+    equal(a, [1], 'Array#most | third param should also be the array');
+    return el;
+  });
+
+
+  equal([1,2,3].least(null), 1, 'Array#least | null');
+  equal([1,2,3].least(undefined), 1, 'Array#least | undefined');
+  equal([1,2,3].least(4), 1, 'Array#least | number');
+
+  equal(people.least(), people[0], 'Array#least | contains mary | does not return most');
+  equal(people.least(function(person) { return person.age; }).age != 27, true, 'Array#least | map age | does not return most');
+  equal(people.least(function(person) { return person.age; }, true).sortBy('name'), [people[1], people[2]], 'Array#least | contains mary and ronnie');
+  equal(people.least(function(person) { return person.age; }, true).sortBy('age'), [{name:'ronnie',age:13,hair:'brown'}, {name:'mary',age:52,hair:'blonde'}], 'Array#least | age and sorted by age');
+
+  equal(people.least(function(person) { return person.hair; }), people[0], 'Array#least | hair');
+
+  equal([].least(), undefined, 'Array#least | empty array');
+  equal([1,2,3].least(), 1, 'Array#least | 1,2,3');
+  equal([1,2,3,3].least(), 1, 'Array#least | 1,2,3,3');
+  equal([1,2,3,3].least(function(n){ return n; }, true), [1,2], 'Array#least | 1,2,3,3 | all');
+  equal([1,1,2,3,3].least(), 2, 'Array#least | 1,1,2,3,3');
+  equal([1,1,1,2,2,3,3,3].least(), 2, 'Array#least | 1,1,1,2,2,3,3,3');
+  equal(['a','b','c'].least(), 'a', 'Array#least | a,b,c');
+  equal(['a','b','c','c'].least(), 'a', 'Array#least | a,b,c,c');
+  equal(['a','b','c','c'].least(function(n) { return n; }, true), ['a','b'], 'Array#least | a,b,c,c | all');
+  equal(['a','a','b','c','c'].least(), 'b', 'Array#least | a,a,b,c,c');
+
+  // Leaving this here as a reference for how to collect the actual number of occurences.
+  equal(people.least(function(person) { return person.age; }, true).length, 2, 'Array#least | collect actual number of occurences');
+
+  [1].least(function(el,i,a) {
+    equal(this, [1], 'Array#least | scope should be the array');
+    equal(i, 0, 'Array#least | second param should be the index');
+    equal(a, [1], 'Array#least | third param should also be the array');
+    return e;
+  });
+
+  equal([12,87,55].sum(), 154, 'Array#sum | 12,87,55');
+  equal([12,87,128].sum(), 227, 'Array#sum | 12,87,128');
+  equal([].sum(), 0, 'Array#sum | empty array is 0');
+  equal([null, false].sum(), 0, 'Array#sum | [null,false] is 0');
+  equal([{a:1,b:5},{a:2,b:5},{a:3,b:5}].sum(function(el) { return el['a']; }), 6, 'Array#sum | key "a"');
+  equal([{a:1,b:5},{a:2,b:5},{a:3,b:5}].sum('a'), 6, 'Array#sum | shortcut for key "a"');
+
+  equal([13,18,13,14,13,16,14,21,13].average(), 15, 'Array#average | 13,18,13,14,13,16,14,21,13');
+  equal([2,2,2].average(), 2, 'Array#average | 2,2,2');
+  equal([2,3,4].average(), 3, 'Array#average | 2,3,4');
+  equal([2,3,4,2].average(), 2.75, 'Array#average | 2,3,4,2');
+  equal([].average(), 0, 'Array#average | empty array is 0');
+  equal([null, false].average(), 0, 'Array#average | [null, false] is 0');
+  equal([{a:1,b:5},{a:2,b:5},{a:3,b:5}].average(function(el) { return el['a']; }), 2, 'Array#average | key "a"');
+  equal([{a:1,b:5},{a:2,b:5},{a:3,b:5}].average('a'), 2, 'Array#average | shortcut for key "a"');
+
+
+  equal(people.average('age'), 29.75, 'Array#average | people average age is 29.75');
+  equal(people.average(function(p) { return p.age; }), 29.75, 'Array#average | people average age is 29.75 by function');
+  equal(isNaN(people.average(function(p) { return p.hair; })), true, 'Array#average | people average hair is NaN');
+
+
+  var grouped;
+
+  equal([].groupBy(), {}, 'Array#groupBy | empty array');
+  equal([1,1,2,2,3,3,4].groupBy(), {1:[1,1],2:[2,2],3:[3,3],4:[4]}, 'Array#groupBy | 1,1,2,2,3,3,4');
+  equal(['a','b','c','a','e','c'].groupBy(), {'a':['a','a'],'b':['b'],'c':['c','c'],'e':['e']}, 'Array#groupBy | a,b,c,a,e,c');
+  equal([{a:1,b:5},{a:8,b:5},{a:8,b:3}].groupBy('a'), {8:[{a:8,b:5},{a:8,b:3}],1:[{a:1,b:5}]}, 'Array#groupBy | grouping by "a"');
+  equal([{a:1,b:5},{a:8,b:5},{a:8,b:3}].groupBy(function(el) { return el['a']; }), {8:[{a:8,b:5},{a:8,b:3}],1:[{a:1,b:5}]}, 'Array#groupBy | grouping by "a" by function');
+
+
+  people = people.sortBy('hair');
+  equal(people.groupBy(function(p) { return p.age; }), {27: [{name:'edmund',age:27,hair:'blonde'},{name:'jim',age:27,hair:'brown'}],52:[{name:'mary',age:52,hair:'blonde'}],13:[{name:'ronnie',age:13,hair:'brown'}]}, 'Array#groupBy | grouping people by age');
+
+  equal([1,2,3].groupBy(undefined), { 1: [1], 2: [2], 3: [3] }, 'Array#groupBy | undefined');
+  equal([1,2,3].groupBy(null), { 1: [1], 2: [2], 3: [3] }, 'Array#groupBy | null');
+  equal([1,2,3].groupBy(4), { 'undefined': [1,2,3] }, 'Array#groupBy | number');
+  equal(['one','two','three'].groupBy('length').keys, undefined, 'Array#groupBy | result should not be an extended object');
+
+  var counter = 0;
+  ['one','two','three'].groupBy('length', function() {
+    counter++;
+  });
+
+  equal(counter, 2, 'Array#groupBy | should allow a callback fn');
+
+  var arr1 = ['a','b','c'];
+  var arr2 = ['d','e','f'];
+  var obj = arr1.groupBy(function(el, i) {
+    return arr2[i];
+  });
+
+  [1].groupBy(function(el,i,a) {
+    equal(this, [1], 'Array#groupBy | scope should be the array');
+    equal(i, 0, 'Array#groupBy | second param should be the index');
+    equal(a, [1], 'Array#groupBy | third param should also be the array');
+  });
+
+  equal(obj, { 'd':['a'],'e':['b'],'f':['c'] }, 'Array#groupBy | should use an index');
+
+
+
+
+
+  equal([1,2,3,4,5,6,7,8,9,10].inGroups(1), [[1,2,3,4,5,6,7,8,9,10]], 'Array#inGroups | in groups of 1');
+  equal([1,2,3,4,5,6,7,8,9,10].inGroups(2), [[1,2,3,4,5],[6,7,8,9,10]], 'Array#inGroups | in groups of 2');
+  equal([1,2,3,4,5,6,7,8,9,10].inGroups(3), [[1,2,3,4],[5,6,7,8],[9,10]], 'Array#inGroups | in groups of 3');
+  equal([1,2,3,4,5,6,7,8,9,10].inGroups(4), [[1,2,3],[4,5,6],[7,8,9],[10]], 'Array#inGroups | in groups of 4');
+  equal([1,2,3,4,5,6,7,8,9,10].inGroups(5), [[1,2],[3,4],[5,6],[7,8],[9,10]], 'Array#inGroups | in groups of 5');
+  equal([1,2,3,4,5,6,7,8,9,10].inGroups(6), [[1,2],[3,4],[5,6],[7,8],[9,10],[]], 'Array#inGroups | in groups of 6');
+  equal([1,2,3,4,5,6,7,8,9,10].inGroups(7), [[1,2],[3,4],[5,6],[7,8],[9,10],[],[]], 'Array#inGroups | in groups of 7');
+
+
+  equal([1,2,3,4,5,6,7,8,9,10].inGroups(3, null), [[1,2,3,4],[5,6,7,8],[9,10,null,null]], 'Array#inGroups | pad with null | in groups of 3');
+  equal([1,2,3,4,5,6,7,8,9,10].inGroups(4, null), [[1,2,3],[4,5,6],[7,8,9],[10,null,null]], 'Array#inGroups | pad with null | in groups of 4');
+  equal([1,2,3,4,5,6,7,8,9,10].inGroups(5, null), [[1,2],[3,4],[5,6],[7,8],[9,10]], 'Array#inGroups | pad with null | in groups of 5');
+  equal([1,2,3,4,5,6,7,8,9,10].inGroups(6, null), [[1,2],[3,4],[5,6],[7,8],[9,10],[null,null]], 'Array#inGroups | pad with null | in groups of 6');
+  equal([1,2,3,4,5,6,7,8,9,10].inGroups(7, null), [[1,2],[3,4],[5,6],[7,8],[9,10],[null,null],[null,null]], 'Array#inGroups | pad with null | in groups of 7');
+
+
+
+  equal([1,2,3,4,5,6,7,8,9,10].inGroupsOf(3), [[1,2,3],[4,5,6],[7,8,9],[10,null,null]], 'Array#inGroupsOf | groups of 3 | 1 to 10');
+  equal([1,2,3,4,5,6,7,8,9].inGroupsOf(3), [[1,2,3],[4,5,6],[7,8,9]], 'Array#inGroupsOf | groups of 3 | 1 to 9');
+  equal([1,2,3,4,5,6,7,8].inGroupsOf(3), [[1,2,3],[4,5,6],[7,8,null]], 'Array#inGroupsOf | groups of 3 | 1 to 8');
+  equal([1,2,3,4,5,6,7].inGroupsOf(3), [[1,2,3],[4,5,6],[7,null,null]], 'Array#inGroupsOf | groups of 3 | 1 to 7');
+  equal([1,2,3,4,5,6].inGroupsOf(3), [[1,2,3],[4,5,6]], 'Array#inGroupsOf | groups of 3 | 1 to 6');
+  equal([1,2,3,4,5].inGroupsOf(3), [[1,2,3],[4,5,null]], 'Array#inGroupsOf | groups of 3 | 1 to 5');
+  equal([1,2,3,4].inGroupsOf(3), [[1,2,3],[4,null,null]], 'Array#inGroupsOf | groups of 3 | 1 to 4');
+  equal([1,2,3].inGroupsOf(3), [[1,2,3]], 'Array#inGroupsOf | groups of 3 | 1 to 3');
+  equal([1,2].inGroupsOf(3), [[1,2,null]], 'Array#inGroupsOf | groups of 3 | 1 to 2');
+  equal([1].inGroupsOf(3), [[1,null,null]], 'Array#inGroupsOf | groups of 3 | 1');
+
+  equal([1,2,3,4,5,6,7,8,9,10].inGroupsOf(3, null), [[1,2,3],[4,5,6],[7,8,9],[10, null, null]], 'Array#inGroupsOf | groups of 3 | pad with null | 1 to 10');
+  equal([1,2,3,4,5,6,7,8,9].inGroupsOf(3, null), [[1,2,3],[4,5,6],[7,8,9]], 'Array#inGroupsOf | groups of 3 | pad with null | 1 to 9');
+  equal([1,2,3,4,5,6,7,8].inGroupsOf(3, null), [[1,2,3],[4,5,6],[7,8, null]], 'Array#inGroupsOf | groups of 3 | pad with null | 1 to 8');
+  equal([1,2,3,4,5,6,7].inGroupsOf(3, null), [[1,2,3],[4,5,6],[7, null, null]], 'Array#inGroupsOf | groups of 3 | pad with null | 1 to 7');
+  equal([1,2,3,4,5,6].inGroupsOf(3, null), [[1,2,3],[4,5,6]], 'Array#inGroupsOf | groups of 3 | pad with null | 1 to 6');
+  equal([1,2,3,4,5].inGroupsOf(3, null), [[1,2,3],[4,5,null]], 'Array#inGroupsOf | groups of 3 | pad with null | 1 to 5');
+  equal([1,2,3,4].inGroupsOf(3, null), [[1,2,3],[4,null,null]], 'Array#inGroupsOf | groups of 3 | pad with null | 1 to 4');
+  equal([1,2,3].inGroupsOf(3, null), [[1,2,3]], 'Array#inGroupsOf | groups of 3 | pad with null | 1 to 3');
+  equal([1,2].inGroupsOf(3, null), [[1,2,null]], 'Array#inGroupsOf | groups of 3 | pad with null | 1 to 2');
+  equal([1].inGroupsOf(3, null), [[1,null,null]], 'Array#inGroupsOf | groups of 3 | pad with null | 1');
+
+  equal([1].inGroupsOf(3, ' '), [[1,' ',' ']], 'Array#inGroupsOf | pad with spaces');
+  equal([1].inGroupsOf(3, true), [[1,true,true]], 'Array#inGroupsOf | pad with true');
+  equal([1].inGroupsOf(3, false), [[1,false,false]], 'Array#inGroupsOf | pad with false');
+
+  equal([1].inGroupsOf(), [[1]], 'Array#inGroupsOf | no argument', { prototype: [] });
+  equal([1].inGroupsOf(1, null), [[1]], 'Array#inGroupsOf | pad with null | no argument');
+
+  equal([1].inGroupsOf(0), [1], 'Array#inGroupsOf | 0');
+  equal([1].inGroupsOf(0, null), [1], 'Array#inGroupsOf | pad with null | 0');
+
+  equal([1].inGroupsOf(3, null), [[1, null, null]], 'Array#inGroupsOf | pad with null | 3');
+  equal([1].inGroupsOf(1, null), [[1]], 'Array#inGroupsOf | pad with null | 1');
+  equal([].inGroupsOf(3), [], 'Array#inGroupsOf | empty array');
+  equal([].inGroupsOf(3, null), [], 'Array#inGroupsOf | pad with null | empty array');
+  equal([null].inGroupsOf(3), [[null,null,null]], 'Array#inGroupsOf | [null] in groups of 3');
+  equal([null].inGroupsOf(3, null), [[null,null,null]], 'Array#inGroupsOf | pad with null | [null] in groups of 3');
+  equal([1].inGroupsOf(3, undefined), [[1,null,null]], 'Array#inGroupsOf | passing undefined reverts to null');
+
+
+  // Issue #142 - inGroupsOf corrupting array length
+  arr = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
+  arr.inGroupsOf(3);
+  equal(arr.length, 20, 'Array#inGroupsOf | does not corrupt original array length');
+
+
+  // Emulating example of Enumerable#each_slice
+  equal([1,2,3,4,5,6,7,8,9,10].inGroupsOf(3).map(function(g) { return g[1]; }).compact(), [2,5,8], 'Array#inGroupsOf | 1 to 10 in groups of 3 compacted');
+
+
+
+  equal([1,2,3].compact(), [1,2,3], 'Array#compact | 1,2,3');
+  equal([1,2,null,3].compact(), [1,2,3], 'Array#compact | 1,2,null,3');
+  equal([1,2,undefined,3].compact(), [1,2,3], 'Array#compact | 1,2,undefined,3');
+  equal([undefined,undefined,undefined].compact(), [], 'Array#compact | undefined,undefined,undefined');
+  equal([null,null,null].compact(), [], 'Array#compact | null,null,null');
+  equal([NaN,NaN,NaN].compact(), [], 'Array#compact | NaN,NaN,NaN', { prototype: [NaN,NaN,NaN] });
+  equal(['','',''], ['','',''], 'Array#compact | empty strings');
+  equal([false,false,false].compact(), [false,false,false], 'Array#compact | false is left alone');
+  equal([0,1,2].compact(), [0,1,2], 'Array#compact | 0,1,2');
+  equal([].compact(), [], 'Array#compact | empty array');
+  equal(['a','b','c'].compact(), ['a','b','c'], 'Array#compact | a,b,c');
+  equal([f1, f2].compact(), [f1, f2], 'Array#compact | functions');
+  equal([null,[null],[false,[null,undefined,3]]].compact(), [[],[false,[3]]], 'Array#compact | deep compacts as well', { prototype: [[null],[false,[null,undefined,3]]] });
+  equal([null,null,null,[null],null].compact(), [[]], "Array#compact | deep compact doesn't have index conflicts", { prototype: [[null]] });
+
+  equal([false,false,false].compact(true), [], 'Array#compact | falsy | false is removed', { prototype: [false,false,false] });
+  equal([0,0].compact(true), [], 'Array#compact | falsy | 0', { prototype: [0,0] });
+  equal(['',''].compact(true), [], 'Array#compact | falsy | empty string', { prototype: ['',''] });
+  equal([' ',' '].compact(true), [' ',' '], 'Array#compact | falsy | strings with spaces are kept');
+  equal([8,3].compact(true), [8,3], 'Array#compact | falsy | numbers are kept');
+  equal([false,undefined,false,null,NaN].compact(true), [], 'Array#compact | falsy | others are also handled', { prototype: [false,false,NaN] });
+
+  equal([1,2,2,3].count(), 4, 'Array#count | no arugment numeric');
+  equal([1,2,2,3].count(2), 2, 'Array#count | count 2s');
+  equal(['a','b','c','c'].count(), 4, 'Array#count | no argument alphabet');
+  equal(['a','b','c','c'].count('c'), 2, 'Array#count | count "c"s');
+  equal([1,2,2,3].count(function(el) { return el % 2 == 0; }), 2, 'Array#count | count all odd numbers');
+  equal([1,2,2,3].count(function(el) { return el > 2; }), 1, 'Array#count | count all numbers greater than 2');
+  equal([1,2,2,3].count(function(el) { return el > 20; }), 0, 'Array#count | count all numbers greater than 20');
+  equal([{a:1},{a:2},{a:1}].count({a:1}), 2, 'Array#count | count all a:1', { prototype: 0 });
+
+
+
+
+
+  equal([1,2,2,3].remove(), [1,2,2,3], 'Array#remove | no argument numeric');
+  equal([1,2,2,3].remove(2), [1,3], 'Array#remove | remove 2s');
+  equal([0,1,2].exclude(0), [1,2], 'Array#remove | finds 0');
+  equal(['a','b','c','c'].remove(), ['a','b','c','c'], 'Array#remove | no argument alphabet');
+  equal(['a','b','c','c'].remove('c'), ['a','b'], 'Array#remove | remove "c"s');
+  equal([1,2,2,3].remove(function(el) { return el % 2 == 0; }), [1,3], 'Array#remove | remove all odd numbers');
+  equal([1,2,2,3].remove(function(el) { return el > 2; }), [1,2,2], 'Array#remove | remove all numbers greater than 2');
+  equal([1,2,2,3].remove(function(el) { return el > 20; }), [1,2,2,3], 'Array#remove | remove all numbers greater than 20');
+  equal([{a:1},{a:2},{a:1}].remove({a:1}), [{a:2}], 'Array#remove | remove all a:1');
+  ['a'].remove(function(el,i,arr) {
+    equal(el, 'a', 'Array#remove | first param should be the element');
+    equal(i, 0, 'Array#remove | second param should be the index');
+    equal(arr, ['a'], 'Array#remove | third param should be the array');
+  });
+
+  arr = [1,2,3];
+  arr.remove(2);
+  equal(arr, [1,3], 'Array#remove | should affect the original array');
+
+  arr = [1,2,3];
+  arr.remove(2,3);
+  equal(arr, [1], 'Array#remove | can remove multiple elements');
+
+  equal([f1].remove(f1), [], 'Array#remove | can find via strict equality');
+
+  equal([1,2,3].remove([1,3]), [1,2,3], 'Array#remove | each argument is a separate element');
+  equal([1,2,3].remove(1,3), [2], 'Array#remove | however multiple arguments still work');
+  equal([[1,3],2].remove([1,3]), [2], 'Array#remove | and those elements are still properly found');
+
+
+
+  equal([1,2,2,3].exclude(), [1,2,2,3], 'Array#exclude | no argument numeric');
+  equal([1,2,2,3].exclude(2), [1,3], 'Array#exclude | exclude 2s');
+  equal([0,1,2].exclude(0), [1,2], 'Array#exclude | finds 0');
+  equal(['a','b','c','c'].exclude(), ['a','b','c','c'], 'Array#exclude | no argument alphabet');
+  equal(['a','b','c','c'].exclude('c'), ['a','b'], 'Array#exclude | exclude "c"s');
+  equal([1,2,2,3].exclude(function(el){ return el % 2 == 0; }), [1,3], 'Array#exclude | exclude all odd numbers');
+  equal([1,2,2,3].exclude(function(el){ return el > 2; }), [1,2,2], 'Array#exclude | exclude all numbers greater than 2');
+  equal([1,2,2,3].exclude(function(el){ return el > 20; }), [1,2,2,3], 'Array#exclude | exclude all numbers greater than 20');
+  equal([{a:1},{a:2},{a:1}].exclude({a:1}), [{a:2}], 'Array#exclude | exclude all a:1');
+  ['a'].exclude(function(el,i,arr){
+    equal(el, 'a', 'Array#exclude | first param should be the element');
+    equal(i, 0, 'Array#exclude | second param should be the index');
+    equal(arr, ['a'], 'Array#exclude | third param should be the array');
+  });
+
+  arr = [1,2,3];
+  arr.exclude(2);
+  equal(arr, [1,2,3], 'Array#exclude | should not affect the original array');
+
+  equal([1,2,2,3].exclude(2,3), [1], 'Array#exclude | can handle multiple arguments');
+  equal([f1].exclude(f1), [], 'Array#exclude | can find via strict equality');
+
+  equal([1,2,3].exclude([1,3]), [1,2,3], 'Array#exclude | each argument is a separate element');
+  equal([1,2,3].exclude(1,3), [2], 'Array#exclude | however multiple arguments still work');
+  equal([[1,3],2].exclude([1,3]), [2], 'Array#exclude | and those elements are still properly found');
+
+
+
+
+
+  equal([1,2,2,3].removeAt(), [1,2,2,3], 'Array#removeAt | numeric | no argument');
+  equal([1,2,2,3].removeAt(0), [2,2,3], 'Array#removeAt | numeric | 0');
+  equal([1,2,2,3].removeAt(1), [1,2,3], 'Array#removeAt | numeric | 1');
+  equal([1,2,2,3].removeAt(2), [1,2,3], 'Array#removeAt | numeric | 2');
+  equal([1,2,2,3].removeAt(3), [1,2,2], 'Array#removeAt | numeric | 3');
+  equal([1,2,2,3].removeAt(4), [1,2,2,3], 'Array#removeAt | numeric | 4');
+  equal(['a','b','c','c'].removeAt(), ['a','b','c','c'], 'Array#removeAt | alphabet | no argument');
+  equal(['a','b','c','c'].removeAt(0), ['b','c','c'], 'Array#removeAt | alphabet | 0');
+  equal(['a','b','c','c'].removeAt(1), ['a','c','c'], 'Array#removeAt | alphabet | 1');
+  equal(['a','b','c','c'].removeAt(2), ['a','b','c'], 'Array#removeAt | alphabet | 2');
+  equal(['a','b','c','c'].removeAt(3), ['a','b','c'], 'Array#removeAt | alphabet | 3');
+  equal(['a','b','c','c'].removeAt(4), ['a','b','c','c'], 'Array#removeAt | alphabet | 4');
+  equal([{a:1},{a:2},{a:1}].removeAt(1), [{a:1},{a:1}], 'Array#removeAt | objects | 1');
+  equal([1,2,2,3].removeAt(0,1), [2,3], 'Array#removeAt | 0 to 1');
+  equal([1,2,2,3].removeAt(0,2), [3], 'Array#removeAt | 0 to 2');
+  equal([1,2,2,3].removeAt(1,2), [1,3], 'Array#removeAt | 1 to 2');
+  equal([1,2,2,3].removeAt(1,5), [1], 'Array#removeAt | 1 to 5');
+  equal([1,2,2,3].removeAt(0,5), [], 'Array#removeAt | 0 to 5');
+  equal([1,2,2,3].removeAt(null,5), [], 'Array#removeAt | also accepts null');
+
+  arr = [1,2,3];
+  arr.removeAt(1);
+  equal(arr, [1,3], 'Array#removeAt | should affect the original array');
+
+
+
+
+
+
+
+  equal([1,2,3].add(4), [1,2,3,4], 'Array#add | 1,2,3 + 4');
+  equal(['a','b','c'].add('d'), ['a','b','c','d'], 'Array#add | a,b,c + d');
+  equal([{a:1},{a:2}].add({a:3}), [{a:1},{a:2},{a:3}], 'Array#add | a:1,a:2 + a:3');
+  equal([1,2,3].add([3,4,5]), [1,2,3,3,4,5], 'Array#add | 1,2,3 + 3,4,5');
+  equal(['a','b','c'].add(['c','d','e']), ['a','b','c','c','d','e'], 'Array#add | a,b,c + c,d,e');
+  equal([1,2,3].add([1,2,3]), [1,2,3,1,2,3], 'Array#add | 1,2,3 + 1,2,3');
+  equal([1,2,3].add([3,2,1]), [1,2,3,3,2,1], 'Array#add | 1,2,3 + 3,2,1');
+  equal([].add([3]), [3], 'Array#add | empty array + 3');
+  equal([3].add([]), [3], 'Array#add | 3 + empty array');
+  equal([].add([]), [], 'Array#add | 2 empty arrays');
+  equal([null].add([]), [null], 'Array#add | [null] + empty array');
+  equal([null].add([null]), [null, null], 'Array#add | [null] + [null]');
+  equal([false].add([false]), [false, false], 'Array#add | [false] + [false]');
+  equal([false].add([0]), [false, 0], 'Array#add | [false] + [0]');
+  equal([false].add([null]), [false, null], 'Array#add | [false] + [null]');
+  equal([false].add([undefined]), [false, undefined], 'Array#add | [false] + [undefined]');
+  equal([{a:1},{b:2}].add([{b:2},{c:3}]), [{a:1},{b:2},{b:2},{c:3}], 'Array#add | a:1,b:2 + b:2,c:3');
+  equal([1,1,3].add([1,5,6]), [1,1,3,1,5,6], 'Array#add | 1,1,3 + 1,5,6');
+  equal([1,2,3].add([4,5,6]), [1,2,3,4,5,6], 'Array#add | 1,2,3 + 4,5,6');
+  equal([1,2,3].add(1), [1,2,3,1], 'Array#add | 1,2,3 + 1');
+
+  equal([1,2,3].add(4, 1), [1,4,2,3], 'Array#add | index 1 | 4');
+  equal(['a','b','c'].add('d', 1), ['a','d','b','c'], 'Array#add | index 1 | d');
+  equal([{a:1},{a:2}].add({a:3}, 1), [{a:1},{a:3},{a:2}], 'Array#add | index 1 | a:3');
+  equal([1,2,3].add(4, 2), [1,2,4,3], 'Array#add | index 2 | 4');
+  equal(['a','b','c'].add('d', 2), ['a','b','d','c'], 'Array#add | index 2 | d');
+  equal([{a:1},{a:2}].add({a:3}, 2), [{a:1},{a:2},{a:3}], 'Array#add | index 2 | a:3');
+
+  equal(['a','b','c'].add('d', 5), ['a','b','c','d'], 'Array#add | index 5 | d');
+  equal(['a','b','c'].add('d', 0), ['d','a','b','c'], 'Array#add | index 0 | d');
+  equal(['a','b','c'].add('d', -1), ['a','b','d','c'], 'Array#add | index -1 | d');
+  equal(['a','b','c'].add('d', -2), ['a','d','b','c'], 'Array#add | index -2 | d');
+  equal(['a','b','c'].add('d', -3), ['d','a','b','c'], 'Array#add | index -3 | d');
+  equal(['a','b','c'].add('d', -4), ['d','a','b','c'], 'Array#add | index -4 | d');
+  equal(['a','b','c'].add('d', null), ['d','a','b','c'], 'Array#add | null index | d');
+  equal(['a','b','c'].add('d', undefined), ['a','b','c','d'], 'Array#add | undefined index | d');
+  equal(['a','b','c'].add('d', 'a'), ['a','b','c','d'], 'Array#add | index a | d');
+  equal(['a','b','c'].add('d', NaN), ['a','b','c','d'], 'Array#add | index NaN | d');
+
+  arr = [1,2,3];
+  arr.add(4);
+  equal(arr, [1,2,3,4], 'Array#add | should affect the original array');
+
+
+
+  equal([1,2,3].insert(4), [1,2,3,4], 'Array#insert | 1,2,3 + 4');
+  equal(['a','b','c'].insert('d'), ['a','b','c','d'], 'Array#insert | a,b,c + d');
+  equal([{a:1},{a:2}].insert({a:3}), [{a:1},{a:2},{a:3}], 'Array#insert | a:1,a:2 + a:3');
+  equal([1,2,3].insert([3,4,5]), [1,2,3,3,4,5], 'Array#insert | 1,2,3 + 3,4,5');
+  equal(['a','b','c'].insert(['c','d','e']), ['a','b','c','c','d','e'], 'Array#insert | a,b,c + c,d,e');
+  equal([1,2,3].insert([1,2,3]), [1,2,3,1,2,3], 'Array#insert | 1,2,3 + 1,2,3');
+  equal([1,2,3].insert([3,2,1]), [1,2,3,3,2,1], 'Array#insert | 1,2,3 + 3,2,1');
+  equal([].insert([3]), [3], 'Array#insert | empty array + 3');
+  equal([3].insert([]), [3], 'Array#insert | 3 + empty array');
+  equal([].insert([]), [], 'Array#insert | 2 empty arrays');
+  equal([null].insert([]), [null], 'Array#insert | [null] + empty array');
+  equal([null].insert([null]), [null, null], 'Array#insert | [null] + [null]');
+  equal([false].insert([false]), [false, false], 'Array#insert | [false] + [false]');
+  equal([false].insert([0]), [false, 0], 'Array#insert | [false] + [0]');
+  equal([false].insert([null]), [false, null], 'Array#insert | [false] + [null]');
+  equal([false].insert([undefined]), [false, undefined], 'Array#insert | [false] + [undefined]');
+  equal([{a:1},{b:2}].insert([{b:2},{c:3}]), [{a:1},{b:2},{b:2},{c:3}], 'Array#insert | a:1,b:2 + b:2,c:3');
+  equal([1,1,3].insert([1,5,6]), [1,1,3,1,5,6], 'Array#insert | 1,1,3 + 1,5,6');
+  equal([1,2,3].insert([4,5,6]), [1,2,3,4,5,6], 'Array#insert | 1,2,3 + 4,5,6');
+  equal([1,2,3].insert(1), [1,2,3,1], 'Array#insert | 1,2,3 + 1');
+
+  equal([1,2,3].insert(4, 1), [1,4,2,3], 'Array#insert | index 1 | 4');
+  equal(['a','b','c'].insert('d', 1), ['a','d','b','c'], 'Array#insert | index 1 | d');
+  equal([{a:1},{a:2}].insert({a:3}, 1), [{a:1},{a:3},{a:2}], 'Array#insert | index 1 | a:3');
+  equal([1,2,3].insert(4, 2), [1,2,4,3], 'Array#insert | index 2 | 4');
+  equal(['a','b','c'].insert('d', 2), ['a','b','d','c'], 'Array#insert | index 2 | d');
+  equal([{a:1},{a:2}].insert({a:3}, 2), [{a:1},{a:2},{a:3}], 'Array#insert | index 2 | a:3');
+  equal(['a','b','c'].insert('d', 5), ['a','b','c','d'], 'Array#insert | index 5 | d');
+
+  equal(['a','b','c'].insert('d', 0), ['d','a','b','c'], 'Array#insert | index 0 | d');
+  equal(['a','b','c'].insert('d', -1), ['a','b','d','c'], 'Array#insert | index -1 | d');
+  equal(['a','b','c'].insert('d', -2), ['a','d','b','c'], 'Array#insert | index -2 | d');
+  equal(['a','b','c'].insert('d', -3), ['d','a','b','c'], 'Array#insert | index -3 | d');
+  equal(['a','b','c'].insert('d', -4), ['d','a','b','c'], 'Array#insert | index -4 | d');
+  equal(['a','b','c'].insert('d', null), ['d','a','b','c'], 'Array#insert | null index | d');
+  equal(['a','b','c'].insert('d', undefined), ['a','b','c','d'], 'Array#insert | undefined index | d');
+  equal(['a','b','c'].insert('d', 'a'), ['a','b','c','d'], 'Array#insert | index a | d');
+  equal(['a','b','c'].insert('d', NaN), ['a','b','c','d'], 'Array#insert | index NaN | d');
+
+  equal(['a','b','c'].insert('d', '0'), ['d','a','b','c'], 'Array#insert | string numerals should also be recognized');
+
+  arr = [1,2,3];
+  arr.insert(4);
+  equal(arr, [1,2,3,4], 'Array#insert | should affect the original array');
+
+
+
+  equal([1,2,3].include(4), [1,2,3,4], 'Array#include | 1,2,3 + 4', { prototype: false });
+  equal(['a','b','c'].include('d'), ['a','b','c','d'], 'Array#include | a,b,c + d', { prototype: false });
+  equal([{a:1},{a:2}].include({a:3}), [{a:1},{a:2},{a:3}], 'Array#include | a:1,a:2 + a:3', { prototype: false });
+  equal([1,2,3].include([3,4,5]), [1,2,3,3,4,5], 'Array#include | 1,2,3 + 3,4,5', { prototype: false, mootools: [1,2,3,[3,4,5]] });
+  equal(['a','b','c'].include(['c','d','e']), ['a','b','c','c','d','e'], 'Array#include | a,b,c + c,d,e', { prototype: false, mootools: ['a','b','c',['c','d','e']] });
+  equal([1,2,3].include([1,2,3]), [1,2,3,1,2,3], 'Array#include | 1,2,3 + 1,2,3', { prototype: false, mootools: [1,2,3,[1,2,3]] });
+  equal([1,2,3].include([3,2,1]), [1,2,3,3,2,1], 'Array#include | 1,2,3 + 3,2,1', { prototype: false, mootools: [1,2,3,[3,2,1]] });
+  equal([].include([3]), [3], 'Array#include | empty array + 3', { prototype: false, mootools: [[3]] });
+  equal([3].include([]), [3], 'Array#include | 3 + empty array', { prototype: false, mootools: [3,[]] });
+  equal([].include([]), [], 'Array#include | 2 empty arrays', { prototype: false, mootools: [[]] });
+  equal([null].include([]), [null], 'Array#include | [null] + empty array', { prototype: false, mootools: [null,[]] });
+  equal([null].include([null]), [null, null], 'Array#include | [null] + [null]', { prototype: false, mootools: [null,[null]] });
+  equal([false].include([false]), [false, false], 'Array#include | [false] + [false]', { prototype: false, mootools: [false,[false]] });
+  equal([false].include([0]), [false, 0], 'Array#include | [false] + [0]', { prototype: true, mootools: [false,[0]] });
+  equal([false].include([null]), [false, null], 'Array#include | [false] + [null]', { prototype: true, mootools: [false, [null]] });
+  equal([false].include([undefined]), [false, undefined], 'Array#include | [false] + [undefined]', { prototype: true, mootools: [false,[undefined]] });
+  equal([{a:1},{b:2}].include([{b:2},{c:3}]), [{a:1},{b:2},{b:2},{c:3}], 'Array#include | a:1,b:2 + b:2,c:3', { prototype: false, mootools: [{a:1},{b:2},[{b:2},{c:3}]] });
+  equal([1,1,3].include([1,5,6]), [1,1,3,1,5,6], 'Array#include | 1,1,3 + 1,5,6', { prototype: false, mootools: [1,1,3,[1,5,6]] });
+  equal([1,2,3].include([4,5,6]), [1,2,3,4,5,6], 'Array#include | 1,2,3 + 4,5,6', { prototype: false, mootools: [1,2,3,[4,5,6]] });
+  equal([1,2,3].include(1), [1,2,3,1], 'Array#include | 1,2,3 + 1', { prototype: true, mootools: [1,2,3] });
+
+  equal([1,2,3].include(4, 1), [1,4,2,3], 'Array#include | index 1 | 4', { prototype: false, mootools: [1,2,3,4] });
+  equal(['a','b','c'].include('d', 1), ['a','d','b','c'], 'Array#include | index 1 | d', { prototype: false, mootools: ['a','b','c','d'] });
+  equal([{a:1},{a:2}].include({a:3}, 1), [{a:1},{a:3},{a:2}], 'Array#include | index 1 | a:3', { prototype: false, mootools: [{a:1},{a:2},{a:3}] });
+  equal([1,2,3].include(4, 2), [1,2,4,3], 'Array#include | index 2 | 4', { prototype: false, mootools: [1,2,3,4] });
+  equal(['a','b','c'].include('d', 2), ['a','b','d','c'], 'Array#include | index 2 | d', { prototype: false, mootools: ['a','b','c','d'] });
+  equal([{a:1},{a:2}].include({a:3}, 2), [{a:1},{a:2},{a:3}], 'Array#include | index 2 | a:3', { prototype: false });
+  equal(['a','b','c'].include('d', 5), ['a','b','c','d'], 'Array#include | index 5 | d', { prototype: false });
+
+  equal(['a','b','c'].include('d', 0), ['d','a','b','c'], 'Array#include | index 0 | d', { prototype: false, mootools: ['a','b','c','d'] });
+  equal(['a','b','c'].include('d', -1), ['a','b','d','c'], 'Array#include | index -1 | d', { prototype: false, mootools: ['a','b','c','d'] });
+  equal(['a','b','c'].include('d', -2), ['a','d','b','c'], 'Array#include | index -2 | d', { prototype: false, mootools: ['a','b','c','d'] });
+  equal(['a','b','c'].include('d', -3), ['d','a','b','c'], 'Array#include | index -3 | d', { prototype: false, mootools: ['a','b','c','d'] });
+  equal(['a','b','c'].include('d', -4), ['d','a','b','c'], 'Array#include | index -4 | d', { prototype: false, mootools: ['a','b','c','d'] });
+  equal(['a','b','c'].include('d', null), ['d','a','b','c'], 'Array#include | null index | d', { prototype: false });
+  equal(['a','b','c'].include('d', undefined), ['a','b','c','d'], 'Array#include | undefined index | d', { prototype: false });
+  equal(['a','b','c'].include('d', 'a'), ['a','b','c','d'], 'Array#include | index a | d', { prototype: false });
+  equal(['a','b','c'].include('d', NaN), ['a','b','c','d'], 'Array#include | index NaN | d', { prototype: false });
+
+  arr = [1,2,3];
+  arr.include(4);
+  equal(arr, [1,2,3], 'Array#include | should not affect the original array', { mootools: [1,2,3,4] });
+
+
+  arr = [1,2,3];
+  var arr2 = arr.clone();
+  equal(arr, arr2, 'Array#clone | should clone the array');
+  arr2.remove(2);
+  equal(arr, [1,2,3], 'Array#clone | original array should be untouched');
+  equal(arr2, [1,3], 'Array#clone | new array should be modified');
+
+
+
+
+
+
+  equal([1,2,3].isEmpty(), false, 'Array#empty | 1,2,3');
+  equal([].isEmpty(), true, 'Array#empty | empty array');
+  equal([null].isEmpty(), true, 'Array#empty | [null]');
+  equal([undefined].isEmpty(), true, 'Array#empty | [undefined]');
+  equal([null,null].isEmpty(), true, 'Array#empty | [null,null]');
+  equal([undefined,undefined].isEmpty(), true, 'Array#empty | [undefined,undefined]');
+  equal([false,false].isEmpty(), false, 'Array#empty | [false,false]');
+  equal([0,0].isEmpty(), false, 'Array#empty | [0,0]');
+
+
+  raisesError(function(){ [1,2,3].any() }, 'Array#any | no argument raises a TypeError', { prototype: false });
+  equal([1,2,3].any(1), true, 'Array#any | numeric | 1');
+  equal([1,2,3].any(4), false, 'Array#any | numeric | 4');
+  equal([1,2,3].any('a'), false, 'Array#any | numeric | a');
+  equal(['a','b','c'].any('a'), true, 'Array#any | alphabet | a');
+  equal(['a','b','c'].any('f'), false, 'Array#any | alphabet | f');
+  equal(['a','b','c'].any(/[a-f]/), true, 'Array#any | alphabet | /[a-f]/', { prototype: false });
+  equal(['a','b','c'].any(/[m-z]/), false, 'Array#any | alphabet | /[m-z]/');
+  equal([{a:1},{a:2},{a:1}].any(1), false, 'Array#any | objects | 1');
+  equal([0].any(0), true, 'Array#any | [0] | 0');
+  equal([{a:1},{a:2},{a:1}].any({a:1}), true, 'Array#any | objects | a:1', { prototype: false });
+
+  equal(['a','b','c'].any(function(e) { return e.length > 1; }), false, 'Array#any | alphabet | length greater than 1');
+  equal(['a','b','c'].any(function(e) { return e.length < 2; }), true, 'Array#any | alphabet | length less than 2');
+  equal(['a','bar','cat'].any(function(e) { return e.length < 2; }), true, 'Array#any | a,bar,cat | length less than 2');
+  equal([{a:1},{a:2},{a:1}].any(function(e) { return e['a'] == 1; }), true, 'Array#any | objects | key "a" is 1');
+  equal([{a:1},{a:2},{a:1}].any(function(e) { return e['b'] == 1; }), false, 'Array#any | objects | key "b" is 1');
+
+  [1].any(function() {
+    equal(this.toString(), 'wasabi', 'Array#any | scope should be passable');
+  }, 'wasabi');
+
+
+
+
+  raisesError(function() { [1,2,3].none(); }, 'Array#none | no argument raises a TypeError', { prototype: false });
+  equal([1,2,3].none(1), false, 'Array#none | numeric | 1');
+  equal([1,2,3].none(4), true, 'Array#none | numeric | 4');
+  equal([1,2,3].none('a'), true, 'Array#none | numeric | a');
+  equal(['a','b','c'].none('a'), false, 'Array#none | alphabet | a');
+  equal(['a','b','c'].none('f'), true, 'Array#none | alphabet | f');
+  equal(['a','b','c'].none(/[a-f]/), false, 'Array#none | alphabet | /[a-f]/', { prototype: true });
+  equal(['a','b','c'].none(/[m-z]/), true, 'Array#none | alphabet | /[m-z]/');
+  equal([{a:1},{a:2},{a:1}].none(1), true, 'Array#none | objects | 1');
+  equal([{a:1},{a:2},{a:1}].none({a:1}), false, 'Array#none | objects | a:1', { prototype: true });
+
+  equal(['a','b','c'].none(function(e) { return e.length > 1; }), true, 'Array#none | alphabet | length is greater than 1');
+  equal(['a','b','c'].none(function(e) { return e.length < 2; }), false, 'Array#none | alphabet | length is less than 2');
+  equal(['a','bar','cat'].none(function(e) { return e.length < 2; }), false, 'Array#none | a,bar,cat | length is less than 2');
+  equal([{a:1},{a:2},{a:1}].none(function(e) { return e['a'] == 1; }), false, 'Array#none | objects | key "a" is 1');
+  equal([{a:1},{a:2},{a:1}].none(function(e) { return e['b'] == 1; }), true, 'Array#none | objects | key "b" is 1');
+
+
+
+  raisesError(function() { [1,2,3].all(); }, 'Array#all | no argument raises a type error', { prototype: false });
+  equal([1,2,3].all(1), false, 'Array#all | numeric | 1');
+  equal([1,1,1].all(1), true, 'Array#all | numeric | 1 is true for all');
+  equal([1,2,3].all(3), false, 'Array#all | numeric | 3');
+  equal(['a','b','c'].all('a'), false, 'Array#all | alphabet | a');
+  equal(['a','a','a'].all('a'), true, 'Array#all | alphabet | a is true for all');
+  equal(['a','b','c'].all('f'), false, 'Array#all | alphabet | f');
+  equal(['a','b','c'].all(/[a-f]/), true, 'Array#all | alphabet | /[a-f]/', { prototype: false });
+  equal(['a','b','c'].all(/[a-b]/), false, 'Array#all | alphabet | /[m-z]/');
+  equal([{a:1},{a:2},{a:1}].all(1), false, 'Array#all | objects | 1');
+  equal([{a:1},{a:2},{a:1}].all({a:1}), false, 'Array#all | objects | a:1');
+  equal([{a:1},{a:1},{a:1}].all({a:1}), true, 'Array#all | objects | a:1 is true for all', { prototype: false });
+
+
+  equal(['a','b','c'].all(function(e) { return e.length > 1; }), false, 'Array#all | alphabet | length is greater than 1');
+  equal(['a','b','c'].all(function(e) { return e.length < 2; }), true, 'Array#all | alphabet | length is less than 2');
+  equal(['a','bar','cat'].all(function(e) { return e.length < 2; }), false, 'Array#all | a,bar,cat | length is less than 2');
+  equal([{a:1},{a:2},{a:1}].all(function(e) { return e['a'] == 1; }), false, 'Array#all | objects | key "a" is 1');
+  equal([{a:1},{a:2},{a:1}].all(function(e) { return e['b'] == 1; }), false, 'Array#all | objects | key "b" is 1');
+  equal([{a:1},{a:1},{a:1}].all(function(e) { return e['a'] == 1; }), true, 'Array#all | objects | key "a" is 1 for all');
+
+
+  [1].all(function() {
+    equal(this.toString(), 'wasabi', 'Array#all | scope should be passable');
+  }, 'wasabi');
+
+
+
+  equal([1,2,3].flatten(), [1,2,3], 'Array#flatten | 1,2,3');
+  equal(['a','b','c'].flatten(), ['a','b','c'], 'Array#flatten | a,b,c');
+  equal([{a:1},{a:2},{a:1}].flatten(), [{a:1},{a:2},{a:1}], 'Array#flatten | a:1,a:2,a:1');
+  equal([[1],[2],[3]].flatten(), [1,2,3], 'Array#flatten | [1],[2],[3]');
+  equal([[1,2],[3]].flatten(), [1,2,3], 'Array#flatten | [1,2],[3]');
+  equal([[1,2,3]].flatten(), [1,2,3], 'Array#flatten | [1,2,3]');
+  equal([['a'],['b'],['c']].flatten(), ['a','b','c'], 'Array#flatten | [a],[b],[c]');
+  equal([['a','b'],['c']].flatten(), ['a','b','c'], 'Array#flatten | [a,b],[c]');
+  equal([['a','b','c']].flatten(), ['a','b','c'], 'Array#flatten | [a,b,c]');
+  equal([[{a:1}],[{a:2}],[{a:1}]].flatten(), [{a:1},{a:2},{a:1}], 'Array#flatten | [a:1],[a:2],[a:1]');
+  equal([[{a:1},{a:2}],[{a:1}]].flatten(), [{a:1},{a:2},{a:1}], 'Array#flatten | [a:1,a:2],[a:1]');
+  equal([[{a:1},{a:2},{a:1}]].flatten(), [{a:1},{a:2},{a:1}], 'Array#flatten | [a:1,a:2,a:1]');
+  equal([[[['a','b'],'c',['d','e']],'f'],['g']].flatten(), ['a','b','c','d','e','f','g'], 'Array#flatten | [[a,b],c,[d,e],f],g');
+
+  equal([[[['a','b'],'c',['d','e']],'f'],['g']].flatten(1), [[['a','b'],'c',['d','e']],'f','g'], 'Array#flatten | can flatten only first level', { prototype: ['a','b','c','d','e','f','g'] });
+  equal([[[['a','b'],'c',['d','e']],'f'],['g']].flatten(false), ['a','b','c','d','e','f','g'], 'Array#flatten | wont explode on false');
+  equal([[[['a','b'],'c',['d','e']],'f'],['g']].flatten(true), [[['a','b'],'c',['d','e']],'f','g'], 'Array#flatten | wont explode on true', { prototype: ['a','b','c','d','e','f','g'] });
+
+  // Prototype will compact but only if IE, so we can't assert this in that environment.
+  skipEnvironments(['prototype'], function() {
+    equal([undefined].flatten().length, sparseArraySupport ? 1 : 0, 'Array#flatten | should not compact arrays');
+  });
+
+
+
+
+
+  arr = ['more','everyone!','bring','the','family'];
+  equal(arr.sortBy('length'), ['the','more','bring','family','everyone!'], 'Array#sortBy | sorting by length');
+  equal(arr.sortBy('length', true), ['everyone!','family','bring','more','the'], 'Array#sortBy | desc | sorting by length', { prototype: ['the','more','bring','family','everyone!'] });
+
+  equal(arr.sortBy(function(a) { return a.length; }), ['the','more','bring','family','everyone!'], 'Array#sortBy | sort by length by function');
+  equal(arr.sortBy(function(a) { return a.length; }, true), ['everyone!','family','bring','more','the'], 'Array#sortBy | desc | sort by length by function', { prototype: ['the','more','bring','family','everyone!'] });
+
+  arr = [{a:'foo'},{a:'bar'},{a:'skittles'}];
+  equal(arr.sortBy('a'), [{a:'bar'},{a:'foo'},{a:'skittles'}], 'Array#sortBy | sort by key "a"');
+  equal(arr.sortBy('a', true), [{a:'skittles'},{a:'foo'},{a:'bar'}], 'Array#sortBy | desc | sort by key "a"', { prototype: [{a:'bar'},{a:'foo'},{a:'skittles'}] });
+
+  arr = [1,2,3];
+  arr.sortBy(function(n){ return 3 - n; });
+  equal(arr, [1,2,3], 'Array#sortBy | should not be destructive');
+
+  equal([1,2,3].sortBy(undefined), [1,2,3], 'Array#sortBy | undefined');
+  equal([1,2,3].sortBy(null), [1,2,3], 'Array#sortBy | null');
+  equal([1,2,3].sortBy(4), [1,2,3], 'Array#sortBy | number');
+
+  var Simple = function(num) {
+    this.valueOf = function() {
+      return num;
+    }
+  }
+
+  var a = new Simple(5);
+  var b = new Simple(2);
+  var c = new Simple(3);
+  var d = new Simple(1);
+  var e = new Simple(2);
+
+  equal([a,b,c,d,e].sortBy(), [d,b,e,c,a], 'Array#sortBy | objects with "valueOf" defined will also be sorted properly');
+
+  arr = [1,2,3,4,5,6,7,8,9,10];
+  var firsts = [];
+  firsts.push(arr.randomize().first());
+  firsts.push(arr.randomize().first());
+  firsts.push(arr.randomize().first());
+  firsts.push(arr.randomize().first());
+  firsts.push(arr.randomize().first());
+  firsts.push(arr.randomize().first());
+  firsts.push(arr.randomize().first());
+  firsts.push(arr.randomize().first());
+  firsts.push(arr.randomize().first());
+  firsts.push(arr.randomize().first());
+
+  /* Note that there is a built-in 0.00000001% chance that this test will fail */
+  equal(firsts.all(function(a) { return a == 1; }), false, 'Array#randomize');
+
+
+
+
+  // Inherits from array...
+
+  var Soup = function(){}, x;
+  Soup.prototype = [1,2,3];
+
+  x = new Soup();
+  count = 0;
+
+  x.each(function() {
+    count++;
+  });
+  x.find(function() {
+    count++;
+  });
+  x.findAll(function() {
+    count++;
+  });
+
+  equal(count, 9, 'Array | array elements in the prototype chain are also properly iterated');
+
+
+  // Inherits from sparse array...
+
+  arr = ['a'];
+  arr[20] = 'b';
+
+  Soup.prototype = arr;
+
+  x = new Soup();
+  count = 0;
+
+  x.each(function() {
+    count++;
+  });
+
+  equal(count, 2, 'Array | sparse array elements in the prototype chain are also properly iterated');
+
+  // This test cannot be framed in a meaninful way... IE will not set the length property
+  // when pushing new elements and other browsers will not work on sparse arrays...
+  // equal(count, 6, 'Array | objects that inherit from arrays can still iterate');
+
+
+  // Array.create
+
+  equal(Array.create(), [], 'Array.create | no args');
+  equal(Array.create('one'), ['one'], 'Array.create | string');
+  equal(Array.create(stringObj), [stringObj], 'Array.create | string object');
+  equal(Array.create({length: 2}), [{length: 2}], "Array.create | can't trick array-like coercion");
+  equal(Array.create(2), [2], 'Array.create | number');
+  equal(Array.create([2]), [2], 'Array.create | in array | number');
+  equal(Array.create(true), [true], 'Array.create | boolean');
+  equal(Array.create([true]), [true], 'Array.create | in array | boolean');
+  equal(Array.create(null), [null], 'Array.create | null');
+  equal(Array.create([null]), [null], 'Array.create | in array | null');
+  equal(Array.create(undefined), [undefined], 'Array.create | mixed');
+  equal(Array.create([undefined]), [undefined], 'Array.create | in array | mixed');
+  equal(Array.create('one', 2, true, null), ['one', 2, true, null], 'Array.create | mixed 1');
+  equal(Array.create('one', 2, true, undefined), ['one', 2, true, undefined], 'Array.create | mixed 2');
+
+  equal(Array.create([1,2,3]), [1,2,3], 'Array.create | passing an array');
+  equal(Array.create([[1,2,3]]), [[1,2,3]], 'Array.create | in array | is nested');
+  equal(Array.create([1,2,3], [1,2,3]), [1,2,3,1,2,3], 'Array.create | passing two arrays will concat them');
+  equal(Array.create([1,2,3], 'four'), [1,2,3,'four'], 'Array.create | passing an array and another object will concat them');
+
+  equal(Array.create({a:1}), [{a:1}], 'Array.create | object');
+  equal(Array.create([{a:1}]), [{a:1}], 'Array.create | in array | object');
+  equal(Array.create({a:1}, {b:2}), [{a:1},{b:2}], 'Array.create | two objects');
+  equal(Array.create({a:1}, ['b']), [{a:1}, 'b'], 'Array.create | object and array');
+  equal(Array.create({a:1}, 'b'), [{a:1}, 'b'], 'Array.create | object and string');
+
+  equal((function(){ return Array.create(arguments); })('one','two'), ['one','two'], 'Array.create | works on an arguments object');
+  equal((function(){ return Array.create(arguments); })(), [], 'Array.create | works on a zero length arguments object');
+  equal((function(){ return Array.create(arguments); })('one').slice, Array.prototype.slice, 'Array.create | converted arguments object is a true array');
+  equal((function(){ return Array.create(arguments); })('one','two').slice, Array.prototype.slice, 'Array.create | two | converted arguments object is a true array');
+  equal((function(){ return Array.create.apply(null, arguments); })('one','two'), ['one','two'], 'Array.create | arguments using apply');
+
+  var args = (function() { return arguments; })(true, 1, 'two');
+  equal(Array.create([args]), [args], 'Array.create | nested arguments is a nested array');
+
+  if(typeof document !== 'undefined' && document.createElement) {
+    var el = document.createElement('div');
+    equal(Array.create(el), [el], 'Array#create | DOM element');
+    equal(Array.create([el]), [el], 'Array#create | DOM element in array');
+  }
+
+  // Array#zip
+
+  equal([1, 2, 3].zip(), [[1], [2], [3]], 'Array.zip | one array');
+  equal([1, 2, 3].zip([4, 5, 6]), [[1, 4], [2, 5], [3, 6]], 'Array.zip | two arrays');
+  equal([1, 2, 3].zip([4, 5, 6], [7, 8, 9]), [[1, 4, 7], [2, 5, 8], [3, 6, 9]], 'Array.zip | three arrays');
+  equal([1, 2].zip([4, 5, 6], [7, 8, 9]), [[1, 4, 7], [2, 5, 8]], 'Array.zip | constrained by length of first');
+  equal([4, 5, 6].zip([1, 2], [8]), [[4, 1, 8], [5, 2, null], [6, null, null]], 'Array.zip | filled with null', { prototype: [[4,1,8],[5,2,undefined],[6,undefined,undefined]]});
+
+
+
+  // Array#findIndex
+
+  equal(['a','b','c'].findIndex('b'), 1, 'Array#findIndex | b in a,b,c');
+  equal(['a','b','c'].findIndex('b', 0), 1, 'Array#findIndex | b in a,b,c from 0');
+  equal(['a','b','c'].findIndex('a'), 0, 'Array#findIndex | a in a,b,c');
+  equal(['a','b','c'].findIndex('f'), -1, 'Array#findIndex | f in a,b,c');
+
+  equal(['a','b','c','b'].findIndex('b'), 1, 'Array#findIndex | finds first instance');
+
+  equal([5,2,4].findIndex(5), 0, 'Array#findIndex | 5 in 5,2,4');
+  equal([5,2,4].findIndex(2), 1, 'Array#findIndex | 2 in 5,2,4');
+  equal([5,2,4].findIndex(4), 2, 'Array#findIndex | 4 in 5,2,4');
+
+  equal([{ foo: 'bar' }].findIndex({ foo: 'bar' }), 0, 'Array#findIndex | will find deep objects');
+  equal([{ foo: 'bar' }].findIndex(function(a) { return a.foo === 'bar'; }), 0, 'Array#findIndex | will run against a function');
+
+  equal(['a','b','c'].findIndex(/[bz]/), 1, 'Array#findIndex | matches regexp');
+
+  var people = [
+    { name: 'jim',    age: 27, hair: 'brown'  },
+    { name: 'mary',   age: 52, hair: 'blonde' },
+    { name: 'ronnie', age: 13, hair: 'brown'  },
+    { name: 'edmund', age: 27, hair: 'blonde' }
+  ];
+
+  equal(people.findIndex(function(person) { return person.age == 13; }), 2, 'Array#findIndex | JSON objects');
+
+  // Array#sample
+
+  arr = [1,2,3,4,5,6,7,8,9,10];
+  var samples = [];
+  samples.push(arr.sample());
+  samples.push(arr.sample());
+  samples.push(arr.sample());
+  samples.push(arr.sample());
+  samples.push(arr.sample());
+  samples.push(arr.sample());
+  samples.push(arr.sample());
+  samples.push(arr.sample());
+  samples.push(arr.sample());
+  samples.push(arr.sample());
+
+  /* Note that there is a built-in 0.00000001% chance that this test will fail */
+  equal(samples.all(function(a) { return a == 1; }), false, 'Array#sample');
+
+  equal(typeof arr.sample(), 'number', 'Array#sample | no params');
+  equal(arr.sample(1).length, 1, 'Array#sample | 1');
+  equal(arr.sample(2).length, 2, 'Array#sample | 2');
+  equal(arr.sample(3).length, 3, 'Array#sample | 3');
+  equal(arr.sample(4).length, 4, 'Array#sample | 4');
+  equal(arr.sample(11).length, 10, "Array#sample | can't sample more than the length of the array");
+  equal(arr.sample(10).unique().length, arr.length, "Array#sample | should not sample the same element twice");
+
+  equal(arr.sample(0).length, 0, 'Array#sample | 0');
+
+  // Array#findAll - Complex matching
+
+  var people = [
+    { name: 'jim',    age: 27, hair: 'brown'  },
+    { name: 'mary',   age: 52, hair: 'blonde' },
+    { name: 'ronnie', age: 13, hair: 'brown'  },
+    { name: 'edmund', age: 27, hair: 'blonde' },
+    { name: 'buddy',  age: 82, hair: { color: 'red', type: 'long', cost: 15, last_cut: new Date(2010, 4, 18) } }
+  ];
+
+
+  equal(people.findAll({}), people, 'Array#findAll | complex | empty object');
+  equal(people.findAll(), [], 'Array#findAll | complex | no arguments');
+  equal(people.findAll('age'), [], 'Array#findAll | complex | string argument');
+  equal(people.findAll(4), [], 'Array#findAll | complex | number argument');
+  equal(people.findAll({ age: 27 }), [people[0], people[3]], 'Array#findAll | complex | one property');
+  equal(people.findAll({ age: 27, hair: 'brown' }), [people[0]], 'Array#findAll | complex | two properties');
+  equal(people.findAll({ hair: { color: 'red' }}), [people[4]], 'Array#findAll | complex | nested property');
+  equal(people.findAll({ hair: { color: 'green' }}), [], 'Array#findAll | complex | non-matching nested property');
+  equal(people.findAll({ hair: { color: 'red', type: 'long' }}), [people[4]], 'Array#findAll | complex | two nested properties');
+  equal(people.findAll({ hair: { color: 'green', type: 'mean' }}), [], 'Array#findAll | complex | two non-matching nested properties');
+  equal(people.findAll({ hair: { color: 'red', type: 'mean' }}), [], 'Array#findAll | complex | two nested properties, one non-matching');
+  equal(people.findAll({ hair: { color: 'red', life: 'long' }}), [], 'Array#findAll | complex | two nested properties, one non-existing');
+  equal(people.findAll({ hair: { color: /r/ }}), [people[4]], 'Array#findAll | complex | nested regex');
+  equal(people.findAll({ hair: { cost: 15 }}), [people[4]], 'Array#findAll | complex | nested number');
+  equal(people.findAll({ hair: { cost: 23 }}), [], 'Array#findAll | complex | nested non-matching number');
+  equal(people.findAll({ hair: { cost: undefined }}), [], 'Array#findAll | complex | nested undefined property');
+  equal(people.findAll({ hair: { post: undefined }}), [people[4]], 'Array#findAll | complex | nested undefined property non-existent');
+  equal(people.findAll({ hair: { cost: NaN }}), [], 'Array#findAll | complex | nested property is NaN');
+  equal(people.findAll({ hair: { color: function(c){ return c == 'red'; } }}), [people[4]], 'Array#findAll | complex | nested function');
+  equal(people.findAll({ some: { random: { shit: {}}}}), [], 'Array#findAll | complex | totally unrelated properties');
+  equal(people.findAll({ hair: { last_cut: new Date(2010, 4, 18) }}), [people[4]], 'Array#findAll | complex | simple date');
+
+  equal(people.some({ age: 27 }), true, 'Array#some | complex | one property');
+  equal(people.some({ age: 27, hair: 'brown' }), true, 'Array#some | complex | two properties');
+  equal(people.some({ hair: { color: 'red' }}), true, 'Array#some | complex | nested property');
+  equal(people.some({ hair: { color: 'green' }}), false, 'Array#some | complex | non-matching nested property');
+  equal(people.some({ hair: { color: 'red', type: 'long' }}), true, 'Array#some | complex | two nested properties');
+  equal(people.some({ hair: { color: 'green', type: 'mean' }}), false, 'Array#some | complex | two non-matching nested properties');
+  equal(people.some({ hair: { color: 'red', type: 'mean' }}), false, 'Array#some | complex | two nested properties, one non-matching');
+  equal(people.some({ hair: { color: 'red', life: 'long' }}), false, 'Array#some | complex | two nested properties, one non-existing');
+  equal(people.some({ hair: { color: /r/ }}), true, 'Array#some | complex | nested regex');
+  equal(people.some({ hair: { cost: 15 }}), true, 'Array#some | complex | nested number');
+  equal(people.some({ hair: { cost: 23 }}), false, 'Array#some | complex | nested non-matching number');
+  equal(people.some({ hair: { cost: undefined }}), false, 'Array#some | complex | nested undefined property');
+  equal(people.some({ hair: { cost: NaN }}), false, 'Array#some | complex | nested property is NaN');
+  equal(people.some({ hair: { color: function(c){ return c == 'red'; } }}), true, 'Array#some | complex | nested function');
+  equal(people.some({ some: { random: { shit: {}}}}), false, 'Array#some | complex | totally unrelated properties');
+  equal(people.some({ hair: { last_cut: new Date(2010, 4, 18) }}), true, 'Array#some | complex | simple date');
+
+  equal(people.none({ age: 27 }), false, 'Array#none | complex | one property');
+  equal(people.none({ age: 27, hair: 'brown' }), false, 'Array#none | complex | two properties');
+  equal(people.none({ hair: { color: 'red' }}), false, 'Array#none | complex | nested property');
+  equal(people.none({ hair: { color: 'green' }}), true, 'Array#none | complex | non-matching nested property');
+  equal(people.none({ hair: { color: 'red', type: 'long' }}), false, 'Array#none | complex | two nested properties');
+  equal(people.none({ hair: { color: 'green', type: 'mean' }}), true, 'Array#none | complex | two non-matching nested properties');
+  equal(people.none({ hair: { color: 'red', type: 'mean' }}), true, 'Array#none | complex | two nested properties, one non-matching');
+  equal(people.none({ hair: { color: 'red', life: 'long' }}), true, 'Array#none | complex | two nested properties, one non-existing');
+  equal(people.none({ hair: { color: /r/ }}), false, 'Array#none | complex | nested regex');
+  equal(people.none({ hair: { cost: 15 }}), false, 'Array#none | complex | nested number');
+  equal(people.none({ hair: { cost: 23 }}), true, 'Array#none | complex | nested non-matching number');
+  equal(people.none({ hair: { cost: undefined }}), true, 'Array#none | complex | nested undefined property');
+  equal(people.none({ hair: { cost: NaN }}), true, 'Array#none | complex | nested property is NaN');
+  equal(people.none({ hair: { color: function(c){ return c == 'red'; } }}), false, 'Array#none | complex | nested function');
+  equal(people.none({ none: { random: { shit: {}}}}), true, 'Array#none | complex | totally unrelated properties');
+  equal(people.none({ hair: { last_cut: new Date(2010, 4, 18) }}), false, 'Array#none | complex | simple date');
+
+
+  // Testing change to fuzzy finding on objects
+
+
+  arr = [{name: 'joe', age: 25}];
+  var match = { name: /j/ };
+
+  equal(arr.every(match), true, 'Array#every is now fuzzy');
+  equal(arr.some(match), true, 'Array#some is now fuzzy');
+  equal(arr.none(match), false, 'Array#none is now fuzzy');
+  equal(arr.count(match), 1, 'Array#count is now fuzzy');
+  equal(arr.find(match), arr[0], 'Array#find is now fuzzy');
+  equal(arr.findAll(match), [arr[0]], 'Array#findAll is now fuzzy');
+  equal(arr.findIndex(match), 0, 'Array#findIndex is now fuzzy');
+  equal(arr.exclude(match).length, 0, 'Array#exclude is now fuzzy');
+
+
+  equal(arr.clone().remove(match).length, 0, 'Array#remove is now fuzzy');
+  equal(arr.clone().remove(match).length, 0, 'Array#remove is now fuzzy');
+
+  equal([arr].intersect([match]), [], 'Array#intersect is NOT fuzzy');
+  equal([match].intersect([arr]), [], 'Array#intersect reverse is NOT fuzzy');
+
+  equal(arr.subtract([match]), arr, 'Array#subtract is NOT fuzzy');
+  equal([match].subtract([arr]), [match], 'Array#subtract reverse is NOT fuzzy');
+
+  equal(arr.unique(match), arr, 'Array#unique is NOT fuzzy');
+  equal([match].unique(arr), [match], 'Array#unique reverse is NOT fuzzy');
+
+
+
+  // Testing sortBy behavior
+
+  var CapturedSortOrder       = Array.AlphanumericSortOrder;
+  var CapturedSortIgnore      = Array.AlphanumericSortIgnore;
+  var CapturedSortIgnoreCase  = Array.AlphanumericSortIgnoreCase;
+  var CapturedSortEquivalents = Array.AlphanumericSortEquivalents;
+
+
+  equal([0,1,2,3,4].sortBy(), [0,1,2,3,4], 'Array#sortBy | 0 is properly sorted');
+  equal(['0','1','2','3','4'].sortBy(), ['0','1','2','3','4'], 'Array#sortBy | string numerals are properly sorted');
+  equal(['c','B','a'].sortBy(), ['a','B','c'], 'Array#sortBy | upper-case is properly sorted');
+  equal(['back','Bad','banker'].sortBy(), ['back','Bad','banker'], 'Array#sortBy | case is ignored by default');
+  equal(['c','B','a','รค','รฒ','p'].sortBy(), ['a','รค','B','c','รฒ','p'], 'Array#sortBy | should allow normalization if exists');
+  equal(['apple','apples'].sortBy(), ['apple','apples'], 'Array#sortBy | basic string length');
+  equal(['has','hร s','had','hร d'].sortBy(), ['had','hร d','has','hร s'], 'Array#sortBy | special chars basic');
+
+  arr = ['San','San Cristobal','San Juan','San Teodoro','San Tomas','Santa Barbara','Santa Clara','Santa Cruz','Santo Domingo'];
+  equal(arr.sortBy(), arr, 'Array#sortBy | spaces are counted');
+
+  equal(['AM','AB'].sortBy(), ['AB','AM'], '0 index is properly sorted');
+
+
+  arr = ['#foob','(fooc','fooa'];
+  equal(arr.sortBy(), arr, 'Array#sortBy | special chars are not ignored by default');
+
+  arr = [
+    '8braham',
+    'a4raham',
+    'abraham'
+  ];
+
+  equal(arr.sortBy(), arr, 'Array#sortBy | Numbers are filtered to the top');
+
+  arr = [
+    'pine',
+    'pino',
+    'piรฑata'
+  ];
+
+  equal(arr.sortBy(), arr, 'Array#sortBy | Spanish รฑ is respected');
+
+  var french_names = [
+    'abelle',
+    'aceline',
+    'adรฉlaรฏde',
+    'adelais',
+    'adรจle',
+    'adรฉlie',
+    'adeline',
+    'adelle',
+    'adelphe',
+    'adrienne',
+    'agace',
+    'agate',
+    'aglaรซ',
+    'agnรจs',
+    'agrippine',
+    'aimรฉe',
+    'alaina',
+    'alais',
+    'alayna',
+    'albertine',
+    'alexandrie',
+    'alexandrine',
+    'aliรฉnor',
+    'aline',
+    'alison',
+    'alphonsine',
+    'alvery',
+    'amaline',
+    'amandine',
+    'amarante',
+    'ambre',
+    'ambrosine',
+    'amรฉlie',
+    'amorette',
+    'anaรฏs',
+    'anastaise',
+    'anastasie',
+    'andrรฉe',
+    'andromaque',
+    'anette',
+    'angรจle',
+    'angeline',
+    'angelique',
+    'ann',
+    'anne'
+  ];
+
+  equal(french_names.randomize().sortBy(), french_names, 'Array#sortBy | sorting french names');
+  equal(french_names.map('toUpperCase').randomize().sortBy(), french_names.map('toUpperCase'), 'Array#sortBy | sorting french names in upper case');
+
+
+  // MSDN http://msdn.microsoft.com/en-us/library/cc194880.aspx
+  arr = [
+    'andere',
+    'รคndere',
+    'chaque',
+    'chemin',
+    'cote',
+    'cotร‰',
+    'cร”te',
+    'cร”tร‰',
+    'Czech',
+    'ฤŒuฤŒet',
+    'hiล a',
+    'irdisch',
+    'lรคvi',
+    'lie',
+    'lire',
+    'llama',
+    'Lร–wen',
+    'lร’za',
+    'Lรœbeck',
+    'luck',
+    'luฤŒ',
+    'lye',
+    'Mรคnner',
+    'mร€ล ta',
+    'mรŽr',
+    'mร–chten',
+    'myndig',
+    'pint',
+    'piร‘a',
+    'pylon',
+    'sรคmtlich',
+    'savoir',
+    'Sietla',
+    'subtle',
+    'symbol',
+    'ลšlub',
+    'ล ร€ran',
+    'vรคga',
+    'verkehrt',
+    'vox',
+    'waffle',
+    'wood',
+    'yen',
+    'yuan',
+    'yucca',
+    'zoo',
+    'Zรœrich',
+    'Zviedrija',
+    'zysk',
+    'ลฝal',
+    'ลฝena'
+  ];
+
+  equal(arr.randomize().sortBy(), arr, 'Array#sortBy | Default collation');
+
+  arr = [
+    'cweat',
+    'cwect',
+    'ฤweat',
+    'ฤweet',
+    'sweat',
+    'swect',
+    'ลกweat',
+    'ลกweet',
+    'zweat',
+    'zwect',
+    'ลพweat',
+    'ลพweet'
+  ];
+
+  equal(arr.randomize().sortBy(), arr, 'Array#sortBy | Czech/Lithuanian order is respected');
+
+
+  arr = [
+    'cat',
+    'drone',
+    'รฐroll',
+    'ebert'
+  ];
+
+  equal(arr.randomize().sortBy(), arr, 'Array#sortBy | Icelandic รฐ order is respected');
+
+  arr = [
+    'goth',
+    'ฤŸoad',
+    'hover',
+    'sing',
+    'ลŸeparate',
+    'tumble'
+  ];
+
+  equal(arr.randomize().sortBy(), arr, 'Array#sortBy | Turkish order is respected');
+
+  arr = [
+    'ape',
+    'ฤ…ce',
+    'central',
+    'ฤ‡enter',
+    'eulo',
+    'ฤ™ula',
+    'latch',
+    'lever',
+    'ล‚evel',
+    'martyr',
+    'noob',
+    'ล„ookie',
+    'oppai',
+    'sweat',
+    'swect',
+    'ล›weat',
+    'ล›weet',
+    'yeouch',
+    'รฝellow',
+    'zipper',
+    'zoophilia',
+    'ลบebra',
+    'ลผoo'
+  ];
+
+  equal(arr.randomize().sortBy(), arr, 'Array#sortBy | Polish order is respected');
+
+  arr = [
+    'cab',
+    'opec',
+    'still',
+    'zounds',
+    'รฆee',
+    'รธlaf',
+    'รฅlegra'
+  ];
+
+  equal(arr.randomize().sortBy(), arr, 'Array#sortBy | Danish/Norwegian order is respected');
+
+  arr = [
+    'llama',
+    'luck',
+    'lye'
+  ];
+
+
+
+  // Compressions simply can't be handled without a complex collation system
+  // as there is simply no way fundamentally to know what was intended as a
+  // compression. For example "catch a llama" vs "catch Al Lama"
+  equal(arr.randomize().sortBy(), arr, 'Array#sortBy | Compressions are not handled');
+
+
+  arr = [
+    'ร bel',
+    'abet',
+    'รคpe',
+    'apu',
+    'รขvec',
+    'avel',
+    'รกxe',
+    'axiom',
+    'รงoupon',
+    'coupos',
+    'รฉcma',
+    'ecmo',
+    'รชlam',
+    'elan',
+    'รซpic',
+    'epil',
+    'รซthen',
+    'ether',
+    'รฉvac',
+    'eval',
+    'รจxile',
+    'exilo',
+    'รฏce',
+    'icy',
+    'รฎll',
+    'ilp',
+    'รฏmpetum',
+    'impetus',
+    'รญp',
+    'is',
+    'รฌtalian',
+    'italians',
+    'luck',
+    'lye',
+    'รฒblast',
+    'oblong',
+    'รณmam',
+    'omar',
+    'รถpal',
+    'opam',
+    'รดva',
+    'ovum',
+    'รนla',
+    'ule',
+    'รปmar',
+    'umas',
+    'รบni',
+    'uny',
+    'รนral',
+    'uranus',
+    'รผte',
+    'utu'
+  ];
+
+  equal(arr.randomize().sortBy(), arr, 'Array#sortBy | Standard Western-Latin equivalents are enforced');
+
+  // Swedish collation
+  var swedish_words = [
+    'att borsta',
+    'att brรคnna',
+    'att brinna',
+    'att brinna',
+    'att brista',
+    'att bruka',
+    'att bryta',
+    'att bryta i bitar',
+    'att buller',
+    'att bygga',
+    'att byta',
+    'att chocka',
+    'att dela',
+    'att detaljera',
+    'att dimpa',
+    'att dรถ',
+    'att dรถ',
+    'att dรถda',
+    'att dofta',
+    'att dรถlja',
+    'att dรถma',
+    'att dra',
+    'att dra',
+    'att drabba',
+    'att dricka',
+    'att driva',
+    'att driva',
+    'att drรถmma',
+    'att duga',
+    'att erbjuda',
+    'att erkรคnna',
+    'att ersรคtta',
+    'att explodera',
+    'att falla',
+    'att falla',
+    'att fรคngsla',
+    'att fara',
+    'att fรคsta',
+    'att fastna',
+    'att faststรคlla',
+    'att fatta',
+    'att finna',
+    'att finna',
+    'att finnas',
+    'att fira',
+    'att flรคta',
+    'att fรฅ',
+    'att fรฅnga'
+  ];
+
+  equal(swedish_words.sortBy(), swedish_words, 'Array#sortBy | swedish strings sorted on utf8_general_ci');
+
+  var swedish_collated = [
+    'att borsta',
+    'att brinna',
+    'att brinna',
+    'att brista',
+    'att bruka',
+    'att bryta',
+    'att bryta i bitar',
+    'att brรคnna',
+    'att buller',
+    'att bygga',
+    'att byta',
+    'att chocka',
+    'att dela',
+    'att detaljera',
+    'att dimpa',
+    'att dofta',
+    'att dra',
+    'att dra',
+    'att drabba',
+    'att dricka',
+    'att driva',
+    'att driva',
+    'att drรถmma',
+    'att duga',
+    'att dรถ',
+    'att dรถ',
+    'att dรถda',
+    'att dรถlja',
+    'att dรถma',
+    'att erbjuda',
+    'att erkรคnna',
+    'att ersรคtta',
+    'att explodera',
+    'att falla',
+    'att falla',
+    'att fara',
+    'att fastna',
+    'att faststรคlla',
+    'att fatta',
+    'att finna',
+    'att finna',
+    'att finnas',
+    'att fira',
+    'att flรคta',
+    'att fรฅ',
+    'att fรฅnga',
+    'att fรคngsla',
+    'att fรคsta'
+  ];
+
+  Array.AlphanumericSortEquivalents['รถ'] = null;
+  Array.AlphanumericSortEquivalents['รค'] = null;
+
+  equal(swedish_words.sortBy(), swedish_collated, 'Array#sortBy | removing equivalents can restore sort order');
+
+  // Capitals
+
+  arr = [
+    'abner',
+    'aBBey',
+    'Adrian',
+    'aDella'
+  ];
+
+  expected = [
+    'aBBey',
+    'abner',
+    'aDella',
+    'Adrian'
+  ];
+
+  Array.AlphanumericSortIgnoreCase = true;
+  equal(arr.sortBy(), expected, 'Array#sortBy | allows case ignore');
+
+
+  expected = [
+    'aDella',
+    'Adrian',
+    'aBBey',
+    'abner'
+  ];
+
+  Array.AlphanumericSortOrder = 'dba';
+  equal(arr.sortBy(), expected, 'Array#sortBy | allows other order');
+
+  expected = [
+    'aDella',
+    'abner',
+    'Adrian',
+    'aBBey'
+  ];
+
+
+  Array.AlphanumericSortIgnore = /[abcde]/g;
+  equal(arr.sortBy(), expected, 'Array#sortBy | allows custom ignore');
+
+  Array.AlphanumericSortOrder = 'cba';
+  Array.AlphanumericSortIgnore = CapturedSortIgnore;
+  arr = ['cotร‰', 'cร”te', 'cร”tร‰', 'andere', 'รคndere'];
+  equal(arr.sortBy(), arr, 'Array#sortBy | cba');
+
+  Array.AlphanumericSortOrder = CapturedSortOrder;
+  Array.AlphanumericSortIgnore = CapturedSortIgnore;
+  Array.AlphanumericSortIgnoreCase = CapturedSortIgnoreCase;
+  Array.AlphanumericSortEquivalents = CapturedSortEquivalents;
+
+
+  // Issue #282
+
+
+  equal(['2','100','3'].sortBy(), ['2','3','100'], 'Array#sortBy | natural sort by default');
+  equal(['a2','a100','a3'].sortBy(), ['a2','a3','a100'], 'Array#sortBy | natural sort | leading char');
+  equal(['a2.5','a2.54','a2.4'].sortBy(), ['a2.4','a2.5','a2.54'], 'Array#sortBy | natural sort | floating number');
+  equal(['a100b', 'a100c', 'a100a'].sortBy(), ['a100a', 'a100b', 'a100c'], 'Array#sortBy | natural sort | number in middle');
+  equal(['a10.25b', 'a10.42c', 'a10.15a'].sortBy(), ['a10.15a', 'a10.25b', 'a10.42c'], 'Array#sortBy | natural sort | decimals in middle');
+  equal(['a10.15b', 'a10.15c', 'a10.15a'].sortBy(), ['a10.15a', 'a10.15b', 'a10.15c'], 'Array#sortBy | natural sort | middle decimal same, trailing char different');
+
+  equal(['a๏ผ‘๏ผ๏ผŽ๏ผ’๏ผ•b', 'a๏ผ‘๏ผ๏ผŽ๏ผ”๏ผ’c', 'a๏ผ‘๏ผ๏ผŽ๏ผ‘๏ผ•a'].sortBy(), ['a๏ผ‘๏ผ๏ผŽ๏ผ‘๏ผ•a', 'a๏ผ‘๏ผ๏ผŽ๏ผ’๏ผ•b', 'a๏ผ‘๏ผ๏ผŽ๏ผ”๏ผ’c'], 'Array#sortBy | natural sort | decimals in middle');
+  equal(['a๏ผ‘๏ผ๏ผŽ๏ผ‘๏ผ•b', 'a๏ผ‘๏ผ๏ผŽ๏ผ‘๏ผ•c', 'a๏ผ‘๏ผ๏ผŽ๏ผ‘๏ผ•a'].sortBy(), ['a๏ผ‘๏ผ๏ผŽ๏ผ‘๏ผ•a', 'a๏ผ‘๏ผ๏ผŽ๏ผ‘๏ผ•b', 'a๏ผ‘๏ผ๏ผŽ๏ผ‘๏ผ•c'], 'Array#sortBy | natural sort | full-width | middle decimal same, trailing char different');
+  equal(['๏ผ’','๏ผ‘๏ผ๏ผ','๏ผ“'].sortBy(), ['๏ผ’','๏ผ“','๏ผ‘๏ผ๏ผ'], 'Array#sortBy | natural sort | full width');
+  equal(['a๏ผ’','a๏ผ‘๏ผ๏ผ','a๏ผ“'].sortBy(), ['a๏ผ’','a๏ผ“','a๏ผ‘๏ผ๏ผ'], 'Array#sortBy | natural sort | full width | leading char');
+
+
+  equal(['title 1-300', 'title 1-1', 'title 1-5'].sortBy(), ['title 1-1', 'title 1-5', 'title 1-300'], 'Array#sortBy | natural sort | hyphenated');
+
+
+  // The following tests were taken from http://www.overset.com/2008/09/01/javascript-natural-sort-algorithm/
+
+  equal(['10',9,2,'1','4'].sortBy(), ['1',2,'4',9,'10'], 'Array.sortBy | other | simple');
+  equal(['10.0401',10.022,10.042,'10.021999'].sortBy(), ['10.021999',10.022,'10.0401',10.042], 'Array.sortBy | other | floats');
+  equal(['10.04f','10.039F','10.038d','10.037D'].sortBy(), ['10.037D','10.038d','10.039F','10.04f'], 'Array.sortBy | other | float & decimal notation');
+  equal(['1.528535047e5','1.528535047e7','1.528535047e3'].sortBy(), ['1.528535047e3','1.528535047e5','1.528535047e7'], 'Array.sortBy | scientific notation');
+  equal(['192.168.0.100','192.168.0.1','192.168.1.1'].sortBy(), ['192.168.0.1','192.168.0.100','192.168.1.1'], 'Array.sortBy | other | IP addresses');
+  equal(['car.mov','01alpha.sgi','001alpha.sgi','my.string_41299.tif'].sortBy(), ['001alpha.sgi','01alpha.sgi','car.mov','my.string_41299.tif'], 'Array.sortBy | other | filenames');
+  equal(['$10002.00','$10001.02','$10001.01'].sortBy(), ['$10001.01','$10001.02','$10002.00'], 'Array.sortBy | other | money');
+  equal(['1 Title - The Big Lebowski','1 Title - Gattaca','1 Title - Last Picture Show'].sortBy(), ['1 Title - Gattaca','1 Title - Last Picture Show','1 Title - The Big Lebowski'], 'Array.sortBy | stolen | movie titles');
+
+
+  Array.AlphanumericSortNatural = false;
+
+  equal(['2','100','3'].sortBy(), ['100','2','3'], 'Array#sortBy | natural sort off');
+  equal(['a2','a100','a3'].sortBy(), ['a100','a2','a3'], 'Array#sortBy | natural sort off | leading char');
+  equal(['a2.5','a2.54','a2.4'].sortBy(), ['a2.4','a2.5','a2.54'], 'Array#sortBy | natural sort off | with floating number');
+  equal(['๏ผ’','๏ผ‘๏ผ๏ผ','๏ผ“'].sortBy(), ['๏ผ‘๏ผ๏ผ','๏ผ’','๏ผ“'], 'Array#sortBy | natural sort off | full width');
+  equal(['a๏ผ’','a๏ผ‘๏ผ๏ผ','a๏ผ“'].sortBy(), ['a๏ผ‘๏ผ๏ผ','a๏ผ’','a๏ผ“'], 'Array#sortBy | natural sort off | full width leading char');
+
+  Array.AlphanumericSortNatural = true;
+
+  // Testing Array#union and Array#intersect on complex elements as found http://ermouth.com/fastArray/
+  // Thanks to @ermouth!
+
+
+  var yFunc = function () { return 'y'; }
+  var xFunc = function () { return 'x'; }
+
+  var arr1 = [
+    { eccbc87e4b5ce2fe28308fd9f2a7baf3: 3 },
+    /rowdy/,
+    /randy/,
+    yFunc,
+    [6, "1679091c5a880faf6fb5e6087eb1b2dc"],
+    xFunc,
+    2
+  ];
+
+  var arr2 = [
+    { eccbc87e4b5ce2fe28308fd9f2a7baf3: 3 },
+    /rowdy/,
+    /pandy/,
+    xFunc,
+    { e4da3b7fbbce2345d7772b0674a318d5: 5 },
+    [8, "c9f0f895fb98ab9159f51fd0297e236d"]
+  ];
+
+  var unionExpected = [
+    { eccbc87e4b5ce2fe28308fd9f2a7baf3: 3 },
+    /rowdy/,
+    /randy/,
+    yFunc,
+    [6, "1679091c5a880faf6fb5e6087eb1b2dc"],
+    xFunc,
+    2,
+    /pandy/,
+    { e4da3b7fbbce2345d7772b0674a318d5: 5 },
+    [8, "c9f0f895fb98ab9159f51fd0297e236d"]
+  ];
+
+  var intersectExpected = [
+    { eccbc87e4b5ce2fe28308fd9f2a7baf3: 3 },
+    /rowdy/,
+    xFunc
+  ];
+
+
+  equal(arr1.union(arr2), unionExpected, 'Array#union | complex array unions');
+  equal(arr1.intersect(arr2), intersectExpected, 'Array#union | complex array intersects');
+
+
+  equal([function(){ return 'a' }].intersect([function() { return 'a'; }, function() { return 'b'; }]), [], 'Array#intersect | functions are always unique');
+
+  equal([xFunc].intersect([]), [], 'Array#intersect | functions with different content | [x] & []');
+  equal([yFunc].intersect([]), [], 'Array#intersect | functions with different content | [y] & []');
+  equal([].intersect([xFunc]), [], 'Array#intersect | functions with different content | [] & [x]');
+  equal([].intersect([yFunc]), [], 'Array#intersect | functions with different content | [] & [y]');
+  equal([].intersect([xFunc, yFunc]), [], 'Array#intersect | functions with different content | [] & [x,y]');
+  equal([xFunc].intersect([xFunc]), [xFunc], 'Array#intersect | functions with different content | [x] & [x]');
+  equal([xFunc].intersect([yFunc]), [], 'Array#intersect | functions with different content | [x] & [y]');
+  equal([xFunc].intersect([xFunc, yFunc]), [xFunc], 'Array#intersect | functions with different content | [x] & [x,y]');
+  equal([xFunc, xFunc].intersect([xFunc, yFunc]), [xFunc], 'Array#intersect | functions with different content | [x,x] & [x,y]');
+  equal([xFunc, xFunc].intersect([xFunc, xFunc]), [xFunc], 'Array#intersect | functions with different content | [x,x] & [x,x]');
+  equal([xFunc, yFunc].intersect([xFunc, yFunc]), [xFunc,yFunc], 'Array#intersect | functions with different content | [x,y] & [x,y]');
+  equal([xFunc, yFunc].intersect([yFunc, xFunc]), [xFunc,yFunc], 'Array#intersect | functions with different content | [x,y] & [y,x]');
+  equal([xFunc, yFunc].intersect([yFunc, yFunc]), [yFunc], 'Array#intersect | functions with different content | [x,y] & [y,y]');
+  equal([yFunc, xFunc].intersect([yFunc, xFunc]), [yFunc,xFunc], 'Array#intersect | functions with different content | [y,x] & [y,x]');
+  equal([yFunc, xFunc].intersect([xFunc, yFunc]), [yFunc,xFunc], 'Array#intersect | functions with different content | [y,x] & [x,y]');
+  equal([yFunc, xFunc].intersect([xFunc, xFunc]), [xFunc], 'Array#intersect | functions with different content | [y,x] & [x,x]');
+  equal([xFunc, xFunc].intersect([yFunc, yFunc]), [], 'Array#intersect | functions with different content | [x,x] & [y,y]');
+  equal([yFunc, yFunc].intersect([xFunc, xFunc]), [], 'Array#intersect | functions with different content | [y,y] & [x,x]');
+
+  equal([xFunc].subtract([]), [xFunc], 'Array#subtract | functions with different content | [x] - []');
+  equal([yFunc].subtract([]), [yFunc], 'Array#subtract | functions with different content | [y] - []');
+  equal([].subtract([xFunc]), [], 'Array#subtract | functions with different content | [] - [x]');
+  equal([].subtract([yFunc]), [], 'Array#subtract | functions with different content | [] - [y]');
+  equal([].subtract([xFunc, yFunc]), [], 'Array#subtract | functions with different content | [] - [x,y]');
+  equal([xFunc].subtract([xFunc]), [], 'Array#subtract | functions with different content | [x] - [x]');
+  equal([xFunc].subtract([yFunc]), [xFunc], 'Array#subtract | functions with different content | [x] - [y]');
+  equal([xFunc].subtract([xFunc, yFunc]), [], 'Array#subtract | functions with different content | [x] - [x,y]');
+  equal([xFunc, xFunc].subtract([xFunc, yFunc]), [], 'Array#subtract | functions with different content | [x,x] - [x,y]');
+  equal([xFunc, xFunc].subtract([xFunc, xFunc]), [], 'Array#subtract | functions with different content | [x,x] - [x,x]');
+  equal([xFunc, yFunc].subtract([xFunc, yFunc]), [], 'Array#subtract | functions with different content | [x,y] - [x,y]');
+  equal([xFunc, yFunc].subtract([yFunc, xFunc]), [], 'Array#subtract | functions with different content | [x,y] - [y,x]');
+  equal([xFunc, yFunc].subtract([yFunc, yFunc]), [xFunc], 'Array#subtract | functions with different content | [x,y] - [y,y]');
+  equal([yFunc, xFunc].subtract([yFunc, xFunc]), [], 'Array#subtract | functions with different content | [y,x] - [y,x]');
+  equal([yFunc, xFunc].subtract([xFunc, yFunc]), [], 'Array#subtract | functions with different content | [y,x] - [x,y]');
+  equal([yFunc, xFunc].subtract([xFunc, xFunc]), [yFunc], 'Array#subtract | functions with different content | [y,x] - [x,x]');
+  equal([xFunc, xFunc].subtract([yFunc, yFunc]), [xFunc,xFunc], 'Array#subtract | functions with different content | [x,x] - [y,y]');
+  equal([yFunc, yFunc].subtract([xFunc, xFunc]), [yFunc,yFunc], 'Array#subtract | functions with different content | [y,y] - [x,x]');
+
+  xFunc = function() {};
+  yFunc = function() {};
+
+  equal([xFunc].intersect([]), [], 'Array#intersect | functions with identical content | [x] & []');
+  equal([yFunc].intersect([]), [], 'Array#intersect | functions with identical content | [y] & []');
+  equal([].intersect([xFunc]), [], 'Array#intersect | functions with identical content | [] & [x]');
+  equal([].intersect([yFunc]), [], 'Array#intersect | functions with identical content | [] & [y]');
+  equal([].intersect([xFunc, yFunc]), [], 'Array#intersect | functions with identical content | [] & [x,y]');
+  equal([xFunc].intersect([xFunc]), [xFunc], 'Array#intersect | functions with identical content | [x] & [x]');
+  equal([xFunc].intersect([yFunc]), [], 'Array#intersect | functions with identical content | [x] & [y]');
+  equal([xFunc].intersect([xFunc, yFunc]), [xFunc], 'Array#intersect | functions with identical content | [x] & [x,y]');
+  equal([xFunc, xFunc].intersect([xFunc, yFunc]), [xFunc], 'Array#intersect | functions with identical content | [x,x] & [x,y]');
+  equal([xFunc, xFunc].intersect([xFunc, xFunc]), [xFunc], 'Array#intersect | functions with identical content | [x,x] & [x,x]');
+  equal([xFunc, yFunc].intersect([xFunc, yFunc]), [xFunc,yFunc], 'Array#intersect | functions with identical content | [x,y] & [x,y]');
+  equal([xFunc, yFunc].intersect([yFunc, xFunc]), [xFunc,yFunc], 'Array#intersect | functions with identical content | [x,y] & [y,x]');
+  equal([xFunc, yFunc].intersect([yFunc, yFunc]), [yFunc], 'jrray#intersect | functions with identical content | [x,y] & [y,y]');
+  equal([yFunc, xFunc].intersect([yFunc, xFunc]), [yFunc,xFunc], 'Array#intersect | functions with identical content | [y,x] & [y,x]');
+  equal([yFunc, xFunc].intersect([xFunc, yFunc]), [yFunc,xFunc], 'Array#intersect | functions with identical content | [y,x] & [x,y]');
+  equal([yFunc, xFunc].intersect([xFunc, xFunc]), [xFunc], 'Array#intersect | functions with identical content | [y,x] & [x,x]');
+  equal([xFunc, xFunc].intersect([yFunc, yFunc]), [], 'Array#intersect | functions with identical content | [x,x] & [y,y]');
+  equal([yFunc, yFunc].intersect([xFunc, xFunc]), [], 'Array#intersect | functions with identical content | [y,y] & [x,x]');
+
+  equal([xFunc].subtract([]), [xFunc], 'Array#subtract | functions with identical content | [x] - []');
+  equal([yFunc].subtract([]), [yFunc], 'Array#subtract | functions with identical content | [y] - []');
+  equal([].subtract([xFunc]), [], 'Array#subtract | functions with identical content | [] - [x]');
+  equal([].subtract([yFunc]), [], 'Array#subtract | functions with identical content | [] - [y]');
+  equal([].subtract([xFunc, yFunc]), [], 'Array#subtract | functions with identical content | [] - [x,y]');
+  equal([xFunc].subtract([xFunc]), [], 'Array#subtract | functions with identical content | [x] - [x]');
+  equal([xFunc].subtract([yFunc]), [xFunc], 'Array#subtract | functions with identical content | [x] - [y]');
+  equal([xFunc].subtract([xFunc, yFunc]), [], 'Array#subtract | functions with identical content | [x] - [x,y]');
+  equal([xFunc, xFunc].subtract([xFunc, yFunc]), [], 'Array#subtract | functions with identical content | [x,x] - [x,y]');
+  equal([xFunc, xFunc].subtract([xFunc, xFunc]), [], 'Array#subtract | functions with identical content | [x,x] - [x,x]');
+  equal([xFunc, yFunc].subtract([xFunc, yFunc]), [], 'Array#subtract | functions with identical content | [x,y] - [x,y]');
+  equal([xFunc, yFunc].subtract([yFunc, xFunc]), [], 'Array#subtract | functions with identical content | [x,y] - [y,x]');
+  equal([xFunc, yFunc].subtract([yFunc, yFunc]), [xFunc], 'Array#subtract | functions with identical content | [x,y] - [y,y]');
+  equal([yFunc, xFunc].subtract([yFunc, xFunc]), [], 'Array#subtract | functions with identical content | [y,x] - [y,x]');
+  equal([yFunc, xFunc].subtract([xFunc, yFunc]), [], 'Array#subtract | functions with identical content | [y,x] - [x,y]');
+  equal([yFunc, xFunc].subtract([xFunc, xFunc]), [yFunc], 'Array#subtract | functions with identical content | [y,x] - [x,x]');
+  equal([xFunc, xFunc].subtract([yFunc, yFunc]), [xFunc,xFunc], 'Array#subtract | functions with identical content | [x,x] - [y,y]');
+  equal([yFunc, yFunc].subtract([xFunc, xFunc]), [yFunc,yFunc], 'Array#subtract | functions with identical content | [y,y] - [x,x]');
+
+  equal([function(){ return 'a' }, function() { return 'b'; }].subtract([function() { return 'a'; }]).length, 2, 'Array#subtract | functions are always unique');
+  equal([xFunc, yFunc].subtract([xFunc]), [yFunc], 'Array#subtract | function references are ===');
+
+
+  equal([['a',1]].intersect([['a',1],['b',2]]), [['a',1]], 'Array#intersect | nested arrays are not flattened');
+  equal([['a',1],['b',2]].subtract([['a',1]]), [['b',2]], 'Array#subtract | nested arrays are not flattened');
+
+
+
+  // Comprehensive unit tests for new uniquing method.
+
+
+  var aFunc = function(){
+    return 'a';
+  }
+  var bFunc = function(){
+    return 'b';
+  }
+  var cFunc = function(){
+    return 'c';
+  }
+  var dFunc = function(){
+    return 'd';
+  }
+
+
+  arrayEquivalent([1,2,3].union([3,4,5]), [1,2,3,4,5], 'Array#union | Basic');
+  arrayEquivalent([1,2,3].union(['1','2','3']), [1,2,3,'1','2','3'], 'Array#union | Numbers vs. Strings');
+  arrayEquivalent([[1,2,3]].union([['1','2','3']]), [[1,2,3],['1','2','3']], 'Array#union | Numbers vs. Strings nested');
+
+  arrayEquivalent([1,2,3].union([1,2,3]), [1,2,3], 'Array#union | Number array');
+  arrayEquivalent([[1,2,3]].union([[1,2,3]]), [[1,2,3]], 'Array#union | Nested number array');
+  arrayEquivalent([[1,2,3]].union([[3,2,1]]), [[1,2,3],[3,2,1]], 'Array#union | Nested and reversed');
+
+  arrayEquivalent([aFunc].union([bFunc]), [aFunc, bFunc], 'Array#union | Function references');
+  arrayEquivalent([aFunc].union([bFunc, cFunc]), [aFunc, bFunc, cFunc], 'Array#union | Function references');
+  arrayEquivalent([aFunc, bFunc].union([bFunc, cFunc]), [aFunc, bFunc, cFunc], 'Array#union | Function references');
+  arrayEquivalent([aFunc, bFunc, cFunc].union([aFunc, bFunc, cFunc]), [aFunc, bFunc, cFunc], 'Array#union | Function references');
+  arrayEquivalent([cFunc, cFunc].union([cFunc, cFunc]), [cFunc], 'Array#union | Function references');
+  arrayEquivalent([].union([aFunc]), [aFunc], 'Array#union | Function references');
+
+  equal([function() { return 'a'; }].union([function() { return 'a'; }]).length, 2, 'Array#union | Functions are never equivalent');
+
+
+  arrayEquivalent([/bar/].union([/bas/]), [/bar/,/bas/], 'Array#union | Regexes');
+  arrayEquivalent([[/bar/]].union([[/bas/,/bap/]]), [[/bar/],[/bas/,/bap/]], 'Array#union | Nested Regexes');
+  arrayEquivalent([{ reg: /bar/ }].union([{ reg: /bar/ }, { reg: /map/ }]), [{ reg: /bar/ }, { reg: /map/ }], 'Array#union | Object Regexes');
+
+  arrayEquivalent([true].union([false]), [true,false], 'Array#union | Booleans');
+  arrayEquivalent([true].union([true]), [true], 'Array#union | Same Booleans');
+  arrayEquivalent([[true]].union([[true, false]]), [[true],[true, false]], 'Array#union | Nested Booleans');
+  arrayEquivalent([{ b: false }].union([{ b: false }, { b: true }]), [{ b: false }, { b: true }], 'Array#union | Object Booleans');
+
+
+  arrayEquivalent([{},{}].union([{},{}]), [{}], 'Array#union | empty object array');
+  arrayEquivalent([[{}]].union([[{},{}]]), [[{}],[{},{}]], 'Array#union | nested empty object array');
+  arrayEquivalent([[{},{}]].union([[{},{}]]), [[{},{}]], 'Array#union | nested double object array');
+
+  arrayEquivalent([{0:1}].union([[1]]), [{0:1},[1]], 'Array#union | object posing as array');
+  arrayEquivalent([{}].union([[]]), [{},[]], 'Array#union | empty object vs. empty array');
+
+  arrayEquivalent([[[],1]].union([[[1]]]), [[[],1], [[1]]], 'Array#union | empty array, 1 vs. empty array WITH one');
+
+  var aObj = {
+    text: 'foo',
+    arr:  ['a','b','c'],
+    reg: /moofa/,
+    arr: [{foo:'bar'},{moo:'car'}],
+    date: new Date(2001, 5, 15)
+  }
+
+  var bObj = {
+    text: 'foo',
+    arr:  ['a','b','c'],
+    reg: /moofa/,
+    arr: [{foo:'bar'},{moo:'car'}],
+    date: new Date(2001, 5, 15)
+  }
+
+  var cObj = {
+    text: 'foo',
+    arr:  ['a','b','c'],
+    reg: /moofo/,
+    arr: [{foo:'bar'},{moo:'car'}],
+    date: new Date(2001, 5, 15)
+  }
+
+  var dObj = {
+    text: 'foo',
+    arr:  ['a','b','c'],
+    reg: /moofa/,
+    arr: [{foo:'bar'},{moo:'car'}],
+    date: new Date(2001, 8, 15)
+  }
+
+  var eObj = {
+    text: 'foo',
+    arr:  ['a','b','c'],
+    reg: /moofa/,
+    arr: [{foo:'bar'},{moo:'par'}],
+    date: new Date(2001, 8, 15)
+  }
+
+
+  arrayEquivalent([aObj].union([aObj]), [aObj], 'Array#union | Nested objects a + a');
+  arrayEquivalent([aObj].union([bObj]), [aObj], 'Array#union | Nested objects a + b');
+  arrayEquivalent([aObj,bObj,cObj].union([]), [aObj, cObj], 'Array#union | Nested objects a,b,c + []');
+  arrayEquivalent([].union([aObj,bObj,cObj]), [aObj, cObj], 'Array#union | Nested objects [] + a,b,c');
+  arrayEquivalent([aObj,bObj].union([cObj]), [aObj, cObj], 'Array#union | Nested objects a,b + c');
+  arrayEquivalent([cObj, cObj].union([cObj, cObj]), [cObj], 'Array#union | Nested objects c,c + c,c');
+  arrayEquivalent([aObj, bObj, cObj, dObj].union([]), [aObj, cObj, dObj], 'Array#union | Nested objects a,b,c,d + []');
+  arrayEquivalent([].union([aObj, bObj, cObj, dObj]), [aObj, cObj, dObj], 'Array#union | Nested objects a,b,c,d + a,c,d');
+  arrayEquivalent([aObj, bObj].union([cObj, dObj]), [aObj, cObj, dObj], 'Array#union | Nested objects a,b + c,d');
+
+  arrayEquivalent([aObj, bObj, cObj, dObj, eObj].union([aObj, bObj, cObj, dObj, eObj]), [aObj, cObj, dObj, eObj], 'Array#union | Nested objects a,b,c,d,e + a,b,c,d,e');
+
+  var aFuncObj = {
+    text: 'foo',
+    func: function() { return 'a'; },
+    arr:  ['a','b','c'],
+    reg: /moofa/,
+    date: new Date(2001, 5, 15)
+  }
+
+  var bFuncObj = {
+    text: 'foo',
+    func: function() { return 'a'; },
+    arr:  ['a','b','c'],
+    reg: /moofa/,
+    date: new Date(2001, 5, 15)
+  }
+
+  var cFuncObj = {
+    text: 'foo',
+    func: function() { return 'c'; },
+    arr:  ['a','b','c'],
+    reg: /moofa/,
+    date: new Date(2001, 5, 15)
+  }
+
+
+  arrayEquivalent([aFuncObj].union([aFuncObj]), [aFuncObj], 'Array#union | Nested objects with functions');
+  arrayEquivalent([aFuncObj].union([bFuncObj]), [aFuncObj], 'Array#union | Nested objects with functions');
+  arrayEquivalent([aFuncObj,bFuncObj,cFuncObj].union([]), [aFuncObj, cFuncObj], 'Array#union | Nested objects with functions');
+  arrayEquivalent([aFuncObj,bFuncObj].union([cFuncObj]), [aFuncObj, cFuncObj], 'Array#union | Nested objects with functions');
+  arrayEquivalent([cFuncObj, cFuncObj].union([cFuncObj, cFuncObj]), [cFuncObj], 'Array#union | Nested objects with functions meh');
+
+
+  arrayEquivalent([NaN,NaN].union([NaN,NaN]), [NaN], 'Array#union | NaN');
+  arrayEquivalent([null,null].union([null,null]), [null], 'Array#union | Null');
+  arrayEquivalent([undefined].union([undefined]), sparseArraySupport ? [undefined] : [], 'Array#union | undefined');
+
+
+  var aObj = {
+    one:    1,
+    two:    2,
+    three:  3
+  }
+
+  var bObj = {
+    three:  3,
+    two:    2,
+    one:    1
+  }
+
+  equal([aObj].union([bObj]).length, 1, 'Array#union | Properties may not be in the same order.');
+
+
+  xFunc = function (){ return 'x'; }
+  yFunc = function (){ return 'y'; }
+
+  equal([xFunc].union([]), [xFunc], 'Array#union | functions with different content | [x] + []');
+  equal([yFunc].union([]), [yFunc], 'Array#union | functions with different content | [y] + []');
+  equal([].union([xFunc]), [xFunc], 'Array#union | functions with different content | [] + [x]');
+  equal([].union([yFunc]), [yFunc], 'Array#union | functions with different content | [] + [y]');
+  equal([].union([xFunc, yFunc]), [xFunc,yFunc], 'Array#union | functions with different content | [] + [x,y]');
+  equal([xFunc].union([xFunc]), [xFunc], 'Array#union | functions with different content | [x] + [x]');
+  equal([xFunc].union([yFunc]), [xFunc,yFunc], 'Array#union | functions with different content | [x] + [y]');
+  equal([xFunc].union([xFunc, yFunc]), [xFunc,yFunc], 'Array#union | functions with different content | [x] + [x,y]');
+  equal([xFunc, xFunc].union([xFunc, yFunc]), [xFunc,yFunc], 'Array#union | functions with different content | [x,x] + [x,y]');
+  equal([xFunc, xFunc].union([xFunc, xFunc]), [xFunc], 'Array#union | functions with different content | [x,x] + [x,x]');
+  equal([xFunc, yFunc].union([xFunc, yFunc]), [xFunc,yFunc], 'Array#union | functions with different content | [x,y] + [x,y]');
+  equal([xFunc, yFunc].union([yFunc, xFunc]), [xFunc,yFunc], 'Array#union | functions with different content | [x,y] + [y,x]');
+  equal([xFunc, yFunc].union([yFunc, yFunc]), [xFunc,yFunc], 'Array#union | functions with different content | [x,y] + [y,y]');
+  equal([yFunc, xFunc].union([yFunc, xFunc]), [yFunc,xFunc], 'Array#union | functions with different content | [y,x] + [y,x]');
+  equal([yFunc, xFunc].union([xFunc, yFunc]), [yFunc,xFunc], 'Array#union | functions with different content | [y,x] + [x,y]');
+  equal([yFunc, xFunc].union([xFunc, xFunc]), [yFunc,xFunc], 'Array#union | functions with different content | [y,x] + [x,x]');
+  equal([xFunc, xFunc].union([yFunc, yFunc]), [xFunc,yFunc], 'Array#union | functions with different content | [x,x] + [y,y]');
+  equal([yFunc, yFunc].union([xFunc, xFunc]), [yFunc,xFunc], 'Array#union | functions with different content | [y,y] + [x,x]');
+
+
+  xFunc = function (){}
+  yFunc = function (){}
+
+  equal([xFunc].union([]), [xFunc], 'Array#union | functions with identical content | [x] + []');
+  equal([yFunc].union([]), [yFunc], 'Array#union | functions with identical content | [y] + []');
+  equal([].union([xFunc]), [xFunc], 'Array#union | functions with identical content | [] + [x]');
+  equal([].union([yFunc]), [yFunc], 'Array#union | functions with identical content | [] + [y]');
+  equal([].union([xFunc, yFunc]), [xFunc,yFunc], 'Array#union | functions with identical content | [] + [x,y]');
+  equal([xFunc].union([xFunc]), [xFunc], 'Array#union | functions with identical content | [x] + [x]');
+  equal([xFunc].union([yFunc]), [xFunc,yFunc], 'Array#union | functions with identical content | [x] + [y]');
+  equal([xFunc].union([xFunc, yFunc]), [xFunc,yFunc], 'Array#union | functions with identical content | [x] + [x,y]');
+  equal([xFunc, xFunc].union([xFunc, yFunc]), [xFunc,yFunc], 'Array#union | functions with identical content | [x,x] + [x,y]');
+  equal([xFunc, xFunc].union([xFunc, xFunc]), [xFunc], 'Array#union | functions with identical content | [x,x] + [x,x]');
+  equal([xFunc, yFunc].union([xFunc, yFunc]), [xFunc,yFunc], 'Array#union | functions with identical content | [x,y] + [x,y]');
+  equal([xFunc, yFunc].union([yFunc, xFunc]), [xFunc,yFunc], 'Array#union | functions with identical content | [x,y] + [y,x]');
+  equal([xFunc, yFunc].union([yFunc, yFunc]), [xFunc,yFunc], 'Array#union | functions with identical content | [x,y] + [y,y]');
+  equal([yFunc, xFunc].union([yFunc, xFunc]), [yFunc,xFunc], 'Array#union | functions with identical content | [y,x] + [y,x]');
+  equal([yFunc, xFunc].union([xFunc, yFunc]), [yFunc,xFunc], 'Array#union | functions with identical content | [y,x] + [x,y]');
+  equal([yFunc, xFunc].union([xFunc, xFunc]), [yFunc,xFunc], 'Array#union | functions with identical content | [y,x] + [x,x]');
+  equal([xFunc, xFunc].union([yFunc, yFunc]), [xFunc,yFunc], 'Array#union | functions with identical content | [x,x] + [y,y]');
+  equal([yFunc, yFunc].union([xFunc, xFunc]), [yFunc,xFunc], 'Array#union | functions with identical content | [y,y] + [x,x]');
+
+
+
+  // Issue #157 Ensure that instances can be subject to fuzzy matches despite not being "objects"
+
+  function Foo(a) {
+    this.a = a;
+  }
+
+  var one   = new Foo('one');
+  var two   = new Foo('two');
+  var three = new Foo('three');
+  var four  = new Foo(new Date(2001, 3, 15));
+
+  equal([one, two, three, four].findAll({ a: 'one' }), [one], 'Array#findAll | matches class instances | object with string');
+  equal([one, two, three, four].findAll({ a: /t/ }), [two, three], 'Array#findAll | matches class instances | object with regex');
+  equal([one, two, three, four].findAll('one'), [], 'Array#findAll | matches class instances | string');
+  equal([one, two, three, four].findAll(/t/), [one, two, three, four], 'Array#findAll | directly passing a regex is matching the objects stringified');
+  equal([one, two, three, four].findAll(/x/), [], 'Array#findAll | directly passing a regex with no matching letter');
+  equal([one, two, three, four].findAll(true), [], 'Array#findAll | matches class instances | boolean');
+  equal([one, two, three, four].findAll(new Date()), [], 'Array#findAll | matches class instances | now');
+  equal([one, two, three, four].findAll(new Date(2001, 3, 15)), [], 'Array#findAll | matches class instances | correct date');
+  equal([one, two, three, four].findAll(null), [], 'Array#findAll | matches class instances | null');
+  equal([one, two, three, four].findAll(undefined), [], 'Array#findAll | matches class instances | undefined');
+  equal([one, two, three, four].findAll({ a: 'twof' }), [], 'Array#findAll | matches class instances | nonexistent string');
+  equal([one, two, three, four].findAll({ b: 'one' }), [], 'Array#findAll | matches class instances | nonexistent property');
+  equal([one, two, three, four].findAll({}), [one, two, three, four], 'Array#findAll | matches class instances | empty object');
+  equal([one, two, three, four].findAll({ a: new Date(2001, 3, 15) }), [four], 'Array#findAll | matches class instances | object with correct date');
+  equal([one, two, three, four].findAll({ b: new Date(2001, 3, 15) }), [], 'Array#findAll | matches class instances | object with correct date but wrong property');
+  equal([one, two, three, four].findAll({ a: new Date(2001, 3, 16) }), [], 'Array#findAll | matches class instances | object with incorrect date');
+  equal([one, two, three, four].findAll({ a: new Date(2001, 3, 15, 0, 0, 0, 1) }), [], 'Array#findAll | matches class instances | object with date off by 1ms');
+
+
+  var date = new Date(2001, 3, 15);
+  var timestamp = date.getTime();
+  var obj = { a: { getTime: function () { return timestamp; } }};
+  equal([obj].findAll({ a: date }), [obj], 'Array#findAll | duck typing for date matching');
+
+  var five = new Foo(one);
+
+  equal([five].findAll({ a: 'one' }), [], 'Array#findAll | nested instances | object with string');
+  equal([five].findAll({ a: { a: 'one' } }), [five], 'Array#findAll | nested instances | object with double nested string');
+  equal([five].findAll({ a: { a: 'two' } }), [], 'Array#findAll | nested instances | object with double nested string but incorrect');
+
+
+
+
+
+
+
+  // Object enumerable methods
+
+  var obj1 = {
+    foo: 3,
+    bar: 4,
+    moo: 5,
+    car: 6
+  }
+
+  var obj2 = {
+   foo: { age: 11 },
+   bar: { age: 22 },
+   moo: { age: 33 },
+   car: { age: 44 }
+  }
+
+  testClassAndInstance('any', obj1, function(key, value, o) {
+    equal(typeof key, 'string', 'Object enumerable methods | first argument is always the key');
+    equal(value, obj1[key],     'Object enumerable methods | second argument is always the value');
+    equal(o, obj1,              'Object enumerable methods | third argument is always the original object');
+    equal(this, obj1,           'Object enumerable methods | "this" is always the original object');
+    return true;
+  }, true, 'Object.any | placeholder for callback arguments');
+
+  testClassAndInstance('map', obj1, function(k, v) { return v * 2; }, {foo:6,bar:8,moo:10,car:12}, 'Object.map | function');
+  testClassAndInstance('map', obj1, 'toString', {foo:'3',bar:'4',moo:'5',car:'6'}, 'Object.map | string shortcut');
+  testClassAndInstance('map', obj1, [], obj1, 'Object.map | no args');
+  testClassAndInstance('map', obj2, function(k, v) { return v.age; }, {foo:11,bar:22,moo:33,car:44}, 'Object.map | mapping nested properties');
+  testClassAndInstance('map', obj2, 'age', {foo:11,bar:22,moo:33,car:44}, 'Object.map | mapping nested properties with string shortcut');
+
+  testClassAndInstance('any', obj1, function(key, value) { return key == 'foo'; }, true, 'Object.any | key is foo');
+  testClassAndInstance('any', obj1, function(key, value) { return key.length > 3; }, false, 'Object.any | key length is greater than 3');
+  testClassAndInstance('any', obj1, function(key, value) { return key.length > 0; }, true, 'Object.any | key length is greater than 0');
+  testClassAndInstance('any', obj1, function(key, value) { return value > 0; }, true, 'Object.any | value is greater than 0');
+  testClassAndInstance('any', obj1, function(key, value) { return value > 5; }, true, 'Object.any | value is greater than 5');
+  testClassAndInstance('any', obj1, function(key, value) { return value > 6; }, false, 'Object.any | value is greater than 6');
+  testClassAndInstance('any', obj1, 5, true,  'Object.any | shortcut | 5');
+  testClassAndInstance('any', obj1, 7, false, 'Object.any | shortcut | 7');
+
+  testClassAndInstance('all', obj1, function(key, value) { return key == 'foo'; }, false, 'Object.all | key is foo');
+  testClassAndInstance('all', obj1, function(key, value) { return key.length > 3; }, false, 'Object.all | key length is greater than 3');
+  testClassAndInstance('all', obj1, function(key, value) { return key.length > 0; }, true, 'Object.all | key length is greater than 0');
+  testClassAndInstance('all', obj1, function(key, value) { return value > 0; }, true, 'Object.all | value is greater than 0');
+  testClassAndInstance('all', obj1, function(key, value) { return value > 5; }, false, 'Object.all | value is greater than 5');
+  testClassAndInstance('all', obj1, function(key, value) { return value > 6; }, false, 'Object.all | value is greater than 6');
+  testClassAndInstance('all', obj1, 5, false,  'Object.all | shortcut | 5');
+  testClassAndInstance('all', obj1, 7, false, 'Object.all | shortcut | 7');
+
+  testClassAndInstance('none', obj1, function(key, value) { return key == 'foo'; }, false, 'Object.none | key is foo');
+  testClassAndInstance('none', obj1, function(key, value) { return key.length > 3; }, true, 'Object.none | key length is greater than 3');
+  testClassAndInstance('none', obj1, function(key, value) { return key.length > 0; }, false, 'Object.none | key length is greater than 0');
+  testClassAndInstance('none', obj1, function(key, value) { return value > 0; }, false, 'Object.none | value is greater than 0');
+  testClassAndInstance('none', obj1, function(key, value) { return value > 5; }, false, 'Object.none | value is greater than 5');
+  testClassAndInstance('none', obj1, function(key, value) { return value > 6; }, true, 'Object.none | value is greater than 6');
+  testClassAndInstance('none', obj1, 5, false,  'Object.none | shortcut | 5');
+  testClassAndInstance('none', obj1, 7, true, 'Object.none | shortcut | 7');
+
+  testClassAndInstance('count', obj1, function(key, value) { return key == 'foo'; }, 1, 'Object.count | key is foo');
+  testClassAndInstance('count', obj1, function(key, value) { return key.length > 3; }, 0, 'Object.count | key length is greater than 3');
+  testClassAndInstance('count', obj1, function(key, value) { return key.length > 0; }, 4, 'Object.count | key length is greater than 0');
+  testClassAndInstance('count', obj1, function(key, value) { return value > 0; }, 4, 'Object.count | value is greater than 0');
+  testClassAndInstance('count', obj1, function(key, value) { return value > 5; }, 1, 'Object.count | value is greater than 5');
+  testClassAndInstance('count', obj1, function(key, value) { return value > 6; }, 0, 'Object.count | value is greater than 6');
+  testClassAndInstance('count', obj1, 5, 1,  'Object.count | shortcut | 5');
+  testClassAndInstance('count', obj1, 7, 0, 'Object.count | shortcut | 7');
+
+  testClassAndInstance('sum', obj1, [], 18, 'Object.sum | no args is sum of values');
+  testClassAndInstance('sum', obj1, function(key, value) { return value; }, 18, 'Object.sum | key is foo');
+  testClassAndInstance('sum', obj1, function(key, value) { return key === 'foo' ? value : 0; }, 3, 'Object.sum | key is foo');
+  testClassAndInstance('sum', obj2, 'age', 110, 'Object.sum | accepts a string shortcut');
+
+  testClassAndInstance('average', obj1, [], 4.5, 'Object.average | no args is average of values');
+  testClassAndInstance('average', obj1, function(key, value) { return value; }, 4.5, 'Object.average | key is foo');
+  testClassAndInstance('average', obj1, function(key, value) { return key === 'foo' ? value : 0; }, .75, 'Object.average | key is foo');
+  testClassAndInstance('average', obj2, 'age', 27.5, 'Object.average | accepts a string shortcut');
+
+  testClassAndInstance('find', obj1, function(key, value) { return key == 'foo'; }, 'foo', 'Object.find | key is foo');
+  testClassAndInstance('find', obj1, function(key, value) { return key.length > 3; }, undefined, 'Object.find | key length is greater than 3');
+  testClassAndInstance('find', obj1, function(key, value) { return key.length > 0; }, 'foo', 'Object.find | key length is greater than 0');
+  testClassAndInstance('find', obj1, function(key, value) { return value > 0; }, 'foo', 'Object.find | value is greater than 0');
+  testClassAndInstance('find', obj1, function(key, value) { return value > 5; }, 'car', 'Object.find | value is greater than 5');
+  testClassAndInstance('find', obj1, function(key, value) { return value > 6; }, undefined, 'Object.find | value is greater than 6');
+  testClassAndInstance('find', obj1, 5, 'moo',  'Object.find | shortcut | 5');
+  testClassAndInstance('find', obj1, 7, undefined, 'Object.find | shortcut | 7');
+  testClassAndInstance('find', {foo:'bar'}, /b/, 'foo', 'Object.find | uses multi-match');
+
+  testClassAndInstance('findAll', obj1, function(key, value) { return key == 'foo'; }, {foo:3}, 'Object.findAll | key is foo');
+  testClassAndInstance('findAll', obj1, function(key, value) { return key.length > 3; }, {}, 'Object.findAll | key length is greater than 3');
+  testClassAndInstance('findAll', obj1, function(key, value) { return key.length > 0; }, obj1, 'Object.findAll | key length is greater than 0');
+  testClassAndInstance('findAll', obj1, function(key, value) { return value > 0; }, obj1, 'Object.findAll | value is greater than 0');
+  testClassAndInstance('findAll', obj1, function(key, value) { return value > 5; }, {car:6}, 'Object.findAll | value is greater than 5');
+  testClassAndInstance('findAll', obj1, function(key, value) { return value > 6; }, {}, 'Object.findAll | value is greater than 6');
+  testClassAndInstance('findAll', obj1, 5, {moo:5},  'Object.findAll | shortcut | 5');
+  testClassAndInstance('findAll', obj1, 7, {}, 'Object.findAll | shortcut | 7');
+  testClassAndInstance('findAll', {foo:'bar',moo:'car'}, /a/, {foo:'bar',moo:'car'}, 'Object.findAll | uses multi-match');
+
+  var obj3 = testCloneObject(obj1); obj3['blue'] = 4;
+  var obj4 = testCloneObject(obj2); obj4['blue'] = {age:11};
+
+  testClassAndInstance('min', obj3, [], 'foo', 'Object.min | no args is min of values');
+  testClassAndInstance('min', obj3, function(key, value) { return value; }, 'foo', 'Object.min | return value');
+  testClassAndInstance('min', obj3, function(key, value) { return key.length; }, 'foo', 'Object.min | return key.length');
+  testClassAndInstance('min', obj3, [function(key, value) { return key.length; }, true], {foo:3,bar:4,moo:5,car:6}, 'Object.min | return key.length');
+  testClassAndInstance('min', obj3, [function(key, value) { return key.charCodeAt(0); }, true], {bar: 4,blue:4}, 'Object.min | all | return the char code of first letter');
+  testClassAndInstance('min', obj4, 'age', 'foo', 'Object.min | accepts a string shortcut');
+  testClassAndInstance('min', obj4, ['age', true], {foo: {age:11},blue:{age:11}}, 'Object.min | all | accepts a string shortcut');
+
+
+  testClassAndInstance('max', obj3, [], 'car', 'Object.max | no args is first object');
+  testClassAndInstance('max', obj3, [function(key, value) { return value; }], 'car', 'Object.max | return value');
+  testClassAndInstance('max', obj3, [function(key, value) { return key.length; }], 'blue', 'Object.max | return key.length');
+  testClassAndInstance('max', obj3, [function(key, value) { return key.charCodeAt(0); }], 'moo', 'Object.max | return the char code of first letter');
+  testClassAndInstance('max', obj4, ['age'], 'car', 'Object.max | accepts a string shortcut');
+
+
+  testClassAndInstance('max', obj3, [function(key, value) { return value; }, true], {car:6}, 'Object.max | all | return value');
+  testClassAndInstance('max', obj3, [function(key, value) { return key.length; }, true], {blue:4}, 'Object.max | all | return key.length');
+  testClassAndInstance('max', obj3, [function(key, value) { return key.charCodeAt(0); }, true], {moo:5}, 'Object.max | all | return the char code of first letter');
+  testClassAndInstance('max', obj4, ['age', true], {car:{age:44}}, 'Object.max | all | accepts a string shortcut');
+
+  testClassAndInstance('least', obj3, [], 'foo', 'Object.least | no args is least of values');
+  testClassAndInstance('least', obj3, function(key, value) { return value; }, 'foo', 'Object.least | return value');
+  testClassAndInstance('least', obj3, function(key, value) { return key.length; }, 'blue', 'Object.least | return key.length');
+  testClassAndInstance('least', obj4, 'age', 'bar', 'Object.least | accepts a string shortcut');
+
+  testClassAndInstance('least', obj3, [function(key, value) { return value; }, true], {foo:3,moo:5,car:6}, 'Object.least | all | return value');
+  testClassAndInstance('least', obj3, [function(key, value) { return key.length; }, true], {blue:4}, 'Object.least | all | return key.length');
+  testClassAndInstance('least', obj4, ['age', true], {bar: {age:22},moo:{age:33},car:{age:44}}, 'Object.least | all | accepts a string shortcut');
+
+  testClassAndInstance('most', obj3, [], 'bar', 'Object.most | no args is most of values');
+  testClassAndInstance('most', obj3, function(key, value) { return value; }, 'bar', 'Object.most | return value');
+  testClassAndInstance('most', obj3, function(key, value) { return key.length; }, 'foo', 'Object.most | return key.length');
+  testClassAndInstance('most', obj3, function(key, value) { return key.charCodeAt(0); }, 'bar', 'Object.most | return the char code of first letter');
+  testClassAndInstance('most', obj4, 'age', 'foo', 'Object.most | accepts a string shortcut');
+
+  testClassAndInstance('most', obj3, [function(key, value) { return value; }, true], {bar: 4,blue:4}, 'Object.most | all | return value');
+  testClassAndInstance('most', obj3, [function(key, value) { return key.length; }, true], {foo:3,bar:4,moo:5,car:6}, 'Object.most | all | return key.length');
+  testClassAndInstance('most', obj3, [function(key, value) { return key.charCodeAt(0); }, true], {bar: 4,blue:4}, 'Object.most | all | return the char code of first letter');
+  testClassAndInstance('most', obj4, ['age', true], {foo: {age:11},blue:{age:11}}, 'Object.most | all | accepts a string shortcut');
+
+  testClassAndInstance('reduce', obj1, [function(acc, b) { return acc + b; }], 18, 'Object.reduce | obj1 | default');
+  testClassAndInstance('reduce', obj1, [function(acc, b) { return acc + b; }, 10], 28, 'Object.reduce | obj1 | with initial');
+  testClassAndInstance('reduce', obj1, [function(acc, b) { return acc - b; }], -12, 'Object.reduce | obj1 | a - b');
+  testClassAndInstance('reduce', obj1, [function(acc, b) { return acc - b; }, 10], -8, 'Object.reduce | obj1 | a - b with initial');
+  testClassAndInstance('reduce', obj1, [function(acc, b) { return acc * b; }, 0], 0, 'Object.reduce | obj1 | a * b with 0 initial is 0');
+
+  testClassAndInstance('reduce', obj2, [function(acc, b) { return (acc.age ? acc.age : acc) + b.age; }], 110, 'Object.reduce | obj2 | a + b');
+  testClassAndInstance('reduce', obj2, [function(acc, b) { return acc - b.age; }, 10], -100, 'Object.reduce | obj2 | a - b with initial');
+
+
+  // Object.isEmpty
+
+  testClassAndInstance('isEmpty', {}, [], true, 'Object.isEmpty | object is empty');
+  testClassAndInstance('isEmpty', { broken: 'wear' }, [], false, 'Object.isEmpty | object is not empty');
+  testClassAndInstance('isEmpty', { length: 0 }, [], false, 'Object.isEmpty | simple object with length property is not empty');
+  testClassAndInstance('isEmpty', { foo: null }, [], false, 'Object.isEmpty | null is still counted');
+  testClassAndInstance('isEmpty', { foo: undefined }, [], false, 'Object.isEmpty | undefined is still counted');
+  testClassAndInstance('isEmpty', { foo: NaN }, [], false, 'Object.isEmpty | undefined is still counted');
+  testClassAndInstance('isEmpty', [], [], true, 'Object.isEmpty | empty array is empty');
+  testClassAndInstance('isEmpty', null, [], true, 'Object.isEmpty | null is empty');
+  testClassAndInstance('isEmpty', undefined, [], true, 'Object.isEmpty | undefined is empty');
+  testClassAndInstance('isEmpty', '', [], true, 'Object.isEmpty | empty string is empty');
+  testClassAndInstance('isEmpty', new String(''), [], true, 'Object.isEmpty | empty string object is empty');
+  testClassAndInstance('isEmpty', 'wasabi', [], false, 'Object.isEmpty | non-empty string is not empty');
+  testClassAndInstance('isEmpty', new String('wasabi'), [], false, 'Object.isEmpty | non-empty string object is not empty');
+  testClassAndInstance('isEmpty', NaN, [], true, 'Object.isEmpty | NaN is empty');
+  testClassAndInstance('isEmpty', 8, [], true, 'Object.isEmpty | 8 is empty');
+  testClassAndInstance('isEmpty', new Number(8), [], true, 'Object.isEmpty | 8 object is empty');
+
+
+  // Object.size
+
+  testClassAndInstance('size', {}, [], 0, 'Object.size | empty object');
+  testClassAndInstance('size', {foo:'bar'}, [], 1, 'Object.size | 1 property');
+  testClassAndInstance('size', {foo:'bar',moo:'car'}, [], 2, 'Object.size | 2 properties');
+  testClassAndInstance('size', {foo:1}, [], 1, 'Object.size | numbers');
+  testClassAndInstance('size', {foo:/bar/}, [], 1, 'Object.size | regexes');
+  testClassAndInstance('size', {foo:function(){}}, [], 1, 'Object.size | functions');
+  testClassAndInstance('size', {foo:{bar:'car'}}, [], 1, 'Object.size | nested object');
+  testClassAndInstance('size', {foo:[1]}, [], 1, 'Object.size | nested array');
+  testClassAndInstance('size', ['a'], [], 1, 'Object.size | array');
+  testClassAndInstance('size', ['a','b'], [], 2, 'Object.size | array 2 elements');
+  testClassAndInstance('size', ['a','b','c'], [], 3, 'Object.size | array 3 elements');
+  testClassAndInstance('size', 'foo', [], 3, 'Object.size | string primitive');
+  testClassAndInstance('size', new String('foo'), [], 3, 'Object.size | string object');
+  testClassAndInstance('size', 1, [], 0, 'Object.size | number primitive');
+  testClassAndInstance('size', new Number(1), [], 0, 'Object.size | number object');
+  testClassAndInstance('size', true, [], 0, 'Object.size | boolean primitive');
+  testClassAndInstance('size', new Boolean(true), [], 0, 'Object.size | boolean object');
+  testClassAndInstance('size', null, [], 0, 'Object.size | null');
+  testClassAndInstance('size', undefined, [], 0, 'Object.size | undefined');
+
+  var Foo = function(){};
+  testClassAndInstance('size', new Foo, [], 0, 'Object.size | class instances');
+
+  var Foo = function(a){ this.a = a; };
+  testClassAndInstance('size', new Foo, [], 1, 'Object.size | class instances with a single property');
+
+
+
+  // Fuzzy matching behavior on functions.
+
+  var fn = function(){ count ++; };
+  count = 0;
+
+  [1,2,3].findAll(fn);
+  equal(count, 3, 'Array#findAll | functions treated as callbacks when matching against non-functions');
+
+  count = 0;
+  [function() {}, function() {}, function() {}].findAll(fn);
+  equal(count, 3, 'Array#findAll | functions are not directly matched');
+
+
+  if(Object.equal) {
+    var fn1 = function() {};
+    var fn2 = function() {};
+    equal([fn1, fn1, fn1].all(function(el) { return Object.equal(el, fn1); }), true, 'Array#all | functions can be matched inside the callback');
+    equal([fn1, fn1, fn2].all(function(el) { return Object.equal(el, fn1); }), false, 'Array#all | functions can be matched inside the callback');
+    equal([fn1, fn1, fn2].any(function(el) { return Object.equal(el, fn1); }), true, 'Array#any | functions can be matched inside the callback');
+    equal([fn1, fn2, fn1].findAll(function(el) { return Object.equal(el, fn1); }), [fn1, fn1], 'Array#findAll | functions can be matched inside the callback');
+    equal([fn1, fn2, fn1].findAll(function(el) { return Object.equal(el, fn2); }), [fn2], 'Array#findAll | fn2 | functions can be matched inside the callback');
+  }
+
+  // Object.each
+
+  var fn = function () {};
+  var obj = {
+    number: 3,
+    person: 'jim',
+    date: d,
+    func: fn
+  };
+
+  var keys = ['number','person','date','func'];
+  var values = [3, 'jim', d, fn];
+  var count = 0;
+
+  count = 0;
+  result = Object.each(obj, function(key, value, o) {
+    equal(key, keys[count], 'Object.each | accepts a block', { mootools: values[count] });
+    equal(value, values[count], 'Object.each | accepts a block', { mootools: keys[count] });
+    equal(o, obj, 'Object.each | accepts a block | object is third param');
+    count++;
+  });
+  equal(count, 4, 'Object.each | accepts a block | iterated properly');
+  equal(result, obj, 'Object.each | accepts a block | result should equal object passed in', { mootools: undefined });
+
+  raisesError(function(){
+    Object.each({foo:'bar'});
+  }, 'Object.each | no iterator raises an error');
+
+  testClassAndInstance('each', obj, [function () {}], obj, 'Object.size | each returns itself');
+
+  // Issue #273 - exposing collateString
+
+  var arr = ['c','b','a','ร ','รฅ','รค','รถ'];
+
+  var viaSort   = arr.sort(Array.AlphanumericSort);
+  var viaSortBy = arr.sortBy();
+
+  equal(viaSort, viaSortBy, 'Array.SugarCollateStrings | should be exposed to allow sorting via native Array#sort');
+
+
+
+  // Array#findFrom
+
+  equal(['foo','bar'].findFrom(/^[a-f]/, 1), 'bar', 'Array#findFrom | /a-f/ from index 1', { prototype: undefined });
+  equal(['foo','bar','zak'].findFrom(/^[a-f]/, 2, true), 'foo', 'Array#findFrom | /a-f/ from index 1 looping', { prototype: undefined });
+
+  equal([1,2,3].findFrom(function(e) { return e > 0; }, 0), 1, 'Array#findFrom | greater than 0 from index 0');
+  equal([1,2,3].findFrom(function(e) { return e > 0; }, 1), 2, 'Array#findFrom | greater than 0 from index 1', { prototype: 1 });
+  equal([1,2,3].findFrom(function(e) { return e > 0; }, 2), 3, 'Array#findFrom | greater than 0 from index 2', { prototype: 1 });
+  equal([1,2,3].findFrom(function(e) { return e > 0; }, 3), undefined, 'Array#findFrom | greater than 0 from index 3', { prototype: 1 });
+  equal([1,2,3].findFrom(function(e) { return e > 1; }, 0), 2, 'Array#findFrom | greater than 1 from index 0');
+  equal([1,2,3].findFrom(function(e) { return e > 1; }, 1), 2, 'Array#findFrom | greater than 1 from index 1');
+  equal([1,2,3].findFrom(function(e) { return e > 1; }, 2), 3, 'Array#findFrom | greater than 1 from index 2', { prototype: 2 });
+  equal([1,2,3].findFrom(function(e) { return e > 2; }, 0), 3, 'Array#findFrom | greater than 2 from index 0');
+  equal([1,2,3].findFrom(function(e) { return e > 3; }, 0), undefined, 'Array#findFrom | greater than 3 from index 0');
+
+  equal([1,2,3].findFrom(function(e) { return e > 0; }, 0, true), 1, 'Array#findFrom | loop | greater than 0 from index 0');
+  equal([1,2,3].findFrom(function(e) { return e > 0; }, 1, true), 2, 'Array#findFrom | loop | greater than 0 from index 1', { prototype: 1 });
+  equal([1,2,3].findFrom(function(e) { return e > 0; }, 2, true), 3, 'Array#findFrom | loop | greater than 0 from index 2', { prototype: 1 });
+  equal([1,2,3].findFrom(function(e) { return e > 0; }, 3, true), 1, 'Array#findFrom | loop | greater than 0 from index 3', { prototype: 1 });
+  equal([1,2,3].findFrom(function(e) { return e > 1; }, 0, true), 2, 'Array#findFrom | loop | greater than 1 from index 0');
+  equal([1,2,3].findFrom(function(e) { return e > 1; }, 1, true), 2, 'Array#findFrom | loop | greater than 1 from index 1');
+  equal([1,2,3].findFrom(function(e) { return e > 1; }, 2, true), 3, 'Array#findFrom | loop | greater than 1 from index 2', { prototype: 2 });
+  equal([1,2,3].findFrom(function(e) { return e > 2; }, 0, true), 3, 'Array#findFrom | loop | greater than 2 from index 0');
+  equal([1,2,3].findFrom(function(e) { return e > 3; }, 0, true), undefined, 'Array#findFrom | loop | greater than 3 from index 0');
+
+  equal([{a:10},{a:8},{a:3}].findFrom(function(e) { return e['a'] > 5; }, 0), {a:10}, 'Array#findFrom | key "a" greater than 5');
+  equal([{a:10},{a:8},{a:3}].findFrom(function(e) { return e['a'] > 5; }, 1), {a:8}, 'Array#findFrom | key "a" greater than 5 from index 1', { prototype: {a:10} });
+  equal([{a:10},{a:8},{a:3}].findFrom(function(e) { return e['a'] > 5; }, 2), undefined, 'Array#findFrom | key "a" greater than 5 from index 2', { prototype: {a:10} });
+  equal([function() {}].findFrom(function(e) {}, 1), undefined, 'Array#findFrom | null function from index 1');
+  equal([null, null].findFrom(null, 1), null, 'Array#findFrom | null from index 1');
+  equal([undefined, undefined].findFrom(undefined, 1), undefined, 'Array#findFrom | undefined from index 1');
+
+
+  // Array#findIndexFrom
+
+  equal(['a','b','c','b'].findIndexFrom('b', 2), 3, 'Array#findIndexFrom | finds first instance from index');
+  equal([5,2,4,4].findIndexFrom(4, 3), 3, 'Array#findIndexFrom | 4 in 5,2,4,4 from index 3');
+  equal([5,2,4,4].findIndexFrom(4, 10), -1, 'Array#findIndexFrom | 4 in 5,2,4,4 from index 10');
+  equal([5,2,4,4].findIndexFrom(4, -10), 2, 'Array#findIndexFrom | 4 in 5,2,4,4 from index -10');
+  equal([5,2,4,4].findIndexFrom(4, -1), 3, 'Array#findIndexFrom | 4 in 5,2,4,4 from index -1');
+
+  equal(['a','b','c','b'].findIndexFrom('b', 1, true), 1, 'Array#findIndexFrom | finds first instance from index');
+  equal([5,2,4,4,7,0].findIndexFrom(4, 4, true), 2, 'Array#findIndexFrom | 4 in 5,2,4,4 from index 3');
+  equal([5,2,4,4,7,0].findIndexFrom(4, 10, true), 2, 'Array#findIndexFrom | 4 in 5,2,4,4 from index 10');
+  equal([5,2,4,4,7,0].findIndexFrom(8, 10, true), -1, 'Array#findIndexFrom | 8 in 5,2,4,4 from index 10');
+  equal([5,2,4,4,7,0].findIndexFrom(4, -10, true), 2, 'Array#findIndexFrom | 4 in 5,2,4,4 from index -10');
+  equal([5,2,4,4,7,0].findIndexFrom(4, -1, true), 2, 'Array#findIndexFrom | 4 in 5,2,4,4 from index -1');
+
+
+
+});
+
level2/node_modules/sugar/test/environments/sugar/date.js
@@ -0,0 +1,2768 @@
+test('Date', function () {
+
+  equal(Date.getLocale().code !== undefined, true, 'Current locale must be something... other libs may overwrite this');
+
+  // Imaginary local to test locale switching
+  Date.addLocale('fo', {
+    units: 'do,re,mi,fa,so,la,ti,do',
+    months: 'do,re,mi,fa,so,la,ti,do',
+    dateParse: '{year}kupo',
+    duration: '{num}{unit}momoney',
+    long: 'yeehaw'
+  });
+
+  Date.setLocale('en');
+
+  // Mootools over-stepping itself here with the "create" method implemented as a Function instance method,
+  // which interferes with class methods as classes themselves are functions. Taking back this class method
+  // for the sake of the tests.
+  if(typeof Date.create() === 'function') {
+    Date.restore('create');
+  };
+
+  var day, d, date1, date2, dst, o;
+  var isCurrentlyGMT = new Date(2011, 0, 1).getTimezoneOffset() === 0;
+  var now = new Date();
+  var thisYear = now.getFullYear();
+
+  // Invalid date
+  equal(new Date('a fridge too far').isValid(), false, 'Date#isValid | new Date invalid');
+  equal(new Date().isValid(), true, 'Date#isValid | new Date valid');
+
+  equal(Date.create().isValid(), true, 'Date#isValid | created date is valid');
+  equal(Date.create('a fridge too far').isValid(), false, 'Date#isValid | Date#create invalid');
+
+
+  d = new Date(1998, 0);
+
+  equal(d.isUTC(), d.getTimezoneOffset() === 0, 'Date#isUTC | UTC is true if the current timezone has no offset');
+
+  // UTC is still false even if the time is reset to the intended utc equivalent, as timezones can never be changed
+  equal(d.clone().addMinutes(d.getTimezoneOffset()).isUTC(), d.getTimezoneOffset() === 0, 'Date#isUTC | UTC cannot be forced');
+
+  dateEqual(Date.create(), new Date(), 'Date#create | empty');
+
+
+
+  // Date constructor accepts enumerated parameters
+
+  dateEqual(Date.create(1998), new Date(1998), 'Date#create | enumerated | 1998');
+  dateEqual(Date.create(1998,1), new Date(1998,1), 'Date#create | enumerated | January, 1998');
+  dateEqual(Date.create(1998,1,23), new Date(1998,1,23), 'Date#create | enumerated | January 23, 1998');
+  dateEqual(Date.create(1998,1,23,11), new Date(1998,1,23,11), 'Date#create | enumerated | January 23, 1998 11am');
+  dateEqual(Date.create(1998,1,23,11,54), new Date(1998,1,23,11,54), 'Date#create | enumerated | January 23, 1998 11:54am');
+  dateEqual(Date.create(1998,1,23,11,54,32), new Date(1998,1,23,11,54,32), 'Date#create | enumerated | January 23, 1998 11:54:32');
+  dateEqual(Date.create(1998,1,23,11,54,32,454), new Date(1998,1,23,11,54,32,454), 'Date#create | enumerated | January 23, 1998 11:54:32.454');
+
+  dateEqual(Date.create('1998', true), new Date(1998, 0), 'Date#create | will not choke on a boolean as second param');
+  dateEqual(Date.create('1998', ''), new Date(1998, 0), 'Date#create | will not choke on an empty string as second param');
+
+
+  // Date constructor accepts an object
+
+  dateEqual(Date.create({ year: 1998 }), new Date(1998, 0), 'Date#create | object | 1998');
+  dateEqual(Date.create({ year: 1998, month: 1 }), new Date(1998,1), 'Date#create | object | January, 1998');
+  dateEqual(Date.create({ year: 1998, month: 1, day: 23 }), new Date(1998,1,23), 'Date#create | object | January 23, 1998');
+  dateEqual(Date.create({ year: 1998, month: 1, day: 23, hour: 11 }), new Date(1998,1,23,11), 'Date#create | object | January 23, 1998 11am');
+  dateEqual(Date.create({ year: 1998, month: 1, day: 23, hour: 11, minutes: 54 }), new Date(1998,1,23,11,54), 'Date#create | object | January 23, 1998 11:54am');
+  dateEqual(Date.create({ year: 1998, month: 1, day: 23, hour: 11, minutes: 54, seconds: 32 }), new Date(1998,1,23,11,54,32), 'Date#create | object | January 23, 1998 11:54:32');
+  dateEqual(Date.create({ year: 1998, month: 1, day: 23, hour: 11, minutes: 54, seconds: 32, milliseconds: 454 }), new Date(1998,1,23,11,54,32,454), 'Date#create | object | January 23, 1998 11:54:32.454');
+
+
+  dateEqual(new Date(new Date(2008, 6, 22)), new Date(2008, 6, 22), 'Date | date accepts itself as a constructor');
+
+
+  var timestamp = 1294012800000;
+  d = Date.create(timestamp); // 2011-01-03 00:00:00 
+
+  equal(d.getFullYear(), 2011, 'Date#create | Accepts numbers | 2011')
+  equal(d.getMonth(), 0, 'Date#create | Accepts numbers | January');
+  equal(d.getDate(), Math.floor(3 - (d.getTimezoneOffset() / 60 / 24)), 'Date#create | Accepts numbers | January');
+
+  equal(d.is(timestamp), true, 'Date#is | Accepts numbers');
+
+
+  dateEqual(Date.create('1999'), new Date(1999, 0), 'Date#create | Just the year');
+
+  dateEqual(Date.create('June'), new Date(thisYear, 5), 'Date#create | Just the month');
+  dateEqual(Date.create('June 15'), new Date(thisYear, 5, 15), 'Date#create | Month and day');
+  dateEqual(Date.create('June 15th'), new Date(thisYear, 5, 15), 'Date#create | Month and ordinal day');
+
+  // Slashes (American style)
+  dateEqual(Date.create('08/25'), new Date(thisYear, 7, 25), 'Date#create | American style slashes | mm/dd');
+  dateEqual(Date.create('8/25'), new Date(thisYear, 7, 25), 'Date#create | American style slashes | m/dd');
+  dateEqual(Date.create('08/25/1978'), new Date(1978, 7, 25), 'Date#create | American style slashes | mm/dd/yyyy');
+  dateEqual(Date.create('8/25/1978'), new Date(1978, 7, 25), 'Date#create | American style slashes | /m/dd/yyyy');
+  dateEqual(Date.create('8/25/78'), new Date(1978, 7, 25), 'Date#create | American style slashes | m/dd/yy');
+  dateEqual(Date.create('08/25/78'), new Date(1978, 7, 25), 'Date#create | American style slashes | mm/dd/yy');
+  dateEqual(Date.create('8/25/01'), new Date(2001, 7, 25), 'Date#create | American style slashes | m/dd/01');
+  dateEqual(Date.create('8/25/49'), new Date(2049, 7, 25), 'Date#create | American style slashes | m/dd/49');
+  dateEqual(Date.create('8/25/50'), new Date(1950, 7, 25), 'Date#create | American style slashes | m/dd/50');
+
+
+  // August 25, 0001... the numeral 1 gets interpreted as 1901...
+  // freakin' unbelievable...
+  dateEqual(toUTC(Date.create('08/25/0001')), new Date(-62115206400000), 'Date#create | American style slashes | mm/dd/0001');
+
+  // Dashes (American style)
+  dateEqual(Date.create('08-25-1978'), new Date(1978, 7, 25), 'Date#create | American style dashes | mm-dd-yyyy');
+  dateEqual(Date.create('8-25-1978'), new Date(1978, 7, 25), 'Date#create | American style dashes | m-dd-yyyy');
+
+
+  // dd-dd-dd is NOT a valid ISO 8601 representation as of 2004, hence this format will
+  // revert to a little endian representation, where year truncation is allowed. See:
+  // http://en.wikipedia.org/wiki/ISO_8601#Truncated_representations
+  dateEqual(Date.create('08-05-05'), new Date(2005, 7, 5), 'Date#create | dd-dd-dd is an ISO8601 format');
+
+  // Dots (American style)
+  dateEqual(Date.create('08.25.1978'), new Date(1978, 7, 25), 'Date#create | American style dots | mm.dd.yyyy');
+  dateEqual(Date.create('8.25.1978'), new Date(1978, 7, 25), 'Date#create | American style dots | m.dd.yyyy');
+
+
+
+
+
+
+  // Abbreviated reverse slash format yy/mm/dd cannot exist because it clashes with forward
+  // slash format dd/mm/yy (with european variant). This rule however, doesn't follow for dashes,
+  // which is abbreviated ISO8601 format: yy-mm-dd
+  dateEqual(Date.create('01/02/03'), new Date(2003, 0, 2), 'Date#create | Ambiguous 2 digit format mm/dd/yy');
+
+
+  dateEqual(Date.create('08/10', 'en-GB'), new Date(thisYear, 9, 8), 'Date#create | European style slashes | dd/mm');
+  dateEqual(Date.create('8/10', 'en-GB'), new Date(thisYear, 9, 8), 'Date#create | European style slashes | d/mm');
+  dateEqual(Date.create('08/10/1978', 'en-GB'), new Date(1978, 9, 8), 'Date#create | European style slashes | dd/mm/yyyy');
+  dateEqual(Date.create('8/10/1978', 'en-GB'), new Date(1978, 9, 8), 'Date#create | European style slashes | d/mm/yyyy');
+  dateEqual(Date.create('8/10/78', 'en-GB'), new Date(1978, 9, 8), 'Date#create | European style slashes | d/mm/yy');
+  dateEqual(Date.create('08/10/78', 'en-GB'), new Date(1978, 9, 8), 'Date#create | European style slashes | dd/mm/yy');
+  dateEqual(Date.create('8/10/01', 'en-GB'), new Date(2001, 9, 8), 'Date#create | European style slashes | d/mm/01');
+  dateEqual(Date.create('8/10/49', 'en-GB'), new Date(2049, 9, 8), 'Date#create | European style slashes | d/mm/49');
+  dateEqual(Date.create('8/10/50', 'en-GB'), new Date(1950, 9, 8), 'Date#create | European style slashes | d/mm/50');
+
+  dateEqual(Date.create('08/10', 'en-AU'), new Date(thisYear, 9, 8), 'Date#create | European style slashes | any English locale suffix should work and not use US format');
+
+  // Dashes (European style)
+  dateEqual(Date.create('08-10-1978', 'en-GB'), new Date(1978, 9, 8), 'Date#create | European style dashes | mm-dd-yyyy');
+
+  // Dots (European style)
+  dateEqual(Date.create('08.10.1978', 'en-GB'), new Date(1978, 9, 8), 'Date#create | European style dots | dd.mm.yyyy');
+  dateEqual(Date.create('8.10.1978', 'en-GB'), new Date(1978, 9, 8), 'Date#create | European style dots | d.mm.yyyy');
+  dateEqual(Date.create('08-05-05', 'en-GB'), new Date(2005, 4, 8), 'Date#create | dd-dd-dd is NOT an ISO8601 format');
+
+  dateEqual(Date.create('8/10/85'), new Date(1985, 7, 10), 'Date#create | American format will now revert back');
+
+
+  Date.setLocale('en-GB');
+  dateEqual(Date.create('8/10/85'), new Date(1985, 9, 8), 'Date#create | European style slashes | after global set');
+  Date.setLocale('en');
+  dateEqual(Date.create('8/10/85'), new Date(1985, 7, 10), 'Date#create | European style slashes | before global reset');
+
+
+  // Stolen with love from XDate, ability to parse IETF format
+  dateEqual(Date.create('Mon Sep 05 2011 12:30:00 GMT-0700 (PDT)'), getUTCDate(2011,9,5,19,30), 'Date#create | IETF format');
+
+
+  /*
+   * Reverse slashes
+   * This seems like it should be invalid. Looking at the link below it seems that there isn't a format
+   * that uses slashes in combination with the "big endian" format. If this changes or there is a valid
+   * use case here then we can rethink...
+   *
+   * http://en.wikipedia.org/wiki/Calendar_date
+   *
+   */
+  //dateEqual(Date.create('1978/08/25'), new Date(1978, 7, 25), 'Date#create | Reverse slashes | yyyy/mm/dd');
+  //dateEqual(Date.create('1978/8/25'), new Date(1978, 7, 25), 'Date#create | Reverse slashes | yyyy/m/dd');
+  //dateEqual(Date.create('1978/08'), new Date(1978, 7), 'Date#create | Reverse slashes | yyyy/mm');
+  //dateEqual(Date.create('1978/8'), new Date(1978, 7), 'Date#create | Reverse slashes | yyyy/m');
+
+  // Reverse dashes
+  dateEqual(Date.create('1978-08-25'), new Date(1978, 7, 25), 'Date#create | Reverse dashes | yyyy-mm-dd');
+  dateEqual(Date.create('1978-08'), new Date(1978, 7), 'Date#create | Reverse dashes | yyyy-mm');
+  dateEqual(Date.create('1978-8'), new Date(1978, 7), 'Date#create | Reverse dashes | yyyy-m');
+
+  // Reverse dots
+  dateEqual(Date.create('1978.08.25'), new Date(1978, 7, 25), 'Date#create | Reverse dots | yyyy.mm.dd');
+  dateEqual(Date.create('1978.08'), new Date(1978, 7), 'Date#create | Reverse dots | yyyy.mm');
+  dateEqual(Date.create('1978.8'), new Date(1978, 7), 'Date#create | Reverse dots | yyyy.m');
+  dateEqual(Date.create('01-02-03', 'en-GB'), new Date(2003, 1, 1), 'Date#create | Ambiguous 2 digit variant yy-mm-dd is NOT ISO 8601');
+  dateEqual(Date.create('01/02/03', 'en-GB'), new Date(2003, 1, 1), 'Date#create | Ambiguous 2 digit European variant dd/mm/yy');
+
+
+  // Text based formats
+  dateEqual(Date.create('June 2008'), new Date(2008, 5), 'Date#create | Full text | Month yyyy');
+  dateEqual(Date.create('June-2008'), new Date(2008, 5), 'Date#create | Full text | Month-yyyy');
+  dateEqual(Date.create('June.2008'), new Date(2008, 5), 'Date#create | Full text | Month.yyyy');
+  dateEqual(Date.create('06-2008'), new Date(2008, 5), 'Date#create | Full text | mm-yyyy');
+  dateEqual(Date.create('6-2008'), new Date(2008, 5), 'Date#create | Full text | m-yyyy');
+  dateEqual(Date.create('June 1st, 2008'), new Date(2008, 5, 1), 'Date#create | Full text | Month 1st, yyyy');
+  dateEqual(Date.create('June 2nd, 2008'), new Date(2008, 5, 2), 'Date#create | Full text | Month 2nd, yyyy');
+  dateEqual(Date.create('June 3rd, 2008'), new Date(2008, 5, 3), 'Date#create | Full text | Month 3rd, yyyy');
+  dateEqual(Date.create('June 4th, 2008'), new Date(2008, 5, 4), 'Date#create | Full text | Month 4th, yyyy');
+  dateEqual(Date.create('June 15th, 2008'), new Date(2008, 5, 15), 'Date#create | Full text | Month 15th, yyyy');
+  dateEqual(Date.create('June 1st 2008'), new Date(2008, 5, 1), 'Date#create | Full text | Month 1st yyyy');
+  dateEqual(Date.create('June 2nd 2008'), new Date(2008, 5, 2), 'Date#create | Full text | Month 2nd yyyy');
+  dateEqual(Date.create('June 3rd 2008'), new Date(2008, 5, 3), 'Date#create | Full text | Month 3rd yyyy');
+  dateEqual(Date.create('June 4th 2008'), new Date(2008, 5, 4), 'Date#create | Full text | Month 4th yyyy');
+  dateEqual(Date.create('June 15, 2008'), new Date(2008, 5, 15), 'Date#create | Full text | Month dd, yyyy');
+  dateEqual(Date.create('June 15 2008'), new Date(2008, 5, 15), 'Date#create | Full text | Month dd yyyy');
+  dateEqual(Date.create('15 July, 2008'), new Date(2008, 6, 15), 'Date#create | Full text | dd Month, yyyy');
+  dateEqual(Date.create('15 July 2008'), new Date(2008, 6, 15), 'Date#create | Full text | dd Month yyyy');
+  dateEqual(Date.create('juNe 1St 2008'), new Date(2008, 5, 1), 'Date#create | Full text | Month 1st yyyy case insensitive');
+
+
+  // Special cases
+  dateEqual(Date.create(' July 4th, 1987 '), new Date(1987, 6, 4), 'Date#create | Special Cases | Untrimmed full text');
+  dateEqual(Date.create('  7/4/1987 '), new Date(1987, 6, 4), 'Date#create | Special Cases | Untrimmed American');
+  dateEqual(Date.create('   1987-07-04    '), new Date(1987, 6, 4), 'Date#create | Special Cases | Untrimmed ISO8601');
+
+  // Abbreviated formats
+  dateEqual(Date.create('Dec 1st, 2008'), new Date(2008, 11, 1), 'Date#create | Abbreviated | without dot');
+  dateEqual(Date.create('Dec. 1st, 2008'), new Date(2008, 11, 1), 'Date#create | Abbreviated | with dot');
+  dateEqual(Date.create('1 Dec. 2008'), new Date(2008, 11, 1), 'Date#create | Abbreviated | reversed with dot');
+  dateEqual(Date.create('1 Dec., 2008'), new Date(2008, 11, 1), 'Date#create | Abbreviated | reversed with dot and comma');
+  dateEqual(Date.create('1 Dec, 2008'), new Date(2008, 11, 1), 'Date#create | Abbreviated | reversed with comma and no dot');
+
+
+  // http://en.wikipedia.org/wiki/Calendar_date
+  dateEqual(Date.create('09-May-78'), new Date(1978, 4, 9), 'Date#create | Abbreviated | little endian yy');
+  dateEqual(Date.create('09-May-1978'), new Date(1978, 4, 9), 'Date#create | Abbreviated | little endian yyyy');
+  dateEqual(Date.create('1978-May-09'), new Date(1978, 4, 9), 'Date#create | Abbreviated | big endian');
+  dateEqual(Date.create('Wednesday July 3rd, 2008'), new Date(2008, 6, 3), 'Date#create | Full Text | With day of week');
+  dateEqual(Date.create('Wed July 3rd, 2008'), new Date(2008, 6, 3), 'Date#create | Full Text | With day of week abbreviated');
+  dateEqual(Date.create('Wed. July 3rd, 2008'), new Date(2008, 6, 3), 'Date#create | Full Text | With day of week abbreviated plus dot');
+  dateEqual(Date.create('Wed, 03 Jul 2008 08:00:00 EST'), new Date(Date.UTC(2008, 6, 3, 13)), 'Date#create | RFC822');
+
+
+
+
+  // ISO 8601
+  dateEqual(Date.create('2001-1-1'), new Date(2001, 0, 1), 'Date#create | ISO8601 | not padded');
+  dateEqual(Date.create('2001-01-1'), new Date(2001, 0, 1), 'Date#create | ISO8601 | month padded');
+  dateEqual(Date.create('2001-01-01'), new Date(2001, 0, 1), 'Date#create | ISO8601 | month and day padded');
+  dateEqual(Date.create('2010-11-22'), new Date(2010, 10,22), 'Date#create | ISO8601 | month and day padded 2010');
+  dateEqual(Date.create('20101122'), new Date(2010, 10,22), 'Date#create | ISO8601 | digits strung together');
+  dateEqual(Date.create('17760523T024508+0830'), getUTCDate(1776, 5, 22, 18, 15, 08), 'Date#create | ISO8601 | full datetime strung together');
+  dateEqual(Date.create('-0002-07-26'), new Date(-2, 6, 26), 'Date#create | ISO8601 | minus sign (bc)'); // BC
+  dateEqual(Date.create('+1978-04-17'), new Date(1978, 3, 17), 'Date#create | ISO8601 | plus sign (ad)'); // AD
+
+
+
+  // Date with time formats
+  dateEqual(Date.create('08/25/1978 12:04'), new Date(1978, 7, 25, 12, 4), 'Date#create | Date/Time | Slash format');
+  dateEqual(Date.create('08-25-1978 12:04'), new Date(1978, 7, 25, 12, 4), 'Date#create | Date/Time | Dash format');
+  dateEqual(Date.create('1978/08/25 12:04'), new Date(1978, 7, 25, 12, 4), 'Date#create | Date/Time | Reverse slash format');
+  dateEqual(Date.create('June 1st, 2008 12:04'), new Date(2008, 5, 1, 12, 4), 'Date#create | Date/Time | Full text format');
+
+
+  dateEqual(Date.create('08-25-1978 12:04:57'), new Date(1978, 7, 25, 12, 4, 57), 'Date#create | Date/Time | with seconds');
+  dateEqual(Date.create('08-25-1978 12:04:57.322'), new Date(1978, 7, 25, 12, 4, 57, 322), 'Date#create | Date/Time | with milliseconds');
+
+  dateEqual(Date.create('08-25-1978 12pm'), new Date(1978, 7, 25, 12), 'Date#create | Date/Time | with am/pm');
+  dateEqual(Date.create('08-25-1978 12:42pm'), new Date(1978, 7, 25, 12, 42), 'Date#create | Date/Time | with minutes and am/pm');
+  dateEqual(Date.create('08-25-1978 12:42:32pm'), new Date(1978, 7, 25, 12, 42, 32), 'Date#create | Date/Time | with seconds and am/pm');
+  dateEqual(Date.create('08-25-1978 12:42:32.488pm'), new Date(1978, 7, 25, 12, 42, 32, 488), 'Date#create | Date/Time | with seconds and am/pm');
+
+  dateEqual(Date.create('08-25-1978 00:00am'), new Date(1978, 7, 25, 0, 0, 0, 0), 'Date#create | Date/Time | with zero am');
+  dateEqual(Date.create('08-25-1978 00:00:00am'), new Date(1978, 7, 25, 0, 0, 0, 0), 'Date#create | Date/Time | with seconds and zero am');
+  dateEqual(Date.create('08-25-1978 00:00:00.000am'), new Date(1978, 7, 25, 0, 0, 0, 0), 'Date#create | Date/Time | with milliseconds and zero am');
+
+  dateEqual(Date.create('08-25-1978 1pm'), new Date(1978, 7, 25, 13), 'Date#create | Date/Time | 1pm am/pm');
+  dateEqual(Date.create('08-25-1978 1:42pm'), new Date(1978, 7, 25, 13, 42), 'Date#create | Date/Time | 1pm minutes and am/pm');
+  dateEqual(Date.create('08-25-1978 1:42:32pm'), new Date(1978, 7, 25, 13, 42, 32), 'Date#create | Date/Time | 1pm seconds and am/pm');
+  dateEqual(Date.create('08-25-1978 1:42:32.488pm'), new Date(1978, 7, 25, 13, 42, 32, 488), 'Date#create | Date/Time | 1pm seconds and am/pm');
+
+  dateEqual(Date.create('08-25-1978 1am'), new Date(1978, 7, 25, 1), 'Date#create | Date/Time | 1am am/pm');
+  dateEqual(Date.create('08-25-1978 1:42am'), new Date(1978, 7, 25, 1, 42), 'Date#create | Date/Time | 1am minutes and am/pm');
+  dateEqual(Date.create('08-25-1978 1:42:32am'), new Date(1978, 7, 25, 1, 42, 32), 'Date#create | Date/Time | 1am seconds and am/pm');
+  dateEqual(Date.create('08-25-1978 1:42:32.488am'), new Date(1978, 7, 25, 1, 42, 32, 488), 'Date#create | Date/Time | 1am seconds and am/pm');
+
+  dateEqual(Date.create('08-25-1978 11pm'), new Date(1978, 7, 25, 23), 'Date#create | Date/Time | 11pm am/pm');
+  dateEqual(Date.create('08-25-1978 11:42pm'), new Date(1978, 7, 25, 23, 42), 'Date#create | Date/Time | 11pm minutes and am/pm');
+  dateEqual(Date.create('08-25-1978 11:42:32pm'), new Date(1978, 7, 25, 23, 42, 32), 'Date#create | Date/Time | 11pm seconds and am/pm');
+  dateEqual(Date.create('08-25-1978 11:42:32.488pm'), new Date(1978, 7, 25, 23, 42, 32, 488), 'Date#create | Date/Time | 11pm seconds and am/pm');
+
+  dateEqual(Date.create('08-25-1978 11am'), new Date(1978, 7, 25, 11), 'Date#create | Date/Time | 11am am/pm');
+  dateEqual(Date.create('08-25-1978 11:42am'), new Date(1978, 7, 25, 11, 42), 'Date#create | Date/Time | 11am minutes and am/pm');
+  dateEqual(Date.create('08-25-1978 11:42:32am'), new Date(1978, 7, 25, 11, 42, 32), 'Date#create | Date/Time | 11am seconds and am/pm');
+  dateEqual(Date.create('08-25-1978 11:42:32.488am'), new Date(1978, 7, 25, 11, 42, 32, 488), 'Date#create | Date/Time | 11am seconds and am/pm');
+
+
+  dateEqual(Date.create('2010-11-22T22:59Z'), getUTCDate(2010,11,22,22,59), 'Date#create | ISO8601 | full with UTC timezone');
+  dateEqual(Date.create('1997-07-16T19:20+00:00'), getUTCDate(1997, 7, 16, 19, 20), 'Date#create | ISO8601 | zero minutes with timezone');
+  dateEqual(Date.create('1997-07-16T19:20+01:00'), getUTCDate(1997, 7, 16, 18, 20), 'Date#create | ISO8601 | minutes with timezone');
+  dateEqual(Date.create('1997-07-16T19:20:30+01:00'), getUTCDate(1997, 7, 16, 18, 20, 30), 'Date#create | ISO8601 | seconds with timezone');
+  dateEqual(Date.create('1997-07-16T19:20:30.45+01:00'), getUTCDate(1997, 7, 16, 18, 20, 30, 450), 'Date#create | ISO8601 | milliseconds with timezone');
+  dateEqual(Date.create('1994-11-05T08:15:30-05:00'), getUTCDate(1994, 11, 5, 13, 15, 30), 'Date#create | ISO8601 | Full example 1');
+  dateEqual(Date.create('1994-11-05T13:15:30Z'), getUTCDate(1994, 11, 5, 13, 15, 30), 'Date#create | ISO8601 | Full example 1');
+
+  equal(Date.create('1994-11-05T13:15:30Z')._utc, false, 'Date#create | ISO8601 | does not forcefully set UTC flag');
+
+  dateEqual(Date.create('1776-05-23T02:45:08-08:30'), getUTCDate(1776, 5, 23, 11, 15, 08), 'Date#create | ISO8601 | Full example 3');
+  dateEqual(Date.create('1776-05-23T02:45:08+08:30'), getUTCDate(1776, 5, 22, 18, 15, 08), 'Date#create | ISO8601 | Full example 4');
+  dateEqual(Date.create('1776-05-23T02:45:08-0830'), getUTCDate(1776, 5, 23, 11, 15, 08), 'Date#create | ISO8601 | Full example 5');
+  dateEqual(Date.create('1776-05-23T02:45:08+0830'), getUTCDate(1776, 5, 22, 18, 15, 08), 'Date#create | ISO8601 | Full example 6');
+
+
+  // No limit on the number of millisecond decimals, so....
+  dateEqual(Date.create('1997-07-16T19:20:30.4+01:00'), getUTCDate(1997, 7, 16, 18, 20, 30, 400), 'Date#create | ISO8601 | milliseconds have no limit 1');
+  dateEqual(Date.create('1997-07-16T19:20:30.46+01:00'), getUTCDate(1997, 7, 16, 18, 20, 30, 460), 'Date#create | ISO8601 | milliseconds have no limit 2');
+  dateEqual(Date.create('1997-07-16T19:20:30.462+01:00'), getUTCDate(1997, 7, 16, 18, 20, 30, 462), 'Date#create | ISO8601 | milliseconds have no limit 3');
+  dateEqual(Date.create('1997-07-16T19:20:30.4628+01:00'), getUTCDate(1997, 7, 16, 18, 20, 30, 463), 'Date#create | ISO8601 | milliseconds have no limit 4');
+  dateEqual(Date.create('1997-07-16T19:20:30.46284+01:00'), getUTCDate(1997, 7, 16, 18, 20, 30, 463), 'Date#create | ISO8601 | milliseconds have no limit 5');
+
+
+  // .NET output
+  dateEqual(Date.create('2012-04-23T07:58:42.7940000z'), getUTCDate(2012, 4, 23, 7, 58, 42, 794), 'Date#create | ISO8601 | .NET long format');
+
+  // Fractions in ISO8601 dates
+  dateEqual(Date.create('1997-07-16T14:30:40.5'), new Date(1997, 6, 16, 14, 30, 40, 500), 'Date#create | ISO8601 | fractions in seconds');
+  dateEqual(Date.create('1997-07-16T14:30.5'), new Date(1997, 6, 16, 14, 30, 30), 'Date#create | ISO8601 | fractions in minutes');
+
+  // Comma based fractions in ISO8601 dates
+  dateEqual(Date.create('1997-07-16T14:30:40,5'), new Date(1997, 6, 16, 14, 30, 40, 500), 'Date#create | ISO8601 | fractions in seconds');
+  dateEqual(Date.create('1997-07-16T14:30,5'), new Date(1997, 6, 16, 14, 30, 30), 'Date#create | ISO8601 | fractions in minutes');
+
+  // Fractional hours in ISO dates
+  dateEqual(Date.create('1997-07-16T14.5'), new Date(1997, 6, 16, 14, 30), 'Date#create | ISO8601 | fractions in hours');
+  dateEqual(Date.create('1997-07-16T14,5'), new Date(1997, 6, 16, 14, 30), 'Date#create | ISO8601 | fractions in hours');
+
+  // These are all the same moment...
+  dateEqual(Date.create('2001-04-03T18:30Z'), getUTCDate(2001,4,3,18,30), 'Date#create | ISO8601 | Synonymous dates with timezone 1');
+  dateEqual(Date.create('2001-04-03T22:30+04'), getUTCDate(2001,4,3,18,30), 'Date#create | ISO8601 | Synonymous dates with timezone 2');
+  dateEqual(Date.create('2001-04-03T1130-0700'), getUTCDate(2001,4,3,18,30), 'Date#create | ISO8601 | Synonymous dates with timezone 3');
+  dateEqual(Date.create('2001-04-03T15:00-03:30'), getUTCDate(2001,4,3,18,30), 'Date#create | ISO8601 | Synonymous dates with timezone 4');
+
+
+  dateEqual(Date.create('\/Date(628318530718)\/'), new Date(628318530718), 'Date#create | handles .NET JSON date format');
+  dateEqual(Date.create('\/Date(1318287600+0100)\/'), new Date(1318287600), 'Date#create | handles .NET JSON + date format with timezone');
+  dateEqual(Date.create('\/Date(1318287600-0700)\/'), new Date(1318287600), 'Date#create | handles .NET JSON - date format with timezone');
+
+
+
+  // Fuzzy dates
+  dateEqual(Date.create('now'), new Date(), 'Date#create | Fuzzy Dates | Now');
+  dateEqual(Date.create('Just now'), new Date(), 'Date#create | Fuzzy Dates | Just Now');
+  dateEqual(Date.create('today'), new Date(now.getFullYear(), now.getMonth(), now.getDate()), 'Date#create | Fuzzy Dates | Today');
+  dateEqual(Date.create('yesterday'), new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1), 'Date#create | Fuzzy Dates | Yesterday');
+  dateEqual(Date.create('tomorrow'), new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1), 'Date#create | Fuzzy Dates | Tomorrow');
+  dateEqual(Date.create('4pm'), new Date(now.getFullYear(), now.getMonth(), now.getDate(), 16), 'Date#create | Fuzzy Dates | 4pm');
+  dateEqual(Date.create('today at 4pm'), new Date(now.getFullYear(), now.getMonth(), now.getDate(), 16), 'Date#create | Fuzzy Dates | Today at 4pm');
+  dateEqual(Date.create('today at 4 pm'), new Date(now.getFullYear(), now.getMonth(), now.getDate(), 16), 'Date#create | Fuzzy Dates | Today at 4 pm');
+  dateEqual(Date.create('4pm today'), new Date(now.getFullYear(), now.getMonth(), now.getDate(), 16), 'Date#create | Fuzzy Dates | 4pm today');
+
+
+  dateEqual(Date.create('The day after tomorrow'), new Date(now.getFullYear(), now.getMonth(), now.getDate() + 2), 'Date#create | Fuzzy Dates | The day after tomorrow');
+  dateEqual(Date.create('The day before yesterday'), new Date(now.getFullYear(), now.getMonth(), now.getDate() - 2), 'Date#create | Fuzzy Dates | The day before yesterday');
+  dateEqual(Date.create('One day after tomorrow'), new Date(now.getFullYear(), now.getMonth(), now.getDate() + 2), 'Date#create | Fuzzy Dates | One day after tomorrow');
+  dateEqual(Date.create('One day before yesterday'), new Date(now.getFullYear(), now.getMonth(), now.getDate() - 2), 'Date#create | Fuzzy Dates | One day before yesterday');
+  dateEqual(Date.create('Two days after tomorrow'), new Date(now.getFullYear(), now.getMonth(), now.getDate() + 3), 'Date#create | Fuzzy Dates | Two days after tomorrow');
+  dateEqual(Date.create('Two days before yesterday'), new Date(now.getFullYear(), now.getMonth(), now.getDate() - 3), 'Date#create | Fuzzy Dates | Two days before yesterday');
+  dateEqual(Date.create('Two days after today'), new Date(now.getFullYear(), now.getMonth(), now.getDate() + 2), 'Date#create | Fuzzy Dates | Two days after today');
+  dateEqual(Date.create('Two days before today'), new Date(now.getFullYear(), now.getMonth(), now.getDate() - 2), 'Date#create | Fuzzy Dates | Two days before today');
+  dateEqual(Date.create('Two days from today'), new Date(now.getFullYear(), now.getMonth(), now.getDate() + 2), 'Date#create | Fuzzy Dates | Two days from today');
+
+  dateEqual(Date.create('tWo dAyS after toMoRRoW'), new Date(now.getFullYear(), now.getMonth(), now.getDate() + 3), 'Date#create | Fuzzy Dates | tWo dAyS after toMoRRoW');
+  dateEqual(Date.create('2 days after tomorrow'), new Date(now.getFullYear(), now.getMonth(), now.getDate() + 3), 'Date#create | Fuzzy Dates | 2 days after tomorrow');
+  dateEqual(Date.create('2 day after tomorrow'), new Date(now.getFullYear(), now.getMonth(), now.getDate() + 3), 'Date#create | Fuzzy Dates | 2 day after tomorrow');
+  dateEqual(Date.create('18 days after tomorrow'), new Date(now.getFullYear(), now.getMonth(), now.getDate() + 19), 'Date#create | Fuzzy Dates | 18 days after tomorrow');
+  dateEqual(Date.create('18 day after tomorrow'), new Date(now.getFullYear(), now.getMonth(), now.getDate() + 19), 'Date#create | Fuzzy Dates | 18 day after tomorrow');
+
+
+  dateEqual(Date.create('2 years ago'), getRelativeDate(-2), 'Date#create | Fuzzy Dates | 2 years ago');
+  dateEqual(Date.create('2 months ago'), getRelativeDate(null, -2), 'Date#create | Fuzzy Dates | 2 months ago');
+  dateEqual(Date.create('2 weeks ago'), getRelativeDate(null, null, -14), 'Date#create | Fuzzy Dates | 2 weeks ago');
+  dateEqual(Date.create('2 days ago'), getRelativeDate(null, null, -2), 'Date#create | Fuzzy Dates | 2 days ago');
+  dateEqual(Date.create('2 hours ago'), getRelativeDate(null, null, null, -2), 'Date#create | Fuzzy Dates | 2 hours ago');
+  dateEqual(Date.create('2 minutes ago'), getRelativeDate(null, null, null, null, -2), 'Date#create | Fuzzy Dates | 2 minutes ago');
+  dateEqual(Date.create('2 seconds ago'), getRelativeDate(null, null, null, null, null, -2), 'Date#create | Fuzzy Dates | 2 seconds ago');
+  dateEqual(Date.create('2 milliseconds ago'), getRelativeDate(null, null, null, null, null, null, -2), 'Date#create | Fuzzy Dates | 2 milliseconds ago');
+  dateEqual(Date.create('a second ago'), getRelativeDate(null, null, null, null, null, -1), 'Date#create | Fuzzy Dates | a second ago');
+
+  dateEqual(Date.create('2 years from now'), getRelativeDate(2), 'Date#create | Fuzzy Dates | 2 years from now');
+  dateEqual(Date.create('2 months from now'), getRelativeDate(null, 2), 'Date#create | Fuzzy Dates | 2 months from now');
+  dateEqual(Date.create('2 weeks from now'), getRelativeDate(null, null, 14), 'Date#create | Fuzzy Dates | 2 weeks from now');
+  dateEqual(Date.create('2 days from now'), getRelativeDate(null, null, 2), 'Date#create | Fuzzy Dates | 2 days from now');
+  dateEqual(Date.create('2 hours from now'), getRelativeDate(null, null, null, 2), 'Date#create | Fuzzy Dates | 2 hours from now');
+  dateEqual(Date.create('2 minutes from now'), getRelativeDate(null, null, null, null, 2), 'Date#create | Fuzzy Dates | 2 minutes from now');
+  dateEqual(Date.create('2 seconds from now'), getRelativeDate(null, null, null, null, null, 2), 'Date#create | Fuzzy Dates | 2 seconds from now');
+  dateEqual(Date.create('2 milliseconds from now'), getRelativeDate(null, null, null, null, null, null, 2), 'Date#create | Fuzzy Dates | 2 milliseconds from now');
+
+  dateEqual(Date.create('2 years later'), getRelativeDate(2), 'Date#create | Fuzzy Dates | 2 years later');
+  dateEqual(Date.create('2 months later'), getRelativeDate(null, 2), 'Date#create | Fuzzy Dates | 2 months later');
+  dateEqual(Date.create('2 weeks later'), getRelativeDate(null, null, 14), 'Date#create | Fuzzy Dates | 2 weeks later');
+  dateEqual(Date.create('2 days later'), getRelativeDate(null, null, 2), 'Date#create | Fuzzy Dates | 2 days later');
+  dateEqual(Date.create('2 hours later'), getRelativeDate(null, null, null, 2), 'Date#create | Fuzzy Dates | 2 hours later');
+  dateEqual(Date.create('2 minutes later'), getRelativeDate(null, null, null, null, 2), 'Date#create | Fuzzy Dates | 2 minutes later');
+  dateEqual(Date.create('2 seconds later'), getRelativeDate(null, null, null, null, null, 2), 'Date#create | Fuzzy Dates | 2 seconds later');
+  dateEqual(Date.create('2 milliseconds later'), getRelativeDate(null, null, null, null, null, null, 2), 'Date#create | Fuzzy Dates | 2 milliseconds later');
+
+  // Article trouble
+  dateEqual(Date.create('an hour ago'), getRelativeDate(null, null, null, -1), 'Date#create | Fuzzy Dates | an hours ago');
+  dateEqual(Date.create('an hour from now'), getRelativeDate(null, null, null, 1), 'Date#create | Fuzzy Dates | an hour from now');
+
+  dateEqual(Date.create('Monday'), getDateWithWeekdayAndOffset(1), 'Date#create | Fuzzy Dates | Monday');
+  dateEqual(Date.create('The day after Monday'), getDateWithWeekdayAndOffset(2), 'Date#create | Fuzzy Dates | The day after Monday');
+  dateEqual(Date.create('The day before Monday'), getDateWithWeekdayAndOffset(0), 'Date#create | Fuzzy Dates | The day before Monday');
+  dateEqual(Date.create('2 days after monday'), getDateWithWeekdayAndOffset(3), 'Date#create | Fuzzy Dates | 2 days after monday');
+  dateEqual(Date.create('2 days before monday'), getDateWithWeekdayAndOffset(6, -7), 'Date#create | Fuzzy Dates | 2 days before monday');
+  dateEqual(Date.create('2 weeks after monday'), getDateWithWeekdayAndOffset(1, 14), 'Date#create | Fuzzy Dates | 2 weeks after monday');
+
+  dateEqual(Date.create('Next Monday'), getDateWithWeekdayAndOffset(1, 7), 'Date#create | Fuzzy Dates | Next Monday');
+  dateEqual(Date.create('next week monday'), getDateWithWeekdayAndOffset(1, 7), 'Date#create | Fuzzy Dates | next week monday');
+  dateEqual(Date.create('Next friDay'), getDateWithWeekdayAndOffset(5, 7), 'Date#create | Fuzzy Dates | Next friDay');
+  dateEqual(Date.create('next week thursday'), getDateWithWeekdayAndOffset(4, 7), 'Date#create | Fuzzy Dates | next week thursday');
+
+  dateEqual(Date.create('last Monday'), getDateWithWeekdayAndOffset(1, -7), 'Date#create | Fuzzy Dates | last Monday');
+  dateEqual(Date.create('last week monday'), getDateWithWeekdayAndOffset(1, -7), 'Date#create | Fuzzy Dates | last week monday');
+  dateEqual(Date.create('last friDay'), getDateWithWeekdayAndOffset(5, -7), 'Date#create | Fuzzy Dates | last friDay');
+  dateEqual(Date.create('last week thursday'), getDateWithWeekdayAndOffset(4, -7), 'Date#create | Fuzzy Dates | last week thursday');
+  dateEqual(Date.create('last Monday at 4pm'), getDateWithWeekdayAndOffset(1, -7, 16), 'Date#create | Fuzzy Dates | last Monday at 4pm');
+
+  dateEqual(Date.create('this Monday'), getDateWithWeekdayAndOffset(1, 0), 'Date#create | Fuzzy Dates | this Monday');
+  dateEqual(Date.create('this week monday'), getDateWithWeekdayAndOffset(1, 0), 'Date#create | Fuzzy Dates | this week monday');
+  dateEqual(Date.create('this friDay'), getDateWithWeekdayAndOffset(5, 0), 'Date#create | Fuzzy Dates | this friDay');
+  dateEqual(Date.create('this week thursday'), getDateWithWeekdayAndOffset(4, 0), 'Date#create | Fuzzy Dates | this week thursday');
+
+  dateEqual(Date.create('Monday of last week'), getDateWithWeekdayAndOffset(1, -7), 'Date#create | Fuzzy Dates | Monday of last week');
+  dateEqual(Date.create('saturday of next week'), getDateWithWeekdayAndOffset(6, 7), 'Date#create | Fuzzy Dates | saturday of next week');
+  dateEqual(Date.create('Monday last week'), getDateWithWeekdayAndOffset(1, -7), 'Date#create | Fuzzy Dates | Monday last week');
+  dateEqual(Date.create('saturday next week'), getDateWithWeekdayAndOffset(6, 7), 'Date#create | Fuzzy Dates | saturday next week');
+
+  dateEqual(Date.create('Monday of this week'), getDateWithWeekdayAndOffset(1, 0), 'Date#create | Fuzzy Dates | Monday of this week');
+  dateEqual(Date.create('saturday of this week'), getDateWithWeekdayAndOffset(6, 0), 'Date#create | Fuzzy Dates | saturday of this week');
+  dateEqual(Date.create('Monday this week'), getDateWithWeekdayAndOffset(1, 0), 'Date#create | Fuzzy Dates | Monday this week');
+  dateEqual(Date.create('saturday this week'), getDateWithWeekdayAndOffset(6, 0), 'Date#create | Fuzzy Dates | saturday this week');
+
+  dateEqual(Date.create('Tue of last week'), getDateWithWeekdayAndOffset(2, -7), 'Date#create | Fuzzy Dates | Tue of last week');
+  dateEqual(Date.create('Tue. of last week'), getDateWithWeekdayAndOffset(2, -7), 'Date#create | Fuzzy Dates | Tue. of last week');
+
+
+  dateEqual(Date.create('Next week'), getRelativeDate(null, null, 7), 'Date#create | Fuzzy Dates | Next week');
+  dateEqual(Date.create('Last week'), getRelativeDate(null, null, -7), 'Date#create | Fuzzy Dates | Last week');
+  dateEqual(Date.create('Next month'), getRelativeDate(null, 1), 'Date#create | Fuzzy Dates | Next month');
+  dateEqual(Date.create('Next year'), getRelativeDate(1), 'Date#create | Fuzzy Dates | Next year');
+  dateEqual(Date.create('this year'), getRelativeDate(0), 'Date#create | Fuzzy Dates | this year');
+
+  dateEqual(Date.create('beginning of the week'), getDateWithWeekdayAndOffset(0), 'Date#create | Fuzzy Dates | beginning of the week');
+  dateEqual(Date.create('beginning of this week'), getDateWithWeekdayAndOffset(0), 'Date#create | Fuzzy Dates | beginning of this week');
+  dateEqual(Date.create('end of this week'), getDateWithWeekdayAndOffset(6, 0, 23, 59, 59, 999), 'Date#create | Fuzzy Dates | end of this week');
+  dateEqual(Date.create('beginning of next week'), getDateWithWeekdayAndOffset(0, 7), 'Date#create | Fuzzy Dates | beginning of next week');
+  dateEqual(Date.create('the beginning of next week'), getDateWithWeekdayAndOffset(0, 7), 'Date#create | Fuzzy Dates | the beginning of next week');
+
+  dateEqual(Date.create('beginning of the month'), new Date(now.getFullYear(), now.getMonth()), 'Date#create | Fuzzy Dates | beginning of the month');
+  dateEqual(Date.create('beginning of this month'), new Date(now.getFullYear(), now.getMonth()), 'Date#create | Fuzzy Dates | beginning of this month');
+  dateEqual(Date.create('beginning of next month'), new Date(now.getFullYear(), now.getMonth() + 1), 'Date#create | Fuzzy Dates | beginning of next month');
+  dateEqual(Date.create('the beginning of next month'), new Date(now.getFullYear(), now.getMonth() + 1), 'Date#create | Fuzzy Dates | the beginning of next month');
+  dateEqual(Date.create('the end of next month'), new Date(now.getFullYear(), now.getMonth() + 1, getDaysInMonth(now.getFullYear(), now.getMonth() + 1), 23, 59, 59, 999), 'Date#create | Fuzzy Dates | the end of next month');
+  dateEqual(Date.create('the end of the month'), new Date(now.getFullYear(), now.getMonth(), getDaysInMonth(now.getFullYear(), now.getMonth()), 23, 59, 59, 999), 'Date#create | Fuzzy Dates | the end of the month');
+
+  dateEqual(Date.create('the beginning of the year'), new Date(now.getFullYear(), 0), 'Date#create | Fuzzy Dates | the beginning of the year');
+  dateEqual(Date.create('the beginning of this year'), new Date(now.getFullYear(), 0), 'Date#create | Fuzzy Dates | the beginning of this year');
+  dateEqual(Date.create('the beginning of next year'), new Date(now.getFullYear() + 1, 0), 'Date#create | Fuzzy Dates | the beginning of next year');
+  dateEqual(Date.create('the beginning of last year'), new Date(now.getFullYear() - 1, 0), 'Date#create | Fuzzy Dates | the beginning of last year');
+  dateEqual(Date.create('the end of next year'), new Date(now.getFullYear() + 1, 11, 31, 23, 59, 59, 999), 'Date#create | Fuzzy Dates | the end of next year');
+  dateEqual(Date.create('the end of last year'), new Date(now.getFullYear() - 1, 11, 31, 23, 59, 59, 999), 'Date#create | Fuzzy Dates | the end of last year');
+
+  dateEqual(Date.create('the beginning of the day'), new Date(now.getFullYear(), now.getMonth(), now.getDate()), 'Date#create | Fuzzy Dates | the beginning of the day');
+
+  dateEqual(Date.create('beginning of March'), new Date(now.getFullYear(), 2), 'Date#create | Fuzzy Dates | beginning of March');
+  dateEqual(Date.create('end of March'), new Date(now.getFullYear(), 2, 31, 23, 59, 59, 999), 'Date#create | Fuzzy Dates | end of March');
+  dateEqual(Date.create('the first day of March'), new Date(now.getFullYear(), 2), 'Date#create | Fuzzy Dates | the first day of March');
+  dateEqual(Date.create('the last day of March'), new Date(now.getFullYear(), 2, 31), 'Date#create | Fuzzy Dates | the last day of March');
+
+  dateEqual(Date.create('beginning of 1998'), new Date(1998, 0), 'Date#create | Fuzzy Dates | beginning of 1998');
+  dateEqual(Date.create('end of 1998'), new Date(1998, 11, 31, 23, 59, 59, 999), 'Date#create | Fuzzy Dates | end of 1998');
+  dateEqual(Date.create('the first day of 1998'), new Date(1998, 0), 'Date#create | Fuzzy Dates | the first day of 1998');
+  dateEqual(Date.create('the last day of 1998'), new Date(1998, 11, 31), 'Date#create | Fuzzy Dates | the last day of 1998');
+
+
+
+
+
+  dateEqual(Date.create('The 15th of last month.'), new Date(now.getFullYear(), now.getMonth() - 1, 15), 'Date#create | Fuzzy Dates | The 15th of last month');
+  dateEqual(Date.create('January 30th of last year.'), new Date(now.getFullYear() - 1, 0, 30), 'Date#create | Fuzzy Dates | January 30th of last year');
+  dateEqual(Date.create('January of last year.'), new Date(now.getFullYear() - 1, 0), 'Date#create | Fuzzy Dates | January of last year');
+
+  dateEqual(Date.create('First day of may'), new Date(now.getFullYear(), 4, 1), 'Date#create | Fuzzy Dates | First day of may');
+  dateEqual(Date.create('Last day of may'), new Date(now.getFullYear(), 4, 31), 'Date#create | Fuzzy Dates | Last day of may');
+  dateEqual(Date.create('Last day of next month'), new Date(now.getFullYear(), now.getMonth() + 1, getDaysInMonth(now.getFullYear(), now.getMonth() + 1)), 'Date#create | Fuzzy Dates | Last day of next month');
+  dateEqual(Date.create('Last day of november'), new Date(now.getFullYear(), 10, 30), 'Date#create | Fuzzy Dates | Last day of november');
+
+  // Just the time
+  dateEqual(Date.create('1pm'), new Date(now.getFullYear(), now.getMonth(), now.getDate(), 13), 'Date#create | ISO8601 | 1pm');
+  dateEqual(Date.create('1:30pm'), new Date(now.getFullYear(), now.getMonth(), now.getDate(), 13, 30), 'Date#create | ISO8601 | 1:30pm');
+  dateEqual(Date.create('1:30:22pm'), new Date(now.getFullYear(), now.getMonth(), now.getDate(), 13, 30, 22), 'Date#create | ISO8601 | 1:30:22pm');
+  dateEqual(Date.create('1:30:22.432pm'), new Date(now.getFullYear(), now.getMonth(), now.getDate(), 13, 30, 22, 432), 'Date#create | ISO8601 | 1:30:22.432pm');
+  dateEqual(Date.create('17:48:03.947'), new Date(now.getFullYear(), now.getMonth(), now.getDate(), 17, 48, 3, 947), 'Date#create | ISO8601 | 17:48:03.947');
+
+  dateEqual(Date.create('the first day of next January'), new Date(now.getFullYear() + 1, 0, 1), 'Date#create | Fuzzy Dates | the first day of next january');
+
+  var d;
+
+  d = new Date('August 25, 2010 11:45:20');
+  d.set(2008, 5, 18, 4, 25, 30, 400);
+
+  equal(d.getFullYear(), 2008, 'Date#set | year');
+  equal(d.getMonth(), 5, 'Date#set | month');
+  equal(d.getDate(), 18, 'Date#set | date');
+  equal(d.getHours(), 4, 'Date#set | hours');
+  equal(d.getMinutes(), 25, 'Date#set | minutes');
+  equal(d.getSeconds(), 30, 'Date#set | seconds');
+  equal(d.getMilliseconds(), 400, 'Date#set | milliseconds');
+
+  d = new Date('August 25, 2010 11:45:20');
+  d.set({ year: 2008, month: 5, date: 18, hour: 4, minute: 25, second: 30, millisecond: 400 });
+
+  equal(d.getFullYear(), 2008, 'Date#set | object | year');
+  equal(d.getMonth(), 5, 'Date#set | object | month');
+  equal(d.getDate(), 18, 'Date#set | object | date');
+  equal(d.getHours(), 4, 'Date#set | object | hours');
+  equal(d.getMinutes(), 25, 'Date#set | object | minutes');
+  equal(d.getSeconds(), 30, 'Date#set | object | seconds');
+  equal(d.getMilliseconds(), 400, 'Date#set | object | milliseconds');
+
+  d = new Date('August 25, 2010 11:45:20');
+  d.set({ years: 2008, months: 5, date: 18, hours: 4, minutes: 25, seconds: 30, milliseconds: 400 });
+
+  equal(d.getFullYear(), 2008, 'Date#set | object plural | year');
+  equal(d.getMonth(), 5, 'Date#set | object plural | month');
+  equal(d.getDate(), 18, 'Date#set | object plural | date');
+  equal(d.getHours(), 4, 'Date#set | object plural | hours');
+  equal(d.getMinutes(), 25, 'Date#set | object plural | minutes');
+  equal(d.getSeconds(), 30, 'Date#set | object plural | seconds');
+  equal(d.getMilliseconds(), 400, 'Date#set | object plural | milliseconds');
+
+  d.set({ weekday: 2 });
+  equal(d.getDate(), 17, 'Date#set | object | weekday 2');
+  d.set({ weekday: 5 });
+  equal(d.getDate(), 20, 'Date#set | object | weekday 5');
+
+
+  d.set({ weekday: 2 }, true);
+  equal(d.getDate(), 17, 'Date#set | object | reset time | weekday 2');
+  d.set({ weekday: 5 }, true);
+  equal(d.getDate(), 20, 'Date#set | object | reset time | weekday 5');
+
+
+  d = new Date('August 25, 2010 11:45:20');
+  d.set({ years: 2005, hours: 2 });
+
+  equal(d.getFullYear(), 2005, 'Date#set | no reset | year');
+  equal(d.getMonth(), 7, 'Date#set | no reset | month');
+  equal(d.getDate(), 25, 'Date#set | no reset | date');
+  equal(d.getHours(), 2, 'Date#set | no reset | hours');
+  equal(d.getMinutes(), 45, 'Date#set | no reset | minutes');
+  equal(d.getSeconds(), 20, 'Date#set | no reset | seconds');
+  equal(d.getMilliseconds(), 0, 'Date#set | no reset | milliseconds');
+
+
+
+  d = new Date('August 25, 2010 11:45:20');
+  d.set({ years: 2008, hours: 4 }, true);
+
+  equal(d.getFullYear(), 2008, 'Date#set | reset | year');
+  equal(d.getMonth(), 7, 'Date#set | reset | month');
+  equal(d.getDate(), 25, 'Date#set | reset | date');
+  equal(d.getHours(), 4, 'Date#set | reset | hours');
+  equal(d.getMinutes(), 0, 'Date#set | reset | minutes');
+  equal(d.getSeconds(), 0, 'Date#set | reset | seconds');
+  equal(d.getMilliseconds(), 0, 'Date#set | reset | milliseconds');
+
+
+  d = new Date('August 25, 2010 11:45:20').utc();
+  d.set({ years: 2008, hours: 4 }, true);
+
+  equal(d.getFullYear(), 2008, 'Date#set | utc | reset utc | year');
+  equal(d.getMonth(), 7, 'Date#set | utc | reset utc | month');
+  equal(d.getDate(), d.getTimezoneOffset() > 240 ? 24 : 25, 'Date#set | utc | reset utc | date');
+  equal(d.getHours(), getHours(4 - (d.getTimezoneOffset() / 60)), 'Date#set | utc | reset utc | hours');
+  equal(d.getMinutes(), Math.abs(d.getTimezoneOffset() % 60), 'Date#set | utc | reset utc | minutes');
+  equal(d.getSeconds(), 0, 'Date#set | utc | reset utc | seconds');
+  equal(d.getMilliseconds(), 0, 'Date#set | utc | reset utc | milliseconds');
+
+
+  d = new Date('August 25, 2010 11:45:20').utc();
+  d.set({ years: 2005, hours: 2 }, false);
+
+  equal(d.getFullYear(), 2005, 'Date#set | utc | no reset utc | year');
+  equal(d.getMonth(), 7, 'Date#set | utc | no reset utc | month');
+  equal(d.getDate(), d.getTimezoneOffset() >= 135 ? 24 : 25, 'Date#set | utc | no reset utc | date');
+  equal(d.getHours(), getHours(2 - (d.getTimezoneOffset() / 60)), 'Date#set | utc | no reset utc | hours');
+  equal(d.getMinutes(), 45, 'Date#set | utc | no reset utc | minutes');
+  equal(d.getSeconds(), 20, 'Date#set | utc | no reset utc | seconds');
+  equal(d.getMilliseconds(), 0, 'Date#set | utc | no reset utc | milliseconds');
+
+
+  d = new Date('August 25, 2010 11:45:20').utc();
+  d.set({ years: 2005, hours: 2 }, false);
+
+  equal(d.getFullYear(), 2005, 'Date#set | utc | no reset | year');
+  equal(d.getMonth(), 7, 'Date#set | utc | no reset | month');
+  equal(d.getDate(), d.getTimezoneOffset() >= 135 ? 24 : 25, 'Date#set | utc | no reset | date');
+  equal(d.getHours(), getHours(2 - (d.getTimezoneOffset() / 60)), 'Date#set | utc | no reset | hours');
+  equal(d.getMinutes(), 45, 'Date#set | utc | no reset | minutes');
+  equal(d.getSeconds(), 20, 'Date#set | utc | no reset | seconds');
+  equal(d.getMilliseconds(), 0, 'Date#set | utc | no reset | milliseconds');
+
+
+  dateEqual(Date.create('Next week'), getRelativeDate(null, null, 7), 'Date#create | Fuzzy Dates | Next week');
+
+  d = new Date('August 25, 2010 11:45:20');
+
+  equal(d.getWeekday(), 3, 'Date#getWeekday | wednesday');
+
+  d.setWeekday(0);
+  equal(d.getDate(), 22, 'Date#setWeekday | sunday');
+  d.setWeekday(1);
+  equal(d.getDate(), 23, 'Date#setWeekday | monday');
+  d.setWeekday(2);
+  equal(d.getDate(), 24, 'Date#setWeekday | tuesday');
+  d.setWeekday(3);
+  equal(d.getDate(), 25, 'Date#setWeekday | wednesday');
+  d.setWeekday(4);
+  equal(d.getDate(), 26, 'Date#setWeekday | thursday');
+  d.setWeekday(5);
+  equal(d.getDate(), 27, 'Date#setWeekday | friday');
+  d.setWeekday(6);
+  equal(d.getDate(), 28, 'Date#setWeekday | saturday');
+
+  equal(d.setWeekday(6), d.getTime(), 'Date#setWeekday | should return the timestamp');
+
+  d = new Date('August 25, 2010 11:45:20').utc();
+
+  equal(d.getWeekday(), 3, 'Date#getUTCWeekday | wednesday');
+
+  d.setWeekday(0);
+  equal(d.getDate(), 22, 'Date#setWeekday | utc | sunday');
+  d.setWeekday(1);
+  equal(d.getDate(), 23, 'Date#setWeekday | utc | monday');
+  d.setWeekday(2);
+  equal(d.getDate(), 24, 'Date#setWeekday | utc | tuesday');
+  d.setWeekday(3);
+  equal(d.getDate(), 25, 'Date#setWeekday | utc | wednesday');
+  d.setWeekday(4);
+  equal(d.getDate(), 26, 'Date#setWeekday | utc | thursday');
+  d.setWeekday(5);
+  equal(d.getDate(), 27, 'Date#setWeekday | utc | friday');
+  d.setWeekday(6);
+  equal(d.getDate(), 28, 'Date#setWeekday | utc | saturday');
+
+  equal(d.setWeekday(6), d.getTime(), 'Date#setWeekday | utc | should return the timestamp');
+
+
+  d = new Date('August 25, 2010 11:45:20');
+
+  d.setDate(12);
+  equal(d.getWeekday(), 4, 'Date#getWeekday | Thursday');
+  equal(d.clone().utc().getWeekday(), 4, 'Date#setWeekday | utc | Thursday');
+
+  d.setDate(13);
+  equal(d.getWeekday(), 5, 'Date#getWeekday | Friday');
+  equal(d.clone().utc().getWeekday(), 5, 'Date#setWeekday | utc | Friday');
+
+  d.setDate(14);
+  equal(d.getWeekday(), 6, 'Date#getWeekday | Saturday');
+  equal(d.clone().utc().getWeekday(), 6, 'Date#getWeekday | utc | Saturday');
+
+  d.setDate(15);
+  equal(d.getWeekday(), 0, 'Date#getWeekday | Sunday');
+  equal(d.clone().utc().getWeekday(), 0, 'Date#getWeekday | utc | Sunday');
+
+  d.setDate(16);
+  equal(d.getWeekday(), 1, 'Date#getWeekday | Monday');
+  equal(d.clone().utc().getWeekday(), 1, 'Date#getWeekday | utc | Monday');
+
+  d.setDate(17);
+  equal(d.getWeekday(), 2, 'Date#getWeekday | Tuesday');
+  equal(d.clone().utc().getWeekday(), 2, 'Date#getWeekday | utc | Tuesday');
+
+  d.setDate(18);
+  equal(d.getWeekday(), 3, 'Date#getWeekday | Wednesday');
+  equal(d.clone().utc().getWeekday(), 3, 'Date#getWeekday | utc | Wednesday');
+
+
+  dateEqual(new Date().advance({ weekday: 7 }), new Date(), 'Date#advance | cannot advance by weekdays');
+  dateEqual(new Date().rewind({ weekday: 7 }), new Date(), 'Date#advance | cannot rewind by weekdays');
+
+  // UTC Date
+  var d = Date.utc.create('2010-01-01 03:00', 'en').utc();
+
+  d.setWeekday(1)
+  equal(d.getUTCDay(), 1, 'Date#setWeekday | should account for UTC shift | getUTCDay');
+
+
+
+  var d = new Date(2010, 11, 31, 24, 59, 59);
+
+  equal(d.getWeekday(), d.getDay(), 'Date#getWeekday | equal to getDay');
+  equal(d.getUTCWeekday(), d.getUTCDay(), 'Date#getUTCWeekday | equal to getUTCDay');
+
+
+  d = new Date('August 25, 2010 11:45:20').utc();
+
+  equal(d.getWeekday(), 3, 'Date#getWeekday | utc | wednesday');
+
+  d.setWeekday(0);
+  equal(d.getDate(), 22, 'Date#setWeekday | utc | sunday');
+  d.setWeekday(1);
+  equal(d.getDate(), 23, 'Date#setWeekday | utc | monday');
+  d.setWeekday(2);
+  equal(d.getDate(), 24, 'Date#setWeekday | utc | tuesday');
+  d.setWeekday(3);
+  equal(d.getDate(), 25, 'Date#setWeekday | utc | wednesday');
+  d.setWeekday(4);
+  equal(d.getDate(), 26, 'Date#setWeekday | utc | thursday');
+  d.setWeekday(5);
+  equal(d.getDate(), 27, 'Date#setWeekday | utc | friday');
+  d.setWeekday(6);
+  equal(d.getDate(), 28, 'Date#setWeekday | utc | saturday');
+
+  d.setWeekday();
+  equal(d.getDate(), 28, 'Date#setWeekday | utc | undefined');
+
+
+  d = new Date('August 25, 2010 11:45:20');
+
+  d.advance(1,-3,2,8,12,-2,44);
+
+  equal(d.getFullYear(), 2011, 'Date#advance | year');
+  equal(d.getMonth(), 4, 'Date#advance | month');
+  equal(d.getDate(), 27, 'Date#advance | day');
+  equal(d.getHours(), 19, 'Date#advance | hours');
+  equal(d.getMinutes(), 57, 'Date#advance | minutes');
+  equal(d.getSeconds(), 18, 'Date#advance | seconds');
+  equal(d.getMilliseconds(), 44, 'Date#advance | milliseconds');
+
+
+  d = new Date('August 25, 2010 11:45:20');
+
+  d.rewind(1,-3,2,8,12,-2,4);
+
+  equal(d.getFullYear(), 2009, 'Date#rewind | year');
+  equal(d.getMonth(), 10, 'Date#rewind | month');
+  equal(d.getDate(), 23, 'Date#rewind | day');
+  equal(d.getHours(), 3, 'Date#rewind | hours');
+  equal(d.getMinutes(), 33, 'Date#rewind | minutes');
+  equal(d.getSeconds(), 21, 'Date#rewind | seconds');
+  equal(d.getMilliseconds(), 996, 'Date#rewind | milliseconds');
+
+
+
+  d = new Date('August 25, 2010 11:45:20');
+  d.advance({ year: 1, month: -3, days: 2, hours: 8, minutes: 12, seconds: -2, milliseconds: 44 });
+
+  equal(d.getFullYear(), 2011, 'Date#advance | object | year');
+  equal(d.getMonth(), 4, 'Date#advance | object | month');
+  equal(d.getDate(), 27, 'Date#advance | object | day');
+  equal(d.getHours(), 19, 'Date#advance | object | hours');
+  equal(d.getMinutes(), 57, 'Date#advance | object | minutes');
+  equal(d.getSeconds(), 18, 'Date#advance | object | seconds');
+  equal(d.getMilliseconds(), 44, 'Date#advance | object | milliseconds');
+
+
+  d = new Date('August 25, 2010 11:45:20');
+  d.rewind({ year: 1, month: -3, days: 2, hours: 8, minutes: 12, seconds: -2, milliseconds: 4 });
+
+  equal(d.getFullYear(), 2009, 'Date#rewind | object | year');
+  equal(d.getMonth(), 10, 'Date#rewind | object | month');
+  equal(d.getDate(), 23, 'Date#rewind | object | day');
+  equal(d.getHours(), 3, 'Date#rewind | object | hours');
+  equal(d.getMinutes(), 33, 'Date#rewind | object | minutes');
+  equal(d.getSeconds(), 21, 'Date#rewind | object | seconds');
+  equal(d.getMilliseconds(), 996, 'Date#rewind | object | milliseconds');
+
+
+
+  d = new Date('August 25, 2010 11:45:20');
+  d.advance({ week: 1});
+  dateEqual(d, new Date(2010, 8, 1, 11, 45, 20), 'Date#advance | positive weeks supported');
+  d.advance({ week: -2});
+  dateEqual(d, new Date(2010, 7, 18, 11, 45, 20), 'Date#advance | negative weeks supported');
+
+
+  d = new Date('August 25, 2010 11:45:20');
+  d.rewind({ week: 1});
+  dateEqual(d, new Date(2010, 7, 18, 11, 45, 20), 'Date#rewind | positive weeks supported');
+  d.rewind({ week: -1});
+  dateEqual(d, new Date(2010, 7, 25, 11, 45, 20), 'Date#rewind | negative weeks supported');
+
+
+
+  dateEqual(new Date().advance({ years: 1 }), Date.create('one year from now'), 'Date#advance | advancing 1 year');
+  dateEqual(new Date().rewind({ years: 1 }), Date.create('one year ago'), 'Date#rewind | rewinding 1 year');
+
+
+
+
+
+
+
+
+
+  d.set({ month: 0 })
+  equal(d.daysInMonth(), 31, 'Date#daysInMonth | jan');
+  d.set({ month: 1 })
+  equal(d.daysInMonth(), 28, 'Date#daysInMonth | feb');
+  d.set({ month: 2 })
+  equal(d.daysInMonth(), 31, 'Date#daysInMonth | mar');
+  d.set({ month: 3 })
+  // This test fails in Casablanca in Windows XP! Reason unknown.
+  equal(d.daysInMonth(), 30, 'Date#daysInMonth | apr');
+  d.set({ month: 4 })
+  equal(d.daysInMonth(), 31, 'Date#daysInMonth | may');
+  d.set({ month: 5 })
+  equal(d.daysInMonth(), 30, 'Date#daysInMonth | jun');
+  d.set({ month: 6 })
+  equal(d.daysInMonth(), 31, 'Date#daysInMonth | jul');
+  d.set({ month: 7 })
+  equal(d.daysInMonth(), 31, 'Date#daysInMonth | aug');
+  d.set({ month: 8 })
+  equal(d.daysInMonth(), 30, 'Date#daysInMonth | sep');
+  d.set({ month: 9 })
+  equal(d.daysInMonth(), 31, 'Date#daysInMonth | oct');
+  d.set({ month: 10 })
+  equal(d.daysInMonth(), 30, 'Date#daysInMonth | nov');
+  d.set({ month: 11 })
+  equal(d.daysInMonth(), 31, 'Date#daysInMonth | dec');
+
+  d.set({ year: 2012, month: 1 });
+  equal(d.daysInMonth(), 29, 'Date#daysInMonth | feb leap year');
+
+
+  d = new Date('August 5, 2010 13:45:02');
+  d.setMilliseconds(234);
+  d.set({ month: 3 });
+
+  equal(d.getFullYear(), 2010, 'Date#set | does not reset year');
+  equal(d.getMonth(), 3, 'Date#set | does reset month');
+  equal(d.getDate(), 5, 'Date#set | does not reset date');
+  equal(d.getHours(), 13, 'Date#set | does not reset hours');
+  equal(d.getMinutes(), 45, 'Date#set | does not reset minutes');
+  equal(d.getSeconds(), 02, 'Date#set | does not reset seconds');
+  equal(d.getMilliseconds(), 234, 'Date#set | does not reset milliseconds');
+
+
+
+  d = new Date('August 5, 2010 13:45:02');
+  d.set({ month: 3 }, true);
+
+  equal(d.getFullYear(), 2010, 'Date#set | does not reset year');
+  equal(d.getMonth(), 3, 'Date#set | does reset month');
+  equal(d.getDate(), 1, 'Date#set | does reset date');
+  equal(d.getHours(), 0, 'Date#set | does reset hours');
+  equal(d.getMinutes(), 0, 'Date#set | does reset minutes');
+  equal(d.getSeconds(), 0, 'Date#set | does reset seconds');
+  equal(d.getMilliseconds(), 0, 'Date#set | does reset milliseconds');
+
+
+
+  // Catch for DST inequivalencies
+  // FAILS IN DAMASCUS IN XP!
+  equal(new Date(2010, 11, 9, 17).set({ year: 1998, month: 3, day: 3}, true).getHours(), 0, 'Date#set | handles DST properly');
+
+
+  d = new Date(2010, 0, 31);
+  dateEqual(d.set({ month: 1 }, true), new Date(2010,1), 'Date#set | reset dates will not accidentally traverse into a different month');
+
+  d = new Date(2010, 0, 31);
+  dateEqual(d.advance({ month: 1 }), new Date(2010,1,28), 'Date#set | reset dates will not accidentally traverse into a different month');
+
+  d = new Date('August 25, 2010 11:45:20');
+  d.setISOWeek(1);
+  dateEqual(d, new Date(2010,0,6,11,45,20), 'Date#setISOWeek | week 1');
+  d.setISOWeek(15);
+  dateEqual(d, new Date(2010,3,14,11,45,20), 'Date#setISOWeek | week 15');
+  d.setISOWeek(27);
+  dateEqual(d, new Date(2010,6,7,11,45,20), 'Date#setISOWeek | week 27');
+  d.setISOWeek(52);
+  dateEqual(d, new Date(2010,11,29,11,45,20), 'Date#setISOWeek | week 52');
+  d.setISOWeek();
+  dateEqual(d, new Date(2010,11,29,11,45,20), 'Date#setISOWeek | week stays set');
+
+
+  d = Date.create('August 25, 2010 11:45:20', 'en');
+  equal(d.setISOWeek(1), new Date(2010, 0, 6, 11, 45, 20).getTime(), 'Date#setISOWeek | returns a timestamp');
+
+
+  d = Date.utc.create('January 1, 2010 02:15:20', 'en').utc(true);
+
+  d.setISOWeek(1);
+  dateEqual(d, new Date(Date.UTC(2010,0,8,2,15,20)), 'Date#setISOWeek | utc | week 1');
+  d.setISOWeek(15);
+  dateEqual(d, new Date(Date.UTC(2010,3,16,2,15,20)), 'Date#setISOWeek | utc | week 15');
+  d.setISOWeek(27);
+  dateEqual(d, new Date(Date.UTC(2010,6,9,2,15,20)), 'Date#setISOWeek | utc | week 27');
+  d.setISOWeek(52);
+  dateEqual(d, new Date(Date.UTC(2010,11,31,2,15,20)), 'Date#setISOWeek | utc | week 52');
+  d.setISOWeek();
+  dateEqual(d, new Date(Date.UTC(2010,11,31,2,15,20)), 'Date#setISOWeek | utc | week stays set');
+
+
+  // Date formatting. Much thanks to inspiration taken from Date.js here.
+  // I quite like the formatting patterns in Date.js, however there are a few
+  // notable limitations. One example is a format such as 4m23s which would have
+  // to be formatted as mmss and wouldn't parse at all without special massaging.
+  // Going to take a different tack here with a format that's more explicit and
+  // easy to remember, if not quite as terse and elegant.
+
+
+  d = new Date('August 5, 2010 13:45:02');
+
+
+  equal(d.format(), 'August 5, 2010 1:45pm', 'Date#format | no arguments is standard format with no time');
+
+  equal(d.format('{ms}'), '0', 'Date#format | custom formats | ms');
+  equal(d.format('{milliseconds}'), '0', 'Date#format | custom formats | milliseconds');
+  equal(d.format('{f}'), '0', 'Date#format | custom formats | f');
+  equal(d.format('{ff}'), '00', 'Date#format | custom formats | ff');
+  equal(d.format('{fff}'), '000', 'Date#format | custom formats | fff');
+  equal(d.format('{ffff}'), '0000', 'Date#format | custom formats | ffff');
+  equal(d.format('{s}'), '2', 'Date#format | custom formats | s');
+  equal(d.format('{ss}'), '02', 'Date#format | custom formats | ss');
+  equal(d.format('{seconds}'), '2', 'Date#format | custom formats | seconds');
+  equal(d.format('{m}'), '45', 'Date#format | custom formats | m');
+  equal(d.format('{mm}'), '45', 'Date#format | custom formats | mm');
+  equal(d.format('{minutes}'), '45', 'Date#format | custom formats | minutes');
+  equal(d.format('{h}'), '1', 'Date#format | custom formats | h');
+  equal(d.format('{hh}'), '01', 'Date#format | custom formats | hh');
+  equal(d.format('{H}'), '13', 'Date#format | custom formats | H');
+  equal(d.format('{HH}'), '13', 'Date#format | custom formats | HH');
+  equal(d.format('{hours}'), '1', 'Date#format | custom formats | hours');
+  equal(d.format('{24hr}'), '13', 'Date#format | custom formats | 24hr');
+  equal(d.format('{12hr}'), '1', 'Date#format | custom formats | 12hr');
+  equal(d.format('{d}'), '5', 'Date#format | custom formats | d');
+  equal(d.format('{dd}'), '05', 'Date#format | custom formats | dd');
+  equal(d.format('{date}'), '5', 'Date#format | custom formats | date');
+  equal(d.format('{day}'), '5', 'Date#format | custom formats | days');
+  equal(d.format('{dow}'), 'thu', 'Date#format | custom formats | dow');
+  equal(d.format('{Dow}'), 'Thu', 'Date#format | custom formats | Dow');
+  equal(d.format('{weekday}'), 'thursday', 'Date#format | custom formats | weekday');
+  equal(d.format('{Weekday}'), 'Thursday', 'Date#format | custom formats | Weekday');
+  equal(d.format('{M}'), '8', 'Date#format | custom formats | M');
+  equal(d.format('{MM}'), '08', 'Date#format | custom formats | MM');
+  equal(d.format('{month}'), 'august', 'Date#format | custom formats | month');
+  equal(d.format('{Mon}'), 'Aug', 'Date#format | custom formats | Mon');
+  equal(d.format('{Month}'), 'August', 'Date#format | custom formats | Month');
+  equal(d.format('{yy}'), '10', 'Date#format | custom formats | yy');
+  equal(d.format('{yyyy}'), '2010', 'Date#format | custom formats | yyyy');
+  equal(d.format('{year}'), '2010', 'Date#format | custom formats | year');
+  equal(d.format('{t}'), 'p', 'Date#format | custom formats | t');
+  equal(d.format('{T}'), 'P', 'Date#format | custom formats | T');
+  equal(d.format('{tt}'), 'pm', 'Date#format | custom formats | tt');
+  equal(d.format('{TT}'), 'PM', 'Date#format | custom formats | TT');
+  equal(d.format('{ord}'), '5th', 'Date#format | custom formats | ord');
+
+  equal(d.format('{Z}'), d.getUTCOffset(), 'Date#format | custom formats | Z');
+  equal(d.format('{ZZ}'), d.getUTCOffset().replace(/(\d{2})$/, ':$1'), 'Date#format | custom formats | ZZ');
+
+
+  d = new Date('August 5, 2010 04:03:02');
+
+  equal(d.format('{mm}'), '03', 'Date#format | custom formats | mm pads the digit');
+  equal(d.format('{dd}'), '05', 'Date#format | custom formats | dd pads the digit');
+  equal(d.format('{hh}'), '04', 'Date#format | custom formats | hh pads the digit');
+  equal(d.format('{ss}'), '02', 'Date#format | custom formats | ss pads the digit');
+
+
+  equal(d.format('{M}/{d}/{yyyy}'), '8/5/2010', 'Date#format | full formats | slashes');
+  equal(d.format('{Weekday}, {Month} {dd}, {yyyy}'), 'Thursday, August 05, 2010', 'Date#format | full formats | text date');
+  equal(d.format('{Weekday}, {Month} {dd}, {yyyy} {12hr}:{mm}:{ss} {tt}'), 'Thursday, August 05, 2010 4:03:02 am', 'Date#format | full formats | text date with time');
+  equal(d.format('{Month} {dd}'), 'August 05', 'Date#format | full formats | month and day');
+  equal(d.format('{Dow}, {dd} {Mon} {yyyy} {hh}:{mm}:{ss} GMT'), 'Thu, 05 Aug 2010 04:03:02 GMT', 'Date#format | full formats | full GMT');
+  equal(d.format('{yyyy}-{MM}-{dd}T{hh}:{mm}:{ss}'), '2010-08-05T04:03:02', 'Date#format | full formats | ISO8601 without timezone');
+  equal(d.format('{12hr}:{mm} {tt}'), '4:03 am', 'Date#format | full formats | hr:min');
+  equal(d.format('{12hr}:{mm}:{ss} {tt}'), '4:03:02 am', 'Date#format | full formats | hr:min:sec');
+  equal(d.format('{yyyy}-{MM}-{dd} {hh}:{mm}:{ss}Z'), '2010-08-05 04:03:02Z', 'Date#format | full formats | ISO8601 UTC');
+  equal(d.format('{Month}, {yyyy}'), 'August, 2010', 'Date#format | full formats | month and year');
+
+
+
+  // Locale specific output formats/shortcuts
+
+  equal(d.format('short'), 'August 5, 2010', 'Date#format | shortcuts | short');
+  equal(d.short(), 'August 5, 2010', 'Date#format | shortcuts | short method');
+  equal(d.format('long'), 'August 5, 2010 4:03am', 'Date#format | shortcuts | long');
+  equal(d.long(), 'August 5, 2010 4:03am', 'Date#format | shortcuts | long method');
+  equal(d.format('full'), 'Thursday August 5, 2010 4:03:02am', 'Date#format | shortcuts | full');
+  equal(d.full(), 'Thursday August 5, 2010 4:03:02am', 'Date#format | shortcuts | full method');
+
+  // Don't want this for now as there's no set concept of a "time" necessarily without
+  // raising questions like "with seconds?" etc... treads on short/long/full so find a better
+  // way to handle this if necessary.
+  // equal(d.format('w00t {time}'), 'w00t 4:03:02am', 'Date#format | shortcuts | custom time format');
+
+
+
+
+  // Be VERY careful here. Timezone offset is NOT always guaranteed to be the same for a given timezone,
+  // as DST may come into play.
+  var offset = d.getTimezoneOffset();
+  var isotzd = testPadNumber(Math.floor(-offset / 60), 2, true) + ':' + testPadNumber(Math.abs(offset % 60), 2);
+  var tzd = isotzd.replace(/:/, '');
+  if(d.isUTC()) {
+    isotzd = 'Z';
+    tzd = '+0000';
+  }
+
+  equal(d.getUTCOffset(), tzd, 'Date#getUTCOffset | no colon');
+  equal(d.getUTCOffset(true), isotzd, 'Date#getUTCOffset | colon');
+
+  equal(d.format(Date.ISO8601_DATE), '2010-08-05', 'Date#format | constants | ISO8601_DATE');
+  equal(d.format(Date.ISO8601_DATETIME), '2010-08-05T04:03:02.000'+isotzd, 'Date#format | constants | ISO8601_DATETIME');
+
+
+  equal(d.format('ISO8601_DATE'), '2010-08-05', 'Date#format | string constants | ISO8601_DATE');
+  equal(d.format('ISO8601_DATETIME'), '2010-08-05T04:03:02.000'+isotzd, 'Date#format | constants | ISO8601_DATETIME');
+
+  var iso = d.getUTCFullYear()+'-'+testPadNumber(d.getUTCMonth()+1, 2)+'-'+testPadNumber(d.getUTCDate(), 2)+'T'+testPadNumber(d.getUTCHours(), 2)+':'+testPadNumber(d.getUTCMinutes(), 2)+':'+testPadNumber(d.getUTCSeconds(), 2)+'.'+testPadNumber(d.getUTCMilliseconds(), 3)+'Z';
+
+
+  equal(d.clone().utc().format(Date.ISO8601_DATETIME), iso, 'Date#format | constants | ISO8601_DATETIME UTC HOLY');
+  equal(d.clone().utc().format('ISO8601_DATETIME'), iso, 'Date#format | string constants | ISO8601_DATETIME UTC');
+
+
+  var rfc1123 = testCapitalize(getWeekdayFromDate(d).slice(0,3))+', '+testPadNumber(d.getDate(), 2)+' '+testCapitalize(getMonthFromDate(d).slice(0,3))+' '+d.getFullYear()+' '+testPadNumber(d.getHours(), 2)+':'+testPadNumber(d.getMinutes(), 2)+':'+testPadNumber(d.getSeconds(), 2)+' '+d.getUTCOffset();
+  var rfc1036 = testCapitalize(getWeekdayFromDate(d))+', '+testPadNumber(d.getDate(), 2)+'-'+testCapitalize(getMonthFromDate(d).slice(0,3))+'-'+d.getFullYear().toString().slice(2)+' '+testPadNumber(d.getHours(), 2)+':'+testPadNumber(d.getMinutes(), 2)+':'+testPadNumber(d.getSeconds(), 2)+' '+d.getUTCOffset();
+  equal(d.format(Date.RFC1123), rfc1123, 'Date#format | constants | RFC1123');
+  equal(d.format(Date.RFC1036), rfc1036, 'Date#format | constants | RFC1036');
+  equal(d.format('RFC1123'), rfc1123, 'Date#format | string constants | RFC1123');
+  equal(d.format('RFC1036'), rfc1036, 'Date#format | string constants | RFC1036');
+
+
+  rfc1123 = testCapitalize(getWeekdayFromDate(d,true).slice(0,3))+', '+testPadNumber(d.getUTCDate(), 2)+' '+testCapitalize(getMonthFromDate(d,true).slice(0,3))+' '+d.getUTCFullYear()+' '+testPadNumber(d.getUTCHours(), 2)+':'+testPadNumber(d.getUTCMinutes(), 2)+':'+testPadNumber(d.getUTCSeconds(), 2)+' +0000';
+  rfc1036 = testCapitalize(getWeekdayFromDate(d,true))+', '+testPadNumber(d.getUTCDate(), 2)+'-'+testCapitalize(getMonthFromDate(d,true).slice(0,3))+'-'+d.getUTCFullYear().toString().slice(2)+' '+testPadNumber(d.getUTCHours(), 2)+':'+testPadNumber(d.getUTCMinutes(), 2)+':'+testPadNumber(d.getUTCSeconds(), 2)+' +0000';
+
+  equal(d.clone().utc().format('RFC1123'), rfc1123, 'Date#format | string constants | RFC1123 UTC');
+  equal(d.clone().utc().format('RFC1036'), rfc1036, 'Date#format | string constants | RFC1036 UTC');
+
+
+  equal(Date.create('totally invalid').format(Date.ISO8601_DATETIME), 'Invalid Date', 'Date#format | invalid');
+
+
+
+  // ISO format
+
+  equal(d.toISOString(), d.clone().utc().format(Date.ISO8601_DATETIME), 'Date#toISOString is an alias for the ISO8601_DATETIME format in UTC');
+  equal(d.iso(), d.clone().utc().format(Date.ISO8601_DATETIME), 'Date#iso is an alias for the ISO8601_DATETIME format in UTC');
+
+
+
+
+  // relative time formatting
+
+
+  skipEnvironments(['mootools'], function() {
+
+    var dyn = function(value, unit, ms, loc) {
+      if(ms < -(1).year()) {
+        return '{Month} {date}, {year}';
+      }
+    }
+
+    equal(Date.create('5 minutes ago').format(dyn), getRelativeDate(null, null, null, null, -5).format(), 'Date#format | relative fn | 5 minutes should stay relative');
+    equal(Date.create('13 months ago').format(dyn), Date.create('13 months ago').format('{Month} {date}, {year}'), 'Date#format | relative fn | higher reverts to absolute');
+
+    // globalize system with plurals
+
+    var strings = ['ใƒŸใƒช็ง’','็ง’','ๅˆ†','ๆ™‚้–“','ๆ—ฅ','้€ฑ้–“','ๆœˆ','ๅนด'];
+
+    dyn = function(value, unit, ms, loc) {
+      equal(value, 5, 'Date#format | relative fn | 5 minutes ago | value is the closest relevant value');
+      equal(unit, 2, 'Date#format | relative fn | 5 minutes ago | unit is the closest relevant unit');
+      equalWithMargin(ms, -300000, 10, 'Date#format | relative fn | 5 minutes ago | ms is the offset in ms');
+      equal(loc.code, 'en', 'Date#format | relative fn | 4 hours ago | 4th argument is the locale object');
+      return value + strings[unit] + (ms < 0 ? 'ๅ‰' : 'ๅพŒ');
+    }
+
+    equal(Date.create('5 minutes ago').format(dyn), '5ๅˆ†ๅ‰', 'Date#format | relative fn | 5 minutes ago');
+
+
+    dyn = function(value, unit, ms, loc) {
+      equal(value, 1, 'Date#format | relative fn | 1 minute from now | value is the closest relevant value');
+      equal(unit, 2, 'Date#format | relative fn | 1 minute from now | unit is the closest relevant unit');
+      equalWithMargin(ms, 61000, 5, 'Date#format | relative fn | 1 minute from now | ms is the offset in ms');
+      equal(loc.code, 'en', 'Date#format | relative fn | 4 hours ago | 4th argument is the locale object');
+      return value + strings[unit] + (ms < 0 ? 'ๅ‰' : 'ๅพŒ');
+    }
+
+    equal(Date.create('61 seconds from now').format(dyn), '1ๅˆ†ๅพŒ', 'Date#format | relative fn | 1 minute from now');
+
+
+
+    dyn = function(value, unit, ms, loc) {
+      equal(value, 4, 'Date#format | relative fn | 4 hours ago | value is the closest relevant value');
+      equal(unit, 3, 'Date#format | relative fn | 4 hours ago | unit is the closest relevant unit');
+      equalWithMargin(ms, -14400000, 10, 'Date#format | relative fn | 4 hours ago | ms is the offset in ms');
+      equal(loc.code, 'en', 'Date#format | relative fn | 4 hours ago | 4th argument is the locale object');
+      return value + strings[unit] + (ms < 0 ? 'ๅ‰' : 'ๅพŒ');
+    }
+
+    equal(Date.create('240 minutes ago').format(dyn), '4ๆ™‚้–“ๅ‰', 'Date#format | relative fn | 4 hours ago');
+
+    Date.create('223 milliseconds ago').format(function(value, unit) {
+      equalWithMargin(value, 223, 10, 'Date format | relative fn | still passes < 1 second');
+      equal(unit, 0, 'Date format | relative fn | still passes millisecond is zero');
+    });
+
+    equal(Date.create('2002-02-17').format(function() {}), 'February 17, 2002 12:00am', 'Date#format | function that returns undefined defaults to standard format');
+
+  });
+
+  equal(Date.create().relative(), '1 second ago', 'Date#relative | relative | 6 milliseconds');
+  equal(Date.create('6234 milliseconds ago').relative(), '6 seconds ago', 'Date#relative | relative | 6 milliseconds');
+  equal(Date.create('6 seconds ago').relative(), '6 seconds ago', 'Date#relative | relative | 6 seconds');
+  equal(Date.create('360 seconds ago').relative(), '6 minutes ago', 'Date#relative | relative | 360 seconds');
+  equal(Date.create('360 minutes ago').relative(), '6 hours ago', 'Date#relative | relative | minutes');
+  equal(Date.create('360 hours ago').relative(), '2 weeks ago', 'Date#relative | relative | hours');
+  equal(Date.create('340 days ago').relative(), '11 months ago', 'Date#relative | relative | 340 days');
+  equal(Date.create('360 days ago').relative(), '1 year ago', 'Date#relative | relative | 360 days');
+  equal(Date.create('360 weeks ago').relative(), '6 years ago', 'Date#relative | relative | weeks');
+  equal(Date.create('360 months ago').relative(), '30 years ago', 'Date#relative | relative | months');
+  equal(Date.create('360 years ago').relative(), '360 years ago', 'Date#relative | relative | years');
+  equal(Date.create('12 months ago').relative(), '1 year ago', 'Date#relative | relative | 12 months ago');
+
+  equal(Date.create('6234 milliseconds from now').relative(), '6 seconds from now', 'Date#relative | relative future | 6 milliseconds');
+  equal(Date.create('361 seconds from now').relative(), '6 minutes from now', 'Date#relative | relative future | 360 seconds');
+  equal(Date.create('361 minutes from now').relative(), '6 hours from now', 'Date#relative | relative future | minutes');
+  equal(Date.create('360 hours from now').relative(), '2 weeks from now', 'Date#relative | relative future | hours');
+  equal(Date.create('340 days from now').relative(), '11 months from now', 'Date#relative | relative future | 340 days');
+  equal(Date.create('360 days from now').relative(), '1 year from now', 'Date#relative | relative future | 360 days');
+  equal(Date.create('360 weeks from now').relative(), '6 years from now', 'Date#relative | relative future | weeks');
+  equal(Date.create('360 months from now').relative(), '30 years from now', 'Date#relative | relative future | months');
+  equal(Date.create('360 years from now').relative(), '360 years from now', 'Date#relative | relative future | years');
+  equal(Date.create('13 months from now').relative(), '1 year from now', 'Date#relative | relative future | 12 months ago');
+
+  var dyn = function(value, unit, ms, loc) {
+    if(ms < -(1).year()) {
+      return '{Month} {date}, {year}';
+    }
+  }
+  equal(Date.create('2002-02-17').relative(dyn), 'February 17, 2002', 'Date#relative | function that returns a format uses that format');
+  equal(Date.create('45 days ago').relative(dyn), '1 month ago', 'Date#relative | function that returns undefined uses relative format');
+
+
+  d = new Date(2010,7,5,13,45,2,542);
+
+  equal(d.is('nonsense'), false, 'Date#is | nonsense');
+  equal(d.is('August'), true, 'Date#is | August');
+  equal(d.is('August 5th, 2010'), true, 'Date#is | August 5th, 2010');
+  equal(d.is('August 5th, 2010 13:45'), true, 'Date#is | August 5th, 2010, 13:45');
+  equal(d.is('August 5th, 2010 13:45:02'), true, 'Date#is | August 5th 2010, 13:45:02');
+  equal(d.is('August 5th, 2010 13:45:02.542'), true, 'Date#is | August 5th 2010, 13:45:02:542');
+  equal(d.is('September'), false, 'Date#is | September');
+  equal(d.is('August 6th, 2010'), false, 'Date#is | August 6th, 2010');
+  equal(d.is('August 5th, 2010 13:46'), false, 'Date#is | August 5th, 2010, 13:46');
+  equal(d.is('August 5th, 2010 13:45:03'), false, 'Date#is | August 5th 2010, 13:45:03');
+  equal(d.is('August 5th, 2010 13:45:03.543'), false, 'Date#is | August 5th 2010, 13:45:03:543');
+  equal(d.is('July'), false, 'Date#is | July');
+  equal(d.is('August 4th, 2010'), false, 'Date#is | August 4th, 2010');
+  equal(d.is('August 5th, 2010 13:44'), false, 'Date#is | August 5th, 2010, 13:44');
+  equal(d.is('August 5th, 2010 13:45:01'), false, 'Date#is | August 5th 2010, 13:45:01');
+  equal(d.is('August 5th, 2010 13:45:03.541'), false, 'Date#is | August 5th 2010, 13:45:03:541');
+  equal(d.is('2010'), true, 'Date#is | 2010');
+  equal(d.is('today'), false, 'Date#is | today');
+  equal(d.is('now'), false, 'Date#is | now');
+  equal(d.is('weekday'), true, 'Date#is | weekday');
+  equal(d.is('weekend'), false, 'Date#is | weekend');
+  equal(d.is('Thursday'), true, 'Date#is | Thursday');
+  equal(d.is('Friday'), false, 'Date#is | Friday');
+
+  equal(d.is(d), true, 'Date#is | self is true');
+  equal(d.is(new Date(2010,7,5,13,45,2,542)), true, 'Date#is | equal date is true');
+  equal(d.is(new Date()), false, 'Date#is | other dates are not true');
+  equal(d.is(1281015902542 + (offset * 60 * 1000)), true, 'Date#is | timestamps also accepted');
+
+  equal(new Date().is('now', 10), true, 'Date#is | now is now');
+  equal(new Date(2010,7,5,13,42,42,324).is('August 5th, 2010 13:42:42.324'), true, 'Date#is | August 5th, 2010 13:42:42.324');
+  equal(new Date(2010,7,5,13,42,42,324).is('August 5th, 2010 13:42:42.319'), false, 'Date#is | August 5th, 2010 13:42:42.319');
+  equal(new Date(2010,7,5,13,42,42,324).is('August 5th, 2010 13:42:42.325'), false, 'Date#is | August 5th, 2010 13:42:42.325');
+  equal(new Date(2010,7,5,13,42,42,324).is('August 5th, 2010 13:42:42.323'), false, 'Date#is | August 5th, 2010 13:42:42.323');
+
+  equal(new Date(2001, 0).is('the beginning of 2001'), true, 'Date#is | the beginning of 2001');
+  equal(new Date(now.getFullYear(), 2).is('the beginning of March'), true, 'Date#is | the beginning of March');
+  equal(new Date(2001, 11, 31, 23, 59, 59, 999).is('the end of 2001'), true, 'Date#is | the end of 2001');
+  equal(new Date(now.getFullYear(), 2, 31, 23, 59, 59, 999).is('the end of March'), true, 'Date#is | the end of March');
+  equal(new Date(2001, 11, 31).is('the last day of 2001'), true, 'Date#is | the last day of 2001');
+  equal(new Date(now.getFullYear(), 2, 31).is('the last day of March'), true, 'Date#is | the last day of March');
+
+  equal(Date.create('the beginning of the week').is('the beginning of the week'), true, 'Date#is | the beginning of the week is the beginning of the week');
+  equal(Date.create('the end of the week').is('the end of the week'), true, 'Date#is | the end of the week is the end of the week');
+  equal(Date.create('tuesday').is('the beginning of the week'), false, 'Date#is | tuesday is the beginning of the week');
+  equal(Date.create('tuesday').is('the end of the week'), false, 'Date#is | tuesday is the end of the week');
+
+  equal(Date.create('sunday').is('the beginning of the week'), true, 'Date#is | sunday is the beginning of the week');
+  equal(Date.create('sunday').is('the beginning of the week'), true, 'Date#is | sunday is the beginning of the week');
+
+  equal(Date.create('tuesday').is('tuesday'), true, 'Date#is | tuesday is tuesday');
+  equal(Date.create('sunday').is('sunday'), true, 'Date#is | sunday is sunday');
+  equal(Date.create('saturday').is('saturday'), true, 'Date#is | saturday is saturday');
+
+  equal(getDateWithWeekdayAndOffset(0).is('the beginning of the week'), true, 'Date#is | the beginning of the week');
+  equal(getDateWithWeekdayAndOffset(6, 0, 23, 59, 59, 999).is('the end of the week'), true, 'Date#is | the end of the week');
+
+
+
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,15,22,3,1,431)), false, 'Date#is | accuracy | accurate to millisecond by default | 431');
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,15,22,3,1,432)), true, 'Date#is | accuracy |  accurate to millisecond by default | 432');
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,15,22,3,1,433)), false, 'Date#is | accuracy | accurate to millisecond by default | 433');
+
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,15,22,3,1,431), 2), true, 'Date#is | accuracy | accuracy can be overridden | 431');
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,15,22,3,1,432), 2), true, 'Date#is | accuracy | accuracy can be overridden | 432');
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,15,22,3,1,433), 2), true, 'Date#is | accuracy | accuracy can be overridden | 433');
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,15,22,3,1,429), 2), false, 'Date#is | accuracy | accuracy can be overridden but still is constrained');
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,15,22,3,1,435), 2), false, 'Date#is | accuracy | accuracy can be overridden but still is constrained');
+
+
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,15,22,3,1,431), -500), false, 'Date#is | accuracy | negative accuracy reverts to zero');
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,15,22,3,1,432), -500), true, 'Date#is | accuracy | negative accuracy reverts to zero');
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,15,22,3,1,433), -500), false, 'Date#is | accuracy | negative accuracy reverts to zero');
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,15,22,3,1,429), -500), false, 'Date#is | accuracy | negative accuracy reverts to zero');
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,15,22,3,1,435), -500), false, 'Date#is | accuracy | negative accuracy reverts to zero');
+
+
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,15,22,3,1,432), 86400000), true, 'Date#is | accuracy | accurate to a day');
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,15,23,3,1,432), 86400000), true, 'Date#is | accuracy | accurate to a day');
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,15,21,3,1,432), 86400000), true, 'Date#is | accuracy | accurate to a day');
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,14,22,3,1,432), 86400000), true, 'Date#is | accuracy | accurate to a day');
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,16,22,3,1,432), 86400000), true, 'Date#is | accuracy | accurate to a day');
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,14,22,3,1,431), 86400000), false, 'Date#is | accuracy | accurate to a day is still contstrained');
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,16,22,3,1,433), 86400000), false, 'Date#is | accuracy | accurate to a day is still contstrained');
+
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1969,4,14,22,3,2,432), 31536000000), false, 'Date#is | accuracy | 1969 accurate to a year is still contstrained');
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1969,4,15,22,3,1,432), 31536000000), true, 'Date#is | accuracy | 1969 accurate to a year');
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1970,4,15,22,3,1,432), 31536000000), true, 'Date#is | accuracy | 1970 accurate to a year');
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1971,4,15,22,3,1,432), 31536000000), true, 'Date#is | accuracy | 1971 accurate to a year');
+  equal(new Date(1970,4,15,22,3,1,432).is(new Date(1971,4,16,22,3,1,432), 31536000000), false, 'Date#is | accuracy | 1971 accurate to a year is still contstrained');
+
+
+
+
+
+  // Note that relative #is formats can only be considered to be accurate to within a few milliseconds
+  // to avoid complications rising from the date being created momentarily after the function is called.
+  equal(getRelativeDate(null,null,null,null,null,null, -5).is('3 milliseconds ago'), false, 'Date#is | 3 milliseconds ago is accurate to milliseconds');
+  equal(getRelativeDate(null,null,null,null,null,null, -5).is('5 milliseconds ago', 5), true, 'Date#is | 5 milliseconds ago is accurate to milliseconds');
+  equal(getRelativeDate(null,null,null,null,null,null, -5).is('7 milliseconds ago'), false, 'Date#is | 7 milliseconds ago is accurate to milliseconds');
+
+  equal(getRelativeDate(null,null,null,null,null,-5).is('4 seconds ago'), false, 'Date#is | 4 seconds ago is not 5 seconds ago');
+  equal(getRelativeDate(null,null,null,null,null,-5).is('5 seconds ago'), true, 'Date#is | 5 seconds ago is 5 seconds ago');
+  equal(getRelativeDate(null,null,null,null,null,-5).is('6 seconds ago'), false, 'Date#is | 6 seconds ago is not 5 seconds ago');
+  equal(getRelativeDate(null,null,null,null,null,-5).is('7 seconds ago'), false, 'Date#is | 7 seconds ago is not 5 seconds ago');
+
+  equal(getRelativeDate(null,null,null,null,-5).is('4 minutes ago'), false, 'Date#is | 4 minutes ago is not 5 minutes ago');
+  equal(getRelativeDate(null,null,null,null,-5).is('5 minutes ago'), true, 'Date#is | 5 minutes ago is 5 minutes ago');
+  equal(getRelativeDate(null,null,null,null,-5).is('6 minutes ago'), false, 'Date#is | 6 minutes ago is not 5 minutes ago');
+  equal(getRelativeDate(null,null,null,null,-5).is('7 minutes ago'), false, 'Date#is | 7 minutes ago is not 5 minutes ago');
+
+  equal(getRelativeDate(null,null,null,-5).is('4 hours ago'), false, 'Date#is | 4 hours ago is not 5 hours ago');
+  equal(getRelativeDate(null,null,null,-5).is('5 hours ago'), true, 'Date#is | 5 hours ago is 5 hours ago');
+  equal(getRelativeDate(null,null,null,-5, 15).is('5 hours ago'), true, 'Date#is | 5:15 hours ago is still 5 hours ago');
+  equal(getRelativeDate(null,null,null,-5).is('6 hours ago'), false, 'Date#is | 6 hours ago is not 5 hours ago');
+  equal(getRelativeDate(null,null,null,-5).is('7 hours ago'), false, 'Date#is | 7 hours ago is not 5 hours ago');
+
+  equal(getRelativeDate(null,null,-5).is('4 days ago'), false, 'Date#is | 4 days ago is not 5 days ago');
+  equal(getRelativeDate(null,null,-5).is('5 days ago'), true, 'Date#is | 5 days ago is 5 hours ago');
+  equal(getRelativeDate(null,null,-5).is('6 days ago'), false, 'Date#is | 6 days ago is not 5 days ago');
+  equal(getRelativeDate(null,null,-5).is('7 days ago'), false, 'Date#is | 7 days ago is not 5 days ago');
+
+  equal(getRelativeDate(null,-5).is('4 months ago'), false, 'Date#is | 4 months ago is not 5 months ago');
+  equal(getRelativeDate(null,-5).is('5 months ago'), true, 'Date#is | 5 months ago is 5 months ago');
+  equal(getRelativeDate(null,-5).is('6 months ago'), false, 'Date#is | 6 months ago is not 5 months ago');
+  equal(getRelativeDate(null,-5).is('7 months ago'), false, 'Date#is | 7 months ago is accurate to months');
+
+  equal(getRelativeDate(-5).is('4 years ago'), false, 'Date#is | 4 years ago is not 5 years ago');
+  equal(getRelativeDate(-5).is('5 years ago'), true, 'Date#is | 5 years ago is 5 years ago');
+  equal(getRelativeDate(-5).is('6 years ago'), false, 'Date#is | 6 years ago is not 5 years ago');
+  equal(getRelativeDate(-5).is('7 years ago'), false, 'Date#is | 7 years ago is not 5 years ago');
+
+
+
+  equal(Date.create('tomorrow').is('future'), true, 'Date#is | tomorrow is the future');
+  equal(Date.create('tomorrow').is('past'), false, 'Date#is | tomorrow is the past');
+
+  equal(new Date().is('future'), false, 'Date#is | now is the future');
+
+  // now CAN be in the past if there is any lag between when the dates are
+  // created, so give this a bit of a buffer...
+  equal(new Date().advance({ milliseconds: 5 }).is('past', 5), false, 'Date#is | now is the past');
+
+  equal(Date.create('yesterday').is('future'), false, 'Date#is | yesterday is the future');
+  equal(Date.create('yesterday').is('past'), true, 'Date#is | yesterday is the past');
+
+  equal(Date.create('monday').is('weekday'), true, 'Date#is | monday is a weekday');
+  equal(Date.create('monday').is('weekend'), false, 'Date#is | monday is a weekend');
+
+  equal(Date.create('friday').is('weekday'), true, 'Date#is | friday is a weekday');
+  equal(Date.create('friday').is('weekend'), false, 'Date#is | friday is a weekend');
+
+  equal(Date.create('saturday').is('weekday'), false, 'Date#is | saturday is a weekday');
+  equal(Date.create('saturday').is('weekend'), true, 'Date#is | saturday is a weekend');
+
+  equal(Date.create('sunday').is('weekday'), false, 'Date#is | sunday is a weekday');
+  equal(Date.create('sunday').is('weekend'), true, 'Date#is | sunday is a weekend');
+
+
+
+  equal(new Date(2001,5,4,12,22,34,445).is(new Date(2001,5,4,12,22,34,445)), true, 'Date#is | straight dates passed in are accurate to the millisecond');
+  equal(new Date(2001,5,4,12,22,34,445).is(new Date(2001,5,4,12,22,34,444)), false, 'Date#is | straight dates passed in are accurate to the millisecond');
+  equal(new Date(2001,5,4,12,22,34,445).is(new Date(2001,5,4,12,22,34,446)), false, 'Date#is | straight dates passed in are accurate to the millisecond');
+
+  equal(Date.create('3 hours ago').is('now', 'bloopie'), false, 'Date#is | does not die on string-based precision');
+
+
+  equal(Date.create('2008').isLeapYear(), true, 'Date#leapYear | 2008');
+  equal(Date.create('2009').isLeapYear(), false, 'Date#leapYear | 2009');
+  equal(Date.create('2010').isLeapYear(), false, 'Date#leapYear | 2010');
+  equal(Date.create('2011').isLeapYear(), false, 'Date#leapYear | 2011');
+  equal(Date.create('2012').isLeapYear(), true, 'Date#leapYear | 2012');
+  equal(Date.create('2016').isLeapYear(), true, 'Date#leapYear | 2016');
+  equal(Date.create('2020').isLeapYear(), true, 'Date#leapYear | 2020');
+  equal(Date.create('2021').isLeapYear(), false, 'Date#leapYear | 2021');
+  equal(Date.create('1600').isLeapYear(), true, 'Date#leapYear | 1600');
+  equal(Date.create('1700').isLeapYear(), false, 'Date#leapYear | 1700');
+  equal(Date.create('1800').isLeapYear(), false, 'Date#leapYear | 1800');
+  equal(Date.create('1900').isLeapYear(), false, 'Date#leapYear | 1900');
+  equal(Date.create('2000').isLeapYear(), true, 'Date#leapYear | 2000');
+
+
+  d = new Date(2010,7,5,13,45,2,542);
+
+  equal(d.getISOWeek(), 31, 'Date#getISOWeek | basic August 5th, 2010');
+  dateEqual(d, new Date(2010,7,5,13,45,2,542), 'Date#getISOWeek | should not modify the date');
+
+  equal(new Date(2010, 0, 1).getISOWeek(), 53, 'Date#getISOWeek | January 1st, 2010');
+  equal(new Date(2010, 0, 6).getISOWeek(), 1, 'Date#getISOWeek | January 6th, 2010');
+  equal(new Date(2010, 0, 7).getISOWeek(), 1, 'Date#getISOWeek | January 7th, 2010');
+  equal(new Date(2010, 0, 7, 23, 59, 59, 999).getISOWeek(), 1, 'Date#getISOWeek | January 7th, 2010 h23:59:59.999');
+  equal(new Date(2010, 0, 8).getISOWeek(), 1, 'Date#getISOWeek | January 8th, 2010');
+  equal(new Date(2010, 3, 15).getISOWeek(), 15, 'Date#getISOWeek | April 15th, 2010');
+
+  d = new Date(2010,7,5,13,45,2,542).utc();
+
+  equal(d.getISOWeek(), d.getTimezoneOffset() > 615 ? 32 : 31, 'Date#getISOWeek | utc | basic');
+  equal(new Date(2010, 0, 1).getISOWeek(), 53, 'Date#getISOWeek | utc | January 1st UTC is actually 2009');
+  equal(new Date(2010, 0, 6).getISOWeek(), 1, 'Date#getISOWeek | utc | January 6th');
+  equal(new Date(2010, 0, 7).getISOWeek(), 1, 'Date#getISOWeek | utc | January 7th');
+  equal(new Date(2010, 0, 7, 23, 59, 59, 999).getISOWeek(), 1, 'Date#getISOWeek | utc | January 7th 23:59:59.999');
+  equal(new Date(2010, 0, 8).getISOWeek(), 1, 'Date#getISOWeek | utc | January 8th');
+  equal(new Date(2010, 3, 15).getISOWeek(), 15, 'Date#getISOWeek | utc | April 15th');
+
+
+  d = new Date(2010,7,5,13,45,2,542);
+
+  equal(new Date(2010,7,5,13,45,2,543).millisecondsSince(d), 1, 'Date#millisecondsSince | 1 milliseconds since');
+  equal(new Date(2010,7,5,13,45,2,541).millisecondsUntil(d), 1, 'Date#millisecondsUntil | 1 milliseconds until');
+  equal(new Date(2010,7,5,13,45,3,542).secondsSince(d), 1, 'Date#secondsSince | 1 seconds since');
+  equal(new Date(2010,7,5,13,45,1,542).secondsUntil(d), 1, 'Date#secondsUntil | 1 seconds until');
+  equal(new Date(2010,7,5,13,46,2,542).minutesSince(d), 1, 'Date#minutesSince | 1 minutes since');
+  equal(new Date(2010,7,5,13,44,2,542).minutesUntil(d), 1, 'Date#minutesUntil | 1 minutes until');
+  equal(new Date(2010,7,5,14,45,2,542).hoursSince(d), 1, 'Date#hoursSince | 1 hours since');
+  equal(new Date(2010,7,5,12,45,2,542).hoursUntil(d), 1, 'Date#hoursUntil | 1 hours until');
+  equal(new Date(2010,7,6,13,45,2,542).daysSince(d), 1, 'Date#daysSince | 1 days since');
+  equal(new Date(2010,7,4,13,45,2,542).daysUntil(d), 1, 'Date#daysUntil | 1 days until');
+  equal(new Date(2010,7,12,13,45,2,542).weeksSince(d), 1, 'Date#weeksSince | 1 weeks since');
+  equal(new Date(2010,6,29,13,45,2,542).weeksUntil(d), 1, 'Date#weeksUntil | 1 weeks until');
+  equal(new Date(2010,8,5,13,45,2,542).monthsSince(d), 1, 'Date#monthsSince | 1 months since');
+  equal(new Date(2010,6,5,13,45,2,542).monthsUntil(d), 1, 'Date#monthsUntil | 1 months until');
+  equal(new Date(2011,7,5,13,45,2,542).yearsSince(d), 1, 'Date#yearsSince | 1 years since');
+  equal(new Date(2009,7,5,13,45,2,542).yearsUntil(d), 1, 'Date#yearsUntil | 1 years until');
+
+
+  equal(new Date(2011,7,5,13,45,2,542).millisecondsSince(d), 31536000000, 'Date#millisecondsSince | milliseconds since last year');
+  equal(new Date(2011,7,5,13,45,2,542).millisecondsUntil(d), -31536000000, 'Date#millisecondsUntil | milliseconds until last year');
+  equal(new Date(2011,7,5,13,45,2,542).secondsSince(d), 31536000, 'Date#secondsSince | seconds since last year');
+  equal(new Date(2011,7,5,13,45,2,542).secondsUntil(d), -31536000, 'Date#secondsUntil | seconds until last year');
+  equal(new Date(2011,7,5,13,45,2,542).minutesSince(d), 525600, 'Date#minutesSince | minutes since last year');
+  equal(new Date(2011,7,5,13,45,2,542).minutesUntil(d), -525600, 'Date#minutesUntil | minutes until last year');
+  equal(new Date(2011,7,5,13,45,2,542).hoursSince(d), 8760, 'Date#hoursSince | hours since last year');
+  equal(new Date(2011,7,5,13,45,2,542).hoursUntil(d), -8760, 'Date#hoursUntil | hours until last year');
+  equal(new Date(2011,7,5,13,45,2,542).daysSince(d), 365, 'Date#daysSince | days since last year');
+  equal(new Date(2011,7,5,13,45,2,542).daysUntil(d), -365, 'Date#daysUntil | days until last year');
+  equal(new Date(2011,7,5,13,45,2,542).weeksSince(d), 52, 'Date#weeksSince | weeks since last year');
+  equal(new Date(2011,7,5,13,45,2,542).weeksUntil(d), -52, 'Date#weeksUntil | weeks until last year');
+  equal(new Date(2011,7,5,13,45,2,542).monthsSince(d), 12, 'Date#monthsSince | months since last year');
+  equal(new Date(2011,7,5,13,45,2,542).monthsUntil(d), -12, 'Date#monthsUntil | months until last year');
+  equal(new Date(2011,7,5,13,45,2,542).yearsSince(d), 1, 'Date#yearsSince | years since last year');
+  equal(new Date(2011,7,5,13,45,2,542).yearsUntil(d), -1, 'Date#yearsUntil | years until last year');
+
+
+
+  equal(new Date(2010,7,5,13,45,2,543).millisecondsFromNow(d), 1, 'Date#millisecondsFromNow | FromNow alias | milliseconds');
+  equal(new Date(2010,7,5,13,45,2,541).millisecondsAgo(d), 1, 'Date#millisecondsAgo | from now alias | milliseconds');
+  equal(new Date(2010,7,5,13,45,3,542).secondsFromNow(d), 1, 'Date#secondsFromNow | FromNow alias | seconds');
+  equal(new Date(2010,7,5,13,45,1,542).secondsAgo(d), 1, 'Date#secondsAgo | Ago alias | seconds');
+  equal(new Date(2010,7,5,13,46,2,542).minutesFromNow(d), 1, 'Date#minutesFromNow | FromNow alias | minutes');
+  equal(new Date(2010,7,5,13,44,2,542).minutesAgo(d), 1, 'Date#minutesAgo | Ago alias | minutes');
+  equal(new Date(2010,7,5,14,45,2,542).hoursFromNow(d), 1, 'Date#hoursFromNow | FromNow alias | hours');
+  equal(new Date(2010,7,5,12,45,2,542).hoursAgo(d), 1, 'Date#hoursAgo | Ago alias | hours');
+  equal(new Date(2010,7,6,13,45,2,542).daysFromNow(d), 1, 'Date#daysFromNow | FromNow alias | days');
+  equal(new Date(2010,7,4,13,45,2,542).daysAgo(d), 1, 'Date#daysAgo | Ago alias | days');
+  equal(new Date(2010,7,12,13,45,2,542).weeksFromNow(d), 1, 'Date#weeksFromNow | FromNow alias | weeks');
+  equal(new Date(2010,6,29,13,45,2,542).weeksAgo(d), 1, 'Date#weeksAgo | Ago alias | weeks');
+  equal(new Date(2010,8,5,13,45,2,542).monthsFromNow(d), 1, 'Date#monthsFromNow | FromNow alias | months');
+  equal(new Date(2010,6,5,13,45,2,542).monthsAgo(d), 1, 'Date#monthsAgo | Ago alias | months');
+  equal(new Date(2011,7,5,13,45,2,542).yearsFromNow(d), 1, 'Date#yearsFromNow | FromNow alias | years');
+  equal(new Date(2009,7,5,13,45,2,542).yearsAgo(d), 1, 'Date#yearsAgo | Ago alias | years');
+
+  dst = (d.getTimezoneOffset() - new Date(2011, 11, 31).getTimezoneOffset()) * 60 * 1000;
+
+  // Works with Date.create?
+  equal(d.millisecondsSince('the last day of 2011'), -44273697458 + dst, 'Date#millisecondsSince | milliseconds since the last day of 2011');
+  equal(d.millisecondsUntil('the last day of 2011'), 44273697458 - dst, 'Date#millisecondsUntil | milliseconds until the last day of 2011');
+  equal(d.secondsSince('the last day of 2011'), -44273697 + (dst / 1000), 'Date#secondsSince | seconds since the last day of 2011');
+  equal(d.secondsUntil('the last day of 2011'), 44273697 - (dst / 1000), 'Date#secondsUntil | seconds until the last day of 2011');
+  equal(d.minutesSince('the last day of 2011'), -737894 + (dst / 60 / 1000), 'Date#minutesSince | minutes since the last day of 2011');
+  equal(d.minutesUntil('the last day of 2011'), 737894 - (dst / 60 / 1000), 'Date#minutesUntil | minutes until the last day of 2011');
+  equal(d.hoursSince('the last day of 2011'), -12298 + (dst / 60 / 60 / 1000), 'Date#hoursSince | hours since the last day of 2011');
+  equal(d.hoursUntil('the last day of 2011'), 12298 - (dst / 60 / 60 / 1000), 'Date#hoursUntil | hours until the last day of 2011');
+  equal(d.daysSince('the last day of 2011'), -512, 'Date#daysSince | days since the last day of 2011');
+  equal(d.daysUntil('the last day of 2011'), 512, 'Date#daysUntil | days until the last day of 2011');
+  equal(d.weeksSince('the last day of 2011'), -73, 'Date#weeksSince | weeks since the last day of 2011');
+  equal(d.weeksUntil('the last day of 2011'), 73, 'Date#weeksUntil | weeks until the last day of 2011');
+  equal(d.monthsSince('the last day of 2011'), -16, 'Date#monthsSince | months since the last day of 2011');
+  equal(d.monthsUntil('the last day of 2011'), 16, 'Date#monthsUntil | months until the last day of 2011');
+  equal(d.yearsSince('the last day of 2011'), -1, 'Date#yearsSince | years since the last day of 2011');
+  equal(d.yearsUntil('the last day of 2011'), 1, 'Date#yearsUntil | years until the last day of 2011');
+
+
+
+  d = new Date();
+  var offset = d.getTime() - new Date(d).advance({ week: -1 });
+  var since, until;
+
+  // I'm occasionally seeing some REALLY big lags with IE here (up to 500ms), so giving a 1s buffer here.
+  //
+  var msSince = d.millisecondsSince('last week');
+  var msUntil = d.millisecondsUntil('last week');
+  var actualMsSince = Math.round(offset);
+  var actualMsUntil = Math.round(-offset);
+
+  equal((msSince <= actualMsSince + 1000) && (msSince >= actualMsSince - 1000), true, 'Date#millisecondsSince | milliseconds since last week');
+  equal((msUntil <= actualMsUntil + 1000) && (msUntil >= actualMsUntil - 1000), true, 'Date#millisecondsUntil | milliseconds until last week');
+
+  var secSince = d.secondsSince('last week');
+  var secUntil = d.secondsUntil('last week');
+  var actualSecSince = Math.round(offset / 1000);
+  var actualSecUntil = Math.round(-offset / 1000);
+
+  equal((secSince <= actualSecSince + 5) && (secSince >= actualSecSince - 5), true, 'Date#secondsSince | seconds since last week');
+  equal((secUntil <= actualSecUntil + 5) && (secUntil >= actualSecUntil - 5), true, 'Date#secondsUntil | seconds until last week');
+
+  equal(d.minutesSince('last week'), Math.round(offset / 1000 / 60), 'Date#minutesSince | minutes since last week');
+  equal(d.minutesUntil('last week'), Math.round(-offset / 1000 / 60), 'Date#minutesUntil | minutes until last week');
+  equal(d.hoursSince('last week'), Math.round(offset / 1000 / 60 / 60), 'Date#hoursSince | hours since last week');
+  equal(d.hoursUntil('last week'), Math.round(-offset / 1000 / 60 / 60), 'Date#hoursUntil | hours until last week');
+  equal(d.daysSince('last week'), Math.round(offset / 1000 / 60 / 60 / 24), 'Date#daysSince | days since last week');
+  equal(d.daysUntil('last week'), Math.round(-offset / 1000 / 60 / 60 / 24), 'Date#daysUntil | days until last week');
+  equal(d.weeksSince('last week'), Math.round(offset / 1000 / 60 / 60 / 24 / 7), 'Date#weeksSince | weeks since last week');
+  equal(d.weeksUntil('last week'), Math.round(-offset / 1000 / 60 / 60 / 24 / 7), 'Date#weeksUntil | weeks until last week');
+  equal(d.monthsSince('last week'), Math.round(offset / 1000 / 60 / 60 / 24 / 30.4375), 'Date#monthsSince | months since last week');
+  equal(d.monthsUntil('last week'), Math.round(-offset / 1000 / 60 / 60 / 24 / 30.4375), 'Date#monthsUntil | months until last week');
+  equal(d.yearsSince('last week'), Math.round(offset / 1000 / 60 / 60 / 24 / 365.25), 'Date#yearsSince | years since last week');
+  equal(d.yearsUntil('last week'), Math.round(-offset / 1000 / 60 / 60 / 24 / 365.25), 'Date#yearsUntil | years until the last day of 2011');
+
+
+
+  d = new Date('August 5, 2010 13:45:02');
+
+  dateEqual(new Date(d).beginningOfDay(), new Date(2010, 7, 5), 'Date#beginningOfDay');
+  dateEqual(new Date(d).beginningOfWeek(), new Date(2010, 7, 1), 'Date#beginningOfWeek');
+  dateEqual(new Date(d).beginningOfMonth(), new Date(2010, 7), 'Date#beginningOfMonth');
+  dateEqual(new Date(d).beginningOfYear(), new Date(2010, 0), 'Date#beginningOfYear');
+
+  dateEqual(new Date(d).endOfDay(), new Date(2010, 7, 5, 23, 59, 59, 999), 'Date#endOfDay');
+  dateEqual(new Date(d).endOfWeek(), new Date(2010, 7, 7, 23, 59, 59, 999), 'Date#endOfWeek');
+  dateEqual(new Date(d).endOfMonth(), new Date(2010, 7, 31, 23, 59, 59, 999), 'Date#endOfMonth');
+  dateEqual(new Date(d).endOfYear(), new Date(2010, 11, 31, 23, 59, 59, 999), 'Date#endOfYear');
+
+
+  d = new Date('January 1, 1979 01:33:42');
+
+  dateEqual(new Date(d).beginningOfDay(), new Date(1979, 0, 1), 'Date#beginningOfDay | January 1, 1979');
+  dateEqual(new Date(d).beginningOfWeek(), new Date(1978, 11, 31), 'Date#beginningOfWeek | January 1, 1979');
+  dateEqual(new Date(d).beginningOfMonth(), new Date(1979, 0), 'Date#beginningOfMonth | January 1, 1979');
+  dateEqual(new Date(d).beginningOfYear(), new Date(1979, 0), 'Date#beginningOfYear | January 1, 1979');
+
+  dateEqual(new Date(d).endOfDay(), new Date(1979, 0, 1, 23, 59, 59, 999), 'Date#endOfDay | January 1, 1979');
+  dateEqual(new Date(d).endOfWeek(), new Date(1979, 0, 6, 23, 59, 59, 999), 'Date#endOfWeek | January 1, 1979');
+  dateEqual(new Date(d).endOfMonth(), new Date(1979, 0, 31, 23, 59, 59, 999), 'Date#endOfMonth | January 1, 1979');
+  dateEqual(new Date(d).endOfYear(), new Date(1979, 11, 31, 23, 59, 59, 999), 'Date#endOfYear | January 1, 1979');
+
+
+  d = new Date('December 31, 1945 01:33:42');
+
+  dateEqual(new Date(d).beginningOfDay(), new Date(1945, 11, 31), 'Date#beginningOfDay | January 1, 1945');
+  dateEqual(new Date(d).beginningOfWeek(), new Date(1945, 11, 30), 'Date#beginningOfWeek | January 1, 1945');
+  dateEqual(new Date(d).beginningOfMonth(), new Date(1945, 11), 'Date#beginningOfMonth | January 1, 1945');
+  dateEqual(new Date(d).beginningOfYear(), new Date(1945, 0), 'Date#beginningOfYear | January 1, 1945');
+
+  dateEqual(new Date(d).endOfDay(), new Date(1945, 11, 31, 23, 59, 59, 999), 'Date#endOfDay | January 1, 1945');
+  dateEqual(new Date(d).endOfWeek(), new Date(1946, 0, 5, 23, 59, 59, 999), 'Date#endOfWeek | January 1, 1945');
+  dateEqual(new Date(d).endOfMonth(), new Date(1945, 11, 31, 23, 59, 59, 999), 'Date#endOfMonth | January 1, 1945');
+  dateEqual(new Date(d).endOfYear(), new Date(1945, 11, 31, 23, 59, 59, 999), 'Date#endOfYear | January 1, 1945');
+
+
+  d = new Date('February 29, 2012 22:15:42');
+
+  dateEqual(new Date(d).beginningOfDay(), new Date(2012, 1, 29), 'Date#beginningOfDay | February 29, 2012');
+  dateEqual(new Date(d).beginningOfWeek(), new Date(2012, 1, 26), 'Date#beginningOfWeek | February 29, 2012');
+  dateEqual(new Date(d).beginningOfMonth(), new Date(2012, 1), 'Date#beginningOfMonth | February 29, 2012');
+  dateEqual(new Date(d).beginningOfYear(), new Date(2012, 0), 'Date#beginningOfYear | February 29, 2012');
+
+  dateEqual(new Date(d).endOfDay(), new Date(2012, 1, 29, 23, 59, 59, 999), 'Date#endOfDay | February 29, 2012');
+  dateEqual(new Date(d).endOfWeek(), new Date(2012, 2, 3, 23, 59, 59, 999), 'Date#endOfWeek | February 29, 2012');
+  dateEqual(new Date(d).endOfMonth(), new Date(2012, 1, 29, 23, 59, 59, 999), 'Date#endOfMonth | February 29, 2012');
+  dateEqual(new Date(d).endOfYear(), new Date(2012, 11, 31, 23, 59, 59, 999), 'Date#endOfYear | February 29, 2012');
+
+  dateEqual(new Date(d).beginningOfDay(true), new Date(2012, 1, 29), 'Date#beginningOfDay | reset if true | February 29, 2012');
+  dateEqual(new Date(d).beginningOfWeek(true), new Date(2012, 1, 26), 'Date#beginningOfWeek | reset if true | February 29, 2012');
+  dateEqual(new Date(d).beginningOfMonth(true), new Date(2012, 1), 'Date#beginningOfMonth | reset if true | February 29, 2012');
+  dateEqual(new Date(d).beginningOfYear(true), new Date(2012, 0), 'Date#beginningOfYear | reset if true | February 29, 2012');
+
+  dateEqual(new Date(d).endOfDay(true), new Date(2012, 1, 29, 23, 59, 59, 999), 'Date#endOfDay | reset if true | February 29, 2012');
+  dateEqual(new Date(d).endOfWeek(true), new Date(2012, 2, 3, 23, 59, 59, 999), 'Date#endOfWeek | reset if true | February 29, 2012');
+  dateEqual(new Date(d).endOfMonth(true), new Date(2012, 1, 29, 23, 59, 59, 999), 'Date#endOfMonth | reset if true | February 29, 2012');
+  dateEqual(new Date(d).endOfYear(true), new Date(2012, 11, 31, 23, 59, 59, 999), 'Date#endOfYear | reset if true | February 29, 2012');
+
+
+  d = Date.utc.create('January 1, 2010 02:00:00', 'en').utc(true);
+
+  dateEqual(d.clone().beginningOfDay(), new Date(Date.UTC(2010, 0)), 'Date#beginningOfDay | utc');
+  dateEqual(d.clone().beginningOfWeek(), new Date(Date.UTC(2009, 11, 27)), 'Date#beginningOfWeek | utc');
+  dateEqual(d.clone().beginningOfMonth(), new Date(Date.UTC(2010, 0)), 'Date#beginningOfMonth | utc');
+  dateEqual(d.clone().beginningOfYear(), new Date(Date.UTC(2010, 0)), 'Date#beginningOfYear | utc');
+
+  dateEqual(d.clone().endOfDay(), new Date(Date.UTC(2010, 0, 1, 23, 59, 59, 999)), 'Date#endOfDay | utc');
+  dateEqual(d.clone().endOfWeek(), new Date(Date.UTC(2010, 0, 2, 23, 59, 59, 999)), 'Date#endOfWeek | utc');
+  dateEqual(d.clone().endOfMonth(), new Date(Date.UTC(2010, 0, 31, 23, 59, 59, 999)), 'Date#endOfMonth | utc');
+  dateEqual(d.clone().endOfYear(), new Date(Date.UTC(2010, 11, 31, 23, 59, 59, 999)), 'Date#endOfYear | utc');
+
+
+
+  d = new Date('February 29, 2012 22:15:42');
+
+
+  dateEqual(new Date(d).addMilliseconds(12), new Date(2012, 1, 29, 22, 15, 42, 12), 'Date#addMilliseconds | 12');
+  dateEqual(new Date(d).addSeconds(12), new Date(2012, 1, 29, 22, 15, 54), 'Date#addSeconds | 12');
+  dateEqual(new Date(d).addMinutes(12), new Date(2012, 1, 29, 22, 27, 42), 'Date#addMinutes | 12');
+  dateEqual(new Date(d).addHours(12), new Date(2012, 2, 1, 10, 15, 42), 'Date#addHours | 12');
+  dateEqual(new Date(d).addDays(12), new Date(2012, 2, 12, 22, 15, 42), 'Date#addDays | 12');
+  dateEqual(new Date(d).addWeeks(12), new Date(2012, 4, 23, 22, 15, 42), 'Date#addWeeks | 12');
+  dateEqual(new Date(d).addMonths(12), new Date(2013, 1, 28, 22, 15, 42), 'Date#addMonths | 12');
+  dateEqual(new Date(d).addYears(12), new Date(2024, 1, 29, 22, 15, 42), 'Date#addYears | 12');
+
+
+  dateEqual(new Date(d).addMilliseconds(-12), new Date(2012, 1, 29, 22, 15, 41, 988), 'Date#addMilliseconds | negative | 12');
+  dateEqual(new Date(d).addSeconds(-12), new Date(2012, 1, 29, 22, 15, 30), 'Date#addSeconds | negative | 12');
+  dateEqual(new Date(d).addMinutes(-12), new Date(2012, 1, 29, 22, 3, 42), 'Date#addMinutes | negative | 12');
+  dateEqual(new Date(d).addHours(-12), new Date(2012, 1, 29, 10, 15, 42), 'Date#addHours | negative | 12');
+  dateEqual(new Date(d).addDays(-12), new Date(2012, 1, 17, 22, 15, 42), 'Date#addDays | negative | 12');
+  dateEqual(new Date(d).addWeeks(-12), new Date(2011, 11, 7, 22, 15, 42), 'Date#addWeeks | negative | 12');
+  dateEqual(new Date(d).addMonths(-12), new Date(2011, 1, 28, 22, 15, 42), 'Date#addMonths | negative | 12');
+  dateEqual(new Date(d).addYears(-12), new Date(2000, 1, 29, 22, 15, 42), 'Date#addYears | negative | 12');
+
+
+  dateEqual(Date.utc.create('February 29, 2012 22:15:42', 'en'), new Date(Date.UTC(2012, 1, 29, 22, 15, 42)), 'Date#create | utc');
+  dateEqual(Date.utc.create('2012-05-31', 'en'), new Date(Date.UTC(2012, 4, 31)), 'Date#create | utc | 2012-05-31');
+  dateEqual(Date.utc.create('1998-02-23 11:54:32', 'en'), new Date(Date.UTC(1998,1,23,11,54,32)), 'Date#create | utc | enumerated params');
+  dateEqual(Date.utc.create({ year: 1998, month: 1, day: 23, hour: 11 }, 'en'), new Date(Date.UTC(1998,1,23,11)), 'Date#create | object');
+  dateEqual(Date.utc.create('08-25-1978 11:42:32.488am', 'en'), new Date(Date.UTC(1978, 7, 25, 11, 42, 32, 488)), 'Date#create | full with ms');
+  dateEqual(Date.utc.create('1994-11-05T13:15:30Z', 'en'), new Date(Date.UTC(1994, 10, 5, 13, 15, 30)), 'Date#create | utc | "Z" is still utc');
+  dateEqual(Date.utc.create('two days ago', 'en'), Date.create('two days ago'), 'Date#create | utc | relative dates are not UTC');
+
+
+
+  d = new Date('February 29, 2012 22:15:42');
+
+  var yearZero = new Date(2000, 0);
+  yearZero.setFullYear(0);
+
+  dateEqual(d.clone().reset(),               new Date(2012, 1, 29), 'Date#resetTime | Clears time');
+
+  dateEqual(d.clone().reset('years'),        yearZero, 'Date#reset | years');
+  dateEqual(d.clone().reset('months'),       new Date(2012, 0), 'Date#reset | months');
+  dateEqual(d.clone().reset('weeks'),        new Date(2012, 0, 4), 'Date#reset | weeks | ISO8601');
+  dateEqual(d.clone().reset('days'),         new Date(2012, 1, 1), 'Date#reset | days');
+  dateEqual(d.clone().reset('hours'),        new Date(2012, 1, 29), 'Date#reset | hours');
+  dateEqual(d.clone().reset('minutes'),      new Date(2012, 1, 29, 22), 'Date#reset | minutes');
+  dateEqual(d.clone().reset('seconds'),      new Date(2012, 1, 29, 22, 15), 'Date#reset | seconds');
+  dateEqual(d.clone().reset('milliseconds'), new Date(2012, 1, 29, 22, 15, 42), 'Date#reset | milliseconds');
+
+  dateEqual(d.clone().reset('year'),        yearZero, 'Date#reset | year');
+  dateEqual(d.clone().reset('month'),       new Date(2012, 0), 'Date#reset | month');
+  dateEqual(d.clone().reset('week'),        new Date(2012, 0, 4), 'Date#reset | weeks | ISO8601');
+  dateEqual(d.clone().reset('day'),         new Date(2012, 1, 1), 'Date#reset | day');
+  dateEqual(d.clone().reset('hour'),        new Date(2012, 1, 29), 'Date#reset | hour');
+  dateEqual(d.clone().reset('minute'),      new Date(2012, 1, 29, 22), 'Date#reset | minute');
+  dateEqual(d.clone().reset('second'),      new Date(2012, 1, 29, 22, 15), 'Date#reset | second');
+  dateEqual(d.clone().reset('millisecond'), new Date(2012, 1, 29, 22, 15, 42), 'Date#reset | millisecond');
+
+  dateEqual(d.clone().reset('date'),         new Date(2012, 1, 1), 'Date#reset | date');
+  dateEqual(d.clone().reset('flegh'), new Date(2012, 1, 29, 22, 15, 42), 'Date#reset | an unknown string will do nothing');
+
+  dateEqual(d.clone().addDays(5, true), new Date(2012, 2, 5), 'Date#addDays | can also reset the time');
+
+
+  equal(now.isYesterday(), false, 'Date#isYesterday');
+  equal(now.isToday(), true, 'Date#isToday');
+  equal(now.isTomorrow(), false, 'Date#isTomorrow');
+  equal(now.isWeekday(), now.getDay() !== 0 && now.getDay() !== 6, 'Date#isWeekday');
+  equal(now.isWeekend(), now.getDay() === 0 || now.getDay() === 6, 'Date#isWeekend');
+  equal(now.isFuture(), false, 'Date#isFuture');
+  equal(now.isPast(), true, 'Date#isPast');
+
+
+  d = new Date('February 29, 2008 22:15:42');
+
+  equal(d.isYesterday(), false, 'Date#isYesterday | February 29, 2008');
+  equal(d.isToday(), false, 'Date#isToday | February 29, 2008');
+  equal(d.isTomorrow(), false, 'Date#isTomorrow | February 29, 2008');
+  equal(d.isWeekday(), true, 'Date#isWeekday | February 29, 2008');
+  equal(d.isWeekend(), false, 'Date#isWeekend | February 29, 2008');
+  equal(d.isFuture(), false, 'Date#isFuture | February 29, 2008');
+  equal(d.isPast(), true, 'Date#isPast | February 29, 2008');
+
+
+  d.setFullYear(thisYear + 2);
+
+  equal(d.isYesterday(), false, 'Date#isYesterday | 2 years from now');
+  equal(d.isToday(), false, 'Date#isToday | 2 years from now');
+  equal(d.isTomorrow(), false, 'Date#isTomorrow | 2 years from now');
+  equal(d.isFuture(), true, 'Date#isFuture | 2 years from now');
+  equal(d.isPast(), false, 'Date#isPast | 2 years from now');
+
+
+
+
+  equal(new Date().isLastWeek(), false, 'Date#isLastWeek | now');
+  equal(new Date().isThisWeek(), true, 'Date#isThisWeek | now');
+  equal(new Date().isNextWeek(), false, 'Date#isNextWeek | now');
+  equal(new Date().isLastMonth(), false, 'Date#isLastMonth | now');
+  equal(new Date().isThisMonth(), true, 'Date#isThisMonth | now');
+  equal(new Date().isNextMonth(), false, 'Date#isNextMonth | now');
+  equal(new Date().isLastYear(), false, 'Date#isLastYear | now');
+  equal(new Date().isThisYear(), true, 'Date#isThisYear | now');
+  equal(new Date().isNextYear(), false, 'Date#isNextYear | now');
+
+  equal(getRelativeDate(null, null, -7).isLastWeek(), true, 'Date#isLastWeek | last week');
+  equal(getRelativeDate(null, null, -7).isThisWeek(), false, 'Date#isThisWeek | last week');
+  equal(getRelativeDate(null, null, -7).isNextWeek(), false, 'Date#isNextWeek | last week');
+
+  equal(getRelativeDate(null, null, 7).isLastWeek(), false, 'Date#isLastWeek | next week');
+  equal(getRelativeDate(null, null, 7).isThisWeek(), false, 'Date#isThisWeek | next week');
+  equal(getRelativeDate(null, null, 7).isNextWeek(), true, 'Date#isNextWeek | next week');
+
+  equal(getDateWithWeekdayAndOffset(0).isLastWeek(), false, 'Date#isLastWeek | this week sunday is last week');
+  equal(getDateWithWeekdayAndOffset(0).isThisWeek(), true, 'Date#isThisWeek | this week sunday is this week');
+  equal(getDateWithWeekdayAndOffset(0).isNextWeek(), false, 'Date#isNextWeek | this week sunday is next week');
+
+  equal(getDateWithWeekdayAndOffset(5).isLastWeek(), false, 'Date#isLastWeek | friday is last week');
+  equal(getDateWithWeekdayAndOffset(5).isThisWeek(), true, 'Date#isThisWeek | friday is this week');
+  equal(getDateWithWeekdayAndOffset(5).isNextWeek(), false, 'Date#isNextWeek | friday is next week');
+
+  equal(getDateWithWeekdayAndOffset(6).isLastWeek(), false, 'Date#isLastWeek | satuday is last week');
+  equal(getDateWithWeekdayAndOffset(6).isThisWeek(), true, 'Date#isThisWeek | satuday is this week');
+  equal(getDateWithWeekdayAndOffset(6).isNextWeek(), false, 'Date#isNextWeek | satuday is next week');
+
+  equal(Date.create('last sunday').isLastWeek(), true, 'Date#isLastWeek | last sunday');
+  equal(Date.create('last sunday').isThisWeek(), false, 'Date#isThisWeek | last sunday');
+  equal(Date.create('last sunday').isNextWeek(), false, 'Date#isNextWeek | last sunday');
+
+  equal(Date.create('next sunday').isLastWeek(), false, 'Date#isLastWeek | next sunday');
+  equal(Date.create('next sunday').isThisWeek(), false, 'Date#isThisWeek | next sunday');
+  equal(Date.create('next sunday').isNextWeek(), true, 'Date#isNextWeek | next sunday');
+
+  equal(Date.create('last monday').isLastWeek(), true, 'Date#isLastWeek | last monday');
+  equal(Date.create('last monday').isThisWeek(), false, 'Date#isThisWeek | last monday');
+  equal(Date.create('last monday').isNextWeek(), false, 'Date#isNextWeek | last monday');
+
+  equal(Date.create('next monday').isLastWeek(), false, 'Date#isLastWeek | next monday');
+  equal(Date.create('next monday').isThisWeek(), false, 'Date#isThisWeek | next monday');
+  equal(Date.create('next monday').isNextWeek(), true, 'Date#isNextWeek | next monday');
+
+  equal(Date.create('last friday').isLastWeek(), true, 'Date#isLastWeek | last friday');
+  equal(Date.create('last friday').isThisWeek(), false, 'Date#isThisWeek | last friday');
+  equal(Date.create('last friday').isNextWeek(), false, 'Date#isNextWeek | last friday');
+
+  equal(Date.create('next friday').isLastWeek(), false, 'Date#isLastWeek | next friday');
+  equal(Date.create('next friday').isThisWeek(), false, 'Date#isThisWeek | next friday');
+  equal(Date.create('next friday').isNextWeek(), true, 'Date#isNextWeek | next friday');
+
+  equal(Date.create('last saturday').isLastWeek(), true, 'Date#isLastWeek | last saturday');
+  equal(Date.create('last saturday').isThisWeek(), false, 'Date#isThisWeek | last saturday');
+  equal(Date.create('last saturday').isNextWeek(), false, 'Date#isNextWeek | last saturday');
+
+  equal(Date.create('next saturday').isLastWeek(), false, 'Date#isLastWeek | next saturday');
+  equal(Date.create('next saturday').isThisWeek(), false, 'Date#isThisWeek | next saturday');
+  equal(Date.create('next saturday').isNextWeek(), true, 'Date#isNextWeek | next saturday');
+
+  equal(Date.create('the beginning of the week').isLastWeek(), false, 'Date#isLastWeek | the beginning of the week');
+  equal(Date.create('the beginning of the week').isThisWeek(), true, 'Date#isThisWeek | the beginning of the week');
+  equal(Date.create('the beginning of the week').isNextWeek(), false, 'Date#isNextWeek | the beginning of the week');
+
+  equal(Date.create('the beginning of the week').addMinutes(-1).isLastWeek(), true, 'Date#isLastWeek | the beginning of the week - 1 minute');
+  equal(Date.create('the beginning of the week').addMinutes(-1).isThisWeek(), false, 'Date#isThisWeek | the beginning of the week - 1 minute');
+  equal(Date.create('the beginning of the week').addMinutes(-1).isNextWeek(), false, 'Date#isNextWeek | the beginning of the week - 1 minute');
+
+  equal(Date.create('the end of the week').isLastWeek(), false, 'Date#isLastWeek | the end of the week');
+  equal(Date.create('the end of the week').isThisWeek(), true, 'Date#isThisWeek | the end of the week');
+  equal(Date.create('the end of the week').isNextWeek(), false, 'Date#isNextWeek | the end of the week');
+
+  equal(Date.create('the end of the week').addMinutes(1).isLastWeek(), false, 'Date#isLastWeek | the end of the week + 1 minute');
+  equal(Date.create('the end of the week').addMinutes(1).isThisWeek(), false, 'Date#isThisWeek | the end of the week + 1 minute');
+  equal(Date.create('the end of the week').addMinutes(1).isNextWeek(), true, 'Date#isNextWeek | the end of the week + 1 minute');
+
+
+  equal(Date.create('the beginning of last week').isLastWeek(), true, 'Date#isLastWeek | the beginning of last week');
+  equal(Date.create('the beginning of last week').isThisWeek(), false, 'Date#isThisWeek | the beginning of last week');
+  equal(Date.create('the beginning of last week').isNextWeek(), false, 'Date#isNextWeek | the beginning of last week');
+
+  equal(Date.create('the beginning of last week').addMinutes(-1).isLastWeek(), false, 'Date#isLastWeek | the beginning of last week - 1 minute');
+  equal(Date.create('the beginning of last week').addMinutes(-1).isThisWeek(), false, 'Date#isThisWeek | the beginning of last week - 1 minute');
+  equal(Date.create('the beginning of last week').addMinutes(-1).isNextWeek(), false, 'Date#isNextWeek | the beginning of last week - 1 minute');
+
+  equal(Date.create('the end of next week').isLastWeek(), false, 'Date#isLastWeek | the end of next week');
+  equal(Date.create('the end of next week').isThisWeek(), false, 'Date#isThisWeek | the end of next week');
+  equal(Date.create('the end of next week').isNextWeek(), true, 'Date#isNextWeek | the end of next week');
+
+  equal(Date.create('the end of next week').addMinutes(1).isLastWeek(), false, 'Date#isLastWeek | the end of next week + 1 minute');
+  equal(Date.create('the end of next week').addMinutes(1).isThisWeek(), false, 'Date#isThisWeek | the end of next week + 1 minute');
+  equal(Date.create('the end of next week').addMinutes(1).isNextWeek(), false, 'Date#isNextWeek | the end of next week + 1 minute');
+
+  equal(Date.create('the end of last week').isLastWeek(), true, 'Date#isLastWeek | the end of last week');
+  equal(Date.create('the end of last week').isThisWeek(), false, 'Date#isThisWeek | the end of last week');
+  equal(Date.create('the end of last week').isNextWeek(), false, 'Date#isNextWeek | the end of last week');
+
+  equal(Date.create('the end of last week').addMinutes(1).isLastWeek(), false, 'Date#isLastWeek | the end of last week + 1 minute');
+  equal(Date.create('the end of last week').addMinutes(1).isThisWeek(), true, 'Date#isThisWeek | the end of last week + 1 minute');
+  equal(Date.create('the end of last week').addMinutes(1).isNextWeek(), false, 'Date#isNextWeek | the end of last week + 1 minute');
+
+  equal(Date.create('the beginning of next week').isLastWeek(), false, 'Date#isLastWeek | the beginning of next week');
+  equal(Date.create('the beginning of next week').isThisWeek(), false, 'Date#isThisWeek | the beginning of next week');
+  equal(Date.create('the beginning of next week').isNextWeek(), true, 'Date#isNextWeek | the beginning of next week');
+
+  equal(Date.create('the beginning of next week').addMinutes(-1).isLastWeek(), false, 'Date#isLastWeek | the beginning of next week - 1 minute');
+  equal(Date.create('the beginning of next week').addMinutes(-1).isThisWeek(), true, 'Date#isThisWeek | the beginning of next week - 1 minute');
+  equal(Date.create('the beginning of next week').addMinutes(-1).isNextWeek(), false, 'Date#isNextWeek | the beginning of next week - 1 minute');
+
+
+
+
+  equal(new Date(2001, 11, 28).isSunday(), false, 'Date#isSunday');
+  equal(new Date(2001, 11, 28).isMonday(), false, 'Date#isMonday');
+  equal(new Date(2001, 11, 28).isTuesday(), false, 'Date#isTuesday');
+  equal(new Date(2001, 11, 28).isWednesday(), false, 'Date#isWednesday');
+  equal(new Date(2001, 11, 28).isThursday(), false, 'Date#isThursday');
+  equal(new Date(2001, 11, 28).isFriday(), true, 'Date#isFriday');
+  equal(new Date(2001, 11, 28).isSaturday(), false, 'Date#isSaturday');
+
+  equal(new Date(2001, 11, 28).isJanuary(), false, 'Date#isJanuary');
+  equal(new Date(2001, 11, 28).isFebruary(), false, 'Date#isFebruary');
+  equal(new Date(2001, 11, 28).isMarch(), false, 'Date#isMarch');
+  equal(new Date(2001, 11, 28).isApril(), false, 'Date#isApril');
+  equal(new Date(2001, 11, 28).isMay(), false, 'Date#isMay');
+  equal(new Date(2001, 11, 28).isJune(), false, 'Date#isJune');
+  equal(new Date(2001, 11, 28).isJuly(), false, 'Date#isJuly');
+  equal(new Date(2001, 11, 28).isAugust(), false, 'Date#isAugust');
+  equal(new Date(2001, 11, 28).isSeptember(), false, 'Date#isSeptember');
+  equal(new Date(2001, 11, 28).isOctober(), false, 'Date#isOctober');
+  equal(new Date(2001, 11, 28).isNovember(), false, 'Date#isNovember');
+  equal(new Date(2001, 11, 28).isDecember(), true, 'Date#isDecember');
+
+
+
+
+  equal(getRelativeDate(null, -1).isLastWeek(), false, 'Date#isLastWeek | last month');
+  equal(getRelativeDate(null, -1).isThisWeek(), false, 'Date#isThisWeek | last month');
+  equal(getRelativeDate(null, -1).isNextWeek(), false, 'Date#isNextWeek | last month');
+  equal(getRelativeDate(null, -1).isLastMonth(), true, 'Date#isLastMonth | last month');
+  equal(getRelativeDate(null, -1).isThisMonth(), false, 'Date#isThisMonth | last month');
+  equal(getRelativeDate(null, -1).isNextMonth(), false, 'Date#isNextMonth | last month');
+
+  equal(getRelativeDate(null, 1).isLastWeek(), false, 'Date#isLastWeek | next month');
+  equal(getRelativeDate(null, 1).isThisWeek(), false, 'Date#isThisWeek | next month');
+  equal(getRelativeDate(null, 1).isNextWeek(), false, 'Date#isNextWeek | next month');
+  equal(getRelativeDate(null, 1).isLastMonth(), false, 'Date#isLastMonth | next month');
+  equal(getRelativeDate(null, 1).isThisMonth(), false, 'Date#isThisMonth | next month');
+  equal(getRelativeDate(null, 1).isNextMonth(), true, 'Date#isNextMonth | next month');
+
+
+  equal(getRelativeDate(-1).isLastWeek(), false, 'Date#isLastWeek | last year');
+  equal(getRelativeDate(-1).isThisWeek(), false, 'Date#isThisWeek | last year');
+  equal(getRelativeDate(-1).isNextWeek(), false, 'Date#isNextWeek | last year');
+  equal(getRelativeDate(-1).isLastMonth(), false, 'Date#isLastMonth | last year');
+  equal(getRelativeDate(-1).isThisMonth(), false, 'Date#isThisMonth | last year');
+  equal(getRelativeDate(-1).isNextMonth(), false, 'Date#isNextMonth | last year');
+  equal(getRelativeDate(-1).isLastYear(), true, 'Date#isLastYear | last year');
+  equal(getRelativeDate(-1).isThisYear(), false, 'Date#isThisYear | last year');
+  equal(getRelativeDate(-1).isNextYear(), false, 'Date#isNextYear | last year');
+
+  equal(getRelativeDate(1).isLastWeek(), false, 'Date#isLastWeek | next year');
+  equal(getRelativeDate(1).isThisWeek(), false, 'Date#isThisWeek | next year');
+  equal(getRelativeDate(1).isNextWeek(), false, 'Date#isNextWeek | next year');
+  equal(getRelativeDate(1).isLastMonth(), false, 'Date#isLastMonth | next year');
+  equal(getRelativeDate(1).isThisMonth(), false, 'Date#isThisMonth | next year');
+  equal(getRelativeDate(1).isNextMonth(), false, 'Date#isNextMonth | next year');
+  equal(getRelativeDate(1).isLastYear(), false, 'Date#isLastYear | next year');
+  equal(getRelativeDate(1).isThisYear(), false, 'Date#isThisYear | next year');
+  equal(getRelativeDate(1).isNextYear(), true, 'Date#isNextYear | next year');
+
+
+
+  equal(new Date(2001,1,23).isAfter(new Date(2000,1,23)), true, 'Date#isAfter | January 23, 2000');
+  equal(new Date(2001,1,23).isAfter(new Date(2002,1,23)), false, 'Date#isAfter | January 23, 2002');
+
+  equal(new Date(1999,0).isAfter(new Date(1998)), true, 'Date#isAfter | 1999');
+  equal(new Date(1998,2).isAfter(new Date(1998,1)), true, 'Date#isAfter | March, 1998');
+  equal(new Date(1998,1,24).isAfter(new Date(1998,1,23)), true, 'Date#isAfter | February 24, 1998');
+  equal(new Date(1998,1,23,12).isAfter(new Date(1998,1,23,11)), true, 'Date#isAfter | February 23, 1998 12pm');
+  equal(new Date(1998,1,23,11,55).isAfter(new Date(1998,1,23,11,54)), true, 'Date#isAfter | February 23, 1998 11:55am');
+  equal(new Date(1998,1,23,11,54,33).isAfter(new Date(1998,1,23,11,54,32)), true, 'Date#isAfter | February 23, 1998 11:54:33am');
+  equal(new Date(1998,1,23,11,54,32,455).isAfter(new Date(1998,1,23,11,54,32,454)), true, 'Date#isAfter | February 23, 1998 11:54:32.455am');
+
+  equal(new Date(1999,1).isAfter({ year: 1998 }), true, 'Date#isAfter | object | 1999');
+  equal(new Date(1998,2).isAfter({ year: 1998, month: 1 }), true, 'Date#isAfter | object | March, 1998');
+  equal(new Date(1998,1,24).isAfter({ year: 1998, month: 1, day: 23 }), true, 'Date#isAfter | object | February 24, 1998');
+  equal(new Date(1998,1,23,12).isAfter({ year: 1998, month: 1, day: 23, hour: 11 }), true, 'Date#isAfter | object | February 23, 1998 12pm');
+  equal(new Date(1998,1,23,11,55).isAfter({ year: 1998, month: 1, day: 23, hour: 11, minutes: 54 }), true, 'Date#isAfter | object | February 23, 1998 11:55am');
+  equal(new Date(1998,1,23,11,54,33).isAfter({ year: 1998, month: 1, day: 23, hour: 11, minutes: 54, seconds: 32 }), true, 'Date#isAfter | object | February 23, 1998 11:54:33am');
+  equal(new Date(1998,1,23,11,54,32,455).isAfter({ year: 1998, month: 1, day: 23, hour: 11, minutes: 54, seconds: 32, milliseconds: 454 }), true, 'Date#isAfter | object | February 23, 1998 11:54:32.455am');
+
+  equal(new Date(1999,1).isAfter('1998'), true, 'Date#isAfter | string | 1998');
+  equal(new Date(1998,2).isAfter('February, 1998'), true, 'Date#isAfter | string | February, 1998');
+  equal(new Date(1998,1,24).isAfter('February 23, 1998'), true, 'Date#isAfter | string | February 23, 1998');
+  equal(new Date(1998,1,23,12).isAfter('February 23, 1998 11am'), true, 'Date#isAfter | string | February 23, 1998 11pm');
+  equal(new Date(1998,1,23,11,55).isAfter('February 23, 1998 11:54am'), true, 'Date#isAfter | string | February 23, 1998 11:54am');
+  equal(new Date(1998,1,23,11,54,33).isAfter('February 23, 1998 11:54:32am'), true, 'Date#isAfter | string | February 23, 1998 11:54:32am');
+  equal(new Date(1998,1,23,11,54,32,455).isAfter('February 23, 1998 11:54:32.454am'), true, 'Date#isAfter | string | February 23, 1998 11:54:32.454am');
+
+  equal(new Date(1999,5).isAfter('1999'), true, 'Date#isAfter | June 1999 is after implied January 1999');
+  equal(getRelativeDate(1).isAfter('tomorrow'), true, 'Date#isAfter | relative | next year');
+  equal(getRelativeDate(null, 1).isAfter('tomorrow'), true, 'Date#isAfter | relative | next month');
+  equal(getRelativeDate(null, null, 1).isAfter('tomorrow'), true, 'Date#isAfter | relative | tomorrow');
+
+  equal(getDateWithWeekdayAndOffset(0).isAfter('monday'), false, 'Date#isAfter | relative | sunday');
+  equal(getDateWithWeekdayAndOffset(2).isAfter('monday'), true, 'Date#isAfter | relative | tuesday');
+  equal(getDateWithWeekdayAndOffset(0,7).isAfter('monday'), true, 'Date#isAfter | relative | next week sunday');
+  equal(getDateWithWeekdayAndOffset(0,-7).isAfter('monday'), false, 'Date#isAfter | relative | last week sunday');
+  equal(getDateWithWeekdayAndOffset(0).isAfter('the beginning of this week'), false, 'Date#isAfter | relative | the beginning of this week');
+  equal(getDateWithWeekdayAndOffset(0).isAfter('the beginning of last week'), true, 'Date#isAfter | relative | the beginning of last week');
+  equal(getDateWithWeekdayAndOffset(0).isAfter('the end of this week'), false, 'Date#isAfter | relative | the end of this week');
+
+  equal(new Date(2001,1,23).isAfter(new Date(2000,1,24), 24 * 60 * 60 * 1000), true, 'Date#isAfter | buffers work');
+  equal(new Date(1999,1).isAfter({ year: 1999 }), true, 'Date#isAfter | February 1999 is after implied January 1999');
+  equal(new Date(1998,10).isAfter({ year: 1999 }, 90 * 24 * 60 * 60 * 1000), true, 'Date#isAfter | November 1998 is after implied January 1999 with 90 days of margin');
+
+
+
+  equal(new Date(2001,1,23).isBefore(new Date(2000,1,23)), false, 'Date#isBefore | January 23, 2000');
+  equal(new Date(2001,1,23).isBefore(new Date(2002,1,23)), true, 'Date#isBefore | January 23, 2002');
+
+  equal(new Date(1999,0).isBefore(new Date(1998)), false, 'Date#isBefore | 1999');
+  equal(new Date(1998,2).isBefore(new Date(1998,1)), false, 'Date#isBefore | March, 1998');
+  equal(new Date(1998,1,24).isBefore(new Date(1998,1,23)), false, 'Date#isBefore | February 24, 1998');
+  equal(new Date(1998,1,23,12).isBefore(new Date(1998,1,23,11)), false, 'Date#isBefore | February 23, 1998 12pm');
+  equal(new Date(1998,1,23,11,55).isBefore(new Date(1998,1,23,11,54)), false, 'Date#isBefore | February 23, 1998 11:55am');
+  equal(new Date(1998,1,23,11,54,33).isBefore(new Date(1998,1,23,11,54,34)), true, 'Date#isBefore | February 23, 1998 11:54:34am');
+  equal(new Date(1998,1,23,11,54,32,455).isBefore(new Date(1998,1,23,11,54,32,454)), false, 'Date#isBefore | February 23, 1998 11:54:32.455am');
+
+  equal(new Date(1999,1).isBefore({ year: 1998 }), false, 'Date#isBefore | object | 1999');
+  equal(new Date(1998,2).isBefore({ year: 1998, month: 1 }), false, 'Date#isBefore | object | March, 1998');
+  equal(new Date(1998,1,24).isBefore({ year: 1998, month: 1, day: 23 }), false, 'Date#isBefore | object | February 24, 1998');
+  equal(new Date(1998,1,23,12).isBefore({ year: 1998, month: 1, day: 23, hour: 11 }), false, 'Date#isBefore | object | February 23, 1998 12pm');
+  equal(new Date(1998,1,23,11,55).isBefore({ year: 1998, month: 1, day: 23, hour: 11, minutes: 54 }), false, 'Date#isBefore | object | February 23, 1998 11:55am');
+  equal(new Date(1998,1,23,11,54,33).isBefore({ year: 1998, month: 1, day: 23, hour: 11, minutes: 54, seconds: 32 }), false, 'Date#isBefore | object | February 23, 1998 11:54:33am');
+  equal(new Date(1998,1,23,11,54,32,455).isBefore({ year: 1998, month: 1, day: 23, hour: 11, minutes: 54, seconds: 32, milliseconds: 454 }), false, 'Date#isBefore | object | February 23, 1998 11:54:32.455am');
+
+  equal(new Date(1997,11,31,23,59,59,999).isBefore({ year: 1998 }), true, 'Date#isBefore | object | 1999');
+  equal(new Date(1998,0).isBefore({ year: 1998, month: 1 }), true, 'Date#isBefore | object | March, 1998');
+  equal(new Date(1998,1,22).isBefore({ year: 1998, month: 1, day: 23 }), true, 'Date#isBefore | object | February 24, 1998');
+  equal(new Date(1998,1,23,10).isBefore({ year: 1998, month: 1, day: 23, hour: 11 }), true, 'Date#isBefore | object | February 23, 1998 12pm');
+  equal(new Date(1998,1,23,11,53).isBefore({ year: 1998, month: 1, day: 23, hour: 11, minutes: 54 }), true, 'Date#isBefore | object | February 23, 1998 11:55am');
+  equal(new Date(1998,1,23,11,54,31).isBefore({ year: 1998, month: 1, day: 23, hour: 11, minutes: 54, seconds: 32 }), true, 'Date#isBefore | object | February 23, 1998 11:54:33am');
+  equal(new Date(1998,1,23,11,54,32,453).isBefore({ year: 1998, month: 1, day: 23, hour: 11, minutes: 54, seconds: 32, milliseconds: 454 }), true, 'Date#isBefore | object | February 23, 1998 11:54:32.455am');
+
+  equal(new Date(1999,1).isBefore('1998'), false, 'Date#isBefore | string | 1998');
+  equal(new Date(1998,2).isBefore('February, 1998'), false, 'Date#isBefore | string | February, 1998');
+  equal(new Date(1998,1,24).isBefore('February 23, 1998'), false, 'Date#isBefore | string | February 23, 1998');
+  equal(new Date(1998,1,23,12).isBefore('February 23, 1998 11am'), false, 'Date#isBefore | string | February 23, 1998 11pm');
+  equal(new Date(1998,1,23,11,55).isBefore('February 23, 1998 11:54am'), false, 'Date#isBefore | string | February 23, 1998 11:54am');
+  equal(new Date(1998,1,23,11,54,33).isBefore('February 23, 1998 11:54:32am'), false, 'Date#isBefore | string | February 23, 1998 11:54:32am');
+  equal(new Date(1998,1,23,11,54,32,455).isBefore('February 23, 1998 11:54:32.454am'), false, 'Date#isBefore | string | February 23, 1998 11:54:32.454am');
+
+  equal(new Date(1999,5).isBefore('1999'), false, 'Date#isBefore | June 1999 is not after 1999 in general');
+  equal(getRelativeDate(1).isBefore('tomorrow'), false, 'Date#isBefore | relative | next year');
+  equal(getRelativeDate(null, 1).isBefore('tomorrow'), false, 'Date#isBefore | relative | next month');
+  equal(getRelativeDate(null, null, 1).isBefore('tomorrow'), false, 'Date#isBefore | relative | tomorrow');
+
+  equal(getDateWithWeekdayAndOffset(0).isBefore('monday'), true, 'Date#isBefore | relative | sunday');
+  equal(getDateWithWeekdayAndOffset(2).isBefore('monday'), false, 'Date#isBefore | relative | tuesday');
+  equal(getDateWithWeekdayAndOffset(0,7).isBefore('monday'), false, 'Date#isBefore | relative | next week sunday');
+  equal(getDateWithWeekdayAndOffset(0,-7).isBefore('monday'), true, 'Date#isBefore | relative | last week sunday');
+  equal(getDateWithWeekdayAndOffset(0).isBefore('the beginning of this week'), false, 'Date#isBefore | relative | the beginning of this week');
+  equal(getDateWithWeekdayAndOffset(0).isBefore('the beginning of last week'), false, 'Date#isBefore | relative | the beginning of last week');
+  equal(getDateWithWeekdayAndOffset(0).isBefore('the end of this week'), true, 'Date#isBefore | relative | the end of this week');
+
+  equal(new Date(2001,1,25).isBefore(new Date(2001,1,24), 48 * 60 * 60 * 1000), true, 'Date#isBefore | buffers work');
+
+
+
+
+
+
+  equal(new Date(2001,1,23).isBetween(new Date(2000,1,23), new Date(2002,1,23)), true, 'Date#isBetween | January 23, 2001 is between January 23, 2000 and January 23, 2002');
+  equal(new Date(2001,1,23).isBetween(new Date(2002,1,23), new Date(2000,1,23)), true, 'Date#isBetween | January 23, 2001 is between January 23, 2002 and January 23, 2000');
+  equal(new Date(1999,1,23).isBetween(new Date(2002,1,23), new Date(2000,1,23)), false, 'Date#isBetween | January 23, 1999 is between January 23, 2002 and January 23, 2000');
+  equal(new Date(2003,1,23).isBetween(new Date(2002,1,23), new Date(2000,1,23)), false, 'Date#isBetween | January 23, 2003 is between January 23, 2002 and January 23, 2000');
+
+  equal(new Date(1998,2).isBetween(new Date(1998,1), new Date(1998, 3)), true, 'Date#isBetween | February, 1998 - April, 1998');
+  equal(new Date(1998,2).isBetween(new Date(1998,1), new Date(1998, 0)), false, 'Date#isBetween | February, 1998 - January, 1998');
+  equal(new Date(1998,2).isBetween(new Date(1998,5), new Date(1998, 3)), false, 'Date#isBetween | June, 1998 - April, 1998');
+
+  equal(new Date(1998,1,23,11,54,32,455).isBetween(new Date(1998,1,23,11,54,32,454), new Date(1998,1,23,11,54,32,456)), true, 'Date#isBetween | February 23, 1998 11:54:32.454am - February 23, 1998 11:54:32:456am');
+  equal(new Date(1998,1,23,11,54,32,455).isBetween(new Date(1998,1,23,11,54,32,456), new Date(1998,1,23,11,54,32,454)), true, 'Date#isBetween | February 23, 1998 11:54:32.456am - February 23, 1998 11:54:32:454am');
+  equal(new Date(1998,1,23,11,54,32,455).isBetween(new Date(1998,1,23,11,54,32,454), new Date(1998,1,23,11,54,32,452)), false, 'Date#isBetween | February 23, 1998 11:54:32.454am - February 23, 1998 11:54:32:452am');
+  equal(new Date(1998,1,23,11,54,32,455).isBetween(new Date(1998,1,23,11,54,32,456), new Date(1998,1,23,11,54,32,458)), false, 'Date#isBetween | February 23, 1998 11:54:32.456am - February 23, 1998 11:54:32:458am');
+
+  equal(new Date(1998,1).isBetween({ year: 1998 }, { year: 1999 }), true, 'Date#isBetween | object | Feb 1998 is between 1998 - 1999');
+  equal(new Date(1999,1).isBetween({ year: 1998 }, { year: 1999 }), false, 'Date#isBetween | object | Feb 1999 is between 1998 - 1999');
+  equal(new Date(1999,1).isBetween({ year: 1998 }, { year: 1997 }), false, 'Date#isBetween | object | Feb 1999 is between 1998 - 1997');
+  equal(new Date(1998,2).isBetween({ year: 1998, month: 1 }, { year: 1998, month: 3 }), true, 'Date#isBetween | object | March, 1998 is between February, 1998 and April, 1998');
+  equal(new Date(1998,2).isBetween({ year: 1998, month: 0 }, { year: 1998, month: 1 }), false, 'Date#isBetween | object | March, 1998 is between January, 1998 and February, 1998');
+
+  equal(new Date(1998,1).isBetween('1998', '1999'), true, 'Date#isBetween | string | Feb 1998 is between 1998 - 1999');
+  equal(new Date(1999,1).isBetween('1998', '1999'), false, 'Date#isBetween | string | Feb 1999 is between 1998 - 1999');
+  equal(new Date(1999,1).isBetween('1998', '1997'), false, 'Date#isBetween | string | Feb 1998 is between 1998 - 1997');
+  equal(new Date(1998,2).isBetween('February, 1998', 'April, 1998'), true, 'Date#isBetween | string | March, 1998 is between February, 1998 and April, 1998');
+  equal(new Date(1998,2).isBetween('January, 1998', 'February, 1998'), false, 'Date#isBetween | string | March, 1998 is not between January, 1998 and February, 1998');
+
+  equal(new Date(1999,5).isBetween('1998','1999'), false, 'Date#isBetween | Ambiguous periods are hard coded to the ms, there is no "implied specificity" as with Date#is');
+  equal(new Date().isBetween('yesterday','tomorrow'), true, 'Date#isBetween | relative | now is between today and tomorrow');
+  equal(getRelativeDate(1).isBetween('yesterday','tomorrow'), false, 'Date#isBetween | relative | last year is between today and tomorrow');
+  equal(getRelativeDate(null, 1).isBetween('yesterday','tomorrow'), false, 'Date#isBetween | relative | last month is between today and tomorrow');
+  equal(getRelativeDate(null, null, 0).isBetween('today','tomorrow'), true, 'Date#isBetween | relative | right now is between today and tomorrow');
+  equal(getRelativeDate(null, null, 1).isBetween('today','tomorrow'), false, 'Date#isBetween | relative | tomorrow is between today and tomorrow');
+
+  equal(getDateWithWeekdayAndOffset(0).isBetween('monday', 'friday'), false, 'Date#isBetween | relative | sunday is between monday and friday');
+  equal(getDateWithWeekdayAndOffset(2).isBetween('monday', 'friday'), true, 'Date#isBetween | relative | tuesday is between monday and friday');
+  equal(getDateWithWeekdayAndOffset(0,7).isBetween('monday', 'friday'), false, 'Date#isBetween | relative | next week sunday is between monday and friday');
+  equal(getDateWithWeekdayAndOffset(0,-7).isBetween('monday', 'friday'), false, 'Date#isBetween | relative | last week sunday is between monday and friday');
+  equal(getDateWithWeekdayAndOffset(0).isBetween('the beginning of this week','the beginning of last week'), false, 'Date#isBetween | relative | sunday is between the beginning of this week and the beginning of last week');
+  equal(getDateWithWeekdayAndOffset(0).isBetween('the beginning of this week','the beginning of next week'), false, 'Date#isBetween | relative | sunday is between the beginning of this week and the beginning of next week');
+  equal(getDateWithWeekdayAndOffset(0).isBetween('the beginning of last week','the beginning of next week'), true, 'Date#isBetween | relative | sunday is between the beginning of last week and the beginning of next week');
+  equal(getDateWithWeekdayAndOffset(0).isBetween('the beginning of last week','the end of this week'), true, 'Date#isBetween | relative | sunday is between the beginning of last week and the end of this week');
+
+
+  equal(Date.create('yesterday').isBetween('yesterday', 'today'), false, 'Date#isBetween | today is between yesterday and today');
+  equal(Date.create('yesterday').isBetween('yesterday', 'today', 5), true, 'Date#isBetween | today is between yesterday and today with a 5ms margin');
+  equal(Date.create('tomorrow').isBetween('today', 'tomorrow'), false, 'Date#isBetween | tomrrow is between today and tomorrow');
+  equal(Date.create('tomorrow').isBetween('today', 'tomorrow', 5), true, 'Date#isBetween | tomrrow is between today and tomorrow with a 5ms margin');
+
+  dateEqual(Date.create().rewind((1).day()), new Date(new Date().getTime() - 86400000), 'Date#rewind | can rewind milliseconds');
+  dateEqual(Date.create().advance((1).day()), new Date(new Date().getTime() + 86400000), 'Date#advance | can advance milliseconds');
+
+  equal(Date.create().beginningOfWeek().isLastWeek(), false, 'Date#isLastWeek | the beginning of this week is not last week');
+
+  dateEqual(Date.create().set(0), new Date(0), 'Date#set | handles timestamps');
+
+
+
+  date1 = Date.create('July 4th, 1776');
+  date2 = date1.clone().beginningOfYear();
+
+  equal(date2.getMonth(), 0, 'Date#clone | cloned element is reset to January');
+  equal(date1.getMonth(), 6, 'Date#clone | source element is reset to unchanged');
+
+  date1 = Date.create('invalid');
+  date2 = date1.clone();
+
+  equal(date1.isValid(), false, 'Date#clone | source element is invalid');
+  equal(date2.isValid(), false, 'Date#clone | cloned element is also invalid');
+
+
+  // Date.addFormat
+  Date.addFormat('(\\d+)\\^\\^(\\d+)%%(\\d+), but at the (beginning|end)', ['date','year','month','edge']);
+  dateEqual(Date.create('25^^2008%%02, but at the end'), new Date(2008, 1, 25, 23, 59, 59, 999), 'Date.addFormat | make your own crazy format!');
+
+  Date.addFormat('on ze (\\d+)th of (january|february|march|april|may) lavigne', ['date','month'], 'en');
+  dateEqual(Date.create('on ze 18th of april lavigne', 'en'), new Date(thisYear, 3, 18), 'Date.addFormat | handles other formats');
+
+  equal(typeof Date.getLocale(), 'object', 'Date Locale | current localization object is exposed in case needed');
+  equal(Date.getLocale().code, 'en', 'Date Locale | adding the format did not change the current locale');
+
+
+
+
+  // Date locale setup
+  equal(new Date(2011, 5, 18).format('{Month} {date}, {yyyy}'), 'June 18, 2011', 'Date Locales | Non-initialized defaults to English formatting');
+  equal(getRelativeDate(null, null, null, -1).relative(), '1 hour ago', 'Date Locales | Non-initialized relative formatting is also English');
+  equal(Date.create('June 18, 2011').isValid(), true, 'Date Locales | English dates will also be properly parsed without being initialized or passing a locale code');
+
+
+  Date.setLocale('fo');
+
+  equal(Date.create('2011kupo', 'fo').isValid(), true, 'Date Locales | dates will parse if their locale is passed');
+  equal(Date.create('๏ผ’๏ผ๏ผ‘๏ผ‘ๅนด๏ผ๏ผ–ๆœˆ๏ผ‘๏ผ˜ๆ—ฅ').isValid(), false, 'Date Locales | dates will not parse thereafter as the current locale is still en');
+
+  equal(new Date(2011, 5, 6).format('{Month}'), 'La', 'Date.setLocale | june is La');
+
+  raisesError(function(){ Date.setLocale(); }, 'Date.setLocale | no arguments raises error');
+  equal(Date.getLocale().code, 'fo', 'Date.setLocale | setting locale with no arguments had no effect');
+  equal(new Date(2011, 5, 6).format('{Month}'), 'La', 'Date.setLocale | will not change the locale if no argument passed');
+  equal(new Date(2011, 5, 6).format('', 'en'), 'June 6, 2011 12:00am', 'Date#format | local locale should override global');
+  equal(Date.create('5 months ago', 'en').relative('en'), '5 months ago', 'Date#relative | local locale should override global');
+
+  raisesError(function(){ Date.setLocale(''); }, 'Date.setLocale | "" raises an invalid locale error');
+  equal(new Date(2011, 5, 6).format('{Month}'), 'La', 'Date.setLocale | will not change the locale if blank string passed');
+  dateEqual(Date.create('2010-Jan-25', 'fo'), new Date(2010, 0, 25), 'Date#create | Static input format always matches English months');
+
+  raisesError(function(){ Date.setLocale('pink') }, 'Date.setLocale | Non-existent locales will raise an error');
+  equal(Date.create('2010-Jan-25').format(), 'yeehaw', 'Date#create | will not set the current locale to an invalid locale');
+
+  Date.setLocale('en');
+
+  // If traversing into a new month don't reset the date if the date was also advanced
+
+  d = new Date(2011, 0, 31);
+  dateEqual(d.advance({ month: 1 }), new Date(2011, 1, 28), 'Date#create | basic month traversal will reset the date to the last day');
+
+  d = new Date(2011, 0, 31);
+  dateEqual(d.advance({ month: 1, day: 3 }), new Date(2011, 2, 3), 'Date#create | when the day is specified date reset will not happen');
+
+  d = new Date(2011, 0, 31);
+  dateEqual(d.set({ month: 1, day: 3 }), new Date(2011, 1, 3), 'Date#create | set will also not cause date traversal');
+
+
+
+  // Advance also allows resetting.
+
+  d = new Date(2011, 0, 31, 23, 40, 28, 500);
+  dateEqual(d.clone().advance({ year: 1 }, true), new Date(2012, 0), 'Date#advance | with reset | year');
+  dateEqual(d.clone().advance({ month: 1 }, true), new Date(2011, 1), 'Date#advance | with reset | month');
+  dateEqual(d.clone().advance({ week: 1 }, true), new Date(2011, 1, 7), 'Date#advance | with reset | week');
+  dateEqual(d.clone().advance({ date: 1 }, true), new Date(2011, 1, 1), 'Date#advance | with reset | date');
+  dateEqual(d.clone().advance({ hour: 1 }, true), new Date(2011, 1, 1, 0), 'Date#advance | with reset | hour');
+  dateEqual(d.clone().advance({ minute: 1 }, true), new Date(2011, 0, 31, 23, 41), 'Date#advance | with reset | minute');
+  dateEqual(d.clone().advance({ second: 1 }, true), new Date(2011, 0, 31, 23, 40, 29), 'Date#advance | with reset | second');
+  dateEqual(d.clone().advance({ millisecond: 1 }, true), new Date(2011, 0, 31, 23, 40, 28, 501), 'Date#advance | with reset | millisecond');
+
+  // Advance also allows string methods
+
+  d = new Date(2011, 0, 31, 23, 40, 28, 500);
+  dateEqual(d.clone().advance('3 years'), new Date(2014, 0, 31, 23, 40, 28, 500), 'Date#advance | string input | year');
+  dateEqual(d.clone().advance('3 months'), new Date(2011, 3, 30, 23, 40, 28, 500), 'Date#advance | string input | month');
+  dateEqual(d.clone().advance('3 weeks'), new Date(2011, 1, 21, 23, 40, 28, 500), 'Date#advance | string input | week');
+  dateEqual(d.clone().advance('3 days'), new Date(2011, 1, 3, 23, 40, 28, 500), 'Date#advance | string input | date');
+  dateEqual(d.clone().advance('3 hours'), new Date(2011, 1, 1, 2, 40, 28, 500), 'Date#advance | string input | hour');
+  dateEqual(d.clone().advance('3 minutes'), new Date(2011, 0, 31, 23, 43, 28, 500), 'Date#advance | string input | minute');
+  dateEqual(d.clone().advance('3 seconds'), new Date(2011, 0, 31, 23, 40, 31, 500), 'Date#advance | string input | second');
+  dateEqual(d.clone().advance('3 milliseconds'), new Date(2011, 0, 31, 23, 40, 28, 503), 'Date#advance | string input | millisecond');
+
+  // Number methods
+
+  equal((4).milliseconds(), 4, 'Number#milliseconds | 4');
+  equal((3.25).milliseconds(), 3, 'Number#milliseconds | rounded');
+
+  equal((0).seconds(), 0, 'Number#seconds | 0');
+  equal((1).seconds(), 1000, 'Number#seconds | 1');
+  equal((30).seconds(), 30000, 'Number#seconds | 30');
+  equal((60).seconds(), 60000, 'Number#seconds | 60');
+
+
+  equal((1).minutes(), 60000, 'Number#minutes | 1');
+  equal((10).minutes(), 600000, 'Number#minutes | 10');
+  equal((100).minutes(), 6000000, 'Number#minutes | 100');
+  equal((0).minutes(), 0, 'Number#minutes | 0');
+  equal((0.5).minutes(), 30000, 'Number#minutes | 0.5');
+  equal((1).minutes(), (60).seconds(), 'Number#minutes | 1 minute is 60 seconds');
+
+  equal((1).hours(), 3600000, 'Number#hours | 1');
+  equal((10).hours(), 36000000, 'Number#hours | 10');
+  equal((100).hours(), 360000000, 'Number#hours | 100');
+  equal((0).hours(), 0, 'Number#hours | 0');
+  equal((0.5).hours(), 1800000, 'Number#hours | 0.5');
+  equal((1).hours(), (60).minutes(), 'Number#hours | 1 hour is 60 minutes');
+  equal((1).hours(), (3600).seconds(), 'Number#hours | 1 hour is 3600 seconds');
+
+
+  equal((1).days(), 86400000, 'Number#days | 1');
+  equal((10).days(), 864000000, 'Number#days | 10');
+  equal((100).days(), 8640000000, 'Number#days | 100');
+  equal((0).days(), 0, 'Number#days | 0');
+  equal((0.5).days(), 43200000, 'Number#days | 0.5');
+  equal((1).days(), (24).hours(), 'Number#days | 1 day is 24 hours');
+  equal((1).days(), (1440).minutes(), 'Number#days | 1 day is 1440 minutes');
+  equal((1).days(), (86400).seconds(), 'Number#days | 1 day is 86400 seconds');
+
+
+  equal((1).weeks(), 604800000, 'Number#weeks | 1');
+  equal((0.5).weeks(), 302400000, 'Number#weeks | 0.5');
+  equal((10).weeks(), 6048000000, 'Number#weeks | 10');
+  equal((0).weeks(), 0, 'Number#weeks | 0');
+  equal((1).weeks(), (7).days(), 'Number#weeks | 1 week is 7 days');
+  equal((1).weeks(), (24 * 7).hours(), 'Number#weeks | 1 week is 24 * 7 hours');
+  equal((1).weeks(), (60 * 24 * 7).minutes(), 'Number#weeks | 1 week is 60 * 24 * 7 minutes');
+  equal((1).weeks(), (60 * 60 * 24 * 7).seconds(), 'Number#weeks | 1 week is 60 * 60 * 24 * 7 seconds');
+
+  equal((1).months(), 2629800000, 'Number#months | 1 month');
+  equal((0.5).months(), 1314900000, 'Number#months | 0.5 month');
+  equal((10).months(), 26298000000, 'Number#months | 10 month');
+  equal((0).months(), 0, 'Number#months | 0 months');
+  equal((1).months(), (30.4375).days(), 'Number#months | 1 month is 30.4375 days');
+  equal((1).months(), (24 * 30.4375).hours(), 'Number#months | 1 month is 24 * 30.4375 hours');
+  equal((1).months(), (60 * 24 * 30.4375).minutes(), 'Number#months | 1 month is 60 * 24 * 30.4375 minutes');
+  equal((1).months(), (60 * 60 * 24 * 30.4375).seconds(), 'Number#months | 1 month is 60 * 60 * 24 * 30.4375 seconds');
+
+  equal((1).years(), 31557600000, 'Number#years | 1');
+  equal((0.5).years(), 15778800000, 'Number#years | 0.5');
+  equal((10).years(), 315576000000, 'Number#years | 10');
+  equal((0).years(), 0, 'Number#years | 0');
+  equal((1).years(), (365.25).days(), 'Number#years | 1 year is 365.25 days');
+  equal((1).years(), (24 * 365.25).hours(), 'Number#years | 1 year is 24 * 365.25 hours');
+  equal((1).years(), (60 * 24 * 365.25).minutes(), 'Number#years | 1 year is 60 * 24 * 365.25 minutes');
+  equal((1).years(), (60 * 60 * 24 * 365.25).seconds(), 'Number#years | 1 year is 60 * 60 * 24 * 365.25 seconds');
+
+
+
+  /* compatibility */
+
+  equal((1).second(), 1000, 'Number#second | 1 second');
+  equal((1).minute(), 60000, 'Number#minute | 1 minute');
+  equal((1).hour(), 3600000, 'Number#hour | 1 hour');
+  equal((1).day(), 86400000, 'Number#day | 1 day');
+  equal((1).week(), 604800000, 'Number#week | 1 week');
+  equal((1).month(), 2629800000, 'Number#month | 1 month');
+  equal((1).year(), 31557600000, 'Number#year | 1 year');
+
+
+  dateEqual((1).secondAfter(), 1000, 'Number#secondAfter | 1');
+  dateEqual((5).secondsAfter(), 5000, 'Number#secondsAfter | 5');
+  dateEqual((10).minutesAfter(), 600000, 'Number#minutesAfter | 10');
+
+  dateEqual((1).secondFromNow(), 1000, 'Number#secondFromNow | 1');
+  dateEqual((5).secondsFromNow(), 5000, 'Number#secondsFromNow | 5');
+  dateEqual((10).minutesFromNow(), 600000, 'Number#minutesFromNow | 10');
+
+  dateEqual((1).secondAgo(), -1000, 'Number#secondAgo | 1');
+  dateEqual((5).secondsAgo(), -5000, 'Number#secondAgo | 5');
+  dateEqual((10).secondsAgo(), -10000, 'Number#secondAgo | 10');
+
+  dateEqual((1).secondBefore(), -1000, 'Number#secondBefore | 1');
+  dateEqual((5).secondsBefore(), -5000, 'Number#secondBefore | 5');
+  dateEqual((10).secondsBefore(), -10000, 'Number#secondBefore | 10');
+
+
+  dateEqual((5).minutesAfter((5).minutesAgo()), 0, 'Number#minutesAfter | 5 minutes after 5 minutes ago');
+  dateEqual((10).minutesAfter((5).minutesAgo()), 1000 * 60 * 5, 'Number#minutesAfter | 10 minutes after 5 minutes ago');
+
+  dateEqual((5).minutesFromNow((5).minutesAgo()), 0, 'Number#minutesFromNow | 5 minutes from now 5 minutes ago');
+  dateEqual((10).minutesFromNow((5).minutesAgo()), 1000 * 60 * 5, 'Number#minutesFromNow | 10 minutes from now 5 minutes ago');
+
+  dateEqual((5).minutesAgo((5).minutesFromNow()), 0, 'Number#minutesAgo | 5 minutes ago 5 minutes from now');
+  dateEqual((10).minutesAgo((5).minutesFromNow()), -(1000 * 60 * 5), 'Number#minutesAgo | 10 minutes ago 5 minutes from now');
+
+  dateEqual((5).minutesBefore((5).minutesFromNow()), 0, 'Number#minutesBefore | 5 minutes before 5 minutes from now');
+  dateEqual((10).minutesBefore((5).minutesFromNow()), -(1000 * 60 * 5), 'Number#minutesBefore | 10 minutes before 5 minutes from now');
+
+
+  var christmas = new Date('December 25, 1972');
+
+  dateEqual((5).minutesBefore(christmas), getRelativeDate.call(christmas, null, null, null, null, -5), 'Number#minutesBefore | 5 minutes before christmas');
+  dateEqual((5).minutesAfter(christmas), getRelativeDate.call(christmas, null, null, null, null, 5), 'Number#minutesAfter | 5 minutes after christmas');
+
+  dateEqual((5).hoursBefore(christmas), getRelativeDate.call(christmas, null, null, null, -5), 'Number#hoursBefore | 5 hours before christmas');
+  dateEqual((5).hoursAfter(christmas), getRelativeDate.call(christmas, null, null, null, 5), 'Number#hoursAfter | 5 hours after christmas');
+
+  dateEqual((5).daysBefore(christmas), getRelativeDate.call(christmas, null, null, -5), 'Number#daysBefore | 5 days before christmas');
+  dateEqual((5).daysAfter(christmas), getRelativeDate.call(christmas, null, null, 5), 'Number#daysAfter | 5 days after christmas');
+
+  dateEqual((5).weeksBefore(christmas), getRelativeDate.call(christmas, null, null, -35), 'Number#weeksBefore | 5 weeks before christmas');
+  dateEqual((5).weeksAfter(christmas), getRelativeDate.call(christmas, null, null, 35), 'Number#weeksAfter | 5 weeks after christmas');
+
+  dateEqual((5).monthsBefore(christmas), getRelativeDate.call(christmas, null, -5), 'Number#monthsBefore | 5 months before christmas');
+  dateEqual((5).monthsAfter(christmas), getRelativeDate.call(christmas, null, 5), 'Number#monthsAfter | 5 months after christmas');
+
+  dateEqual((5).yearsBefore(christmas), getRelativeDate.call(christmas, -5), 'Number#yearsBefore | 5 years before christmas');
+  dateEqual((5).yearsAfter(christmas), getRelativeDate.call(christmas, 5), 'Number#yearsAfter | 5 years after christmas');
+
+  dateEqual((5).hoursBefore(1972, 11, 25), getRelativeDate.call(christmas, null, null, null, -5), 'Number#hoursBefore | accepts numbers');
+
+  // Hooking it all up!!
+
+  // Try this in WinXP:
+  // 1. Set timezone to Damascus
+  // 2. var d = new Date(1998, 3, 3, 17); d.setHours(0); d.getHours();
+  // 3. hours = 23
+  // 4. PROFIT $$$
+
+  dateEqual((5).minutesBefore('April 2rd, 1998'), new Date(1998, 3, 1, 23, 55), 'Number#minutesBefore | 5 minutes before April 3rd, 1998');
+  dateEqual((5).minutesAfter('January 2nd, 2005'), new Date(2005, 0, 2, 0, 5), 'Number#minutesAfter | 5 minutes after January 2nd, 2005');
+  dateEqual((5).hoursBefore('the first day of 2005'), new Date(2004, 11, 31, 19), 'Number#hoursBefore | 5 hours before the first day of 2005');
+  dateEqual((5).hoursAfter('the last day of 2006'), new Date(2006, 11, 31, 5), 'Number#hoursAfter | 5 hours after the last day of 2006');
+  dateEqual((5).hoursAfter('the end of 2006'), new Date(2007, 0, 1, 4, 59, 59, 999), 'Number#hoursAfter | 5 hours after the end of 2006');
+  dateEqual((5).daysBefore('last week monday'), getDateWithWeekdayAndOffset(1, -7).rewind({ days: 5 }), 'Number#daysBefore | 5 days before last week monday');
+  dateEqual((5).daysAfter('next tuesday'), getDateWithWeekdayAndOffset(2, 7).advance({ days: 5 }), 'Number#daysAfter | 5 days after next week tuesday');
+  dateEqual((5).weeksBefore('today'), getRelativeDate(null, null, -35).set({ hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }), 'Number#weeksBefore | 5 weeks before today');
+  dateEqual((5).weeksAfter('now'), getRelativeDate(null, null, 35), 'Number#weeksAfter | 5 weeks after now');
+  dateEqual((5).monthsBefore('today'), getRelativeDate(null, -5).set({ hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }), 'Number#monthsBefore | 5 months before today');
+  dateEqual((5).monthsAfter('now'), getRelativeDate(null, 5), 'Number#monthsAfter | 5 months after now');
+
+
+
+  // Date#getISOWeek
+
+  equal(new Date(2011, 0, 1).getISOWeek(), 52, 'String#getISOWeek | January 1, 2011');
+  equal(new Date(2011, 0, 2).getISOWeek(), 52, 'String#getISOWeek | January 2, 2011');
+  equal(new Date(2011, 0, 3).getISOWeek(),  1, 'String#getISOWeek | January 3, 2011');
+  equal(new Date(2011, 0, 4).getISOWeek(),  1, 'String#getISOWeek | January 4, 2011');
+
+  equal(new Date(2011, 11, 25).getISOWeek(), 51, 'String#getISOWeek | December 25, 2011');
+  equal(new Date(2011, 11, 26).getISOWeek(), 52, 'String#getISOWeek | December 26, 2011');
+  equal(new Date(2011, 11, 27).getISOWeek(), 52, 'String#getISOWeek | December 27, 2011');
+
+  equal(new Date(2011, 11, 31).getISOWeek(), 52, 'String#getISOWeek | December 31, 2011');
+  equal(new Date(2012, 0, 1).getISOWeek(),   52, 'String#getISOWeek | January 1, 2012');
+  equal(new Date(2012, 0, 2).getISOWeek(),    1, 'String#getISOWeek | January 2, 2012');
+
+  equal(new Date(2013, 11, 28).getISOWeek(), 52, 'String#getISOWeek | December 28, 2013');
+  equal(new Date(2013, 11, 29).getISOWeek(), 52, 'String#getISOWeek | December 29, 2013');
+  equal(new Date(2013, 11, 30).getISOWeek(),  1, 'String#getISOWeek | December 30, 2013');
+  equal(new Date(2013, 11, 31).getISOWeek(),  1, 'String#getISOWeek | December 31, 2013');
+  equal(new Date(2014,  0,  1).getISOWeek(),  1, 'String#getISOWeek | January 01, 2014');
+  equal(new Date(2014,  0,  2).getISOWeek(),  1, 'String#getISOWeek | January 02, 2014');
+  equal(new Date(2014,  0,  5).getISOWeek(),  1, 'String#getISOWeek | January 05, 2014');
+  equal(new Date(2014,  0,  6).getISOWeek(),  2, 'String#getISOWeek | January 06, 2014');
+
+
+  // Date.restore may not exist in dates-only build.
+
+  if(Date.restore) {
+    Date.prototype.advance = undefined;
+    equal(typeof Date.prototype.advance,  'undefined', 'Date#advance was removed');
+    Date.restore('advance');
+    equal(typeof Date.prototype.advance,  'function', 'Date#advance was restored');
+  }
+
+
+  // Issue #98: System time set to January 31st
+
+  dateEqual(Date.create('2011-09-01T05:00:00Z'), getUTCDate(2011, 9, 1, 5), 'String#toDate | text format');
+
+
+  // Number#duration
+
+  Date.setLocale('en');
+
+  equal((1).duration(), '1 millisecond', 'Number#duration | 1 millisecond');
+  equal((2).duration(), '2 milliseconds', 'Number#duration | 2 milliseconds');
+  equal((100).duration(), '100 milliseconds', 'Number#duration | 100 milliseconds');
+  equal((500).duration(), '500 milliseconds', 'Number#duration | 500 milliseconds');
+  equal((949).duration(), '949 milliseconds', 'Number#duration | 949 milliseconds');
+  equal((950).duration(), '1 second', 'Number#duration | 950 milliseconds');
+  equal((999).duration(), '1 second', 'Number#duration | 999 milliseconds');
+  equal((1000).duration(), '1 second', 'Number#duration | 1 second');
+  equal((1999).duration(), '2 seconds', 'Number#duration | 2 seconds');
+  equal((5000).duration(), '5 seconds', 'Number#duration | 5 seconds');
+  equal((55000).duration(), '55 seconds', 'Number#duration | 55 seconds');
+  equal((56000).duration(), '56 seconds', 'Number#duration | 56 seconds');
+  equal((57000).duration(), '1 minute', 'Number#duration | 57 seconds');
+  equal((60000).duration(), '1 minute', 'Number#duration | 60 seconds');
+  equal((3600000).duration(), '1 hour', 'Number#duration | 360000 seconds');
+  equal((5).hours().duration(), '5 hours', 'Number#duration | 5 hours');
+  equal((22).hours().duration(), '22 hours', 'Number#duration | 22 hours');
+  equal((23).hours().duration(), '1 day', 'Number#duration | 23 hours');
+  equal((6).days().duration(), '6 days', 'Number#duration | 6 days');
+  equal((7).days().duration(), '1 week', 'Number#duration | 1 week');
+  equal((28).days().duration(), '4 weeks', 'Number#duration | 30 days');
+  equal((29).days().duration(), '1 month', 'Number#duration | 1 months');
+  equal((11).months().duration(), '11 months', 'Number#duration | 11 months');
+  equal((12).months().duration(), '1 year', 'Number#duration | 1 year');
+  equal((2).years().duration(), '2 years', 'Number#duration | 2 years');
+  equal((15).years().duration(), '15 years', 'Number#duration | 15 years');
+  equal((1500).years().duration(), '1500 years', 'Number#duration | 1500 years');
+
+  Date.setLocale('fo');
+
+  equal((5).days().duration(), '5somomoney', 'Number#duration | Fake locale | 5 days');
+  equal((150).days().duration(), '4timomoney', 'Number#duration | Fake locale | 150 days');
+  equal((38000).seconds().duration(), '10famomoney', 'Number#duration | Fake locale | 38000 seconds');
+  equal((38000).minutes().duration(), '3lamomoney', 'Number#duration | Fake locale | 38000 minutes');
+  equal((38000).hours().duration(), '4domomoney', 'Number#duration | Fake locale | 38000 hours');
+
+
+  // Duration without setting the locale code
+
+  equal((5).days().duration('en'), '5 days', 'Number#duration | English | 5 days');
+  equal((150).days().duration('en'), '4 months', 'Number#duration | English | 150 days');
+  equal((38000).seconds().duration('en'), '10 hours', 'Number#duration | English | 38000 seconds');
+  equal((38000).minutes().duration('en'), '3 weeks', 'Number#duration | English | 38000 minutes');
+  equal((38000).hours().duration('en'), '4 years', 'Number#duration | English | 38000 hours');
+
+  Date.setLocale('en');
+
+
+  // Custom date formats
+  // https://github.com/andrewplummer/Sugar/issues/119#issuecomment-4520966
+
+  Date.addFormat('(\\d{2})(\\d{2})',['hour','minute']);
+  dateEqual(Date.create('0615'), new Date().set({ hours: 6, minutes: 15 }, true), 'Date.addFormat | Overrides defined formats');
+
+  // Not sure how nuts I want to get with this so for the sake of the tests just push the proper format back over the top...
+  Date.addFormat('(\\d{4})', ['year']);
+
+  // Issue #146 - These tests were failing when system time was set to Friday, June 1, 2012 PDT
+
+  equal(Date.create('2010-01-20T20:00:00.000Z').iso(), '2010-01-20T20:00:00.000Z');
+  equal(Date.create('2010-02-20T20:00:00.000Z').iso(), '2010-02-20T20:00:00.000Z');
+  equal(Date.create('2010-03-20T20:00:00.000Z').iso(), '2010-03-20T20:00:00.000Z');
+  equal(Date.create('2010-04-20T20:00:00.000Z').iso(), '2010-04-20T20:00:00.000Z');
+  equal(Date.create('2010-05-20T20:00:00.000Z').iso(), '2010-05-20T20:00:00.000Z');
+  equal(Date.create('2010-05-20T20:00:00.000Z').iso(), '2010-05-20T20:00:00.000Z');
+  equal(Date.create('2010-06-20T20:00:00.000Z').iso(), '2010-06-20T20:00:00.000Z');
+  equal(Date.create('2010-07-20T20:00:00.000Z').iso(), '2010-07-20T20:00:00.000Z');
+  equal(Date.create('2010-08-20T20:00:00.000Z').iso(), '2010-08-20T20:00:00.000Z');
+  equal(Date.create('2010-09-20T20:00:00.000Z').iso(), '2010-09-20T20:00:00.000Z');
+  equal(Date.create('2010-10-20T20:00:00.000Z').iso(), '2010-10-20T20:00:00.000Z');
+  equal(Date.create('2010-11-20T20:00:00.000Z').iso(), '2010-11-20T20:00:00.000Z');
+  equal(Date.create('2010-12-20T20:00:00.000Z').iso(), '2010-12-20T20:00:00.000Z');
+
+  equal(Date.create('Jan 20 2010 12:00:00 GMT-0800 (PST)').iso(), '2010-01-20T20:00:00.000Z');
+  equal(Date.create('Feb 20 2010 12:00:00 GMT-0800 (PST)').iso(), '2010-02-20T20:00:00.000Z');
+  equal(Date.create('Mar 20 2010 12:00:00 GMT-0800 (PST)').iso(), '2010-03-20T20:00:00.000Z');
+  equal(Date.create('Apr 20 2010 12:00:00 GMT-0800 (PST)').iso(), '2010-04-20T20:00:00.000Z');
+  equal(Date.create('May 20 2010 12:00:00 GMT-0800 (PST)').iso(), '2010-05-20T20:00:00.000Z');
+  equal(Date.create('Jun 20 2010 12:00:00 GMT-0800 (PST)').iso(), '2010-06-20T20:00:00.000Z');
+  equal(Date.create('Jul 20 2010 12:00:00 GMT-0800 (PST)').iso(), '2010-07-20T20:00:00.000Z');
+  equal(Date.create('Aug 20 2010 12:00:00 GMT-0800 (PST)').iso(), '2010-08-20T20:00:00.000Z');
+  equal(Date.create('Sep 20 2010 12:00:00 GMT-0800 (PST)').iso(), '2010-09-20T20:00:00.000Z');
+  equal(Date.create('Oct 20 2010 12:00:00 GMT-0800 (PST)').iso(), '2010-10-20T20:00:00.000Z');
+  equal(Date.create('Nov 20 2010 12:00:00 GMT-0800 (PST)').iso(), '2010-11-20T20:00:00.000Z');
+  equal(Date.create('Dec 20 2010 12:00:00 GMT-0800 (PST)').iso(), '2010-12-20T20:00:00.000Z');
+
+  dateEqual(Date.create('Thursday of next week, 3:30pm'), getDateWithWeekdayAndOffset(4, 7, 15, 30), 'Date#create | Fuzzy Dates | thursday of next week, 3:30pm');
+
+
+  // Issue #152 times should be allowed in front
+  dateEqual(Date.create('3:45 2012-3-15'), new Date(2012, 2, 15, 3, 45), 'Date#create | time in the front');
+  dateEqual(Date.create('3:45pm 2012-3-15'), new Date(2012, 2, 15, 15, 45), 'Date#create | big endian with time');
+  dateEqual(Date.create('3:45pm 3/15/2012'), new Date(2012, 2, 15, 15, 45), 'Date#create | crazy endian slashes with time');
+  dateEqual(Date.create('3:45pm 3/4/2012', 'en-GB'), new Date(2012, 3, 3, 15, 45), 'Date#create | little endian slashes with time');
+
+
+
+  // Issue #144 various time/date formats
+  dateEqual(Date.create('6/30/2012 3:00 PM'), new Date(2012, 5, 30, 15), 'Date#create | 6/30/2012 3:00 PM');
+  dateEqual(Date.create('Thursday at 3:00 PM'), getDateWithWeekdayAndOffset(4).set({ hour: 15 }), 'Date#create | Thursday at 3:00 PM');
+  dateEqual(Date.create('Thursday at 3:00PM'), getDateWithWeekdayAndOffset(4).set({ hour: 15 }), 'Date#create | Thursday at 3:00PM (no space)');
+
+
+
+  // Issue #141 future/past preference
+
+  var weekdayOffset = now.getWeekday();
+
+  equal(Date.past('Sunday').isPast(),    true, 'Date#past | weekdays | Sunday');
+  equal(Date.past('Monday').isPast(),    true, 'Date#past | weekdays | Monday');
+  equal(Date.past('Tuesday').isPast(),   true, 'Date#past | weekdays | Tuesday');
+  equal(Date.past('Wednesday').isPast(), true, 'Date#past | weekdays | Wednesday');
+  equal(Date.past('Thursday').isPast(),  true, 'Date#past | weekdays | Thursday');
+  equal(Date.past('Friday').isPast(),    true, 'Date#past | weekdays | Friday');
+  equal(Date.past('Saturday').isPast(),  true, 'Date#past | weekdays | Saturday');
+
+  equal(Date.future('Sunday').isFuture(),    true, 'Date#future | weekdays | Sunday');
+  equal(Date.future('Monday').isFuture(),    true, 'Date#future | weekdays | Monday');
+  equal(Date.future('Tuesday').isFuture(),   true, 'Date#future | weekdays | Tuesday');
+  equal(Date.future('Wednesday').isFuture(), true, 'Date#future | weekdays | Wednesday');
+  equal(Date.future('Thursday').isFuture(),  true, 'Date#future | weekdays | Thursday');
+  equal(Date.future('Friday').isFuture(),    true, 'Date#future | weekdays | Friday');
+  equal(Date.future('Saturday').isFuture(),  true, 'Date#future | weekdays | Saturday');
+
+  equal(Date.past('January').isPast(),   true, 'Date#past | months | January');
+  equal(Date.past('February').isPast(),  true, 'Date#past | months | February');
+  equal(Date.past('March').isPast(),     true, 'Date#past | months | March');
+  equal(Date.past('April').isPast(),     true, 'Date#past | months | April');
+  equal(Date.past('May').isPast(),       true, 'Date#past | months | May');
+  equal(Date.past('June').isPast(),      true, 'Date#past | months | June');
+  equal(Date.past('July').isPast(),      true, 'Date#past | months | July');
+  equal(Date.past('August').isPast(),    true, 'Date#past | months | August');
+  equal(Date.past('September').isPast(), true, 'Date#past | months | September');
+  equal(Date.past('October').isPast(),   true, 'Date#past | months | October');
+  equal(Date.past('November').isPast(),  true, 'Date#past | months | November');
+  equal(Date.past('December').isPast(),  true, 'Date#past | months | December');
+
+  equal(Date.future('January').isFuture(),   true, 'Date#future | months | January');
+  equal(Date.future('February').isFuture(),  true, 'Date#future | months | February');
+  equal(Date.future('March').isFuture(),     true, 'Date#future | months | March');
+  equal(Date.future('April').isFuture(),     true, 'Date#future | months | April');
+  equal(Date.future('May').isFuture(),       true, 'Date#future | months | May');
+  equal(Date.future('June').isFuture(),      true, 'Date#future | months | June');
+  equal(Date.future('July').isFuture(),      true, 'Date#future | months | July');
+  equal(Date.future('August').isFuture(),    true, 'Date#future | months | August');
+  equal(Date.future('September').isFuture(), true, 'Date#future | months | September');
+  equal(Date.future('October').isFuture(),   true, 'Date#future | months | October');
+  equal(Date.future('November').isFuture(),  true, 'Date#future | months | November');
+  equal(Date.future('December').isFuture(),  true, 'Date#future | months | December');
+
+  // Ensure that dates don't traverse TOO far into the past/future
+  equal(Date.future('January').monthsFromNow() > 12, false, 'Date#future | months | no more than 12 months from now');
+  equal(Date.future('December').monthsFromNow() > 12, false, 'Date#future | months | no more than 12 months from now');
+
+
+  dateEqual(Date.create('the 2nd Tuesday of June, 2012'), new Date(2012, 5, 12), 'Date#create | the 2nd tuesday of June');
+
+  dateEqual(Date.create('the 1st Tuesday of November, 2012'), new Date(2012, 10, 6), 'Date#create | the 1st tuesday of November');
+  dateEqual(Date.create('the 2nd Tuesday of November, 2012'), new Date(2012, 10, 13), 'Date#create | the 2nd tuesday of November');
+  dateEqual(Date.create('the 3rd Tuesday of November, 2012'), new Date(2012, 10, 20), 'Date#create | the 3rd tuesday of November');
+  dateEqual(Date.create('the 4th Tuesday of November, 2012'), new Date(2012, 10, 27), 'Date#create | the 4th tuesday of November');
+  dateEqual(Date.create('the 5th Tuesday of November, 2012'), new Date(2012, 11, 4), 'Date#create | the 5th tuesday of November');
+  dateEqual(Date.create('the 6th Tuesday of November, 2012'), new Date(2012, 11, 11), 'Date#create | the 6th tuesday of November');
+
+  dateEqual(Date.create('the 1st Friday of February, 2012'), new Date(2012, 1, 3), 'Date#create | the 1st Friday of February');
+  dateEqual(Date.create('the 2nd Friday of February, 2012'), new Date(2012, 1, 10), 'Date#create | the 2nd Friday of February');
+  dateEqual(Date.create('the 3rd Friday of February, 2012'), new Date(2012, 1, 17), 'Date#create | the 3rd Friday of February');
+  dateEqual(Date.create('the 4th Friday of February, 2012'), new Date(2012, 1, 24), 'Date#create | the 4th Friday of February');
+  dateEqual(Date.create('the 5th Friday of February, 2012'), new Date(2012, 2, 2), 'Date#create | the 5th Friday of February');
+  dateEqual(Date.create('the 6th Friday of February, 2012'), new Date(2012, 2, 9), 'Date#create | the 6th Friday of February');
+
+  var firstFridayOfFeb = new Date(thisYear, 1);
+  firstFridayOfFeb.setWeekday(5);
+  if(firstFridayOfFeb.getMonth() < 1) {
+    firstFridayOfFeb.add({ weeks: 1 });
+  }
+
+  equal(Date.create('the 1st Friday of February').getFullYear(), thisYear, 'Date#create | 1st friday of February should be this year');
+  equal(Date.future('the 1st Friday of February').getFullYear(), now > firstFridayOfFeb ? thisYear + 1 : thisYear, 'Date#future | 1st friday of February should be this year or next');
+  equal(Date.past('the 1st Friday of February').getFullYear(), now < firstFridayOfFeb ? thisYear - 1 : thisYear, 'Date#past | 1st friday of February should be this year or last');
+
+
+  equal(Date.future('1:00am').isFuture(), true, 'Date#future | 1am should be the future');
+  equal(Date.future('11:00pm').isFuture(), true, 'Date#future | 11pm should be the future');
+
+  equal(Date.future('1:00am') < Date.create('1 day from now'), true, 'Date#future | 1am should be the future');
+  equal(Date.future('11:00pm') < Date.create('1 day from now'), true, 'Date#future | 11pm should be the future');
+
+  dateEqual(Date.create('in 60 seconds'), new Date().addMinutes(1), 'Date#create | in 60 seconds');
+  dateEqual(Date.create('in 45 minutes'), new Date().addMinutes(45), 'Date#create | in 45 minutes');
+  dateEqual(Date.create('in 5 hours'), new Date().addHours(5), 'Date#create | in 5 hours');
+  dateEqual(Date.create('in 5 days'), new Date().addDays(5), 'Date#create | in 5 days');
+  dateEqual(Date.create('in 5 weeks'), new Date().addWeeks(5), 'Date#create | in 5 weeks');
+  dateEqual(Date.create('in 5 months'), new Date().addMonths(5), 'Date#create | in 5 months');
+  dateEqual(Date.create('in 5 years'), new Date().addYears(5), 'Date#create | in 5 years');
+
+
+
+  // Invalid dates should not return true or false
+
+  equal(Date.create('my pants').isPast(), undefined, 'Date#isPast | invalid dates should return false');
+  equal(Date.create('my pants').isFuture(), undefined, 'Date#isFuture | invalid dates should return false');
+  equal(Date.create('my pants').isToday(), undefined, 'Date#isToday | invalid dates should return false');
+  equal(Date.create('my pants').isTomorrow(), undefined, 'Date#isTomorrow | invalid dates should return false');
+  equal(Date.create('my pants').is('today'), undefined, 'Date#is | invalid dates should return false');
+
+  // Issue #160
+  equal(Date.create('12/01/2013').is('November 2013'), false, 'Date#is | December 2013 is not November 2013');
+
+
+  // Adding a locale
+
+  Date.setLocale('en');
+  Date.addLocale('foobar', { months: ['a','b','c'] });
+
+  equal(Date.getLocale().code, Date.getLocale('en').code, 'Date.addLocale | adding a locale does not affect the current locale');
+  equal(Date.getLocale('foobar').months[0], 'a', 'Date.addLocale | new locale has been added');
+  dateEqual(Date.create('a', 'foobar'), Date.create('January'), 'Date.addLocale | formats have been recognized');
+
+
+
+  // Date.create should clone a date
+
+  date1 = new Date(5000);
+  date2 = Date.create(date1);
+  date1.setTime(10000);
+
+  equal(date1.getTime() === date2.getTime(), false, 'Date.create | created date should not be affected');
+
+  // Simple 12:00am
+
+  equal(Date.create('12:00am').getHours(), 0, 'Date.create| 12:00am hours should be 0');
+  equal(Date.create('12am').getHours(), 0, 'Date.create| 12am hours should be 0');
+
+
+  // New handling of UTC dates
+
+  date1 = Date.utc.create('2001-06-15', 'en');
+  date2 = new Date(2001, 5, 15);
+
+  dateEqual(date1, date2.addMinutes(-date2.getTimezoneOffset()), 'Date#create | utc | is equal to date with timezone subtracted');
+  equal(date1._utc, false, 'Date#create | utc | does not set internal flag');
+
+
+  d = new Date(2001, 5, 15).utc();
+
+  equal(d._utc, true, 'Date#utc | sets internal flag');
+  dateEqual(d, new Date(2001, 5, 15), 'Date#utc | does not change date');
+  dateEqual(d.clone().beginningOfMonth(), new Date(Date.UTC(2001, 5, 1)), 'Date#beginningOfMonth | utc');
+  dateEqual(d.clone().endOfMonth(), new Date(Date.UTC(2001, 5, 30, 23, 59, 59, 999)), 'Date#endOfMonth | utc');
+
+  equal(Date.create('1 month ago').utc().isLastMonth(), true, 'Date#utc | isLastMonth');
+  equal(d.minutesSince(Date.utc.create('2001-06-15', 'en')), d.getTimezoneOffset(), 'Date#utc | minutesSince is equal to the timezone offset');
+  equal(d.hoursSince('2001-6-14'), 24, 'Date#utc | hoursSince | does not actually shift time');
+
+  d = Date.utc.create('2001-06-15', 'en').utc(true);
+
+  equal(d.iso(), '2001-06-15T00:00:00.000Z', 'Date#create | utc | will properly be output in UTC');
+  equal(d.format('{tz}'), '+0000', 'Date#format | UTC date will have +0000 offset');
+  equal(d.getUTCOffset(), '+0000', 'Date#getUTCOffset | utc');
+  dateEqual(d.clone().advance('1 month'), new Date(Date.UTC(2001, 6, 15)), 'Date#advance | utc');
+
+  equal(Date.utc.create('2010-02-01', 'en').utc().daysInMonth(), 28, 'Date#daysInMonth | utc | should find correct days in month');
+  equal(Date.utc.create('2000-01', 'en').utc().isLeapYear(), true, 'Date#isLeapYear | accounts for utc dates');
+
+
+  d = Date.utc.create('2000-02-18 11:00pm', 'en').utc(true);
+
+  equal(d.is('Friday', null, true), true, 'Date#is | utc | friday');
+  equal(d.isWeekday(true), true, 'Date#isWeekday | utc | friday');
+  equal(d.is('2000-02-18', null, true), true, 'Date#is | utc | friday | full date');
+  equal(d.isAfter(Date.utc.create('2000-02-18 10:00pm', 'en')), true, 'Date#isAfter | utc | friday | full date');
+  equal(d.clone().reset(), new Date(Date.UTC(2000, 1, 18)), 'Date#reset | utc');
+
+
+  d = Date.utc.create('2000-02-14', 'en').utc(true);
+
+  equal(d.is('Monday', null, true), true, 'Date#is | utc | monday');
+  equal(d.isWeekday(true), true, 'Date#isWeekday | utc | monday');
+  equal(d.is('2000-02-14', null, true), true, 'Date#is | utc | monday | full date');
+
+
+  equal(d.format(), 'February 14, 2000 12:00am', 'Date#format | from UTC time');
+  equal(d.full(), 'Monday February 14, 2000 12:00:00am', 'Date#full | from UTC time');
+  equal(d.long(), 'February 14, 2000 12:00am', 'Date#long | from UTC time');
+  equal(d.short(), 'February 14, 2000', 'Date#short | from UTC time');
+
+  // Relative dates are unaffected
+  equal(Date.utc.create('1 minute ago', 'en').relative(), '1 minute ago', 'Date#relative | utc');
+
+
+  d = new Date(2001, 5, 15).utc().utc(false);
+
+  equal(d._utc, false, 'Date#utc | can turn off');
+
+  d = new Date(2001, 5, 15);
+
+  var hours = d.getHours() - (d.getTimezoneOffset() / 60);
+
+
+  // Issue #203
+
+  dateEqual(Date.create('The day after tomorrow 3:45pm', 'en'), new Date(now.getFullYear(), now.getMonth(), now.getDate() + 2, 15, 45), 'Date#create | Fuzzy Dates | The day after tomorrow at 3:45pm');
+  dateEqual(Date.create('The day before yesterday at 11:15am', 'en'), new Date(now.getFullYear(), now.getMonth(), now.getDate() - 2, 11, 15), 'Date#create | Fuzzy Dates | the day before yesterday at 11:15am');
+
+
+  dateEqual(Date.create('the 28th'), new Date().set({ date:28 }, true), 'Date#create | the 28th');
+  dateEqual(Date.create('28th'), new Date().set({ date:28 }, true), 'Date#create | 28th');
+  dateEqual(Date.create('the 28th of January'), new Date().set({ month: 0, date:28 }, true), 'Date#create | the 28th of January');
+  dateEqual(Date.create('28th of January'), new Date().set({ month: 0, date:28 }, true), 'Date#create | 28th of January');
+
+  // Issue #210
+
+  equal(Date.future('Sunday at 3:00').getWeekday(), 0, 'Date.future | weekday should never be ambiguous');
+
+
+
+  // Issue #219
+
+  equal(Date.create('28:00').isValid(),  true,  'Date#create | hours may fall outside range');
+  equal(Date.create('59:00').isValid(),  true,  'Date#create | no hours allowed outside range');
+  equal(Date.create('139:00').isValid(), false, 'Date#create | 3 digits not supported');
+
+  // These dates actually will parse out natively in V8
+  // equal(Date.create('05:75').isValid(), false, 'Date#create | no minutes allowed outside range');
+  // equal(Date.create('05:59:60').isValid(), false, 'Date#create | no seconds allowed outside range');
+  equal(Date.create('05:59:59').isValid(), true, 'Date#create | seconds within range');
+
+
+  // Issue #221
+
+  dateEqual(new Date(2012, 0).addMonths(-13), new Date(2010, 11), 'Date#addMonths | Month traversal should not kick in when n < -12');
+
+  // Issue #227
+
+  dateEqual(Date.create('0 January'), new Date(now.getFullYear() - 1, 11, 31), 'Date#addMonths | 0 January');
+  dateEqual(Date.create('1 January'), new Date(now.getFullYear(), 0, 1), 'Date#addMonths | 1 January');
+  dateEqual(Date.create('01 January'), new Date(now.getFullYear(), 0, 1), 'Date#addMonths | 01 January');
+  dateEqual(Date.create('15 January'), new Date(now.getFullYear(), 0, 15), 'Date#addMonths | 15 January');
+  dateEqual(Date.create('31 January'), new Date(now.getFullYear(), 0, 31), 'Date#addMonths | 31 January');
+
+  dateEqual(Date.create('1 Jan'), new Date(now.getFullYear(), 0, 1), 'Date#addMonths | 1 Jan');
+  dateEqual(Date.create('01 Jan'), new Date(now.getFullYear(), 0, 1), 'Date#addMonths | 01 Jan');
+  dateEqual(Date.create('15 Jan'), new Date(now.getFullYear(), 0, 15), 'Date#addMonths | 15 Jan');
+  dateEqual(Date.create('31 Jan'), new Date(now.getFullYear(), 0, 31), 'Date#addMonths | 31 Jan');
+
+  dateEqual(Date.create('0 July'), new Date(now.getFullYear(), 5, 30), 'Date#addMonths | 0 July');
+  dateEqual(Date.create('1 July'), new Date(now.getFullYear(), 6, 1), 'Date#addMonths | 1 July');
+  dateEqual(Date.create('01 July'), new Date(now.getFullYear(), 6, 1), 'Date#addMonths | 01 July');
+  dateEqual(Date.create('15 July'), new Date(now.getFullYear(), 6, 15), 'Date#addMonths | 15 July');
+  dateEqual(Date.create('31 July'), new Date(now.getFullYear(), 6, 31), 'Date#addMonths | 31 July');
+  dateEqual(Date.create('32 July'), new Date(now.getFullYear(), 7, 1), 'Date#addMonths | 32 July');
+
+  dateEqual(Date.create('1 Dec'), new Date(now.getFullYear(), 11, 1), 'Date#addMonths | 1 Dec');
+  dateEqual(Date.create('01 Dec'), new Date(now.getFullYear(), 11, 1), 'Date#addMonths | 01 Dec');
+  dateEqual(Date.create('15 Dec'), new Date(now.getFullYear(), 11, 15), 'Date#addMonths | 15 Dec');
+  dateEqual(Date.create('31 Dec'), new Date(now.getFullYear(), 11, 31), 'Date#addMonths | 31 Dec');
+
+  dateEqual(Date.create('1 December'), new Date(now.getFullYear(), 11, 1), 'Date#addMonths | 1 December');
+  dateEqual(Date.create('01 December'), new Date(now.getFullYear(), 11, 1), 'Date#addMonths | 01 December');
+  dateEqual(Date.create('15 December'), new Date(now.getFullYear(), 11, 15), 'Date#addMonths | 15 December');
+  dateEqual(Date.create('31 December'), new Date(now.getFullYear(), 11, 31), 'Date#addMonths | 31 December');
+  dateEqual(Date.create('32 December'), new Date(now.getFullYear() + 1, 0, 1), 'Date#addMonths | 32 December');
+
+  dateEqual(Date.create('1 January 3pm'), new Date(now.getFullYear(), 0, 1, 15), 'Date#addMonths | 1 January 3pm');
+  dateEqual(Date.create('1 January 3:45pm'), new Date(now.getFullYear(), 0, 1, 15, 45), 'Date#addMonths | 1 January 3:45pm');
+
+
+  dateEqual(Date.create("'87"), new Date(1987, 0), "Date#create | '87");
+  dateEqual(Date.create("May '87"), new Date(1987, 4), "Date#create | May '87");
+  dateEqual(Date.create("14 May '87"), new Date(1987, 4, 14), "Date#create | 14 May '87");
+
+  // Issue #235
+
+  equal(Date.create().utc(true).clone().isUTC(), true, 'Date#clone | should preserve UTC');
+  equal(new Date().clone()._utc, false, 'Date#clone | should never be UTC if flag not set');
+  equal(Date.create(new Date().utc(true)).isUTC(), true, 'Date#create | should preserve UTC');
+  equal(Date.create(new Date()).isUTC(), isCurrentlyGMT, 'Date#create | non utc date should not have UTC flag');
+
+
+  // Issue #236
+  equal((14).hoursFromNow().daysFromNow(), 0, 'Date#daysFromNow | should floor the number rather than round');
+
+  // Issue #224
+  equal(Date.create('').isValid(), false, 'Date.create | empty strings are not valid');
+
+
+  // Issue #244
+
+  dateEqual(Date.utc.create('0999'), new Date(Date.UTC(999, 0)), 'Date.utc.create | 3 digit year 999 should be equal to ISO8601');
+  dateEqual(Date.utc.create('0123'), new Date(Date.UTC(123, 0)), 'Date.utc.create | 3 digit year 123 should be equal to ISO8601');
+
+  // Issue #251
+
+
+  equal(new Date(2013, 0).setISOWeek(1), new Date(2013, 0, 1).getTime(), 'Date#setISOWeek | Should follow ISO8601');
+  equal(new Date(2013, 0, 6).setISOWeek(1), new Date(2013, 0, 6).getTime(), 'Date#setISOWeek | Sunday should remain at the end of the week as per ISO8601 standard');
+  equal(new Date(2013, 0, 13).setISOWeek(1), new Date(2013, 0, 6).getTime(), 'Date#setISOWeek | Sunday one week ahead');
+  equal(new Date(2013, 0, 7).setISOWeek(1), new Date(2012, 11, 31).getTime(), 'Date#setISOWeek | Monday should remain at the beginning of the week as per ISO8601 standard');
+  equal(new Date(2013, 0, 14).setISOWeek(2), new Date(2013, 0, 7).getTime(), 'Date#setISOWeek | Monday one week ahead');
+  dateEqual(Date.utc.create(2013, 0, 14).utc().set({ week: 1 }), Date.utc.create(2012, 11, 31), 'Date#set | utc dates should not throw errors on week set');
+
+
+  // Issue #262
+  equal(/\d+-/.test(new Date().format('{timezone}')), false, 'Date#format | Timezone format should not include hyphens')
+
+
+  // Issue #264
+
+  Date.setLocale('fo');
+  equal(Date.create().isToday(), true, 'Date#isToday | should always work regardless of locale');
+  equal(Date.create('yesterday', 'en').isYesterday(), true, 'Date#isYesterday | should always work regardless of locale');
+  equal(Date.create('tomorrow', 'en').isTomorrow(), true, 'Date#isTomorrow | should always work regardless of locale');
+  equal(Date.create('monday', 'en').isMonday(), true, 'Date#isMonday | should always work regardless of locale');
+  equal(Date.create('tuesday', 'en').isTuesday(), true, 'Date#isTuesday | should always work regardless of locale');
+  equal(Date.create('wednesday', 'en').isWednesday(), true, 'Date#isWednesday | should always work regardless of locale');
+  equal(Date.create('thursday', 'en').isThursday(), true, 'Date#isThursday | should always work regardless of locale');
+  equal(Date.create('friday', 'en').isFriday(), true, 'Date#isFriday | should always work regardless of locale');
+  equal(Date.create('saturday', 'en').isSaturday(), true, 'Date#isSaturday | should always work regardless of locale');
+  equal(Date.create('sunday', 'en').isSunday(), true, 'Date#isSunday | should always work regardless of locale');
+  equal(Date.create('1 day ago', 'en').isPast(), true, 'Date#isPast | should always work regardless of locale');
+  equal(Date.create('1 day from now', 'en').isFuture(), true, 'Date#isFuture | should always work regardless of locale');
+  Date.setLocale('en');
+
+
+
+  // Issue #267
+
+  equal(new Date('Mar 01, 2013').daysUntil(new Date('Mar 28, 2013')), 27, 'Date#daysUntil | should not be phased by DST traversal');
+  equal(new Date('Mar 10, 2013').daysUntil(new Date('Mar 11, 2013')), 1, 'Date#daysUntil | exact DST traversal point for CST/CDT');
+
+  // Issue #310
+
+  dateEqual(Date.create('6:30pm in 1 day'), getRelativeDate(null, null, 1).set({hours:18,minutes:30}, true), 'Date#create | 6:30pm in 1 day');
+  dateEqual(Date.create('6:30pm in 3 days'), getRelativeDate(null, null, 3).set({hours:18,minutes:30}, true), 'Date#create | 6:30pm in 3 days');
+  dateEqual(Date.create('6:30pm in -3 days'), getRelativeDate(null, null, -3).set({hours:18,minutes:30}, true), 'Date#create | 6:30pm in -3 days');
+
+  dateEqual(Date.create('6:30pm 2 days ago'), getRelativeDate(null, null, -2).set({hours:18,minutes:30}, true), 'Date#create | 6:30pm in 2 days ago');
+
+  dateEqual(Date.create('21:00 in 2 weeks'), getRelativeDate(null, null, 14).set({hours:21}, true), 'Date#create | 21:00pm in 2 weeks');
+  dateEqual(Date.create('5:00am in a month'), getRelativeDate(null, 1).set({hours:5}, true), 'Date#create | 5:00am in a month');
+  dateEqual(Date.create('5am in a month'), getRelativeDate(null, 1).set({hours:5}, true), 'Date#create | 5am in a month');
+  dateEqual(Date.create('5:01am in a month'), getRelativeDate(null, 1).set({hours:5,minutes:1}, true), 'Date#create | 5:01am in a month');
+
+  equal(Date.create('5am in an hour').isValid(), false, 'Date#create | 5am in an hour is invalid');
+  equal(Date.create('5am in a minute').isValid(), false, 'Date#create | 5am in a minute is invalid');
+
+
+  // Issue #326 begining/endOfISOWeek
+
+
+  dateEqual(new Date(2013, 6, 8).beginningOfISOWeek(),  new Date(2013, 6, 8), 'Date#beginningOfISOWeek  | Mon');
+  dateEqual(new Date(2013, 6, 9).beginningOfISOWeek(),  new Date(2013, 6, 8), 'Date#beginningOfISOWeek  | Tue');
+  dateEqual(new Date(2013, 6, 10).beginningOfISOWeek(), new Date(2013, 6, 8), 'Date#beginningOfISOWeek  | Wed');
+  dateEqual(new Date(2013, 6, 11).beginningOfISOWeek(), new Date(2013, 6, 8), 'Date#beginningOfISOWeek  | Thu');
+  dateEqual(new Date(2013, 6, 12).beginningOfISOWeek(), new Date(2013, 6, 8), 'Date#beginningOfISOWeek  | Fri');
+  dateEqual(new Date(2013, 6, 13).beginningOfISOWeek(), new Date(2013, 6, 8), 'Date#beginningOfISOWeek  | Sat');
+  dateEqual(new Date(2013, 6, 14).beginningOfISOWeek(), new Date(2013, 6, 8), 'Date#beginningOfISOWeek  | Sun');
+  dateEqual(new Date(2013, 6, 15).beginningOfISOWeek(), new Date(2013, 6, 15), 'Date#beginningOfISOWeek | next Mon');
+
+  dateEqual(new Date(2013, 6, 8).endOfISOWeek(),  new Date(2013, 6, 14, 23, 59, 59, 999), 'Date#endOfISOWeek  | Mon');
+  dateEqual(new Date(2013, 6, 9).endOfISOWeek(),  new Date(2013, 6, 14, 23, 59, 59, 999), 'Date#endOfISOWeek  | Tue');
+  dateEqual(new Date(2013, 6, 10).endOfISOWeek(), new Date(2013, 6, 14, 23, 59, 59, 999), 'Date#endOfISOWeek  | Wed');
+  dateEqual(new Date(2013, 6, 11).endOfISOWeek(), new Date(2013, 6, 14, 23, 59, 59, 999), 'Date#endOfISOWeek  | Thu');
+  dateEqual(new Date(2013, 6, 12).endOfISOWeek(), new Date(2013, 6, 14, 23, 59, 59, 999), 'Date#endOfISOWeek  | Fri');
+  dateEqual(new Date(2013, 6, 13).endOfISOWeek(), new Date(2013, 6, 14, 23, 59, 59, 999), 'Date#endOfISOWeek  | Sat');
+  dateEqual(new Date(2013, 6, 14).endOfISOWeek(), new Date(2013, 6, 14, 23, 59, 59, 999), 'Date#endOfISOWeek  | Sun');
+  dateEqual(new Date(2013, 6, 15).endOfISOWeek(), new Date(2013, 6, 21, 23, 59, 59, 999), 'Date#endOfISOWeek | next Mon');
+
+  dateEqual(new Date(2013, 6, 10, 8, 30).beginningOfISOWeek(), new Date(2013, 6, 8), 'Date#beginningOfISOWeek  | resets time');
+  dateEqual(new Date(2013, 6, 12, 8, 30).endOfISOWeek(), new Date(2013, 6, 14, 23, 59, 59, 999), 'Date#endOfISOWeek  | resets time');
+
+
+  // Issue #342 handling offsets for comparison
+
+  Date.SugarNewDate = function() {
+    var d = new Date();
+    d.addMinutes(d.getTimezoneOffset() - 600);
+    // Honolulu time zone
+    return d;
+  };
+
+  var offset = 600 - new Date().getTimezoneOffset();
+  dateEqual(Date.create(), getRelativeDate(null, null, null, null, -offset), 'Date.create | simple create should respect global offset');
+  dateEqual(Date.create('1 day ago'), getRelativeDate(null, null, -1, null, -offset), 'Date.create | relative date should respect global offset');
+  equal(Date.past('4pm').getTime() < (new Date().getTime() + (-offset * 60 * 1000)), true, 'Date.past | repsects global offset');
+  equal(Date.future('4pm').getTime() > (new Date().getTime() + (-offset * 60 * 1000)), true, 'Date.future | repsects global offset');
+
+  d = new Date;
+  d.addMinutes(d.getTimezoneOffset() + 60);
+
+  equal(d.isFuture(), true, 'Date#isFuture | should respect global offset');
+  equal(d.isPast(), false, 'Date#isPast | should respect global offset');
+
+  // Issue #342 internal constructor override
+
+  var AwesomeDate = function() {};
+  AwesomeDate.prototype = new Date();
+  AwesomeDate.prototype.getMinutes = function() {
+  }
+
+  Date.SugarNewDate = function() {
+    return new AwesomeDate();
+  }
+
+  equal(Date.create() instanceof AwesomeDate, true, 'Date.SugarNewDate | Result should be use in Date.create');
+
+  Date.SugarNewDate = null;
+
+
+});
+
level2/node_modules/sugar/test/environments/sugar/date_da.js
@@ -0,0 +1,539 @@
+test('Dates | Danish', function () {
+
+  var now = new Date();
+  Date.setLocale('da');
+
+  dateEqual(
+    Date.create('den 15. maj 2011'),
+    new Date(2011, 4, 15),
+    'Date#create | basic Danish date'
+  );
+
+  dateEqual(
+    Date.create('15 maj 2011'),
+    new Date(2011, 4, 15),
+    'Date#create | basic Danish date'
+  );
+
+  dateEqual(Date.create('tirsdag 5 januar 2012'),
+    new Date(2012, 0, 5),
+    'Date#create | Danish | 2012-01-05'
+  );
+
+  dateEqual(
+    Date.create('tirsdag, 5 januar 2012'),
+    new Date(2012, 0, 5),
+    'Date#create | Danish | 2012-01-05'
+  );
+
+  dateEqual(
+    Date.create('maj 2011'),
+    new Date(2011, 4),
+    'Date#create | Danish | year and month'
+  );
+
+  dateEqual(
+    Date.create('15 maj'),
+    new Date(now.getFullYear(), 4, 15),
+    'Date#create | Danish | month and date'
+  );
+
+  dateEqual(
+    Date.create('2011'),
+    new Date(2011, 0),
+    'Date#create | Danish | year'
+  );
+
+  dateEqual(
+    Date.create('maj'),
+    new Date(now.getFullYear(), 4),
+    'Date#create | Danish | month'
+  );
+
+  dateEqual(
+    Date.create('mandag'),
+    getDateWithWeekdayAndOffset(1),
+    'Date#create | Danish | Monday'
+  );
+
+  dateEqual(
+   Date.create('15 maj 2011 3:45'),
+   new Date(2011, 4, 15, 3, 45),
+   'Date#create | basic Danish date 3:45'
+  );
+
+  dateEqual(
+    Date.create('15 maj 2011 3:45pm'),
+    new Date(2011, 4, 15, 15, 45),
+    'Date#create | basic Danish date 3:45pm'
+  );
+
+  dateEqual(
+    Date.create('for et millisekund siden'),
+    getRelativeDate(null, null, null, null, null, null,-1),
+    'Date#create | Danish | one millisecond ago'
+  );
+
+  dateEqual(
+    Date.create('for et sekund siden'),
+    getRelativeDate(null, null, null, null, null, -1),
+    'Date#create | Danish | one second ago'
+  );
+
+  dateEqual(
+    Date.create('for et minut siden'),
+    getRelativeDate(null, null, null, null, -1),
+    'Date#create | Danish | one minute ago'
+  );
+
+  dateEqual(
+    Date.create('for en time siden'),
+    getRelativeDate(null, null, null, -1),
+    'Date#create | Danish | one hour ago'
+  );
+
+  dateEqual(
+    Date.create('for en dag siden'),
+    getRelativeDate(null, null, -1),
+    'Date#create | Danish | one day ago'
+  );
+
+  dateEqual(
+    Date.create('for en uge siden'),
+    getRelativeDate(null, null, -7),
+    'Date#create | Danish | one week ago'
+  );
+
+  dateEqual(
+    Date.create('for en mรฅned siden'),
+    getRelativeDate(null, -1),
+    'Date#create | Danish | one month ago'
+  );
+
+  dateEqual(
+    Date.create('for et รฅr siden'),
+    getRelativeDate(-1),
+    'Date#create | Danish | one year ago'
+  );
+
+  dateEqual(
+    Date.create('et รฅr siden'),
+    getRelativeDate(-1),
+    'Date#create | Danish | one year ago'
+  );
+
+  dateEqual(
+    Date.create('om 5 millisekunder'),
+    getRelativeDate(null, null, null, null, null, null,5),
+    'Date#create | Danish | five milliseconds from now'
+  );
+
+  dateEqual(
+    Date.create('om 5 sekunder'),
+    getRelativeDate(null, null, null, null, null, 5),
+    'Date#create | Danish | five seconds from now'
+  );
+
+  dateEqual(
+    Date.create('om 5 minutter'),
+    getRelativeDate(null, null, null, null, 5),
+    'Date#create | Danish | five minute from now'
+  );
+
+  dateEqual(
+    Date.create('om 5 timer'),
+    getRelativeDate(null, null, null, 5),
+    'Date#create | Danish | five hour from now'
+  );
+
+  dateEqual(
+    Date.create('om 5 dage'),
+    getRelativeDate(null, null, 5),
+    'Date#create | Danish | five day from now'
+  );
+
+  dateEqual(
+    Date.create('om 5 uger'),
+    getRelativeDate(null, null, 35),
+    'Date#create | Danish | five weeks from now'
+  );
+
+  dateEqual(
+    Date.create('om 5 mรฅneder'),
+    getRelativeDate(null, 5),
+    'Date#create | Danish | five months from now'
+  );
+
+  dateEqual(
+    Date.create('om 5 รฅr'),
+    getRelativeDate(5),
+    'Date#create | Danish | five years from now'
+  );
+
+  dateEqual(
+    Date.create('i forgรฅrs'),
+    getRelativeDate(null, null, -2).reset(),
+    'Date#create | Danish | day before yesterday'
+  );
+
+  dateEqual(
+    Date.create('forgรฅrs'),
+    getRelativeDate(null, null, -2).reset(),
+    'Date#create | Danish | day before yesterday'
+  );
+
+  dateEqual(
+    Date.create('i gรฅr'),
+    getRelativeDate(null, null, -1).reset(),
+    'Date#create | Danish | yesterday'
+  );
+
+  dateEqual(
+    Date.create('i dag'),
+    getRelativeDate(null, null, 0).reset(),
+    'Date#create | Danish | today'
+  );
+
+  dateEqual(
+    Date.create('idag'),
+    getRelativeDate(null, null, 0).reset(),
+    'Date#create | Danish | today'
+  );
+
+  dateEqual(
+    Date.create('imorgen'),
+    getRelativeDate(null, null, 1).reset(),
+    'Date#create | Danish | tomorrow'
+  );
+
+  dateEqual(
+    Date.create('i morgen'),
+    getRelativeDate(null, null, 1).reset(),
+    'Date#create | Danish | tomorrow'
+  );
+
+  dateEqual(
+    Date.create('i overmorgen'),
+    getRelativeDate(null, null, 2).reset(),
+    'Date#create | Danish | day after tomorrow'
+  );
+
+  dateEqual(
+    Date.create('i over morgen'),
+    getRelativeDate(null, null, 2).reset(),
+    'Date#create | Danish | day after tomorrow'
+  );
+
+  dateEqual(
+    Date.create('sidste uge'),
+    getRelativeDate(null, null, -7),
+    'Date#create | Danish | Last week'
+  );
+
+  dateEqual(
+    Date.create('i sidste uge'),
+    getRelativeDate(null, null, -7),
+    'Date#create | Danish | Last week'
+  );
+
+  dateEqual(
+    Date.create('nรฆste uge'),
+    getRelativeDate(null, null, 7),
+    'Date#create | Danish | Next week'
+  );
+
+  dateEqual(
+    Date.create('naeste uge'),
+    getRelativeDate(null, null, 7),
+    'Date#create | Danish | Next week'
+  );
+
+  dateEqual(
+    Date.create('sidste mรฅned'),
+    getRelativeDate(null, -1),
+    'Date#create | Danish | last month'
+  );
+
+  dateEqual(
+    Date.create('nรฆste mรฅned'),
+    getRelativeDate(null, 1),
+    'Date#create | Danish | Next month'
+  );
+
+  dateEqual(
+    Date.create('sidste รฅr'),
+    getRelativeDate(-1),
+    'Date#create | Danish | Last year'
+  );
+
+  dateEqual(
+    Date.create('nรฆste รฅr'),
+    getRelativeDate(1),
+    'Date#create | Danish | Next year'
+  );
+
+  dateEqual(
+    Date.create('sidste mandag'),
+    getDateWithWeekdayAndOffset(1,  -7),
+    'Date#create | Danish | last monday'
+  );
+
+  dateEqual(
+    Date.create('nรฆste mandag'),
+    getDateWithWeekdayAndOffset(1, 7),
+    'Date#create | Danish | next monday'
+  );
+  
+  dateEqual(
+    Date.create('i gรฅr'),
+    getRelativeDate(null, null, -1).reset(),
+    'Date#create | Danish | yesterday'
+  );
+
+  dateEqual(
+    Date.create('i overmorgen'),
+    getRelativeDate(null, null, 2).reset(),
+    'Date#create | Danish | day after tomorrow'
+  );
+
+  dateEqual(
+    Date.create('i over morgen'),
+    getRelativeDate(null, null, 2).reset(),
+    'Date#create | Danish | day after tomorrow'
+  );
+
+  
+  dateEqual(
+    Date.create('sidste uge'),
+    getRelativeDate(null, null, -7),
+    'Date#create | Danish | Last week'
+  );
+
+  dateEqual(
+    Date.create('i sidste uge'),
+    getRelativeDate(null, null, -7),
+    'Date#create | Danish | Last week'
+  );
+
+  dateEqual(
+    Date.create('sidste mรฅned'),
+    getRelativeDate(null, -1),
+    'Date#create | Danish | last month'
+  );
+
+  dateEqual(
+    Date.create('nรฆste mรฅned'),
+    getRelativeDate(null, 1),
+    'Date#create | Danish | Next month'
+  );
+
+  dateEqual(
+    Date.create('sidste รฅr'),
+    getRelativeDate(-1),
+    'Date#create | Danish | Last year'
+  );
+
+  dateEqual(
+    Date.create('nรฆste รฅr'),
+    getRelativeDate(1),
+    'Date#create | Danish | Next year'
+  );
+
+  equal(
+    Date.create('2001-06-14 3:45pm').format(),
+    'den 14. juni 2001 15:45',
+    'Date#create | Danish | format'
+  );
+
+  equal(
+    Date.create('2011-08-25').format('{dd} {month} {yyyy}'),
+    '25 august 2011',
+    'Date#create | Danish | format'
+  );
+
+  equal(
+    Date.create('1 second ago', 'en').relative(),
+    '1 sekund siden',
+    'Date#create | Danish | relative format past'
+  );
+
+  equal(
+    Date.create('1 minute ago', 'en').relative(),
+    '1 minut siden',
+    'Date#create | Danish | relative format past'
+  );
+
+  equal(
+    Date.create('1 hour ago', 'en').relative(),
+    '1 time siden',
+    'Date#create | Danish | relative format past'
+  );
+
+  equal(
+    Date.create('1 day ago', 'en').relative(),
+    '1 dag siden',
+    'Date#create | Danish | relative format past'
+  );
+
+  equal(
+    Date.create('1 week ago', 'en').relative(),
+    '1 uge siden',
+    'Date#create | Danish | relative format past'
+  );
+
+  equal(
+    Date.create('1 month ago', 'en').relative(),
+    '1 mรฅned siden',
+    'Date#create | Danish | relative format past'
+  );
+
+  equal(
+    Date.create('1 year ago', 'en').relative(),
+    '1 รฅr siden',
+    'Date#create | Danish | relative format past'
+  );
+
+  equal(
+    Date.create('5 seconds ago', 'en').relative(),
+    '5 sekunder siden',
+    'Date#create | Danish | relative format past'
+  );
+
+  equal(
+    Date.create('5 minutes ago', 'en').relative(),
+    '5 minutter siden',
+    'Date#create | Danish | relative format past'
+  );
+
+  equal(
+    Date.create('5 hours ago', 'en').relative(),
+    '5 timer siden',
+    'Date#create | Danish | relative format past'
+  );
+
+  equal(
+    Date.create('5 days ago', 'en').relative(),
+    '5 dage siden',
+    'Date#create | Danish | relative format past'
+  );
+
+  equal(
+    Date.create('3 weeks ago', 'en').relative(),
+    '3 uger siden',
+    'Date#create | Danish | relative format past'
+  );
+
+  equal(
+    Date.create('5 weeks ago', 'en').relative(),
+    '1 mรฅned siden',
+    'Date#create | Danish | relative format past'
+  );
+
+  equal(
+    Date.create('5 months ago', 'en').relative(),
+    '5 mรฅneder siden',
+    'Date#create | Danish | relative format past'
+  );
+
+  equal(
+    Date.create('5 years ago', 'en').relative(),
+    '5 รฅr siden',
+    'Date#create | Danish | relative format past'
+  );
+
+  equal(
+    Date.create('1 second from now', 'en').relative(),
+    'om 1 sekund',
+    'Date#create | Danish | relative format future'
+  );
+
+  equal(
+    Date.create('1 minute from now', 'en').relative(),
+    'om 1 minut',
+    'Date#create | Danish | relative format future'
+  );
+
+  equal(
+    Date.create('1 hour from now', 'en').relative(),
+    'om 1 time',
+    'Date#create | Danish | relative format future'
+  );
+
+  equal(
+    Date.create('1 day from now', 'en').relative(),
+    'om 1 dag',
+    'Date#create | Danish | relative format future'
+  );
+
+  equal(
+    Date.create('1 week from now', 'en').relative(),
+    'om 1 uge',
+    'Date#create | Danish | relative format future'
+  );
+
+  equal(
+    Date.create('1 month from now', 'en').relative(),
+    'om 1 mรฅned',
+    'Date#create | Danish | relative format future'
+  );
+
+  equal(
+    Date.create('1 year from now', 'en').relative(),
+    'om 1 รฅr',
+    'Date#create | Danish | relative format future'
+  );
+
+  equal(
+    Date.create('5 second from now', 'en').relative(),
+    'om 5 sekunder',
+    'Date#create | Danish | relative format future'
+  );
+
+  equal(
+    Date.create('5 minutes from now', 'en').relative(),
+    'om 5 minutter',
+    'Date#create | Danish | relative format future'
+  );
+
+  equal(
+    Date.create('5 hour from now', 'en').relative(),
+    'om 5 timer',
+    'Date#create | Danish | relative format future'
+  );
+
+  equal(
+    Date.create('5 day from now', 'en').relative(),
+    'om 5 dage',
+    'Date#create | Danish | relative format future'
+  );
+
+  equal(
+    Date.create('3 weeks from now', 'en').relative(),
+    'om 3 uger',
+    'Date#create | Danish | relative format future'
+  );
+
+  equal(
+    Date.create('5 weeks from now', 'en').relative(),
+    'om 1 mรฅned',
+    'Date#create | Danish | relative format future'
+  );
+
+  equal(
+    Date.create('5 month from now', 'en').relative(),
+    'om 5 mรฅneder',
+    'Date#create | Danish | relative format future'
+  );
+
+  equal(
+    Date.create('5 year from now', 'en').relative(),
+    'om 5 รฅr',
+    'Date#create | Danish | relative format future'
+  );
+
+  equal(
+    (5).hours().duration('da'),
+    '5 timer',
+    'Date#create | Danish | simple duration'
+  );
+});
level2/node_modules/sugar/test/environments/sugar/date_de.js
@@ -0,0 +1,151 @@
+test('Dates | German', function () {
+
+  var now = new Date();
+  var then = new Date(2011, 7, 25, 15, 45, 50);
+  Date.setLocale('de');
+
+  dateEqual(Date.create('15. Mai 2011'), new Date(2011, 4, 15), 'Date#create | basic German date');
+  dateEqual(Date.create('Dienstag, 5. Januar 2012'), new Date(2012, 0, 5), 'Date#create | German | 2012-01-05');
+  dateEqual(Date.create('Mai 2011'), new Date(2011, 4), 'Date#create | German | year and month');
+  dateEqual(Date.create('15. Mai'), new Date(now.getFullYear(), 4, 15), 'Date#create | German | month and date');
+  dateEqual(Date.create('2011'), new Date(2011, 0), 'Date#create | German | year');
+
+  dateEqual(Date.create('Dienstag, 5. Januar 2012 3:45'), new Date(2012, 0, 5, 3, 45), 'Date#create | German | 2012-01-05 3:45');
+  dateEqual(Date.create('Dienstag, 5. Januar 2012 3:45pm'), new Date(2012, 0, 5, 15, 45), 'Date#create | German | 2012-01-05 3:45pm');
+
+  dateEqual(Date.create('Januar'), new Date(now.getFullYear(), 0), 'Date#create | German | January');
+  dateEqual(Date.create('Februar'), new Date(now.getFullYear(), 1), 'Date#create | German | February');
+  dateEqual(Date.create('Marz'), new Date(now.getFullYear(), 2), 'Date#create | German | March');
+  dateEqual(Date.create('Mรคrz'), new Date(now.getFullYear(), 2), 'Date#create | German | March');
+  dateEqual(Date.create('April'), new Date(now.getFullYear(), 3), 'Date#create | German | April');
+  dateEqual(Date.create('Mai'), new Date(now.getFullYear(), 4), 'Date#create | German | May');
+  dateEqual(Date.create('Juni'), new Date(now.getFullYear(), 5), 'Date#create | German | June');
+  dateEqual(Date.create('Juli'), new Date(now.getFullYear(), 6), 'Date#create | German | July');
+  dateEqual(Date.create('August'), new Date(now.getFullYear(), 7), 'Date#create | German | August');
+  dateEqual(Date.create('September'), new Date(now.getFullYear(), 8), 'Date#create | German | September');
+  dateEqual(Date.create('Oktober'), new Date(now.getFullYear(), 9), 'Date#create | German | October');
+  dateEqual(Date.create('November'), new Date(now.getFullYear(), 10), 'Date#create | German | November');
+  dateEqual(Date.create('Dezember'), new Date(now.getFullYear(), 11), 'Date#create | German | December');
+
+
+  dateEqual(Date.create('Sonntag'), getDateWithWeekdayAndOffset(0), 'Date#create | German | Sunday');
+  dateEqual(Date.create('Montag'), getDateWithWeekdayAndOffset(1), 'Date#create | German | Monday');
+  dateEqual(Date.create('Dienstag'), getDateWithWeekdayAndOffset(2), 'Date#create | German | Tuesday');
+  dateEqual(Date.create('Mittwoch'), getDateWithWeekdayAndOffset(3), 'Date#create | German | Wednesday');
+  dateEqual(Date.create('Donnerstag'), getDateWithWeekdayAndOffset(4), 'Date#create | German | Thursday');
+  dateEqual(Date.create('Freitag'), getDateWithWeekdayAndOffset(5), 'Date#create | German | Friday');
+  dateEqual(Date.create('Samstag'), getDateWithWeekdayAndOffset(6), 'Date#create | German | Saturday');
+
+  dateEqual(Date.create('einer Millisekunde vorher'), getRelativeDate(null, null, null, null, null, null,-1), 'Date#create | German | one millisecond ago');
+  dateEqual(Date.create('eine Sekunde vorher'), getRelativeDate(null, null, null, null, null, -1), 'Date#create | German | one second ago');
+  dateEqual(Date.create('einer Minute vorher'), getRelativeDate(null, null, null, null, -1), 'Date#create | German | one minute ago');
+  dateEqual(Date.create('einer Stunde vorher'), getRelativeDate(null, null, null, -1), 'Date#create | German | one hour ago');
+  dateEqual(Date.create('einen Tag vorher'), getRelativeDate(null, null, -1), 'Date#create | German | one day ago');
+  dateEqual(Date.create('eine Woche vorher'), getRelativeDate(null, null, -7), 'Date#create | German | one week ago');
+  dateEqual(Date.create('einen Monat vorher'), getRelativeDate(null, -1), 'Date#create | German | one month ago');
+  dateEqual(Date.create('ein Jahr vorher'), getRelativeDate(-1), 'Date#create | German | one year ago');
+
+  dateEqual(Date.create('vor einer Millisekunde'), getRelativeDate(null, null, null, null, null, null,-1), 'Date#create | German reversed | one millisecond ago');
+  dateEqual(Date.create('vor einer Sekunde'), getRelativeDate(null, null, null, null, null, -1), 'Date#create | German reversed | one second ago');
+  dateEqual(Date.create('vor einer Minute'), getRelativeDate(null, null, null, null, -1), 'Date#create | German reversed | one minute ago');
+  dateEqual(Date.create('vor einer Stunde'), getRelativeDate(null, null, null, -1), 'Date#create | German reversed | one hour ago');
+  dateEqual(Date.create('vor einem Tag'), getRelativeDate(null, null, -1), 'Date#create | German reversed | one day ago');
+  dateEqual(Date.create('vor einer Woche'), getRelativeDate(null, null, -7), 'Date#create | German reversed | one week ago');
+  dateEqual(Date.create('vor einem Monat'), getRelativeDate(null, -1), 'Date#create | German reversed | one month ago');
+  dateEqual(Date.create('vor einem Jahr'), getRelativeDate(-1), 'Date#create | German reversed | one year ago');
+
+
+  dateEqual(Date.create('in 5 Millisekunden'), getRelativeDate(null, null, null, null, null, null,5), 'Date#create | German | dans | five milliseconds from now');
+  dateEqual(Date.create('in 5 Sekunden'), getRelativeDate(null, null, null, null, null, 5), 'Date#create | German | dans | five second from now');
+  dateEqual(Date.create('in 5 Minuten'), getRelativeDate(null, null, null, null, 5), 'Date#create | German | dans | five minute from now');
+  dateEqual(Date.create('in 5 Stunden'), getRelativeDate(null, null, null, 5), 'Date#create | German | dans | five hour from now');
+  dateEqual(Date.create('in 5 Tagen'), getRelativeDate(null, null, 5), 'Date#create | German | dans | five day from now');
+  dateEqual(Date.create('in 5 Wochen'), getRelativeDate(null, null, 35), 'Date#create | German | dans | five weeks from now');
+  dateEqual(Date.create('in 5 Monaten'), getRelativeDate(null, 5), 'Date#create | German | dans | five months from now');
+  dateEqual(Date.create('in 5 Jahren'), getRelativeDate(5), 'Date#create | German | dans | five years from now');
+
+
+  dateEqual(Date.create('vorgestern'), getRelativeDate(null, null, -2).reset(), 'Date#create | German | day before yesterday');
+  dateEqual(Date.create('gestern'), getRelativeDate(null, null, -1).reset(), 'Date#create | German | yesterday');
+  dateEqual(Date.create('heute'), getRelativeDate(null, null, 0).reset(), 'Date#create | German | today');
+  dateEqual(Date.create('morgen'), getRelativeDate(null, null, 1).reset(), 'Date#create | German | tomorrow');
+  dateEqual(Date.create('รผbermorgen'), getRelativeDate(null, null, 2).reset(), 'Date#create | German | day after tomorrow');
+
+  dateEqual(Date.create('letzte Woche'), getRelativeDate(null, null, -7), 'Date#create | German | Last week');
+  dateEqual(Date.create('nรคchste Woche'), getRelativeDate(null, null, 7), 'Date#create | German | Next week');
+
+  dateEqual(Date.create('letzter Monat'), getRelativeDate(null, -1), 'Date#create | German | last month letzter');
+  dateEqual(Date.create('letzten Monat'), getRelativeDate(null, -1), 'Date#create | German | last month letzten');
+  dateEqual(Date.create('nรคchster Monat'), getRelativeDate(null, 1), 'Date#create | German | Next month nachster');
+  dateEqual(Date.create('nรคchsten Monat'), getRelativeDate(null, 1), 'Date#create | German | Next month nachsten');
+
+  dateEqual(Date.create('letztes Jahr'), getRelativeDate(-1), 'Date#create | German | Last year');
+  dateEqual(Date.create('nรคchstes Jahr'), getRelativeDate(1), 'Date#create | German | Next year');
+
+
+  dateEqual(Date.create('kommenden Montag'), getDateWithWeekdayAndOffset(1, 7), 'Date#create | German | kommenden Montag');
+  dateEqual(Date.create('nรคchster Montag'), getDateWithWeekdayAndOffset(1, 7), 'Date#create | German | next monday');
+  dateEqual(Date.create('letztes Montag'), getDateWithWeekdayAndOffset(1, -7), 'Date#create | German | last monday');
+
+  dateEqual(Date.create('letztes Montag 3:45'), getDateWithWeekdayAndOffset(1, -7).set({ hour: 3, minute: 45 }, true), 'Date#create | German | last monday 3:45');
+
+  // no accents
+  dateEqual(Date.create('ubermorgen'), getRelativeDate(null, null, 2).reset(), 'Date#create | German (no accents) | day after tomorrow');
+  dateEqual(Date.create('naechster Monat'), getRelativeDate(null, 1), 'Date#create | German (no accents) | Next month nachster');
+  dateEqual(Date.create('uebermorgen'), getRelativeDate(null, null, 2).reset(), 'Date#create | German | day after tomorrow');
+  dateEqual(Date.create('naechster Monat'), getRelativeDate(null, 1), 'Date#create | German | Next month nachster');
+  dateEqual(Date.create('naechsten Monat'), getRelativeDate(null, 1), 'Date#create | German | Next month nachsten');
+  dateEqual(Date.create('naechstes Jahr'), getRelativeDate(1), 'Date#create | German | Next year');
+
+
+  equal(then.format(), '25. August 2011 15:45', 'Date#create | German | format');
+  equal(then.format('{dd} {Month} {yyyy}'), '25 August 2011', 'Date#create | German | format');
+
+  // Format shortcuts
+  equal(then.format('long'), '25. August 2011 15:45', 'Date#create | German | long format');
+  equal(then.long(), '25. August 2011 15:45', 'Date#create | German | long shortcut');
+  equal(then.format('full'), 'Donnerstag 25. August 2011 15:45:50', 'Date#create | German | full format');
+  equal(then.full(), 'Donnerstag 25. August 2011 15:45:50', 'Date#create | German | full shortcut');
+  equal(then.format('short'), '25. August 2011', 'Date#create | German | short format');
+  equal(then.short(), '25. August 2011', 'Date#create | German | short shortcut');
+
+
+  equal(Date.create('1 second ago', 'en').relative(), 'vor 1 Sekunde', 'Date#create | German | relative format past');
+  equal(Date.create('1 minute ago', 'en').relative(), 'vor 1 Minute',  'Date#create | German | relative format past');
+  equal(Date.create('1 hour ago', 'en').relative(),   'vor 1 Stunde',     'Date#create | German | relative format past');
+  equal(Date.create('1 day ago', 'en').relative(),    'vor 1 Tag',    'Date#create | German | relative format past');
+  equal(Date.create('1 week ago', 'en').relative(),   'vor 1 Woche',  'Date#create | German | relative format past');
+  equal(Date.create('1 month ago', 'en').relative(),  'vor 1 Monat',   'Date#create | German | relative format past');
+  equal(Date.create('1 year ago', 'en').relative(),   'vor 1 Jahr',     'Date#create | German | relative format past');
+
+  equal(Date.create('5 seconds ago', 'en').relative(), 'vor 5 Sekunden', 'Date#create | German | relative format past');
+  equal(Date.create('5 minutes ago', 'en').relative(), 'vor 5 Minuten',  'Date#create | German | relative format past');
+  equal(Date.create('5 hours ago', 'en').relative(),   'vor 5 Stunden',     'Date#create | German | relative format past');
+  equal(Date.create('5 days ago', 'en').relative(),    'vor 5 Tagen',    'Date#create | German | relative format past');
+  equal(Date.create('5 weeks ago', 'en').relative(),   'vor 1 Monat',  'Date#create | German | relative format past');
+  equal(Date.create('5 months ago', 'en').relative(),  'vor 5 Monaten',   'Date#create | German | relative format past');
+  equal(Date.create('5 years ago', 'en').relative(),   'vor 5 Jahren',     'Date#create | German | relative format past');
+
+  equal(Date.create('1 second from now', 'en').relative(), 'in 1 Sekunde', 'Date#create | German | relative format future');
+  equal(Date.create('1 minute from now', 'en').relative(), 'in 1 Minute',  'Date#create | German | relative format future');
+  equal(Date.create('1 hour from now', 'en').relative(),   'in 1 Stunde',     'Date#create | German | relative format future');
+  equal(Date.create('1 day from now', 'en').relative(),    'in 1 Tag',    'Date#create | German | relative format future');
+  equal(Date.create('1 week from now', 'en').relative(),   'in 1 Woche',  'Date#create | German | relative format future');
+  equal(Date.create('1 month from now', 'en').relative(),  'in 1 Monat',   'Date#create | German | relative format future');
+  equal(Date.create('1 year from now', 'en').relative(),   'in 1 Jahr',     'Date#create | German | relative format future');
+
+  equal(Date.create('5 second from now', 'en').relative(), 'in 5 Sekunden', 'Date#create | German | relative format future');
+  equal(Date.create('5 minutes from now', 'en').relative(),'in 5 Minuten',  'Date#create | German | relative format future');
+  equal(Date.create('5 hour from now', 'en').relative(),   'in 5 Stunden',     'Date#create | German | relative format future');
+  equal(Date.create('5 day from now', 'en').relative(),    'in 5 Tagen',    'Date#create | German | relative format future');
+  equal(Date.create('5 week from now', 'en').relative(),   'in 1 Monat',  'Date#create | German | relative format future');
+  equal(Date.create('5 month from now', 'en').relative(),  'in 5 Monaten',   'Date#create | German | relative format future');
+  equal(Date.create('5 year from now', 'en').relative(),   'in 5 Jahren',     'Date#create | German | relative format future');
+
+  dateEqual(Date.create('3:45 15. Mai 2011'), new Date(2011, 4, 15, 3, 45), 'Date#create | time first format');
+
+  dateEqual(Date.create('morgen um 3:30'), getRelativeDate(null, null, 1).set({hours:3,minutes:30}, true), 'Date#create | German | tomorrow at 3:30');
+
+  equal((5).hours().duration('de'), '5 Stunden', 'Date#create | German | simple duration');
+
+});
level2/node_modules/sugar/test/environments/sugar/date_es.js
@@ -0,0 +1,119 @@
+test('Dates | Spanish', function () {
+
+  var now = new Date();
+  var then = new Date(2011, 7, 25, 15, 45, 50);
+  Date.setLocale('es');
+
+  dateEqual(Date.create('la semana pasada'), getRelativeDate(null, null, -7), 'Date#create | Spanish | Last week');
+
+  dateEqual(Date.create('15 de mayo 2011'), new Date(2011, 4, 15), 'Date#create | basic Spanish date');
+  dateEqual(Date.create('5 de enero de 2012'), new Date(2012, 0, 5), 'Date#create | Spanish | 2012-01-05');
+  dateEqual(Date.create('mayo de 2011'), new Date(2011, 4), 'Date#create | Spanish | year and month');
+  dateEqual(Date.create('15 de mayo'), new Date(now.getFullYear(), 4, 15), 'Date#create | Spanish | month and date');
+  dateEqual(Date.create('2011'), new Date(2011, 0), 'Date#create | Spanish | year');
+  dateEqual(Date.create('mayo'), new Date(now.getFullYear(), 4), 'Date#create | Spanish | month');
+  dateEqual(Date.create('lunes'), getDateWithWeekdayAndOffset(1), 'Date#create | Spanish | Monday');
+
+  dateEqual(Date.create('5 de enero de 2012 3:45'), new Date(2012, 0, 5, 3, 45), 'Date#create | Spanish | 2012-01-05 3:45');
+  dateEqual(Date.create('5 de enero de 2012 3:45pm'), new Date(2012, 0, 5, 15, 45), 'Date#create | Spanish | 2012-01-05 3:45pm');
+
+
+  dateEqual(Date.create('hace 1 milisegundo'), getRelativeDate(null, null, null, null, null, null,-1), 'Date#create | Spanish | one millisecond ago');
+  dateEqual(Date.create('hace 1 segundo'), getRelativeDate(null, null, null, null, null, -1), 'Date#create | Spanish | one second ago');
+  dateEqual(Date.create('hace 1 minuto'), getRelativeDate(null, null, null, null, -1), 'Date#create | Spanish | one minute ago');
+  dateEqual(Date.create('hace 1 hora'), getRelativeDate(null, null, null, -1), 'Date#create | Spanish | one hour ago');
+  dateEqual(Date.create('hace 1 dรญa'), getRelativeDate(null, null, -1), 'Date#create | Spanish | one day ago');
+  dateEqual(Date.create('hace 1 semana'), getRelativeDate(null, null, -7), 'Date#create | Spanish | one week');
+  dateEqual(Date.create('hace 1 mes'), getRelativeDate(null, -1), 'Date#create | Spanish | one month ago');
+  dateEqual(Date.create('hace 1 aรฑo'), getRelativeDate(-1), 'Date#create | Spanish | one year ago');
+
+
+  dateEqual(Date.create('dentro de 5 milisegundos'), getRelativeDate(null, null, null, null, null, null,5), 'Date#create | Spanish | five milliseconds from now');
+  dateEqual(Date.create('dentro de 5 segundos'), getRelativeDate(null, null, null, null, null, 5), 'Date#create | Spanish | five second from now');
+  dateEqual(Date.create('dentro de 5 minutos'), getRelativeDate(null, null, null, null, 5), 'Date#create | Spanish | five minute from now');
+  dateEqual(Date.create('dentro de 5 horas'), getRelativeDate(null, null, null, 5), 'Date#create | Spanish | five hour from now');
+  dateEqual(Date.create('dentro de 5 dรญas'), getRelativeDate(null, null, 5), 'Date#create | Spanish | five day from now');
+  dateEqual(Date.create('dentro de 5 semanas'), getRelativeDate(null, null, 35), 'Date#create | Spanish | five weeks from now');
+  dateEqual(Date.create('dentro de 5 meses'), getRelativeDate(null, 5), 'Date#create | Spanish | five months from now');
+  dateEqual(Date.create('dentro de 5 aรฑos'), getRelativeDate(5), 'Date#create | Spanish | five years from now');
+
+
+  dateEqual(Date.create('anteayer'), getRelativeDate(null, null, -2).reset(), 'Date#create | Spanish | ไธ€ๆ˜จๆ—ฅ');
+  dateEqual(Date.create('ayer'), getRelativeDate(null, null, -1).reset(), 'Date#create | Spanish | yesterday');
+  dateEqual(Date.create('hoy'), getRelativeDate(null, null, 0).reset(), 'Date#create | Spanish | today');
+  dateEqual(Date.create('maรฑana'), getRelativeDate(null, null, 1).reset(), 'Date#create | Spanish | tomorrow');
+
+  dateEqual(Date.create('la semana pasada'), getRelativeDate(null, null, -7), 'Date#create | Spanish | Last week');
+  dateEqual(Date.create('la prรณxima semana'), getRelativeDate(null, null, 7), 'Date#create | Spanish | Next week');
+
+  dateEqual(Date.create('el mes pasado'), getRelativeDate(null, -1), 'Date#create | Spanish | last month');
+  dateEqual(Date.create('el prรณximo mes'), getRelativeDate(null, 1), 'Date#create | Spanish | Next month');
+
+  dateEqual(Date.create('proximo lunes'), Date.create('next monday', 'en'), 'Date#create | Spanish | next monday no accent');
+  dateEqual(Date.create('prรณximo lunes'), Date.create('next monday', 'en'), 'Date#create | Spanish | next monday accent');
+  dateEqual(Date.create('pasado lunes'), Date.create('last monday', 'en'), 'Date#create | Spanish | last monday front');
+  dateEqual(Date.create('lunes pasado'), Date.create('last monday', 'en'), 'Date#create | Spanish | last monday back');
+
+  dateEqual(Date.create('lunes pasado 3:45'), Date.create('last monday', 'en').set({ hour: 3, minute: 45 }, true), 'Date#create | Spanish | last monday back 3:45');
+  dateEqual(Date.create('proximo lunes 3:45'), Date.create('next monday', 'en').set({ hour: 3, minute: 45 }, true), 'Date#create | Spanish | next monday no accent 3:45');
+
+  dateEqual(Date.create('el aรฑo pasado'), getRelativeDate(-1), 'Date#create | Spanish | Last year');
+  dateEqual(Date.create('el prรณximo aรฑo'), getRelativeDate(1), 'Date#create | Spanish | Next year');
+
+  // no accents
+  dateEqual(Date.create('hace 1 dia'), getRelativeDate(null, null, -1), 'Date#create | Spanish | one day ago');
+  dateEqual(Date.create('proximo mes'), getRelativeDate(null, 1), 'Date#create | Spanish | Next month');
+  dateEqual(Date.create('proxima semana'), getRelativeDate(null, null, 7), 'Date#create | Spanish | Next week');
+  dateEqual(Date.create('manana'), getRelativeDate(null, null, 1).reset(), 'Date#create | Spanish | tomorrow');
+  dateEqual(Date.create('hace 1 ano'), getRelativeDate(-1), 'Date#create | Spanish | one year ago');
+
+
+  equal(then.format(), '25 agosto 2011 15:45', 'Date#create | Spanish | standard format');
+  equal(then.format('{dd} de {month} de {yyyy}'), '25 de agosto de 2011', 'Date#create | Spanish | format');
+
+  // Format shortcuts
+  equal(then.format('long'), '25 agosto 2011 15:45', 'Date#create | Spanish | long format');
+  equal(then.long(), '25 agosto 2011 15:45', 'Date#create | Spanish | long shortcut');
+  equal(then.format('full'), 'Jueves 25 agosto 2011 15:45:50', 'Date#create | Spanish | full format');
+  equal(then.full(), 'Jueves 25 agosto 2011 15:45:50', 'Date#create | Spanish | full shortcut');
+  equal(then.format('short'), '25 agosto 2011', 'Date#create | Spanish | short format');
+  equal(then.short(), '25 agosto 2011', 'Date#create | Spanish | short shortcut');
+
+
+  equal(Date.create('1 second ago', 'en').relative(), 'hace 1 segundo', 'Date#create | Spanish | relative format past');
+  equal(Date.create('1 minute ago', 'en').relative(), 'hace 1 minuto',  'Date#create | Spanish | relative format past');
+  equal(Date.create('1 hour ago', 'en').relative(),   'hace 1 hora',     'Date#create | Spanish | relative format past');
+  equal(Date.create('1 day ago', 'en').relative(),    'hace 1 dรญa',    'Date#create | Spanish | relative format past');
+  equal(Date.create('1 week ago', 'en').relative(),   'hace 1 semana',  'Date#create | Spanish | relative format past');
+  equal(Date.create('1 month ago', 'en').relative(),  'hace 1 mes',   'Date#create | Spanish | relative format past');
+  equal(Date.create('1 year ago', 'en').relative(),   'hace 1 aรฑo',     'Date#create | Spanish | relative format past');
+
+  equal(Date.create('2 seconds ago', 'en').relative(), 'hace 2 segundos', 'Date#create | Spanish | relative format past');
+  equal(Date.create('2 minutes ago', 'en').relative(), 'hace 2 minutos',  'Date#create | Spanish | relative format past');
+  equal(Date.create('2 hours ago', 'en').relative(),   'hace 2 horas',     'Date#create | Spanish | relative format past');
+  equal(Date.create('2 days ago', 'en').relative(),    'hace 2 dรญas',    'Date#create | Spanish | relative format past');
+  equal(Date.create('2 weeks ago', 'en').relative(),   'hace 2 semanas',  'Date#create | Spanish | relative format past');
+  equal(Date.create('2 months ago', 'en').relative(),  'hace 2 meses',   'Date#create | Spanish | relative format past');
+  equal(Date.create('2 years ago', 'en').relative(),   'hace 2 aรฑos',     'Date#create | Spanish | relative format past');
+
+  equal(Date.create('1 second from now', 'en').relative(), 'dentro de 1 segundo', 'Date#create | Spanish | relative format future');
+  equal(Date.create('1 minute from now', 'en').relative(), 'dentro de 1 minuto',  'Date#create | Spanish | relative format future');
+  equal(Date.create('1 hour from now', 'en').relative(),   'dentro de 1 hora',     'Date#create | Spanish | relative format future');
+  equal(Date.create('1 day from now', 'en').relative(),    'dentro de 1 dรญa',    'Date#create | Spanish | relative format future');
+  equal(Date.create('1 week from now', 'en').relative(),   'dentro de 1 semana',  'Date#create | Spanish | relative format future');
+  equal(Date.create('1 month from now', 'en').relative(),  'dentro de 1 mes',   'Date#create | Spanish | relative format future');
+  equal(Date.create('1 year from now', 'en').relative(),   'dentro de 1 aรฑo',     'Date#create | Spanish | relative format future');
+
+  equal(Date.create('5 second from now', 'en').relative(), 'dentro de 5 segundos', 'Date#create | Spanish | relative format future');
+  equal(Date.create('5 minute from now', 'en').relative(), 'dentro de 5 minutos',  'Date#create | Spanish | relative format future');
+  equal(Date.create('5 hour from now', 'en').relative(),   'dentro de 5 horas',     'Date#create | Spanish | relative format future');
+  equal(Date.create('5 day from now', 'en').relative(),    'dentro de 5 dรญas',    'Date#create | Spanish | relative format future');
+  equal(Date.create('5 week from now', 'en').relative(),   'dentro de 1 mes',  'Date#create | Spanish | relative format future');
+  equal(Date.create('5 month from now', 'en').relative(),  'dentro de 5 meses',   'Date#create | Spanish | relative format future');
+  equal(Date.create('5 year from now', 'en').relative(),   'dentro de 5 aรฑos',     'Date#create | Spanish | relative format future');
+
+  dateEqual(Date.create('maรฑana a las 3:30'), getRelativeDate(null, null, 1).set({hours:3,minutes:30}, true), 'Date#create | Spanish | tomorrow at 3:30');
+
+  equal((5).hours().duration('es'),   '5 horas',     'Date#create | Spanish | simple duration');
+
+});
level2/node_modules/sugar/test/environments/sugar/date_fi.js
@@ -0,0 +1,159 @@
+test('Dates | Finnish', function () {
+
+  var now = new Date();
+  var then = new Date(2011, 7, 25, 15, 45, 50);
+  Date.setLocale('fi');
+
+  //dateEqual(Date.create('15. Mai 2011'), new Date(2011, 4, 15), 'Date#create | basic Finnish date');
+  //dateEqual(Date.create('Dienstag, 5. Januar 2012'), new Date(2012, 0, 5), 'Date#create | Finnish | 2012-01-05');
+  //dateEqual(Date.create('Mai 2011'), new Date(2011, 4), 'Date#create | Finnish | year and month');
+  //dateEqual(Date.create('15. Mai'), new Date(now.getFullYear(), 4, 15), 'Date#create | Finnish | month and date');
+  //dateEqual(Date.create('2011'), new Date(2011, 0), 'Date#create | Finnish | year');
+
+  //dateEqual(Date.create('Dienstag, 5. Januar 2012 3:45'), new Date(2012, 0, 5, 3, 45), 'Date#create | Finnish | 2012-01-05 3:45');
+  //dateEqual(Date.create('Dienstag, 5. Januar 2012 3:45pm'), new Date(2012, 0, 5, 15, 45), 'Date#create | Finnish | 2012-01-05 3:45pm');
+
+  //dateEqual(Date.create('Januar'), new Date(now.getFullYear(), 0), 'Date#create | Finnish | January');
+  //dateEqual(Date.create('Februar'), new Date(now.getFullYear(), 1), 'Date#create | Finnish | February');
+  //dateEqual(Date.create('Marz'), new Date(now.getFullYear(), 2), 'Date#create | Finnish | March');
+  //dateEqual(Date.create('Mรคrz'), new Date(now.getFullYear(), 2), 'Date#create | Finnish | March');
+  //dateEqual(Date.create('April'), new Date(now.getFullYear(), 3), 'Date#create | Finnish | April');
+  //dateEqual(Date.create('Mai'), new Date(now.getFullYear(), 4), 'Date#create | Finnish | May');
+  //dateEqual(Date.create('Juni'), new Date(now.getFullYear(), 5), 'Date#create | Finnish | June');
+  //dateEqual(Date.create('Juli'), new Date(now.getFullYear(), 6), 'Date#create | Finnish | July');
+  //dateEqual(Date.create('August'), new Date(now.getFullYear(), 7), 'Date#create | Finnish | August');
+  //dateEqual(Date.create('September'), new Date(now.getFullYear(), 8), 'Date#create | Finnish | September');
+  //dateEqual(Date.create('Oktober'), new Date(now.getFullYear(), 9), 'Date#create | Finnish | October');
+  //dateEqual(Date.create('November'), new Date(now.getFullYear(), 10), 'Date#create | Finnish | November');
+  //dateEqual(Date.create('Dezember'), new Date(now.getFullYear(), 11), 'Date#create | Finnish | December');
+
+
+  //dateEqual(Date.create('Sonntag'), getDateWithWeekdayAndOffset(0), 'Date#create | Finnish | Sunday');
+  //dateEqual(Date.create('Montag'), getDateWithWeekdayAndOffset(1), 'Date#create | Finnish | Monday');
+  //dateEqual(Date.create('Dienstag'), getDateWithWeekdayAndOffset(2), 'Date#create | Finnish | Tuesday');
+  //dateEqual(Date.create('Mittwoch'), getDateWithWeekdayAndOffset(3), 'Date#create | Finnish | Wednesday');
+  //dateEqual(Date.create('Donnerstag'), getDateWithWeekdayAndOffset(4), 'Date#create | Finnish | Thursday');
+  //dateEqual(Date.create('Freitag'), getDateWithWeekdayAndOffset(5), 'Date#create | Finnish | Friday');
+  //dateEqual(Date.create('Samstag'), getDateWithWeekdayAndOffset(6), 'Date#create | Finnish | Saturday');
+
+  //dateEqual(Date.create('einer Millisekunde vorher'), getRelativeDate(null, null, null, null, null, null,-1), 'Date#create | Finnish | one millisecond ago');
+  //dateEqual(Date.create('eine Sekunde vorher'), getRelativeDate(null, null, null, null, null, -1), 'Date#create | Finnish | one second ago');
+  //dateEqual(Date.create('einer Minute vorher'), getRelativeDate(null, null, null, null, -1), 'Date#create | Finnish | one minute ago');
+  //dateEqual(Date.create('einer Stunde vorher'), getRelativeDate(null, null, null, -1), 'Date#create | Finnish | one hour ago');
+  //dateEqual(Date.create('einen Tag vorher'), getRelativeDate(null, null, -1), 'Date#create | Finnish | one day ago');
+  //dateEqual(Date.create('eine Woche vorher'), getRelativeDate(null, null, -7), 'Date#create | Finnish | one week ago');
+  //dateEqual(Date.create('einen Monat vorher'), getRelativeDate(null, -1), 'Date#create | Finnish | one month ago');
+  //dateEqual(Date.create('ein Jahr vorher'), getRelativeDate(-1), 'Date#create | Finnish | one year ago');
+
+  //dateEqual(Date.create('vor einer Millisekunde'), getRelativeDate(null, null, null, null, null, null,-1), 'Date#create | Finnish reversed | one millisecond ago');
+  //dateEqual(Date.create('vor einer Sekunde'), getRelativeDate(null, null, null, null, null, -1), 'Date#create | Finnish reversed | one second ago');
+  //dateEqual(Date.create('vor einer Minute'), getRelativeDate(null, null, null, null, -1), 'Date#create | Finnish reversed | one minute ago');
+  //dateEqual(Date.create('vor einer Stunde'), getRelativeDate(null, null, null, -1), 'Date#create | Finnish reversed | one hour ago');
+  //dateEqual(Date.create('vor einem Tag'), getRelativeDate(null, null, -1), 'Date#create | Finnish reversed | one day ago');
+  //dateEqual(Date.create('vor einer Woche'), getRelativeDate(null, null, -7), 'Date#create | Finnish reversed | one week ago');
+  //dateEqual(Date.create('vor einem Monat'), getRelativeDate(null, -1), 'Date#create | Finnish reversed | one month ago');
+  //dateEqual(Date.create('vor einem Jahr'), getRelativeDate(-1), 'Date#create | Finnish reversed | one year ago');
+
+
+  //dateEqual(Date.create('in 5 Millisekunden'), getRelativeDate(null, null, null, null, null, null,5), 'Date#create | Finnish | dans | five milliseconds from now');
+  //dateEqual(Date.create('in 5 Sekunden'), getRelativeDate(null, null, null, null, null, 5), 'Date#create | Finnish | dans | five second from now');
+  //dateEqual(Date.create('in 5 Minuten'), getRelativeDate(null, null, null, null, 5), 'Date#create | Finnish | dans | five minute from now');
+  //dateEqual(Date.create('in 5 Stunden'), getRelativeDate(null, null, null, 5), 'Date#create | Finnish | dans | five hour from now');
+  //dateEqual(Date.create('in 5 Tagen'), getRelativeDate(null, null, 5), 'Date#create | Finnish | dans | five day from now');
+  //dateEqual(Date.create('in 5 Wochen'), getRelativeDate(null, null, 35), 'Date#create | Finnish | dans | five weeks from now');
+  //dateEqual(Date.create('in 5 Monaten'), getRelativeDate(null, 5), 'Date#create | Finnish | dans | five months from now');
+  //dateEqual(Date.create('in 5 Jahren'), getRelativeDate(5), 'Date#create | Finnish | dans | five years from now');
+
+
+  //dateEqual(Date.create('vorgestern'), getRelativeDate(null, null, -2).reset(), 'Date#create | Finnish | day before yesterday');
+  //dateEqual(Date.create('gestern'), getRelativeDate(null, null, -1).reset(), 'Date#create | Finnish | yesterday');
+  //dateEqual(Date.create('heute'), getRelativeDate(null, null, 0).reset(), 'Date#create | Finnish | today');
+  //dateEqual(Date.create('morgen'), getRelativeDate(null, null, 1).reset(), 'Date#create | Finnish | tomorrow');
+  //dateEqual(Date.create('รผbermorgen'), getRelativeDate(null, null, 2).reset(), 'Date#create | Finnish | day after tomorrow');
+
+  //dateEqual(Date.create('letzte Woche'), getRelativeDate(null, null, -7), 'Date#create | Finnish | Last week');
+  //dateEqual(Date.create('nรคchste Woche'), getRelativeDate(null, null, 7), 'Date#create | Finnish | Next week');
+
+  //dateEqual(Date.create('letzter Monat'), getRelativeDate(null, -1), 'Date#create | Finnish | last month letzter');
+  //dateEqual(Date.create('letzten Monat'), getRelativeDate(null, -1), 'Date#create | Finnish | last month letzten');
+  //dateEqual(Date.create('nรคchster Monat'), getRelativeDate(null, 1), 'Date#create | Finnish | Next month nachster');
+  //dateEqual(Date.create('nรคchsten Monat'), getRelativeDate(null, 1), 'Date#create | Finnish | Next month nachsten');
+
+  //dateEqual(Date.create('letztes Jahr'), getRelativeDate(-1), 'Date#create | Finnish | Last year');
+  //dateEqual(Date.create('nรคchstes Jahr'), getRelativeDate(1), 'Date#create | Finnish | Next year');
+
+
+  //dateEqual(Date.create('kommenden Montag'), getDateWithWeekdayAndOffset(1, 7), 'Date#create | Finnish | kommenden Montag');
+  //dateEqual(Date.create('nรคchster Montag'), getDateWithWeekdayAndOffset(1, 7), 'Date#create | Finnish | next monday');
+  //dateEqual(Date.create('letztes Montag'), getDateWithWeekdayAndOffset(1, -7), 'Date#create | Finnish | last monday');
+
+  //dateEqual(Date.create('letztes Montag 3:45'), getDateWithWeekdayAndOffset(1, -7).set({ hour: 3, minute: 45 }, true), 'Date#create | Finnish | last monday 3:45');
+
+  //// no accents
+  //dateEqual(Date.create('ubermorgen'), getRelativeDate(null, null, 2).reset(), 'Date#create | Finnish (no accents) | day after tomorrow');
+  //dateEqual(Date.create('naechster Monat'), getRelativeDate(null, 1), 'Date#create | Finnish (no accents) | Next month nachster');
+  //dateEqual(Date.create('uebermorgen'), getRelativeDate(null, null, 2).reset(), 'Date#create | Finnish | day after tomorrow');
+  //dateEqual(Date.create('naechster Monat'), getRelativeDate(null, 1), 'Date#create | Finnish | Next month nachster');
+  //dateEqual(Date.create('naechsten Monat'), getRelativeDate(null, 1), 'Date#create | Finnish | Next month nachsten');
+  //dateEqual(Date.create('naechstes Jahr'), getRelativeDate(1), 'Date#create | Finnish | Next year');
+
+
+  //equal(then.format(), '25. August 2011 15:45', 'Date#create | Finnish | format');
+  //equal(then.format('{dd} {Month} {yyyy}'), '25 August 2011', 'Date#create | Finnish | format');
+
+  //// Format shortcuts
+  //equal(then.format('long'), '25. August 2011 15:45', 'Date#create | Finnish | long format');
+  //equal(then.long(), '25. August 2011 15:45', 'Date#create | Finnish | long shortcut');
+  //equal(then.format('full'), 'Donnerstag 25. August 2011 15:45:50', 'Date#create | Finnish | full format');
+  //equal(then.full(), 'Donnerstag 25. August 2011 15:45:50', 'Date#create | Finnish | full shortcut');
+  //equal(then.format('short'), '25. August 2011', 'Date#create | Finnish | short format');
+  //equal(then.short(), '25. August 2011', 'Date#create | Finnish | short shortcut');
+
+
+  //equal(Date.create('1 second ago', 'en').relative(), 'vor 1 Sekunde', 'Date#create | Finnish | relative format past');
+  //equal(Date.create('1 minute ago', 'en').relative(), 'vor 1 Minute',  'Date#create | Finnish | relative format past');
+  equal(Date.create('1 hour ago', 'en').relative(),   'tunti sitten',     'Date#create | Finnish | relative format past');
+  equal(Date.create('2 hours ago', 'en').relative(),   '2 tuntia sitten',     'Date#create | Finnish | relative format past');
+  //equal(Date.create('1 day ago', 'en').relative(),    'vor 1 Tag',    'Date#create | Finnish | relative format past');
+  //equal(Date.create('1 week ago', 'en').relative(),   'vor 1 Woche',  'Date#create | Finnish | relative format past');
+  //equal(Date.create('1 month ago', 'en').relative(),  'vor 1 Monat',   'Date#create | Finnish | relative format past');
+  //equal(Date.create('1 year ago', 'en').relative(),   'vor 1 Jahr',     'Date#create | Finnish | relative format past');
+
+  //equal(Date.create('5 seconds ago', 'en').relative(), 'vor 5 Sekunden', 'Date#create | Finnish | relative format past');
+  //equal(Date.create('5 minutes ago', 'en').relative(), 'vor 5 Minuten',  'Date#create | Finnish | relative format past');
+  //equal(Date.create('5 hours ago', 'en').relative(),   'vor 5 Stunden',     'Date#create | Finnish | relative format past');
+  //equal(Date.create('5 days ago', 'en').relative(),    'vor 5 Tagen',    'Date#create | Finnish | relative format past');
+  //equal(Date.create('5 weeks ago', 'en').relative(),   'vor 1 Monat',  'Date#create | Finnish | relative format past');
+  //equal(Date.create('5 months ago', 'en').relative(),  'vor 5 Monaten',   'Date#create | Finnish | relative format past');
+  //equal(Date.create('5 years ago', 'en').relative(),   'vor 5 Jahren',     'Date#create | Finnish | relative format past');
+
+  //equal(Date.create('1 second from now', 'en').relative(), 'in 1 Sekunde', 'Date#create | Finnish | relative format future');
+  //equal(Date.create('1 minute from now', 'en').relative(), 'in 1 Minute',  'Date#create | Finnish | relative format future');
+  equal(Date.create('1 hour from now', 'en').relative(),   'tunnin pรครคstรค',     'Date#create | Finnish | relative format future');
+  equal(Date.create('2 hours from now', 'en').relative(),   '2 tunnin pรครคstรค',     'Date#create | Finnish | relative format future');
+
+  //equal(Date.create('1 day from now', 'en').relative(),    'in 1 Tag',    'Date#create | Finnish | relative format future');
+  //equal(Date.create('1 week from now', 'en').relative(),   'in 1 Woche',  'Date#create | Finnish | relative format future');
+  //equal(Date.create('1 month from now', 'en').relative(),  'in 1 Monat',   'Date#create | Finnish | relative format future');
+  //equal(Date.create('1 year from now', 'en').relative(),   'in 1 Jahr',     'Date#create | Finnish | relative format future');
+
+  //equal(Date.create('5 second from now', 'en').relative(), 'in 5 Sekunden', 'Date#create | Finnish | relative format future');
+  //equal(Date.create('5 minutes from now', 'en').relative(),'in 5 Minuten',  'Date#create | Finnish | relative format future');
+  //equal(Date.create('5 hour from now', 'en').relative(),   'in 5 Stunden',     'Date#create | Finnish | relative format future');
+  //equal(Date.create('5 day from now', 'en').relative(),    'in 5 Tagen',    'Date#create | Finnish | relative format future');
+  //equal(Date.create('5 week from now', 'en').relative(),   'in 1 Monat',  'Date#create | Finnish | relative format future');
+  //equal(Date.create('5 month from now', 'en').relative(),  'in 5 Monaten',   'Date#create | Finnish | relative format future');
+  //equal(Date.create('5 year from now', 'en').relative(),   'in 5 Jahren',     'Date#create | Finnish | relative format future');
+
+  equal(Date.create('12 hours ago', 'en').relative(), '12 tuntia sitten', 'Date#create | Finnish | 22 hours ago');
+  equal(Date.create('12 hours from now', 'en').relative(), '12 tunnin pรครคstรค', 'Date#create | Finnish | 22 hours from now');
+
+  // Not sure about this one... cases need to match first...
+  equal(Date.create('125 years ago', 'en').relative(),   '125 tuntia sitten', 'Date#create | Finnish | 22 years ago');
+  equal(Date.create('125 years from now', 'en').relative(), '125 tunnin pรครคstรค', 'Date#create | Finnish | 22 years from now');
+
+  equal(Date.create('125 years ago', 'en'), '125 vuotta sitten', 'Date#create | Finnish | 125 years ago');
+  equal(Date.create('125 years from now', 'en'), '125 vuoden pรครคstรค', 'Date#create | Finnish | 125 years from now');
+
+});
+
level2/node_modules/sugar/test/environments/sugar/date_fr.js
@@ -0,0 +1,156 @@
+test('Dates | French', function () {
+
+  var now = new Date();
+  var then = new Date(2011, 7, 25, 15, 45, 50);
+  Date.setLocale('fr');
+
+
+  dateEqual(Date.create('Le 15 mai 2011'), new Date(2011, 4, 15), 'Date#create | basic French date');
+  dateEqual(Date.create('Le 5 janvier 2012'), new Date(2012, 0, 5), 'Date#create | French | 2012-01-05');
+  dateEqual(Date.create('mai 2011'), new Date(2011, 4), 'Date#create | French | year and month');
+  dateEqual(Date.create('Le 15 mai'), new Date(now.getFullYear(), 4, 15), 'Date#create | French | month and date');
+  dateEqual(Date.create('2011'), new Date(2011, 0), 'Date#create | French | year');
+
+  dateEqual(Date.create('Le 5 janvier 2012 3:45'), new Date(2012, 0, 5, 3, 45), 'Date#create | French | 2012-01-05 3:45');
+  dateEqual(Date.create('Le 5 janvier 2012 3:45pm'), new Date(2012, 0, 5, 15, 45), 'Date#create | French | 2012-01-05 3:45pm');
+
+  dateEqual(Date.create('janvier'), new Date(now.getFullYear(), 0), 'Date#create | French | January');
+  dateEqual(Date.create('fรฉvrier'), new Date(now.getFullYear(), 1), 'Date#create | French | February');
+  dateEqual(Date.create('fevrier'), new Date(now.getFullYear(), 1), 'Date#create | French | February');
+  dateEqual(Date.create('mars'), new Date(now.getFullYear(), 2), 'Date#create | French | March');
+  dateEqual(Date.create('avril'), new Date(now.getFullYear(), 3), 'Date#create | French | April');
+  dateEqual(Date.create('mai'), new Date(now.getFullYear(), 4), 'Date#create | French | May');
+  dateEqual(Date.create('juin'), new Date(now.getFullYear(), 5), 'Date#create | French | June');
+  dateEqual(Date.create('juillet'), new Date(now.getFullYear(), 6), 'Date#create | French | July');
+  dateEqual(Date.create('aoรปt'), new Date(now.getFullYear(), 7), 'Date#create | French | August');
+  dateEqual(Date.create('septembre'), new Date(now.getFullYear(), 8), 'Date#create | French | September');
+  dateEqual(Date.create('octobre'), new Date(now.getFullYear(), 9), 'Date#create | French | October');
+  dateEqual(Date.create('novembre'), new Date(now.getFullYear(), 10), 'Date#create | French | November');
+  dateEqual(Date.create('dรฉcembre'), new Date(now.getFullYear(), 11), 'Date#create | French | December');
+  dateEqual(Date.create('decembre'), new Date(now.getFullYear(), 11), 'Date#create | French | December');
+
+  dateEqual(Date.create('dimanche'), getDateWithWeekdayAndOffset(0), 'Date#create | French | Sunday');
+  dateEqual(Date.create('lundi'), getDateWithWeekdayAndOffset(1), 'Date#create | French | Monday');
+  dateEqual(Date.create('mardi'), getDateWithWeekdayAndOffset(2), 'Date#create | French | Tuesday');
+  dateEqual(Date.create('mercredi'), getDateWithWeekdayAndOffset(3), 'Date#create | French | Wednesday');
+  dateEqual(Date.create('jeudi'), getDateWithWeekdayAndOffset(4), 'Date#create | French | Thursday');
+  dateEqual(Date.create('vendredi'), getDateWithWeekdayAndOffset(5), 'Date#create | French | Friday');
+  dateEqual(Date.create('samedi'), getDateWithWeekdayAndOffset(6), 'Date#create | French | Saturday');
+
+
+  dateEqual(Date.create('il y a une milliseconde'), getRelativeDate(null, null, null, null, null, null,-1), 'Date#create | French | one millisecond ago');
+  dateEqual(Date.create('il y a une seconde'), getRelativeDate(null, null, null, null, null, -1), 'Date#create | French | one second ago');
+  dateEqual(Date.create('il y a une minute'), getRelativeDate(null, null, null, null, -1), 'Date#create | French | one minute ago');
+  dateEqual(Date.create('il y a une heure'), getRelativeDate(null, null, null, -1), 'Date#create | French | one hour ago');
+  dateEqual(Date.create('il y a un jour'), getRelativeDate(null, null, -1), 'Date#create | French | one day ago');
+  dateEqual(Date.create('il y a une semaine'), getRelativeDate(null, null, -7), 'Date#create | French | one week');
+  dateEqual(Date.create('il y a un mois'), getRelativeDate(null, -1), 'Date#create | French | one month ago');
+  dateEqual(Date.create('il y a un an'), getRelativeDate(-1), 'Date#create | French | one year ago');
+
+
+  dateEqual(Date.create('dans 5 millisecondes'), getRelativeDate(null, null, null, null, null, null,5), 'Date#create | French | dans | five milliseconds from now');
+  dateEqual(Date.create('dans 5 secondes'), getRelativeDate(null, null, null, null, null, 5), 'Date#create | French | dans | five second from now');
+  dateEqual(Date.create('dans 5 minutes'), getRelativeDate(null, null, null, null, 5), 'Date#create | French | dans | five minute from now');
+  dateEqual(Date.create('dans 5 heures'), getRelativeDate(null, null, null, 5), 'Date#create | French | dans | five hour from now');
+  dateEqual(Date.create('dans 5 jours'), getRelativeDate(null, null, 5), 'Date#create | French | dans | five day from now');
+  dateEqual(Date.create('dans 5 semaines'), getRelativeDate(null, null, 35), 'Date#create | French | dans | five weeks from now');
+  dateEqual(Date.create('dans 5 mois'), getRelativeDate(null, 5), 'Date#create | French | dans | five months from now');
+  dateEqual(Date.create('dans 5 ans'), getRelativeDate(5), 'Date#create | French | dans | five years from now');
+
+  dateEqual(Date.create("d'ici 5 millisecondes"), getRelativeDate(null, null, null, null, null, null,5), 'Date#create | French | dans | five milliseconds from now');
+  dateEqual(Date.create("d'ici 5 secondes"), getRelativeDate(null, null, null, null, null, 5), 'Date#create | French | dans | five second from now');
+  dateEqual(Date.create("d'ici 5 minutes"), getRelativeDate(null, null, null, null, 5), 'Date#create | French | dans | five minute from now');
+  dateEqual(Date.create("d'ici 5 heures"), getRelativeDate(null, null, null, 5), 'Date#create | French | dans | five hour from now');
+  dateEqual(Date.create("d'ici 5 jours"), getRelativeDate(null, null, 5), 'Date#create | French | dans | five day from now');
+  dateEqual(Date.create("d'ici 5 semaines"), getRelativeDate(null, null, 35), 'Date#create | French | dans | five weeks from now');
+  dateEqual(Date.create("d'ici 5 mois"), getRelativeDate(null, 5), 'Date#create | French | dans | five months from now');
+  dateEqual(Date.create("d'ici 5 ans"), getRelativeDate(5), 'Date#create | French | dans | five years from now');
+
+  dateEqual(Date.create('hier'), getRelativeDate(null, null, -1).reset(), 'Date#create | French | yesterday');
+  dateEqual(Date.create("aujourd'hui"), getRelativeDate(null, null, 0).reset(), 'Date#create | French | today');
+  dateEqual(Date.create('demain'), getRelativeDate(null, null, 1).reset(), 'Date#create | French | tomorrow');
+
+  dateEqual(Date.create('la semaine derniรจre'), getRelativeDate(null, null, -7), 'Date#create | French | Last week');
+  dateEqual(Date.create('la semaine prochaine'), getRelativeDate(null, null, 7), 'Date#create | French | Next week');
+
+  dateEqual(Date.create('le mois dernier'), getRelativeDate(null, -1), 'Date#create | French | last month');
+  dateEqual(Date.create('le mois prochain'), getRelativeDate(null, 1), 'Date#create | French | Next month');
+
+  dateEqual(Date.create("l'annรฉe derniรจre"), getRelativeDate(-1), 'Date#create | French | Last year');
+  dateEqual(Date.create("l'annรฉe prochaine"), getRelativeDate(1), 'Date#create | French | Next year');
+
+  // no accents
+  dateEqual(Date.create('la semaine derniere'), getRelativeDate(null, null, -7), 'Date#create | French | Last week');
+  dateEqual(Date.create("l'annee prochaine"), getRelativeDate(1), 'Date#create | French | Next year');
+
+  dateEqual(Date.create('lundi prochain'), getDateWithWeekdayAndOffset(1, 7), 'Date#create | French | next monday');
+  dateEqual(Date.create('lundi derniรจr'), getDateWithWeekdayAndOffset(1, -7), 'Date#create | French | last monday');
+
+  dateEqual(Date.create('lundi derniรจr 3:45'), getDateWithWeekdayAndOffset(1, -7).set({ hour: 3, minute: 45 }, true), 'Date#create | French | last monday 3:45');
+
+  equal(then.format(), '25 aoรปt 2011 15:45', 'Date#create | French | standard format');
+  equal(then.format('{dd} {month} {yyyy}'), '25 aoรปt 2011', 'Date#create | French | format');
+
+  // Format shortcuts
+  equal(then.format('long'), '25 aoรปt 2011 15:45', 'Date#create | French | long format');
+  equal(then.long(), '25 aoรปt 2011 15:45', 'Date#create | French | long shortcut');
+  equal(then.format('full'), 'Jeudi 25 aoรปt 2011 15:45:50', 'Date#create | French | full format');
+  equal(then.full(), 'Jeudi 25 aoรปt 2011 15:45:50', 'Date#create | French | full format');
+  equal(then.format('short'), '25 aoรปt 2011', 'Date#create | French | short format');
+  equal(then.short(), '25 aoรปt 2011', 'Date#create | French | short shortcut');
+
+  equal(Date.create('1 second ago', 'en').relative(), 'il y a 1 seconde', 'Date#create | French | relative format past');
+  equal(Date.create('1 minute ago', 'en').relative(), 'il y a 1 minute',  'Date#create | French | relative format past');
+  equal(Date.create('1 hour ago', 'en').relative(),   'il y a 1 heure',     'Date#create | French | relative format past');
+  equal(Date.create('1 day ago', 'en').relative(),    'il y a 1 jour',    'Date#create | French | relative format past');
+  equal(Date.create('1 week ago', 'en').relative(),   'il y a 1 semaine',  'Date#create | French | relative format past');
+  equal(Date.create('1 month ago', 'en').relative(),  'il y a 1 mois',   'Date#create | French | relative format past');
+  equal(Date.create('1 year ago', 'en').relative(),   'il y a 1 an',     'Date#create | French | relative format past');
+
+  equal(Date.create('2 seconds ago', 'en').relative(), 'il y a 2 secondes', 'Date#create | French | relative format past');
+  equal(Date.create('2 minutes ago', 'en').relative(), 'il y a 2 minutes',  'Date#create | French | relative format past');
+  equal(Date.create('2 hours ago', 'en').relative(),   'il y a 2 heures',     'Date#create | French | relative format past');
+  equal(Date.create('2 days ago', 'en').relative(),    'il y a 2 jours',    'Date#create | French | relative format past');
+  equal(Date.create('2 weeks ago', 'en').relative(),   'il y a 2 semaines',  'Date#create | French | relative format past');
+  equal(Date.create('2 months ago', 'en').relative(),  'il y a 2 mois',   'Date#create | French | relative format past');
+  equal(Date.create('2 years ago', 'en').relative(),   'il y a 2 ans',     'Date#create | French | relative format past');
+
+  equal(Date.create('1 second from now', 'en').relative(), 'dans 1 seconde', 'Date#create | French | relative format future');
+  equal(Date.create('1 minute from now', 'en').relative(), 'dans 1 minute',  'Date#create | French | relative format future');
+  equal(Date.create('1 hour from now', 'en').relative(),   'dans 1 heure',     'Date#create | French | relative format future');
+  equal(Date.create('1 day from now', 'en').relative(),    'dans 1 jour',    'Date#create | French | relative format future');
+  equal(Date.create('1 week from now', 'en').relative(),   'dans 1 semaine',  'Date#create | French | relative format future');
+  equal(Date.create('1 month from now', 'en').relative(),  'dans 1 mois',   'Date#create | French | relative format future');
+  equal(Date.create('1 year from now', 'en').relative(),   'dans 1 an',     'Date#create | French | relative format future');
+
+  equal(Date.create('5 second from now', 'en').relative(), 'dans 5 secondes', 'Date#create | French | relative format future');
+  equal(Date.create('5 minute from now', 'en').relative(), 'dans 5 minutes',  'Date#create | French | relative format future');
+  equal(Date.create('5 hour from now', 'en').relative(),   'dans 5 heures',     'Date#create | French | relative format future');
+  equal(Date.create('5 day from now', 'en').relative(),    'dans 5 jours',    'Date#create | French | relative format future');
+  equal(Date.create('5 week from now', 'en').relative(),   'dans 1 mois',  'Date#create | French | relative format future');
+  equal(Date.create('5 month from now', 'en').relative(),  'dans 5 mois',   'Date#create | French | relative format future');
+  equal(Date.create('5 year from now', 'en').relative(),   'dans 5 ans',     'Date#create | French | relative format future');
+
+  dateEqual(Date.create('demain ร  3:30'), getRelativeDate(null, null, 1).set({hours:3,minutes:30}, true), 'Date#create | French | tomorrow at 3:30');
+
+  equal((5).hours().duration('fr'), '5 heures', 'Date#create | French | simple duration');
+
+
+  // Issue #249
+
+  dateEqual(Date.create('mardi 11 decembre 2012','fr'), new Date(2012, 11, 11), 'Date#create | French | mardi 11 decembre 2012');
+
+  equal(Date.create().isThisWeek(), true, 'Date#isThisWeek | should be true for today in other locales');
+  equal(Date.create('1 week ago', 'en').isLastWeek(), true, 'Date#isLastWeek | should be true for last week in other locales');
+  equal(Date.create('1 week from now', 'en').isNextWeek(), true, 'Date#isNextWeek | should be true for next week in other locales');
+
+  equal(Date.create().isThisMonth(), true, 'Date#isThisMonth | should be true for today in other locales');
+  equal(Date.create('1 month ago', 'en').isLastMonth(), true, 'Date#isLastMonth | should be true for last month in other locales');
+  equal(Date.create('1 month from now', 'en').isNextMonth(), true, 'Date#isNextMonth | should be true for next month in other locales');
+
+  equal(Date.create().isThisYear(), true, 'Date#isThisYear | should be true for today in other locales');
+  equal(Date.create('1 year ago', 'en').isLastYear(), true, 'Date#isLastYear | should be true for last year in other locales');
+  equal(Date.create('1 year from now', 'en').isNextYear(), true, 'Date#isNextYear | should be true for next year in other locales');
+
+
+});
level2/node_modules/sugar/test/environments/sugar/date_it.js
@@ -0,0 +1,128 @@
+test('Dates | Italian', function () {
+
+  var now = new Date();
+  var then = new Date(2011, 7, 25, 15, 45, 50);
+  Date.setLocale('it');
+
+
+
+  dateEqual(Date.create('15 Maggio 2011'), new Date(2011, 4, 15), 'Date#create | basic Italian date');
+  dateEqual(Date.create('Martedรฌ, 5 Gennaio 2012'), new Date(2012, 0, 5), 'Date#create | Italian | 2012-01-05');
+  dateEqual(Date.create('Maggio 2011'), new Date(2011, 4), 'Date#create | Italian | year and month');
+  dateEqual(Date.create('15 Maggio'), new Date(now.getFullYear(), 4, 15), 'Date#create | Italian | month and date');
+  dateEqual(Date.create('2011'), new Date(2011, 0), 'Date#create | Italian | year');
+  dateEqual(Date.create('Maggio'), new Date(now.getFullYear(), 4), 'Date#create | Italian | month');
+  dateEqual(Date.create('Lunedรฌ'), getDateWithWeekdayAndOffset(1), 'Date#create | Italian | Monday');
+  dateEqual(Date.create('Lun'), getDateWithWeekdayAndOffset(1), 'Date#create | Italian | Monday abbreviated');
+
+  dateEqual(Date.create('Martedรฌ, 5 Gennaio 2012 3:45'), new Date(2012, 0, 5, 3, 45), 'Date#create | Italian | 2012-01-05 3:45');
+  dateEqual(Date.create('Martedรฌ, 5 Gennaio 2012 3:45pm'), new Date(2012, 0, 5, 15, 45), 'Date#create | Italian | 2012-01-05 3:45pm');
+
+  dateEqual(Date.create('un millisecondo fa'), getRelativeDate(null, null, null, null, null, null,-1), 'Date#create | Italian | one millisecond ago');
+  dateEqual(Date.create('un secondo fa'), getRelativeDate(null, null, null, null, null, -1), 'Date#create | Italian | one second ago');
+  dateEqual(Date.create('un minuto fa'), getRelativeDate(null, null, null, null, -1), 'Date#create | Italian | one minuto ago');
+  dateEqual(Date.create("un'ora fa"), getRelativeDate(null, null, null, -1), 'Date#create | Italian | one hour ago');
+  dateEqual(Date.create('un giorno fa'), getRelativeDate(null, null, -1), 'Date#create | Italian | one day ago');
+  dateEqual(Date.create('una settimana fa'), getRelativeDate(null, null, -7), 'Date#create | Italian | one week ago');
+  dateEqual(Date.create('un mese fa'), getRelativeDate(null, -1), 'Date#create | Italian | one month ago');
+  dateEqual(Date.create('un anno fa'), getRelativeDate(-1), 'Date#create | Italian | one year ago');
+
+
+  dateEqual(Date.create('5 millisecondi da adesso'), getRelativeDate(null, null, null, null, null, null,5), 'Date#create | Italian | danni | five milliseconds from now');
+  dateEqual(Date.create('5 secondi da adesso'), getRelativeDate(null, null, null, null, null, 5), 'Date#create | Italian | danni | five second from now');
+  dateEqual(Date.create('5 minuti da adesso'), getRelativeDate(null, null, null, null, 5), 'Date#create | Italian | danni | five minuto from now');
+  dateEqual(Date.create('5 ore da adesso'), getRelativeDate(null, null, null, 5), 'Date#create | Italian | danni | five hour from now');
+  dateEqual(Date.create('5 giorni da adesso'), getRelativeDate(null, null, 5), 'Date#create | Italian | danni | five day from now');
+  dateEqual(Date.create('5 settimane da adesso'), getRelativeDate(null, null, 35), 'Date#create | Italian | danni | five weeks from now');
+  dateEqual(Date.create('5 mesi da adesso'), getRelativeDate(null, 5), 'Date#create | Italian | danni | five months from now');
+  dateEqual(Date.create('5 anni da adesso'), getRelativeDate(5), 'Date#create | Italian | danni | five years from now');
+
+
+  dateEqual(Date.create('ieri'), getRelativeDate(null, null, -1).reset(), 'Date#create | Italian | yesterday');
+  dateEqual(Date.create('oggi'), getRelativeDate(null, null, 0).reset(), 'Date#create | Italian | today');
+  dateEqual(Date.create('domani'), getRelativeDate(null, null, 1).reset(), 'Date#create | Italian | tomorrow');
+  dateEqual(Date.create('dopodomani'), getRelativeDate(null, null, 2).reset(), 'Date#create | Italian | day after tomorrow');
+
+  dateEqual(Date.create('la settimana scorsa'), getRelativeDate(null, null, -7), 'Date#create | Italian | Last week');
+  dateEqual(Date.create('la settimana prossima'), getRelativeDate(null, null, 7), 'Date#create | Italian | Next week');
+
+  dateEqual(Date.create('il mese scorso'), getRelativeDate(null, -1), 'Date#create | Italian | last month');
+  dateEqual(Date.create('il mese prossimo'), getRelativeDate(null, 1), 'Date#create | Italian | Next month');
+
+  dateEqual(Date.create("l'anno scorso"), getRelativeDate(-1), 'Date#create | Italian | Last year');
+  dateEqual(Date.create("l'anno prossimo"), getRelativeDate(1), 'Date#create | Italian | Next year');
+
+  dateEqual(Date.create("prossimo lunedรฌ"), getDateWithWeekdayAndOffset(1, 7), 'Date#create | Italian | next monday');
+  dateEqual(Date.create("scorsa lunedรฌ"), getDateWithWeekdayAndOffset(1, -7), 'Date#create | Italian | last monday');
+
+  dateEqual(Date.create("scorsa lunedรฌ 3:45"), getDateWithWeekdayAndOffset(1, -7).set({ hour: 3, minute: 45 }, true), 'Date#create | Italian | last monday 3:45');
+
+  // No accents
+  dateEqual(Date.create('Martedi, 5 Gennaio 2012'), new Date(2012, 0, 5), 'Date#create | Italian | no accents | 2012-01-05');
+  dateEqual(Date.create('Lunedi'), getDateWithWeekdayAndOffset(1), 'Date#create | Italian | no accents | Monday');
+
+
+  equal(then.format(), '25 Agosto 2011 15:45', 'Date#create | Italian | format');
+  equal(then.format('{dd} {Month} {yyyy}'), '25 Agosto 2011', 'Date#create | Italian | format');
+
+  // Format shortcuts
+  equal(then.format('long'), '25 Agosto 2011 15:45', 'Date#create | Italian | long format');
+  equal(then.long(), '25 Agosto 2011 15:45', 'Date#create | Italian | long shortcut');
+  equal(then.format('full'), 'Giovedรฌ 25 Agosto 2011 15:45:50', 'Date#create | Italian | full format');
+  equal(then.full(), 'Giovedรฌ 25 Agosto 2011 15:45:50', 'Date#create | Italian | full shortcut');
+  equal(then.format('short'), '25 Agosto 2011', 'Date#create | Italian | short format');
+  equal(then.short(), '25 Agosto 2011', 'Date#create | Italian | short shortcut');
+
+
+  equal(Date.create('1 second ago', 'en').relative(), '1 secondo fa', 'Date#create | Italian | relative format past');
+  equal(Date.create('1 minute ago', 'en').relative(), '1 minuto fa',  'Date#create | Italian | relative format past');
+  equal(Date.create('1 hour ago', 'en').relative(),   '1 ora fa',     'Date#create | Italian | relative format past');
+  equal(Date.create('1 day ago', 'en').relative(),    '1 giorno fa',    'Date#create | Italian | relative format past');
+  equal(Date.create('1 week ago', 'en').relative(),   '1 settimana fa',  'Date#create | Italian | relative format past');
+  equal(Date.create('1 month ago', 'en').relative(),  '1 mese fa',   'Date#create | Italian | relative format past');
+  equal(Date.create('1 year ago', 'en').relative(),   '1 anno fa',     'Date#create | Italian | relative format past');
+
+  equal(Date.create('2 seconds ago', 'en').relative(), '2 secondi fa', 'Date#create | Italian | relative format past');
+  equal(Date.create('2 minutes ago', 'en').relative(), '2 minuti fa',  'Date#create | Italian | relative format past');
+  equal(Date.create('2 hours ago', 'en').relative(),   '2 ore fa',     'Date#create | Italian | relative format past');
+  equal(Date.create('2 days ago', 'en').relative(),    '2 giorni fa',    'Date#create | Italian | relative format past');
+  equal(Date.create('2 weeks ago', 'en').relative(),   '2 settimane fa',  'Date#create | Italian | relative format past');
+  equal(Date.create('2 months ago', 'en').relative(),  '2 mesi fa',   'Date#create | Italian | relative format past');
+  equal(Date.create('2 years ago', 'en').relative(),   '2 anni fa',     'Date#create | Italian | relative format past');
+
+  equal(Date.create('1 second from now', 'en').relative(), '1 secondo da adesso', 'Date#create | Italian | relative format future');
+  equal(Date.create('1 minute from now', 'en').relative(), '1 minuto da adesso',  'Date#create | Italian | relative format future');
+  equal(Date.create('1 hour from now', 'en').relative(),   '1 ora da adesso',     'Date#create | Italian | relative format future');
+  equal(Date.create('1 day from now', 'en').relative(),    '1 giorno da adesso',    'Date#create | Italian | relative format future');
+  equal(Date.create('1 week from now', 'en').relative(),   '1 settimana da adesso',  'Date#create | Italian | relative format future');
+  equal(Date.create('1 month from now', 'en').relative(),  '1 mese da adesso',   'Date#create | Italian | relative format future');
+  equal(Date.create('1 year from now', 'en').relative(),   '1 anno da adesso',     'Date#create | Italian | relative format future');
+
+  equal(Date.create('5 second from now', 'en').relative(), '5 secondi da adesso', 'Date#create | Italian | relative format future');
+  equal(Date.create('5 minutes from now', 'en').relative(),'5 minuti da adesso',  'Date#create | Italian | relative format future');
+  equal(Date.create('5 hour from now', 'en').relative(),   '5 ore da adesso',     'Date#create | Italian | relative format future');
+  equal(Date.create('5 day from now', 'en').relative(),    '5 giorni da adesso',    'Date#create | Italian | relative format future');
+  equal(Date.create('5 week from now', 'en').relative(),   '1 mese da adesso',  'Date#create | Italian | relative format future');
+  equal(Date.create('5 month from now', 'en').relative(),  '5 mesi da adesso',   'Date#create | Italian | relative format future');
+  equal(Date.create('5 year from now', 'en').relative(),   '5 anni da adesso',     'Date#create | Italian | relative format future');
+
+  // Issue #152 Italian should not use a variant in any format
+  dateEqual(Date.create('15/3/2012 12:45'), new Date(2012, 2, 15, 12, 45), 'Date#create | Italian | slash format with time');
+  dateEqual(Date.create('12:45 15/3/2012'), new Date(2012, 2, 15, 12, 45), 'Date#create | Italian | slash format with time front');
+
+  // Issue #150 Fully qualified ISO codes should be allowed
+  dateEqual(Date.create('7 gennaio 2012', 'it_IT'), new Date(2012, 0, 7), 'Date#create | Italian | it_IT');
+  dateEqual(Date.create('7 gennaio 2012', 'it-IT'), new Date(2012, 0, 7), 'Date#create | Italian | it-IT');
+
+  // Issue #150 Unrecognized locales will result in invalid dates, but will not throw an error
+  // Update: Now it will fall back to the current locale.
+  equal(Date.create('7 gennaio 2012', 'ux_UX').isValid(), true, 'Date#create | Italian | unknown locale code');
+  equal(Date.create('2012/08/25', 'ux_UX').isValid(), true, 'Date#create | Italian | System intelligible formats are still parsed');
+
+  dateEqual(Date.create('17:32 18 agosto'), new Date(now.getFullYear(), 7, 18, 17, 32), 'Date#create | Italian | August 18, 17:32');
+
+  dateEqual(Date.create('domani alle 3:30'), getRelativeDate(null, null, 1).set({hours:3,minutes:30}, true), 'Date#create | Italian | tomorrow at 3:30');
+
+  equal((5).hours().duration('it'), '5 ore', 'Date#create | Italian | simple duration');
+
+});
level2/node_modules/sugar/test/environments/sugar/date_ja.js
@@ -0,0 +1,158 @@
+test('Dates | Japanese', function () {
+
+  var now  = new Date();
+  var then = new Date(2011, 7, 25, 15, 45, 50);
+
+  Date.setLocale('ja');
+
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ'), new Date(2011, 4, 15), 'Date#create | basic Japanese date');
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ'), new Date(2011, 4, 15), 'Date#create | once a language has been initialized it will always be recognized');
+
+  dateEqual(Date.create('2011ๅนด5ๆœˆ'), new Date(2011, 4), 'Date#create | Japanese | year and month');
+  dateEqual(Date.create('5ๆœˆ15ๆ—ฅ'), new Date(now.getFullYear(), 4, 15), 'Date#create | Japanese | month and date');
+  dateEqual(Date.create('2011ๅนด'), new Date(2011, 0), 'Date#create | Japanese | year');
+  dateEqual(Date.create('5ๆœˆ'), new Date(now.getFullYear(), 4), 'Date#create | Japanese | month');
+  dateEqual(Date.create('15ๆ—ฅ'), new Date(now.getFullYear(), now.getMonth(), 15), 'Date#create | Japanese | date');
+  dateEqual(Date.create('ๆœˆๆ›œๆ—ฅ'), getDateWithWeekdayAndOffset(1), 'Date#create | Japanese | Monday');
+  dateEqual(Date.create('ไนๆ—ฅ'), new Date(now.getFullYear(), now.getMonth(), 9), 'Date#create | Japanese | the 9th');
+  dateEqual(Date.create('ไบŒๅไบ”ๆ—ฅ'), new Date(now.getFullYear(), now.getMonth(), 25), 'Date#create | Japanese | the 25th');
+  dateEqual(Date.create('3ๆ™‚45ๅˆ†'), new Date().set({ hour: 3, minute: 45 }, true), 'Date#create | Japanese | just time');
+
+
+
+  dateEqual(Date.create('ไธ€ใƒŸใƒช็ง’ๅ‰'), getRelativeDate(null, null, null, null, null, null,-1), 'Date#create | Japanese | one millisecond ago');
+  dateEqual(Date.create('ไธ€็ง’ๅ‰'), getRelativeDate(null, null, null, null, null, -1), 'Date#create | Japanese | one second ago');
+  dateEqual(Date.create('ไธ€ๅˆ†ๅ‰'), getRelativeDate(null, null, null, null, -1), 'Date#create | Japanese | one minute ago');
+  dateEqual(Date.create('ไธ€ๆ™‚้–“ๅ‰'), getRelativeDate(null, null, null, -1), 'Date#create | Japanese | one hour ago');
+  dateEqual(Date.create('ไธ€ๆ—ฅๅ‰'), getRelativeDate(null, null, -1), 'Date#create | Japanese | one day ago');
+  dateEqual(Date.create('ไธ€้€ฑ้–“ๅ‰'), getRelativeDate(null, null, -7), 'Date#create | Japanese | one week ago');
+  dateEqual(Date.create('ไธ€ใƒถๆœˆๅ‰'), getRelativeDate(null, -1), 'Date#create | Japanese | one month ago ใƒต');
+  dateEqual(Date.create('ไธ€ใƒตๆœˆๅ‰'), getRelativeDate(null, -1), 'Date#create | Japanese | one month ago ใƒถ');
+  dateEqual(Date.create('ไธ€ๅนดๅ‰'), getRelativeDate(-1), 'Date#create | Japanese | one year ago');
+
+
+  dateEqual(Date.create('2ใƒŸใƒช็ง’ๅ‰'), getRelativeDate(null, null, null, null, null, null,-2), 'Date#create | Japanese | two millisecond ago');
+  dateEqual(Date.create('2็ง’ๅ‰'), getRelativeDate(null, null, null, null, null, -2), 'Date#create | Japanese | two second ago');
+  dateEqual(Date.create('2ๅˆ†ๅ‰'), getRelativeDate(null, null, null, null, -2), 'Date#create | Japanese | two minute ago');
+  dateEqual(Date.create('2ๆ™‚้–“ๅ‰'), getRelativeDate(null, null, null, -2), 'Date#create | Japanese | two hour ago');
+  dateEqual(Date.create('2ๆ—ฅๅ‰'), getRelativeDate(null, null, -2), 'Date#create | Japanese | two day ago');
+  dateEqual(Date.create('2้€ฑ้–“ๅ‰'), getRelativeDate(null, null, -14), 'Date#create | Japanese | two weeks ago');
+  dateEqual(Date.create('2ใƒถๆœˆๅ‰'), getRelativeDate(null, -2), 'Date#create | Japanese | two month ago ใƒต');
+  dateEqual(Date.create('2ใƒตๆœˆๅ‰'), getRelativeDate(null, -2), 'Date#create | Japanese | two month ago ใƒถ');
+  dateEqual(Date.create('2ๅนดๅ‰'), getRelativeDate(-2), 'Date#create | Japanese | two years ago');
+
+  dateEqual(Date.create('5ใƒŸใƒช็ง’ๅพŒ'), getRelativeDate(null, null, null, null, null, null, 5), 'Date#create | Japanese | five millisecond from now');
+  dateEqual(Date.create('5็ง’ๅพŒ'), getRelativeDate(null, null, null, null, null, 5), 'Date#create | Japanese | five second from now');
+  dateEqual(Date.create('5ๅˆ†ๅพŒ'), getRelativeDate(null, null, null, null, 5), 'Date#create | Japanese | five minute from now');
+  dateEqual(Date.create('5ๆ™‚้–“ๅพŒ'), getRelativeDate(null, null, null, 5), 'Date#create | Japanese | five hour from now');
+  dateEqual(Date.create('5ๆ—ฅๅพŒ'), getRelativeDate(null, null, 5), 'Date#create | Japanese | five day from now');
+  dateEqual(Date.create('5้€ฑ้–“ๅพŒ'), getRelativeDate(null, null, 35), 'Date#create | Japanese | five weeks from now');
+  dateEqual(Date.create('5ใƒถๆœˆๅพŒ'), getRelativeDate(null, 5), 'Date#create | Japanese | five month from now ใƒต');
+  dateEqual(Date.create('5ใƒตๆœˆๅพŒ'), getRelativeDate(null, 5), 'Date#create | Japanese | five month from now ใƒถ');
+  dateEqual(Date.create('5ๅนดๅพŒ'), getRelativeDate(5), 'Date#create | Japanese | five years from now');
+
+  dateEqual(Date.create('๏ผ’๏ผ๏ผ‘๏ผ‘ๅนด๏ผ•ๆœˆ๏ผ’๏ผ•ๆ—ฅ'), new Date(2011, 4, 25), 'Date#create | Japanese | full-width chars');
+
+  dateEqual(Date.create('๏ผ•ใƒŸใƒช็ง’ๅพŒ'), getRelativeDate(null, null, null, null, null, null, 5), 'Date#create | Japanese full-width | five millisecond from now');
+  dateEqual(Date.create('๏ผ•็ง’ๅพŒ'), getRelativeDate(null, null, null, null, null, 5), 'Date#create | Japanese full-width | five second from now');
+  dateEqual(Date.create('๏ผ•ๅˆ†ๅพŒ'), getRelativeDate(null, null, null, null, 5), 'Date#create | Japanese full-width | five minute from now');
+  dateEqual(Date.create('๏ผ•ๆ™‚้–“ๅพŒ'), getRelativeDate(null, null, null, 5), 'Date#create | Japanese full-width | five hour from now');
+  dateEqual(Date.create('๏ผ•ๆ—ฅๅพŒ'), getRelativeDate(null, null, 5), 'Date#create | Japanese full-width | five day from now');
+  dateEqual(Date.create('๏ผ•้€ฑ้–“ๅพŒ'), getRelativeDate(null, null, 35), 'Date#create | Japanese full-width | five weeks from now');
+  dateEqual(Date.create('๏ผ•ใƒถๆœˆๅพŒ'), getRelativeDate(null, 5), 'Date#create | Japanese full-width | five month from now ใƒต');
+  dateEqual(Date.create('๏ผ•ใƒตๆœˆๅพŒ'), getRelativeDate(null, 5), 'Date#create | Japanese full-width | five month from now ใƒถ');
+  dateEqual(Date.create('๏ผ•ๅนดๅพŒ'), getRelativeDate(5), 'Date#create | Japanese full-width | five years from now');
+
+
+  dateEqual(Date.create('ไธ€ๆ˜จๆ—ฅ'), getRelativeDate(null, null, -2).reset(), 'Date#create | Japanese | ไธ€ๆ˜จๆ—ฅ');
+  dateEqual(Date.create('ๆ˜จๆ—ฅ'), getRelativeDate(null, null, -1).reset(), 'Date#create | Japanese | yesterday');
+  dateEqual(Date.create('ไปŠๆ—ฅ'), getRelativeDate(null, null, 0).reset(), 'Date#create | Japanese | today');
+  dateEqual(Date.create('ๆ˜Žๆ—ฅ'), getRelativeDate(null, null, 1).reset(), 'Date#create | Japanese | tomorrow');
+  dateEqual(Date.create('ๆ˜ŽๅพŒๆ—ฅ'), getRelativeDate(null, null, 2).reset(), 'Date#create | Japanese | ๆ˜ŽๅพŒๆ—ฅ');
+
+  dateEqual(Date.create('ๅ…ˆ้€ฑ'), getRelativeDate(null, null, -7), 'Date#create | Japanese | Last week');
+  dateEqual(Date.create('ๆฅ้€ฑ'), getRelativeDate(null, null, 7), 'Date#create | Japanese | Next week');
+
+  dateEqual(Date.create('ๅ…ˆๆœˆ'), getRelativeDate(null, -1), 'Date#create | Japanese | Next month');
+  dateEqual(Date.create('ๆฅๆœˆ'), getRelativeDate(null, 1), 'Date#create | Japanese | Next month');
+
+  dateEqual(Date.create('ๅŽปๅนด'), getRelativeDate(-1), 'Date#create | Japanese | Last year');
+  dateEqual(Date.create('ๆฅๅนด'), getRelativeDate(1), 'Date#create | Japanese | Next year');
+
+
+  dateEqual(Date.create('ๅ…ˆ้€ฑๆฐดๆ›œๆ—ฅ'), getDateWithWeekdayAndOffset(3, -7), 'Date#create | Japanese | Last wednesday');
+  dateEqual(Date.create('ๆฅ้€ฑ้‡‘ๆ›œๆ—ฅ'), getDateWithWeekdayAndOffset(5, 7), 'Date#create | Japanese | Next friday');
+
+  equal(then.format(), '2011ๅนด8ๆœˆ25ๆ—ฅ 15ๆ™‚45ๅˆ†', 'Date#create | Japanese | default format');
+  equal(then.format('long'), '2011ๅนด8ๆœˆ25ๆ—ฅ 15ๆ™‚45ๅˆ†', 'Date#create | Japanese | long format');
+  equal(then.format('full'), '2011ๅนด8ๆœˆ25ๆ—ฅ ๆœจๆ›œๆ—ฅ 15ๆ™‚45ๅˆ†50็ง’', 'Date#create | Japanese | full formatting');
+  equal(then.full(), '2011ๅนด8ๆœˆ25ๆ—ฅ ๆœจๆ›œๆ—ฅ 15ๆ™‚45ๅˆ†50็ง’', 'Date#create | Japanese | full shortcut');
+  equal(then.full('en'), 'Thursday August 25, 2011 3:45:50pm', 'Date#create | Japanese | full locale override');
+  equal(then.format('long'), '2011ๅนด8ๆœˆ25ๆ—ฅ 15ๆ™‚45ๅˆ†', 'Date#create | Japanese | long formatting');
+  equal(then.long(), '2011ๅนด8ๆœˆ25ๆ—ฅ 15ๆ™‚45ๅˆ†', 'Date#create | Japanese | long shortcut');
+  equal(then.long('en'), 'August 25, 2011 3:45pm', 'Date#create | Japanese | long locale override');
+  equal(then.format('short'), '2011ๅนด8ๆœˆ25ๆ—ฅ', 'Date#create | Japanese | short formatting');
+  equal(then.short(), '2011ๅนด8ๆœˆ25ๆ—ฅ', 'Date#create | Japanese | short shortcut');
+  equal(then.short('en'), 'August 25, 2011', 'Date#create | Japanese | short locale override');
+  equal(then.format('{yyyy}ๅนด{MM}ๆœˆ{dd}ๆ—ฅ'), '2011ๅนด08ๆœˆ25ๆ—ฅ', 'Date#create | Japanese | custom format');
+
+  equal(Date.create('1 second ago', 'en').relative(), '1็ง’ๅ‰', 'Date#create | Japanese | relative format past');
+  equal(Date.create('1 minute ago', 'en').relative(), '1ๅˆ†ๅ‰',  'Date#create | Japanese | relative format past');
+  equal(Date.create('1 hour ago', 'en').relative(),   '1ๆ™‚้–“ๅ‰',     'Date#create | Japanese | relative format past');
+  equal(Date.create('1 day ago', 'en').relative(),    '1ๆ—ฅๅ‰',    'Date#create | Japanese | relative format past');
+  equal(Date.create('1 week ago', 'en').relative(),   '1้€ฑ้–“ๅ‰',  'Date#create | Japanese | relative format past');
+  equal(Date.create('1 month ago', 'en').relative(),  '1ใƒถๆœˆๅ‰',   'Date#create | Japanese | relative format past');
+  equal(Date.create('1 year ago', 'en').relative(),   '1ๅนดๅ‰',     'Date#create | Japanese | relative format past');
+
+  equal(Date.create('2 seconds ago', 'en').relative(), '2็ง’ๅ‰', 'Date#create | Japanese | relative format past');
+  equal(Date.create('2 minutes ago', 'en').relative(), '2ๅˆ†ๅ‰',  'Date#create | Japanese | relative format past');
+  equal(Date.create('2 hours ago', 'en').relative(),   '2ๆ™‚้–“ๅ‰',     'Date#create | Japanese | relative format past');
+  equal(Date.create('2 days ago', 'en').relative(),    '2ๆ—ฅๅ‰',    'Date#create | Japanese | relative format past');
+  equal(Date.create('2 weeks ago', 'en').relative(),   '2้€ฑ้–“ๅ‰',  'Date#create | Japanese | relative format past');
+  equal(Date.create('2 months ago', 'en').relative(),  '2ใƒถๆœˆๅ‰',   'Date#create | Japanese | relative format past');
+  equal(Date.create('2 years ago', 'en').relative(),   '2ๅนดๅ‰',     'Date#create | Japanese | relative format past');
+
+  equal(Date.create('1 second from now', 'en').relative(), '1็ง’ๅพŒ', 'Date#create | Japanese | relative format future');
+  equal(Date.create('1 minute from now', 'en').relative(), '1ๅˆ†ๅพŒ',  'Date#create | Japanese | relative format future');
+  equal(Date.create('1 hour from now', 'en').relative(),   '1ๆ™‚้–“ๅพŒ',     'Date#create | Japanese | relative format future');
+  equal(Date.create('1 day from now', 'en').relative(),    '1ๆ—ฅๅพŒ',    'Date#create | Japanese | relative format future');
+  equal(Date.create('1 week from now', 'en').relative(),   '1้€ฑ้–“ๅพŒ',  'Date#create | Japanese | relative format future');
+  equal(Date.create('1 month from now', 'en').relative(),  '1ใƒถๆœˆๅพŒ',   'Date#create | Japanese | relative format future');
+  equal(Date.create('1 year from now', 'en').relative(),   '1ๅนดๅพŒ',     'Date#create | Japanese | relative format future');
+
+  equal(Date.create('5 second from now', 'en').relative(), '5็ง’ๅพŒ', 'Date#create | Japanese | relative format future');
+  equal(Date.create('5 minute from now', 'en').relative(), '5ๅˆ†ๅพŒ',  'Date#create | Japanese | relative format future');
+  equal(Date.create('5 hour from now', 'en').relative(),   '5ๆ™‚้–“ๅพŒ',     'Date#create | Japanese | relative format future');
+  equal(Date.create('5 day from now', 'en').relative(),    '5ๆ—ฅๅพŒ',    'Date#create | Japanese | relative format future');
+  equal(Date.create('5 week from now', 'en').relative(),   '1ใƒถๆœˆๅพŒ',  'Date#create | Japanese | relative format future');
+  equal(Date.create('5 month from now', 'en').relative(),  '5ใƒถๆœˆๅพŒ',   'Date#create | Japanese | relative format future');
+  equal(Date.create('5 year from now', 'en').relative(),   '5ๅนดๅพŒ',     'Date#create | Japanese | relative format future');
+
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ 3:45:59'), new Date(2011, 4, 15, 3, 45, 59), 'Date#create | Japanese | full date with time');
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ 3ๆ™‚45ๅˆ†'), new Date(2011, 4, 15, 3, 45, 0), 'Date#create | Japanese | full date with kanji markers');
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ 3ๆ™‚45ๅˆ†59็ง’'), new Date(2011, 4, 15, 3, 45, 59), 'Date#create | Japanese | full date with kanji markers');
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ ๅˆๅ‰3ๆ™‚45ๅˆ†'), new Date(2011, 4, 15, 3, 45), 'Date#create | Japanese | full date with gozen');
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ ๅˆๅพŒ3ๆ™‚45ๅˆ†'), new Date(2011, 4, 15, 15, 45), 'Date#create | Japanese | full date with gogo');
+  dateEqual(Date.create('๏ผ’๏ผ๏ผ‘๏ผ‘ๅนด๏ผ•ๆœˆ๏ผ‘๏ผ•ๆ—ฅใ€€๏ผ“ๆ™‚๏ผ”๏ผ•ๅˆ†'), new Date(2011, 4, 15, 3, 45), 'Date#create | Japanese | full date with zenkaku');
+  dateEqual(Date.create('๏ผ’๏ผ๏ผ‘๏ผ‘ๅนด๏ผ•ๆœˆ๏ผ‘๏ผ•ๆ—ฅใ€€ๅˆๅพŒ๏ผ“ๆ™‚๏ผ”๏ผ•ๅˆ†'), new Date(2011, 4, 15, 15, 45), 'Date#create | Japanese | full date with zenkaku and gogo');
+
+  dateEqual(Date.create('ไบŒๅƒๅไธ€ๅนดไบ”ๆœˆๅไบ”ๆ—ฅใ€€ๅˆๅพŒไธ‰ๆ™‚ๅ››ๅไบ”ๅˆ†'), new Date(2011, 4, 15, 15, 45), 'Date#create | Japanese | full date with full kanji and full markers');
+
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ ๅˆๅพŒ3:45'), new Date(2011, 4, 15, 15, 45), 'Date#create | Japanese | pm still works');
+
+
+  dateEqual(Date.create('5ๆœˆ15ๆ—ฅ 3:45:59'), new Date(now.getFullYear(), 4, 15, 3, 45, 59), 'Date#create | Japanese | mm-dd format with time');
+  dateEqual(Date.create('15ๆ—ฅ 3:45:59'), new Date(now.getFullYear(), now.getMonth(), 15, 3, 45, 59), 'Date#create | Japanese | dd format with time');
+  dateEqual(Date.create('ๅ…ˆ้€ฑๆฐดๆ›œๆ—ฅ 5:15'), getDateWithWeekdayAndOffset(3, -7).set({ hour: 5, minute: 15 }), 'Date#create | Japanese | Last wednesday with time');
+
+
+  // Issue #148 various Japanese dates
+
+  dateEqual(Date.create('ๆฅ้€ฑ็ซๆ›œๆ—ฅๅˆๅพŒ3:00'), getDateWithWeekdayAndOffset(2, 7).set({ hours: 15 }), 'Date#create | Japanese | next Tuesday at 3:00pm');
+  dateEqual(Date.create('็ซๆ›œๆ—ฅ3:00'), getDateWithWeekdayAndOffset(2, 0).set({ hours: 3 }), 'Date.create | Japanese | Tuesday at 3:00am');
+  dateEqual(Date.create('็ซๆ›œๆ—ฅๅˆๅพŒ3:00'), getDateWithWeekdayAndOffset(2, 0).set({ hours: 15 }), 'Date.create | Japanese | Tuesday at 3:00pm');
+  dateEqual(Date.create('2012ๅนด6ๆœˆ5ๆ—ฅ3:00'), new Date(2012, 5, 5, 3), 'Date.create | Japanese | June 5th at 3:00pm');
+
+  equal((5).hours().duration('ja'),   '5ๆ™‚้–“',     'Date#create | Japanese | simple duration');
+
+});
level2/node_modules/sugar/test/environments/sugar/date_ko.js
@@ -0,0 +1,114 @@
+test('Dates | Korean', function () {
+
+  var now = new Date();
+  var then = new Date(2011, 7, 25, 15, 45, 50);
+  Date.setLocale('ko');
+
+  dateEqual(Date.create('2011๋…„5์›”15์ผ'), new Date(2011, 4, 15), 'Date#create | basic Korean date');
+  dateEqual(Date.create('2011๋…„5์›”'), new Date(2011, 4), 'Date#create | Korean | year and month');
+  dateEqual(Date.create('5์›”15์ผ'), new Date(now.getFullYear(), 4, 15), 'Date#create | Korean | month and date');
+  dateEqual(Date.create('2011๋…„'), new Date(2011, 0), 'Date#create | Korean | year');
+  dateEqual(Date.create('5์›”'), new Date(now.getFullYear(), 4), 'Date#create | Korean | month');
+  dateEqual(Date.create('15์ผ'), new Date(now.getFullYear(), now.getMonth(), 15), 'Date#create | Korean | date');
+  dateEqual(Date.create('์›”์š”์ผ'), getDateWithWeekdayAndOffset(1), 'Date#create | Korean | Monday');
+  dateEqual(Date.create('๊ตฌ์ผ'), new Date(now.getFullYear(), now.getMonth(), 9), 'Date#create | Korean | the 9th');
+  dateEqual(Date.create('์ด์‹ญ์˜ค์ผ'), new Date(now.getFullYear(), now.getMonth(), 25), 'Date#create | Korean | the 25th');
+  dateEqual(Date.create('ํ•œ๋‹ฌ ์ „'), getRelativeDate(null, -1), 'Date#create | Korean | one month ago ๋‹ฌ');
+
+
+  dateEqual(Date.create('2011๋…„5์›”15์ผ 3:45'), new Date(2011, 4, 15, 3, 45), 'Date#create | basic Korean date 3:45');
+  dateEqual(Date.create('2011๋…„5์›”15์ผ ์˜คํ›„3:45'), new Date(2011, 4, 15, 15, 45), 'Date#create | basic Korean date 3:45pm');
+  dateEqual(Date.create('2011๋…„5์›”15์ผ ์˜คํ›„ 3์‹œ 45๋ถ„'), new Date(2011, 4, 15, 15, 45), 'Date#create | basic Korean date full korean letters');
+
+  dateEqual(Date.create('1๋ฐ€๋ฆฌ์ดˆ ์ „'), getRelativeDate(null, null, null, null, null, null,-1), 'Date#create | Korean | one millisecond ago');
+  dateEqual(Date.create('1์ดˆ ์ „'), getRelativeDate(null, null, null, null, null, -1), 'Date#create | Korean | one second ago');
+  dateEqual(Date.create('1๋ถ„ ์ „'), getRelativeDate(null, null, null, null, -1), 'Date#create | Korean | one minute ago');
+  dateEqual(Date.create('1์‹œ๊ฐ„ ์ „'), getRelativeDate(null, null, null, -1), 'Date#create | Korean | one hour ago');
+  dateEqual(Date.create('1์ผ ์ „'), getRelativeDate(null, null, -1), 'Date#create | Korean | one day ago');
+  dateEqual(Date.create('1์ฃผ ์ „'), getRelativeDate(null, null, -7), 'Date#create | Korean | one week');
+  dateEqual(Date.create('1๊ฐœ์›” ์ „'), getRelativeDate(null, -1), 'Date#create | Korean | one month ago ๊ฐœ์›”');
+  dateEqual(Date.create('1๋…„ ์ „'), getRelativeDate(-1), 'Date#create | Korean | one year ago');
+
+
+  dateEqual(Date.create('5๋ฐ€๋ฆฌ์ดˆ ํ›„'), getRelativeDate(null, null, null, null, null, null,5), 'Date#create | Korean | five millisecond from now');
+  dateEqual(Date.create('5์ดˆ ํ›„'), getRelativeDate(null, null, null, null, null, 5), 'Date#create | Korean | five second from now');
+  dateEqual(Date.create('5๋ถ„ ํ›„'), getRelativeDate(null, null, null, null, 5), 'Date#create | Korean | five minute from now');
+  dateEqual(Date.create('5์‹œ๊ฐ„ ํ›„'), getRelativeDate(null, null, null, 5), 'Date#create | Korean | five hour from now');
+  dateEqual(Date.create('5์ผ ํ›„'), getRelativeDate(null, null, 5), 'Date#create | Korean | five day from now');
+  dateEqual(Date.create('5์ฃผ ํ›„'), getRelativeDate(null, null, 35), 'Date#create | Korean | five weeks from now');
+  dateEqual(Date.create('5๊ฐœ์›” ํ›„'), getRelativeDate(null, 5), 'Date#create | Korean | five months ๊ฐœ์›”');
+  dateEqual(Date.create('5๋…„ ํ›„'), getRelativeDate(5), 'Date#create | Korean | five years from now');
+
+  dateEqual(Date.create('๊ทธ์ €๊ป˜'), getRelativeDate(null, null, -2).reset(), 'Date#create | Korean | ๊ทธ์ €๊ป˜');
+  dateEqual(Date.create('์–ด์ œ'), getRelativeDate(null, null, -1).reset(), 'Date#create | Korean | yesterday');
+  dateEqual(Date.create('์˜ค๋Š˜'), getRelativeDate(null, null, 0).reset(), 'Date#create | Korean | today');
+  dateEqual(Date.create('๋‚ด์ผ'), getRelativeDate(null, null, 1).reset(), 'Date#create | Korean | tomorrow');
+  dateEqual(Date.create('๋ชจ๋ ˆ'), getRelativeDate(null, null, 2).reset(), 'Date#create | Korean | ๋ชจ๋ ˆ');
+
+  dateEqual(Date.create('๋‚ด์ผ 3:45'), getRelativeDate(null, null, 1).set({ hours: 3, minutes: 45 }, true), 'Date#create | Korean | tomorrow with time 3:45');
+  dateEqual(Date.create('๋‚ด์ผ ์˜คํ›„3:45'), getRelativeDate(null, null, 1).set({ hours: 15, minutes: 45 }, true), 'Date#create | Korean | tomorrow with time 3:45pm');
+  dateEqual(Date.create('์ˆ˜์š”์ผ 3:45'), getDateWithWeekdayAndOffset(3, 0).set({ hours: 3, minutes: 45 }, true), 'Date#create | Korean | wednesday with time 3:45');
+
+  dateEqual(Date.create('์ง€๋‚œ ์ฃผ'), getRelativeDate(null, null, -7), 'Date#create | Korean | Last week');
+  dateEqual(Date.create('์ด๋ฒˆ ์ฃผ'), getRelativeDate(null, null, 0), 'Date#create | Korean | this week');
+  dateEqual(Date.create('๋‹ค์Œ ์ฃผ'), getRelativeDate(null, null, 7), 'Date#create | Korean | Next week');
+
+  dateEqual(Date.create('์ง€๋‚œ ๋‹ฌ'), getRelativeDate(null, -1), 'Date#create | Korean | last month');
+  dateEqual(Date.create('์ด๋ฒˆ ๋‹ฌ'), getRelativeDate(null, 0), 'Date#create | Korean | this month');
+  dateEqual(Date.create('๋‹ค์Œ ๋‹ฌ'), getRelativeDate(null, 1), 'Date#create | Korean | Next month');
+
+  dateEqual(Date.create('์ž‘๋…„'), getRelativeDate(-1), 'Date#create | Korean | Last year');
+  dateEqual(Date.create('๋‚ด๋…„'), getRelativeDate(1), 'Date#create | Korean | Next year');
+
+
+  dateEqual(Date.create('์ง€๋‚œ ์ฃผ ์ˆ˜์š”์ผ'), getDateWithWeekdayAndOffset(3, -7), 'Date#create | Korean | Last wednesday');
+  dateEqual(Date.create('์ด๋ฒˆ ์ผ์š”์ผ'), getDateWithWeekdayAndOffset(0), 'Date#create | Korean | this sunday');
+  dateEqual(Date.create('๋‹ค์Œ ์ฃผ ๊ธˆ์š”์ผ'), getDateWithWeekdayAndOffset(5, 7), 'Date#create | Korean | Next friday');
+
+
+  equal(then.format(), '2011๋…„8์›”25์ผ 15์‹œ45๋ถ„', 'Date#create | Korean | standard format');
+  equal(then.format('{yyyy}๋…„{MM}์›”{dd}์ผ'), '2011๋…„08์›”25์ผ', 'Date#create | Korean | format');
+
+  // Format shortcuts
+  equal(then.format('full'), '2011๋…„8์›”25์ผ ๋ชฉ์š”์ผ 15์‹œ45๋ถ„50์ดˆ', 'Date#create | Korean | full format');
+  equal(then.full(), '2011๋…„8์›”25์ผ ๋ชฉ์š”์ผ 15์‹œ45๋ถ„50์ดˆ', 'Date#create | Korean | full shortcut');
+  equal(then.format('long'), '2011๋…„8์›”25์ผ 15์‹œ45๋ถ„', 'Date#create | Korean | long format');
+  equal(then.long(), '2011๋…„8์›”25์ผ 15์‹œ45๋ถ„', 'Date#create | Korean | long shortcut');
+  equal(then.format('short'), '2011๋…„8์›”25์ผ', 'Date#create | Korean | short format');
+  equal(then.short(), '2011๋…„8์›”25์ผ', 'Date#create | Korean | short shortcut');
+
+  equal(Date.create('1 second ago', 'en').relative(), '1์ดˆ ์ „', 'Date#create | Korean | relative format past');
+  equal(Date.create('1 minute ago', 'en').relative(), '1๋ถ„ ์ „',  'Date#create | Korean | relative format past');
+  equal(Date.create('1 hour ago', 'en').relative(),   '1์‹œ๊ฐ„ ์ „',     'Date#create | Korean | relative format past');
+  equal(Date.create('1 day ago', 'en').relative(),    '1์ผ ์ „',    'Date#create | Korean | relative format past');
+  equal(Date.create('1 week ago', 'en').relative(),   '1์ฃผ ์ „',  'Date#create | Korean | relative format past');
+  equal(Date.create('1 month ago', 'en').relative(),  '1๊ฐœ์›” ์ „',   'Date#create | Korean | relative format past');
+  equal(Date.create('1 year ago', 'en').relative(),   '1๋…„ ์ „',     'Date#create | Korean | relative format past');
+
+  equal(Date.create('2 seconds ago', 'en').relative(), '2์ดˆ ์ „', 'Date#create | Korean | relative format past');
+  equal(Date.create('2 minutes ago', 'en').relative(), '2๋ถ„ ์ „',  'Date#create | Korean | relative format past');
+  equal(Date.create('2 hours ago', 'en').relative(),   '2์‹œ๊ฐ„ ์ „',     'Date#create | Korean | relative format past');
+  equal(Date.create('2 days ago', 'en').relative(),    '2์ผ ์ „',    'Date#create | Korean | relative format past');
+  equal(Date.create('2 weeks ago', 'en').relative(),   '2์ฃผ ์ „',  'Date#create | Korean | relative format past');
+  equal(Date.create('2 months ago', 'en').relative(),  '2๊ฐœ์›” ์ „',   'Date#create | Korean | relative format past');
+  equal(Date.create('2 years ago', 'en').relative(),   '2๋…„ ์ „',     'Date#create | Korean | relative format past');
+
+  equal(Date.create('1 second from now', 'en').relative(), '1์ดˆ ํ›„', 'Date#create | Korean | relative format future');
+  equal(Date.create('1 minute from now', 'en').relative(), '1๋ถ„ ํ›„',  'Date#create | Korean | relative format future');
+  equal(Date.create('1 hour from now', 'en').relative(),   '1์‹œ๊ฐ„ ํ›„',     'Date#create | Korean | relative format future');
+  equal(Date.create('1 day from now', 'en').relative(),    '1์ผ ํ›„',    'Date#create | Korean | relative format future');
+  equal(Date.create('1 week from now', 'en').relative(),   '1์ฃผ ํ›„',  'Date#create | Korean | relative format future');
+  equal(Date.create('1 month from now', 'en').relative(),  '1๊ฐœ์›” ํ›„',   'Date#create | Korean | relative format future');
+  equal(Date.create('1 year from now', 'en').relative(),   '1๋…„ ํ›„',     'Date#create | Korean | relative format future');
+
+  equal(Date.create('5 second from now', 'en').relative(), '5์ดˆ ํ›„', 'Date#create | Korean | relative format future');
+  equal(Date.create('5 minute from now', 'en').relative(), '5๋ถ„ ํ›„',  'Date#create | Korean | relative format future');
+  equal(Date.create('5 hour from now', 'en').relative(),   '5์‹œ๊ฐ„ ํ›„',     'Date#create | Korean | relative format future');
+  equal(Date.create('5 day from now', 'en').relative(),    '5์ผ ํ›„',    'Date#create | Korean | relative format future');
+  equal(Date.create('5 week from now', 'en').relative(),   '1๊ฐœ์›” ํ›„',  'Date#create | Korean | relative format future');
+  equal(Date.create('5 month from now', 'en').relative(),  '5๊ฐœ์›” ํ›„',   'Date#create | Korean | relative format future');
+  equal(Date.create('5 year from now', 'en').relative(),   '5๋…„ ํ›„',     'Date#create | Korean | relative format future');
+
+  equal((5).hours().duration('ko'),   '5์‹œ๊ฐ„',     'Date#create | Korean | simple duration');
+
+});
level2/node_modules/sugar/test/environments/sugar/date_nl.js
@@ -0,0 +1,125 @@
+test('Dates | Dutch', function () {
+
+  var now = new Date();
+  var then = new Date(2011, 7, 25, 15, 45, 50);
+  Date.setLocale('nl');
+
+
+
+  dateEqual(Date.create('15 mei 2011'), new Date(2011, 4, 15), 'Date#create | basic Dutch date');
+  dateEqual(Date.create('Dinsdag, 5 Januari 2012'), new Date(2012, 0, 5), 'Date#create | Dutch | 2012-01-05');
+  dateEqual(Date.create('Mei 2011'), new Date(2011, 4), 'Date#create | Dutch | year and month');
+  dateEqual(Date.create('15 Mei'), new Date(now.getFullYear(), 4, 15), 'Date#create | Dutch | month and date');
+  dateEqual(Date.create('2011'), new Date(2011, 0), 'Date#create | Dutch | year');
+  dateEqual(Date.create('Mei'), new Date(now.getFullYear(), 4), 'Date#create | Dutch | month');
+  dateEqual(Date.create('maandag'), getDateWithWeekdayAndOffset(1), 'Date#create | Dutch | Monday');
+  dateEqual(Date.create('Ma'), getDateWithWeekdayAndOffset(1), 'Date#create | Dutch | Monday abbreviated');
+
+  dateEqual(Date.create('Dinsdag, 5 Januari 2012 3:45'), new Date(2012, 0, 5, 3, 45), 'Date#create | Dutch | 2012-01-05 3:45');
+  equal(Date.create('Dinsdag, 5 Januari 2012 3:45pm').isValid(), false, 'Date#create | Dutch | does not support am/pm');
+
+  dateEqual(Date.create('een milliseconde geleden'), getRelativeDate(null, null, null, null, null, null,-1), 'Date#create | Dutch | one millisecond ago');
+  dateEqual(Date.create('een seconde geleden'), getRelativeDate(null, null, null, null, null, -1), 'Date#create | Dutch | one second ago');
+  dateEqual(Date.create('een minuut geleden'), getRelativeDate(null, null, null, null, -1), 'Date#create | Dutch | one minuto ago');
+  dateEqual(Date.create("een uur geleden"), getRelativeDate(null, null, null, -1), 'Date#create | Dutch | one hour ago');
+  dateEqual(Date.create('een dag geleden'), getRelativeDate(null, null, -1), 'Date#create | Dutch | one day ago');
+  dateEqual(Date.create('een week geleden'), getRelativeDate(null, null, -7), 'Date#create | Dutch | one week ago');
+  dateEqual(Date.create('een maand geleden'), getRelativeDate(null, -1), 'Date#create | Dutch | one month ago');
+  dateEqual(Date.create('een jaar geleden'), getRelativeDate(-1), 'Date#create | Dutch | one year ago');
+
+
+  dateEqual(Date.create('5 milliseconden vanaf nu'), getRelativeDate(null, null, null, null, null, null,5), 'Date#create | Dutch | danni | five milliseconds from now');
+  dateEqual(Date.create('5 seconden vanaf nu'), getRelativeDate(null, null, null, null, null, 5), 'Date#create | Dutch | danni | five second from now');
+  dateEqual(Date.create('5 minuten vanaf nu'), getRelativeDate(null, null, null, null, 5), 'Date#create | Dutch | danni | five minuto from now');
+  dateEqual(Date.create('5 uur vanaf nu'), getRelativeDate(null, null, null, 5), 'Date#create | Dutch | danni | five hour from now');
+  dateEqual(Date.create('5 dagen vanaf nu'), getRelativeDate(null, null, 5), 'Date#create | Dutch | danni | five day from now');
+  dateEqual(Date.create('5 weken vanaf nu'), getRelativeDate(null, null, 35), 'Date#create | Dutch | danni | five weeks from now');
+  dateEqual(Date.create('5 maanden vanaf nu'), getRelativeDate(null, 5), 'Date#create | Dutch | danni | five months from now');
+  dateEqual(Date.create('5 jaar vanaf nu'), getRelativeDate(5), 'Date#create | Dutch | danni | five years from now');
+
+
+  dateEqual(Date.create('gisteren'), getRelativeDate(null, null, -1).reset(), 'Date#create | Dutch | yesterday');
+  dateEqual(Date.create('vandaag'), getRelativeDate(null, null, 0).reset(), 'Date#create | Dutch | today');
+  dateEqual(Date.create('morgen'), getRelativeDate(null, null, 1).reset(), 'Date#create | Dutch | tomorrow');
+  dateEqual(Date.create('overmorgen'), getRelativeDate(null, null, 2).reset(), 'Date#create | Dutch | day after tomorrow');
+
+  dateEqual(Date.create('laatste week'), getRelativeDate(null, null, -7), 'Date#create | Dutch | Last week');
+  dateEqual(Date.create('volgende week'), getRelativeDate(null, null, 7), 'Date#create | Dutch | Next week');
+
+  dateEqual(Date.create('vorige maand'), getRelativeDate(null, -1), 'Date#create | Dutch | last month');
+  dateEqual(Date.create('volgende maand'), getRelativeDate(null, 1), 'Date#create | Dutch | Next month');
+
+  dateEqual(Date.create("afgelopen jaar"), getRelativeDate(-1), 'Date#create | Dutch | Last year');
+  dateEqual(Date.create("volgend jaar"), getRelativeDate(1), 'Date#create | Dutch | Next year');
+
+  dateEqual(Date.create("volgende maandag"), getDateWithWeekdayAndOffset(1, 7), 'Date#create | Dutch | next monday');
+  dateEqual(Date.create("afgelopen maandag"), getDateWithWeekdayAndOffset(1, -7), 'Date#create | Dutch | last monday');
+
+  dateEqual(Date.create("afgelopen maandag 3:45"), getDateWithWeekdayAndOffset(1, -7).set({ hour: 3, minute: 45 }, true), 'Date#create | Dutch | last monday 3:45');
+
+  equal(then.format(), '25 Augustus 2011 15:45', 'Date#create | Dutch | format');
+  equal(then.format('{dd} {Month} {yyyy}'), '25 Augustus 2011', 'Date#create | Dutch | format');
+
+  // Format shortcuts
+  equal(then.format('long'), '25 Augustus 2011 15:45', 'Date#create | Dutch | long format');
+  equal(then.long(), '25 Augustus 2011 15:45', 'Date#create | Dutch | long shortcut');
+  equal(then.format('full'), 'Donderdag 25 Augustus 2011 15:45:50', 'Date#create | Dutch | full format');
+  equal(then.full(), 'Donderdag 25 Augustus 2011 15:45:50', 'Date#create | Dutch | full shortcut');
+  equal(then.format('short'), '25 Augustus 2011', 'Date#create | Dutch | short format');
+  equal(then.short(), '25 Augustus 2011', 'Date#create | Dutch | short shortcut');
+
+
+  equal(Date.create('1 second ago', 'en').relative(), '1 seconde geleden', 'Date#create | Dutch | relative format past');
+  equal(Date.create('1 minute ago', 'en').relative(), '1 minuut geleden',  'Date#create | Dutch | relative format past');
+  equal(Date.create('1 hour ago', 'en').relative(),   '1 uur geleden',     'Date#create | Dutch | relative format past');
+  equal(Date.create('1 day ago', 'en').relative(),    '1 dag geleden',    'Date#create | Dutch | relative format past');
+  equal(Date.create('1 week ago', 'en').relative(),   '1 week geleden',  'Date#create | Dutch | relative format past');
+  equal(Date.create('1 month ago', 'en').relative(),  '1 maand geleden',   'Date#create | Dutch | relative format past');
+  equal(Date.create('1 year ago', 'en').relative(),   '1 jaar geleden',     'Date#create | Dutch | relative format past');
+
+  equal(Date.create('2 seconds ago', 'en').relative(), '2 seconden geleden', 'Date#create | Dutch | relative format past');
+  equal(Date.create('2 minutes ago', 'en').relative(), '2 minuten geleden',  'Date#create | Dutch | relative format past');
+  equal(Date.create('2 hours ago', 'en').relative(),   '2 uur geleden',     'Date#create | Dutch | relative format past');
+  equal(Date.create('2 days ago', 'en').relative(),    '2 dagen geleden',    'Date#create | Dutch | relative format past');
+  equal(Date.create('2 weeks ago', 'en').relative(),   '2 weken geleden',  'Date#create | Dutch | relative format past');
+  equal(Date.create('2 months ago', 'en').relative(),  '2 maanden geleden',   'Date#create | Dutch | relative format past');
+  equal(Date.create('2 years ago', 'en').relative(),   '2 jaar geleden',     'Date#create | Dutch | relative format past');
+
+  equal(Date.create('1 second from now', 'en').relative(), '1 seconde vanaf nu', 'Date#create | Dutch | relative format future');
+  equal(Date.create('1 minute from now', 'en').relative(), '1 minuut vanaf nu',  'Date#create | Dutch | relative format future');
+  equal(Date.create('1 hour from now', 'en').relative(),   '1 uur vanaf nu',     'Date#create | Dutch | relative format future');
+  equal(Date.create('1 day from now', 'en').relative(),    '1 dag vanaf nu',    'Date#create | Dutch | relative format future');
+  equal(Date.create('1 week from now', 'en').relative(),   '1 week vanaf nu',  'Date#create | Dutch | relative format future');
+  equal(Date.create('1 month from now', 'en').relative(),  '1 maand vanaf nu',   'Date#create | Dutch | relative format future');
+  equal(Date.create('1 year from now', 'en').relative(),   '1 jaar vanaf nu',     'Date#create | Dutch | relative format future');
+
+  equal(Date.create('5 second from now', 'en').relative(), '5 seconden vanaf nu', 'Date#create | Dutch | relative format future');
+  equal(Date.create('5 minutes from now', 'en').relative(),'5 minuten vanaf nu',  'Date#create | Dutch | relative format future');
+  equal(Date.create('5 hour from now', 'en').relative(),   '5 uur vanaf nu',     'Date#create | Dutch | relative format future');
+  equal(Date.create('5 day from now', 'en').relative(),    '5 dagen vanaf nu',    'Date#create | Dutch | relative format future');
+  equal(Date.create('5 week from now', 'en').relative(),   '1 maand vanaf nu',  'Date#create | Dutch | relative format future');
+  equal(Date.create('5 month from now', 'en').relative(),  '5 maanden vanaf nu',   'Date#create | Dutch | relative format future');
+  equal(Date.create('5 year from now', 'en').relative(),   '5 jaar vanaf nu',     'Date#create | Dutch | relative format future');
+
+  // Issue #152 Dutch should not use a variant in any format
+  dateEqual(Date.create('15/3/2012 12:45'), new Date(2012, 2, 15, 12, 45), 'Date#create | Dutch | slash format with time');
+  dateEqual(Date.create('12:45 15/3/2012'), new Date(2012, 2, 15, 12, 45), 'Date#create | Dutch | slash format with time front');
+
+  // Issue #150 Fully qualified ISO codes should be allowed
+  dateEqual(Date.create('7 januari 2012', 'nl_NL'), new Date(2012, 0, 7), 'Date#create | Dutch | nl_NL');
+  dateEqual(Date.create('7 januari 2012', 'nl-NL'), new Date(2012, 0, 7), 'Date#create | Dutch | nl-NL');
+
+  // Issue #150 Unrecognized locales will result in invalid dates, but will not throw an error
+  // Update: Now it will fall back to the current locale.
+  equal(Date.create('7 januari 2012', 'ux_UX').isValid(), true, 'Date#create | Dutch | unknown locale code');
+  equal(Date.create('2012/08/25', 'ux_UX').isValid(), true, 'Date#create | Dutch | System intelligible formats are still parsed');
+
+  dateEqual(Date.create('17:32 18 augustus'), new Date(now.getFullYear(), 7, 18, 17, 32), 'Date#create | Dutch | August 18, 17:32');
+
+  dateEqual(Date.create('morgen \'s 3:30'), getRelativeDate(null, null, 1).set({hours:3,minutes:30}, true), 'Date#create | Dutch | tomorrow at 3:30');
+
+  equal((5).hours().duration('nl'), '5 uur', 'Date#create | Dutch | simple duration');
+
+  equal(new Date().format('{tt}'), '', 'Date#format | Dutch | am/pm token should not raise an error');
+
+});
level2/node_modules/sugar/test/environments/sugar/date_pt.js
@@ -0,0 +1,114 @@
+test('Dates | Portuguese', function () {
+
+  var now = new Date();
+  var then = new Date(2011, 7, 25, 15, 45, 50);
+  Date.setLocale('pt');
+
+
+  dateEqual(Date.create('15 de maio 2011'), new Date(2011, 4, 15), 'Date#create | basic Portuguese date');
+  dateEqual(Date.create('5 de janeiro de 2012'), new Date(2012, 0, 5), 'Date#create | Portuguese | 2012-01-05');
+  dateEqual(Date.create('maio de 2011'), new Date(2011, 4), 'Date#create | Portuguese | year and month');
+  dateEqual(Date.create('15 de maio'), new Date(now.getFullYear(), 4, 15), 'Date#create | Portuguese | month and date');
+  dateEqual(Date.create('2011'), new Date(2011, 0), 'Date#create | Portuguese | year');
+  dateEqual(Date.create('maio'), new Date(now.getFullYear(), 4), 'Date#create | Portuguese | month');
+  dateEqual(Date.create('segunda-feira'), getDateWithWeekdayAndOffset(1), 'Date#create | Portuguese | Monday');
+
+  dateEqual(Date.create('5 de janeiro de 2012 3:45'), new Date(2012, 0, 5, 3, 45), 'Date#create | Portuguese | 2012-01-05 3:45');
+  dateEqual(Date.create('5 de janeiro de 2012 3:45pm'), new Date(2012, 0, 5, 15, 45), 'Date#create | Portuguese | 2012-01-05 3:45pm');
+
+  dateEqual(Date.create('um milisegundo atrรกs'), getRelativeDate(null, null, null, null, null, null,-1), 'Date#create | Portuguese | one millisecond ago');
+  dateEqual(Date.create('um segundo atrรกs'), getRelativeDate(null, null, null, null, null, -1), 'Date#create | Portuguese | one second ago');
+  dateEqual(Date.create('um minuto atrรกs'), getRelativeDate(null, null, null, null, -1), 'Date#create | Portuguese | one minute ago');
+  dateEqual(Date.create('uma hora atrรกs'), getRelativeDate(null, null, null, -1), 'Date#create | Portuguese | one hour ago');
+  dateEqual(Date.create('um dia atrรกs'), getRelativeDate(null, null, -1), 'Date#create | Portuguese | one day ago');
+  dateEqual(Date.create('uma semana atrรกs'), getRelativeDate(null, null, -7), 'Date#create | Portuguese | one week');
+  dateEqual(Date.create('hรก um mรชs'), getRelativeDate(null, -1), 'Date#create | Portuguese | one month ago');
+  dateEqual(Date.create('hรก um ano'), getRelativeDate(-1), 'Date#create | Portuguese | one year ago');
+
+  dateEqual(Date.create('daqui a 5 milisegundos'), getRelativeDate(null, null, null, null, null, null,5), 'Date#create | Portuguese | five milliseconds from now');
+  dateEqual(Date.create('daqui a 5 segundos'), getRelativeDate(null, null, null, null, null, 5), 'Date#create | Portuguese | five second from now');
+  dateEqual(Date.create('daqui a 5 minutos'), getRelativeDate(null, null, null, null, 5), 'Date#create | Portuguese | five minute from now');
+  dateEqual(Date.create('daqui a 5 horas'), getRelativeDate(null, null, null, 5), 'Date#create | Portuguese | five hours from now');
+  dateEqual(Date.create('daqui a 5 dias'), getRelativeDate(null, null, 5), 'Date#create | Portuguese | five day from now');
+  dateEqual(Date.create('daqui a 5 semanas'), getRelativeDate(null, null, 35), 'Date#create | Portuguese | five weeks from now');
+  dateEqual(Date.create('daqui a 5 mรชses'), getRelativeDate(null, 5), 'Date#create | Portuguese | five months from now | mรชses');
+  dateEqual(Date.create('daqui a 5 anos'), getRelativeDate(5), 'Date#create | Portuguese | five years from now');
+
+
+  dateEqual(Date.create('anteontem'), getRelativeDate(null, null, -2).reset(), 'Date#create | Portuguese | the day before yesterday');
+  dateEqual(Date.create('ontem'), getRelativeDate(null, null, -1).reset(), 'Date#create | Portuguese | yesterday');
+  dateEqual(Date.create('hoje'), getRelativeDate(null, null, 0).reset(), 'Date#create | Portuguese | today');
+  dateEqual(Date.create('amanhรฃ'), getRelativeDate(null, null, 1).reset(), 'Date#create | Portuguese | tomorrow');
+
+  dateEqual(Date.create('semana passada'), getRelativeDate(null, null, -7), 'Date#create | Portuguese | Last week');
+  dateEqual(Date.create('prรณxima semana'), getRelativeDate(null, null, 7), 'Date#create | Portuguese | Next week');
+
+  dateEqual(Date.create('mรชs passado'), getRelativeDate(null, -1), 'Date#create | Portuguese | last month');
+  dateEqual(Date.create('prรณximo mรชs'), getRelativeDate(null, 1), 'Date#create | Portuguese | Next month');
+
+  dateEqual(Date.create('ano passado'), getRelativeDate(-1), 'Date#create | Portuguese | Last year');
+  dateEqual(Date.create('prรณximo ano'), getRelativeDate(1), 'Date#create | Portuguese | Next year');
+
+  dateEqual(Date.create('prรณximo segunda-feira'), getDateWithWeekdayAndOffset(1,  7), 'Date#create | Portuguese | next monday');
+  dateEqual(Date.create('passada segunda-feira'), getDateWithWeekdayAndOffset(1, -7), 'Date#create | Portuguese | last monday');
+
+  dateEqual(Date.create('passada segunda-feira 3:45'), getDateWithWeekdayAndOffset(1, -7).set({ hour: 3, minute: 45 }, true), 'Date#create | Portuguese | last monday 3:45');
+
+  // no accents
+  dateEqual(Date.create('daqui a 5 meses'), getRelativeDate(null, 5), 'Date#create | Portuguese | five months from now | meses');
+  dateEqual(Date.create('mes passado'), getRelativeDate(null, -1), 'Date#create | Portuguese | last month');
+  dateEqual(Date.create('proximo ano'), getRelativeDate(1), 'Date#create | Portuguese | Next year');
+  dateEqual(Date.create('uma hora atras'), getRelativeDate(null, null, null, -1), 'Date#create | Portuguese | one hour ago');
+  dateEqual(Date.create('ha um ano'), getRelativeDate(-1), 'Date#create | Portuguese | one year ago');
+  dateEqual(Date.create('amanha'), getRelativeDate(null, null, 1).reset(), 'Date#create | Portuguese | tomorrow');
+
+
+  equal(then.format(), '25 de agosto de 2011 15:45', 'Date#create | Portuguese | standard format');
+  equal(then.format('{dd} de {month} {yyyy}'), '25 de agosto 2011', 'Date#create | Portuguese | format');
+
+  // Format shortcuts
+  equal(then.format('long'), '25 de agosto de 2011 15:45', 'Date#create | Portuguese | long format');
+  equal(then.long(), '25 de agosto de 2011 15:45', 'Date#create | Portuguese | long shortcut');
+  equal(then.format('full'), 'Quinta-feira, 25 de agosto de 2011 15:45:50', 'Date#create | Portuguese | full format');
+  equal(then.full(), 'Quinta-feira, 25 de agosto de 2011 15:45:50', 'Date#create | Portuguese | full shortcut');
+  equal(then.format('short'), '25 de agosto de 2011', 'Date#create | Portuguese | short format');
+  equal(then.short(), '25 de agosto de 2011', 'Date#create | Portuguese | short shortcut');
+
+  equal(Date.create('1 second ago', 'en').relative(), '1 segundo atrรกs', 'Date#create | Portuguese | relative format past');
+  equal(Date.create('1 minute ago', 'en').relative(), '1 minuto atrรกs',  'Date#create | Portuguese | relative format past');
+  equal(Date.create('1 hour ago', 'en').relative(),   '1 hora atrรกs',     'Date#create | Portuguese | relative format past');
+  equal(Date.create('1 day ago', 'en').relative(),    '1 dia atrรกs',    'Date#create | Portuguese | relative format past');
+  equal(Date.create('1 week ago', 'en').relative(),   '1 semana atrรกs',  'Date#create | Portuguese | relative format past');
+  equal(Date.create('1 month ago', 'en').relative(),  '1 mรชs atrรกs',   'Date#create | Portuguese | relative format past');
+  equal(Date.create('1 year ago', 'en').relative(),   '1 ano atrรกs',     'Date#create | Portuguese | relative format past');
+
+  equal(Date.create('2 seconds ago', 'en').relative(), '2 segundos atrรกs', 'Date#create | Portuguese | relative format past');
+  equal(Date.create('2 minutes ago', 'en').relative(), '2 minutos atrรกs',  'Date#create | Portuguese | relative format past');
+  equal(Date.create('2 hours ago', 'en').relative(),   '2 horas atrรกs',     'Date#create | Portuguese | relative format past');
+  equal(Date.create('2 days ago', 'en').relative(),    '2 dias atrรกs',    'Date#create | Portuguese | relative format past');
+  equal(Date.create('2 weeks ago', 'en').relative(),   '2 semanas atrรกs',  'Date#create | Portuguese | relative format past');
+  equal(Date.create('2 months ago', 'en').relative(),  '2 mรชses atrรกs',   'Date#create | Portuguese | relative format past');
+  equal(Date.create('2 years ago', 'en').relative(),   '2 anos atrรกs',     'Date#create | Portuguese | relative format past');
+
+  equal(Date.create('1 second from now', 'en').relative(), 'daqui a 1 segundo', 'Date#create | Portuguese | relative format future');
+  equal(Date.create('1 minute from now', 'en').relative(), 'daqui a 1 minuto',  'Date#create | Portuguese | relative format future');
+  equal(Date.create('1 hour from now', 'en').relative(),   'daqui a 1 hora',     'Date#create | Portuguese | relative format future');
+  equal(Date.create('1 day from now', 'en').relative(),    'daqui a 1 dia',    'Date#create | Portuguese | relative format future');
+  equal(Date.create('1 week from now', 'en').relative(),   'daqui a 1 semana',  'Date#create | Portuguese | relative format future');
+  equal(Date.create('1 month from now', 'en').relative(),  'daqui a 1 mรชs',   'Date#create | Portuguese | relative format future');
+  equal(Date.create('1 year from now', 'en').relative(),   'daqui a 1 ano',     'Date#create | Portuguese | relative format future');
+
+  equal(Date.create('5 second from now', 'en').relative(), 'daqui a 5 segundos', 'Date#create | Portuguese | relative format future');
+  equal(Date.create('5 minute from now', 'en').relative(), 'daqui a 5 minutos',  'Date#create | Portuguese | relative format future');
+  equal(Date.create('5 hour from now', 'en').relative(),   'daqui a 5 horas',     'Date#create | Portuguese | relative format future');
+  equal(Date.create('5 day from now', 'en').relative(),    'daqui a 5 dias',    'Date#create | Portuguese | relative format future');
+  equal(Date.create('5 week from now', 'en').relative(),   'daqui a 1 mรชs',  'Date#create | Portuguese | relative format future');
+  equal(Date.create('5 month from now', 'en').relative(),  'daqui a 5 mรชses',   'Date#create | Portuguese | relative format future');
+  equal(Date.create('5 year from now', 'en').relative(),   'daqui a 5 anos',     'Date#create | Portuguese | relative format future');
+
+
+  dateEqual(Date.create('amanhรฃ ร s 3:30'), getRelativeDate(null, null, 1).set({hours:3,minutes:30}, true), 'Date#create | Portuguese | tomorrow at 3:30');
+
+  equal((5).hours().duration('pt'),   '5 horas',     'Date#create | Portuguese | simple duration');
+
+});
level2/node_modules/sugar/test/environments/sugar/date_range.js
@@ -0,0 +1,263 @@
+test('Date Ranges', function () {
+
+  if(Date.setLocale) {
+    Date.setLocale('en');
+  }
+
+  var d1, d2;
+  var range, expected, result, count, range1, range2, tzOffset;
+
+  range = Date.range(NaN, NaN);
+
+  equal(range.isValid(), false, 'Date Range | invalid range');
+  equal(typeof range.start.getTime, 'function', 'Date Range | Invalid | start is not minified');
+  equal(typeof range.end.getTime, 'function', 'Date Range | Invalid | end is not minified');
+  equal(isNaN(range.span()), true, 'Date Range | Invalid | has no duration');
+  equal(range.toString(), 'Invalid Range', 'Date Range | Invalid | invalid toString');
+
+  d1 = new Date(2011,8,10,9);
+  d2 = new Date(2010,10,10,9)
+  range = Date.range(d1, d2);
+
+  equal(range.isValid(), true, 'Date Range | Inverted | range');
+  equal(typeof range.start.getTime, 'function', 'Date Range | Inverted | start is not minified');
+  equal(typeof range.end.getTime, 'function', 'Date Range | Inverted | end is not minified');
+  equal(range.span(), Math.abs(d2 - d1) + 1, 'Date Range | Inverted | duration');
+  equal(range.toString(), new Date(2011,8,10,9).toString() + '..' + new Date(2010,10,10,9).toString(), 'Date Range | Inverted | toString');
+
+  range = Date.range(new Date(2010,6,10,9), new Date(2010,8,10,9));
+
+  tzOffset = range.end.getTimezoneOffset() - range.start.getTimezoneOffset();
+
+  equal(range.toString(), new Date(2010,6,10,9).toString() + '..' + new Date(2010,8,10,9).toString(), 'Date Range | toString');
+  equal(range.span(), new Date(2010,8,10,9) - new Date(2010,6,10,9) + (tzOffset * 60 * 1000) + 1, 'Date Range | duration');
+  equal(range.isValid(), true, 'Date Range | valid range');
+
+  equal(range.every('year'), [new Date(2010,6,10,9)], 'Range#every | 2010-9 - 2010-11');
+
+
+  count = 0;
+  expected = [new Date(2010,6,10,9), new Date(2010,7,10,9), new Date(2010,8,10,9)]
+  result = range.every('month', function(d, index) {
+    dateEqual(d, expected[count], 'Date Range | date is first argument');
+    equal(index, count, 'Date Range | index is second argument');
+    count++;
+  });
+  equal(result, expected, 'Date Range | 2010-9 - 2010-11 | every month');
+  equal(count, 3, 'Date Range | 2010-9 - 2010-11 | has iterated 3 times');
+
+  equal(range.every('year'), [new Date(2010,6,10,9)], 'Date Range | 2010-9 - 2010-11 | every year');
+  equal(range.every('month'), [new Date(2010,6,10,9), new Date(2010,7,10,9), new Date(2010,8,10,9)], 'Date Range | 2010-9 - 2010-11 | every month');
+
+  // Discrepancies exist here because of the definition of a month
+  equal(range.every((2).months()), [new Date(2010,6,10,9), new Date(2010,8,9,6)], 'Date Range | 2010-9 - 2010-11 | every 2 months');
+
+  // This version will run every month then iterate over every other
+  equal(range.every('2 months'), [new Date(2010,6,10,9), new Date(2010,8,10,9)], 'Date Range | 2010-9 - 2010-11 | every 2 months text format');
+
+  equal(range.every((10).days()), [new Date(2010,6,10,9), new Date(2010,6,20,9), new Date(2010,6,30,9), new Date(2010,7,9,9), new Date(2010,7,19,9), new Date(2010,7,29,9), new Date(2010,8,8,9)], 'Date Range | 2010-9 - 2010-11 | every 10 days');
+
+  equal(range.every('day'), [new Date(2010,6,10,9), new Date(2010,6,11,9), new Date(2010,6,12,9), new Date(2010,6,13,9), new Date(2010,6,14,9), new Date(2010,6,15,9), new Date(2010,6,16,9), new Date(2010,6,17,9), new Date(2010,6,18,9), new Date(2010,6,19,9), new Date(2010,6,20,9), new Date(2010,6,21,9), new Date(2010,6,22,9), new Date(2010,6,23,9), new Date(2010,6,24,9), new Date(2010,6,25,9), new Date(2010,6,26,9), new Date(2010,6,27,9), new Date(2010,6,28,9), new Date(2010,6,29,9), new Date(2010,6,30,9), new Date(2010,6,31,9), new Date(2010,7,1,9), new Date(2010,7,2,9), new Date(2010,7,3,9), new Date(2010,7,4,9), new Date(2010,7,5,9), new Date(2010,7,6,9), new Date(2010,7,7,9), new Date(2010,7,8,9), new Date(2010,7,9,9), new Date(2010,7,10,9), new Date(2010,7,11,9), new Date(2010,7,12,9), new Date(2010,7,13,9), new Date(2010,7,14,9), new Date(2010,7,15,9), new Date(2010,7,16,9), new Date(2010,7,17,9), new Date(2010,7,18,9), new Date(2010,7,19,9), new Date(2010,7,20,9), new Date(2010,7,21,9), new Date(2010,7,22,9), new Date(2010,7,23,9), new Date(2010,7,24,9), new Date(2010,7,25,9), new Date(2010,7,26,9), new Date(2010,7,27,9), new Date(2010,7,28,9), new Date(2010,7,29,9), new Date(2010,7,30,9), new Date(2010,7,31,9), new Date(2010,8,1,9), new Date(2010,8,2,9), new Date(2010,8,3,9), new Date(2010,8,4,9), new Date(2010,8,5,9), new Date(2010,8,6,9), new Date(2010,8,7,9), new Date(2010,8,8,9), new Date(2010,8,9,9), new Date(2010,8,10,9) ], 'Date Range | 2010-9 - 2010 - 11 | every day');
+
+  dateEqual(range.start, new Date(2010,6,10,9), 'Date Range | Start has not been modified');
+  dateEqual(range.end, new Date(2010,8,10,9), 'Date Range | End has not been modified');
+
+  equal(range.contains(new Date(2010, 5,  10)), false, 'Date Range#contains | before');
+  equal(range.contains(new Date(2010, 7,  10)), true, 'Date Range#contains | middle');
+  equal(range.contains(new Date(2010, 9, 10)), false, 'Date Range#contains | after');
+  equal(range.contains(Date.range(new Date(2010,6,10,9), new Date(2010,7,10,9))), true, 'Date Range#contains | contained range');
+  equal(range.contains(Date.range(new Date(2010,4,10), new Date(2010,7,10,9))), false, 'Date Range#contains | 9 hours before the start');
+  equal(range.contains(Date.range(new Date(2010,4,10,9), new Date(2010,6,10,10))), false, 'Date Range#contains | 1 minute after the end');
+
+  range = Date.range(new Date(2010,3,25,12), new Date(2010,3,25,18));
+
+  equal(range.every('hour'), [new Date(2010,3,25,12),new Date(2010,3,25,13),new Date(2010,3,25,14),new Date(2010,3,25,15),new Date(2010,3,25,16),new Date(2010,3,25,17),new Date(2010,3,25,18)], 'Date Range | every hour');
+
+  range = Date.range(new Date(2010,3,25,12,30), new Date(2010,3,25,12,40));
+
+  equal(range.every('minute'), [new Date(2010,3,25,12,30),new Date(2010,3,25,12,31),new Date(2010,3,25,12,32),new Date(2010,3,25,12,33),new Date(2010,3,25,12,34),new Date(2010,3,25,12,35),new Date(2010,3,25,12,36),new Date(2010,3,25,12,37),new Date(2010,3,25,12,38),new Date(2010,3,25,12,39),new Date(2010,3,25,12,40)], 'Date Range | every minute');
+
+
+  range = Date.range(new Date(2010,3,25,12,30,30), new Date(2010,3,25,12,30,40));
+
+  equal(range.every('second'), [new Date(2010,3,25,12,30,30),new Date(2010,3,25,12,30,31),new Date(2010,3,25,12,30,32),new Date(2010,3,25,12,30,33),new Date(2010,3,25,12,30,34),new Date(2010,3,25,12,30,35),new Date(2010,3,25,12,30,36),new Date(2010,3,25,12,30,37),new Date(2010,3,25,12,30,38),new Date(2010,3,25,12,30,39),new Date(2010,3,25,12,30,40)], 'Date Range | every second');
+
+
+  range = Date.range(new Date(2010,3,25,12,30,30,500), new Date(2010,3,25,12,30,30,505));
+
+  equal(range.every('millisecond'), [new Date(2010,3,25,12,30,30,500),new Date(2010,3,25,12,30,30,501),new Date(2010,3,25,12,30,30,502),new Date(2010,3,25,12,30,30,503),new Date(2010,3,25,12,30,30,504),new Date(2010,3,25,12,30,30,505)], 'Date Range | every millisecond');
+
+  range = Date.range(new Date(2010,3,25));
+  dateEqual(range.start, new Date(2010,3,25), 'Date Range | null end | start');
+  equal(range.end.is(new Date(), 10), true, 'Date Range | null end is current time');
+
+  range = Date.range(null, new Date(2010,3,25));
+  equal(range.start.is(new Date(), 10), true, 'Date Range | null defaults to current time');
+  dateEqual(range.end, new Date(2010,3,25), 'Date Range | start is reversed when null is current');
+
+  range = Date.range();
+  equal(range.start.is(new Date(), 10), true, 'Date Range | both null | start');
+  equal(range.end.is(new Date(), 10), true, 'Date Range | both null | end');
+
+
+  // Union of overlapping ranges
+
+  range1 = Date.range(Date.create('2001'), Date.create('2003'));
+  range2 = Date.range(Date.create('2002'), Date.create('2004'));
+
+  range = range1.union(range2);
+
+  dateRangeEqual(range, Date.range(Date.create('2001'), Date.create('2004')), 'Date Range#union | simple merge');
+  dateRangeEqual(range1, Date.range(Date.create('2001'), Date.create('2003')), 'Date Range#union | range1 has not changed');
+  dateRangeEqual(range2, Date.range(Date.create('2002'), Date.create('2004')), 'Date Range#union | range2 has not changed');
+
+
+
+  // Union of non-overlapping ranges
+
+  range1 = Date.range(Date.create('2001'), Date.create('2003'));
+  range2 = Date.range(Date.create('2005'), Date.create('2008'));
+
+  range = range1.union(range2);
+
+  dateRangeEqual(range, Date.range(Date.create('2001'), Date.create('2008')), 'Date Range#union | non-overlapping includes middle');
+  dateRangeEqual(range1, Date.range(Date.create('2001'), Date.create('2003')), 'Date Range#union | range1 has not changed');
+  dateRangeEqual(range2, Date.range(Date.create('2005'), Date.create('2008')), 'Date Range#union | range2 has not changed');
+
+
+  // Union of reversed overlapping ranges
+
+  range1 = Date.range(Date.create('2002'), Date.create('2004'));
+  range2 = Date.range(Date.create('2001'), Date.create('2003'));
+
+  range = range1.union(range2);
+
+  dateRangeEqual(range, Date.range(Date.create('2001'), Date.create('2004')), 'Date Range#union | reversed | simple merge');
+
+
+  // Union of reversed non-overlapping ranges
+
+  range1 = Date.range(Date.create('2005'), Date.create('2008'));
+  range2 = Date.range(Date.create('2001'), Date.create('2003'));
+
+  range = range1.union(range2);
+
+  dateRangeEqual(range, Date.range(Date.create('2001'), Date.create('2008')), 'Date Range#union | reversed | includes middle');
+
+
+
+
+  // Intersect of overlapping ranges
+
+  range1 = Date.range(Date.create('2001'), Date.create('2003'));
+  range2 = Date.range(Date.create('2002'), Date.create('2004'));
+
+  range = range1.intersect(range2);
+
+  dateRangeEqual(range, Date.range(Date.create('2002'), Date.create('2003')), 'Date Range#intersect | simple merge');
+  dateRangeEqual(range1, Date.range(Date.create('2001'), Date.create('2003')), 'Date Range#intersect | range1 has not changed');
+  dateRangeEqual(range2, Date.range(Date.create('2002'), Date.create('2004')), 'Date Range#intersect | range2 has not changed');
+
+  // Intersect of non-overlapping ranges
+
+  range1 = Date.range(Date.create('2001'), Date.create('2003'));
+  range2 = Date.range(Date.create('2005'), Date.create('2008'));
+
+  range = range1.intersect(range2);
+
+  equal(range.isValid(), false, 'Date Range#intersect | non-overlapping ranges are invalid');
+
+
+  // Intersect of reversed overlapping ranges
+
+  range1 = Date.range(Date.create('2002'), Date.create('2004'));
+  range2 = Date.range(Date.create('2001'), Date.create('2003'));
+
+  range = range1.intersect(range2);
+
+  dateRangeEqual(range, Date.range(Date.create('2002'), Date.create('2003')), 'Date Range#intersect | simple merge');
+
+  // Intersect of reversed non-overlapping ranges
+
+  range1 = Date.range(Date.create('2005'), Date.create('2008'));
+  range2 = Date.range(Date.create('2001'), Date.create('2003'));
+
+  range = range1.intersect(range2);
+
+  equal(range.isValid(), false, 'Date Range#intersect | non-overlapping ranges are invalid');
+
+
+  // Date ranges should be able to be created from a string
+
+  range = Date.range('2001', '2003');
+
+  dateEqual(range.start, Date.create('2001'), 'Date.range | strings | start is equal');
+  dateEqual(range.end,   Date.create('2003'), 'Date.range | strings | end is equal');
+
+
+  // Modifying the start date of a range shouldn't affect the range.
+
+  var d = new Date();
+
+  range = Date.range(d);
+
+  d.setTime(410194800000);
+
+  equal(range.start.getTime() === 410194800000, false, 'Date.range | start time of the range should not be affected');
+
+
+
+  range = Date.range('2 hours ago', 'now');
+  equal(range.isValid(), true, 'Date.range | understands relative strings');
+
+  // Range cloning (Issue #230)
+
+  range1 = Date.range(Date.create('2002'), Date.create('2004'));
+  range2 = range1.clone();
+
+  equal(range1, range2, 'Date Range#clone | clone object is equal by value');
+  equal(range1 === range2, false, 'Date Range#clone | clone is not strictly equal');
+
+
+
+  // Range clamping
+
+  range = Date.range(new Date(2010, 0), new Date(2011, 0));
+
+  dateEqual(range.clamp(new Date(2008, 0)), new Date(2010, 0), 'Date Range#clamp | low');
+  dateEqual(range.clamp(new Date(2012, 0)), new Date(2011, 0), 'Date Range#clamp | high');
+  dateEqual(range.clamp(new Date(2010, 6)), new Date(2010, 6), 'Date Range#clamp | mid');
+  dateEqual(range.clamp(new Date(2010, 0)), new Date(2010, 0), 'Date Range#clamp | low equal');
+  dateEqual(range.clamp(new Date(2011, 0)), new Date(2011, 0), 'Date Range#clamp | high equal');
+
+  dateEqual(range.clamp(2), new Date(2010, 0), 'Date Range#clamp | low number');
+  dateEqual(range.clamp(new Date(2013, 5).getTime()), new Date(2011, 0), 'Date Range#clamp | high number');
+  equal(range.clamp(new Date(2010, 5).getTime()), new Date(2010, 5).getTime(), 'Date Range#clamp | mid number');
+
+
+
+  // Deep range cloning
+
+  var d1 = new Date(2010, 0);
+  var d2 = new Date(2010, 2);
+
+  range1 = Date.range(d1, d2);
+
+  range2 = range1.clone();
+
+  range2.start.setFullYear(1999);
+  range2.end.setFullYear(2002);
+
+  equal(range1.start.getFullYear(), 2010, 'Date Range | members should be cloned when range is cloned | start');
+  equal(range1.end.getFullYear(), 2010, 'Date Range | members should be cloned when range is cloned | start');
+
+
+  // every()
+
+  var t = 1275318000000;
+  var d1 = new Date(t);
+  var d2 = new Date(t + 2);
+
+  equal(Date.range(d1, d2).every(), [new Date(t), new Date(t + 1), new Date(t + 2)], 'Date Range | every should work without arguments');
+
+});
level2/node_modules/sugar/test/environments/sugar/date_ru.js
@@ -0,0 +1,224 @@
+test('Dates | Russian', function () {
+
+  var now = new Date();
+  var then = new Date(2011, 7, 25, 15, 45, 50);
+  Date.setLocale('ru');
+
+  dateEqual(Date.create('15 ะผะฐั 2011'), new Date(2011, 4, 15), 'Date#create | basic Russian date');
+  dateEqual(Date.create('2 ะผะฐั 1989 ะณะพะดะฐ'), new Date(1989, 4, 2), 'Date#create | Russian | format with year');
+  dateEqual(Date.create('5 ัะฝะฒะฐั€ั 2012'), new Date(2012, 0, 5), 'Date#create | Russian | 2012-01-05');
+  dateEqual(Date.create('ะœะฐะน 2011'), new Date(2011, 4), 'Date#create | Russian | year and month');
+  dateEqual(Date.create('15 ะผะฐั'), new Date(now.getFullYear(), 4, 15), 'Date#create | Russian | month and date');
+  dateEqual(Date.create('2011'), new Date(2011, 0), 'Date#create | Russian | year');
+  dateEqual(Date.create('ะœะฐะน'), new Date(now.getFullYear(), 4), 'Date#create | Russian | month');
+  dateEqual(Date.create('ะฟะพะฝะตะดะตะปัŒะฝะธะบ'), getDateWithWeekdayAndOffset(1), 'Date#create | Russian | Monday');
+
+  dateEqual(Date.create('15 ะผะฐั 2011 3:45'), new Date(2011, 4, 15, 3, 45), 'Date#create | basic Russian date 3:45');
+  dateEqual(Date.create('15 ะผะฐั 2011 3:45 ะฒะตั‡ะตั€ะฐ'), new Date(2011, 4, 15, 15, 45), 'Date#create | basic Russian date 3:45pm');
+
+  dateEqual(Date.create('ะพะดะฝัƒ ะผะธะปะปะธัะตะบัƒะฝะดัƒ ะฝะฐะทะฐะด'), getRelativeDate(null, null, null, null, null, null,-1), 'Date#create | Russian | one millisecond ago');
+  dateEqual(Date.create('ะพะดะฝัƒ ัะตะบัƒะฝะดัƒ ะฝะฐะทะฐะด'), getRelativeDate(null, null, null, null, null, -1), 'Date#create | Russian | one second ago');
+  dateEqual(Date.create('ะพะดะฝัƒ ะผะธะฝัƒั‚ัƒ ะฝะฐะทะฐะด'), getRelativeDate(null, null, null, null, -1), 'Date#create | Russian | one minute ago');
+  dateEqual(Date.create('ะพะดะธะฝ ั‡ะฐั ะฝะฐะทะฐะด'), getRelativeDate(null, null, null, -1), 'Date#create | Russian | one hour ago');
+  dateEqual(Date.create('ะพะดะธะฝ ะดะตะฝัŒ ะฝะฐะทะฐะด'), getRelativeDate(null, null, -1), 'Date#create | Russian | one day ago');
+  dateEqual(Date.create('ะพะดะฝัƒ ะฝะตะดะตะปัŽ ะฝะฐะทะฐะด'), getRelativeDate(null, null, -7), 'Date#create | Russian | one week ago');
+  dateEqual(Date.create('ะพะดะธะฝ ะผะตััั† ะฝะฐะทะฐะด'), getRelativeDate(null, -1), 'Date#create | Russian | one month ago');
+  dateEqual(Date.create('ะพะดะธะฝ ะณะพะด ะฝะฐะทะฐะด'), getRelativeDate(-1), 'Date#create | Russian | one year ago');
+
+  dateEqual(Date.create('ะดะฒะต ะผะธะปะปะธัะตะบัƒะฝะดั‹ ะฝะฐะทะฐะด'), getRelativeDate(null, null, null, null, null, null,-2), 'Date#create | Russian | two milliseconds ago');
+  dateEqual(Date.create('ะดะฒะต ัะตะบัƒะฝะดั‹ ะฝะฐะทะฐะด'), getRelativeDate(null, null, null, null, null, -2), 'Date#create | Russian | two seconds ago');
+  dateEqual(Date.create('ะดะฒะต ะผะธะฝัƒั‚ั‹ ะฝะฐะทะฐะด'), getRelativeDate(null, null, null, null, -2), 'Date#create | Russian | two minutes ago');
+  dateEqual(Date.create('ะดะฒะฐ ั‡ะฐัะฐ ะฝะฐะทะฐะด'), getRelativeDate(null, null, null, -2), 'Date#create | Russian | two hours ago');
+  dateEqual(Date.create('ะ”ะฒะฐ ะดะฝั ะฝะฐะทะฐะด'), getRelativeDate(null, null, -2), 'Date#create | Russian | two days ago');
+  dateEqual(Date.create('ะดะฒะต ะฝะตะดะตะปะธ ะฝะฐะทะฐะด'), getRelativeDate(null, null, -14), 'Date#create | Russian | two weeks ago');
+  dateEqual(Date.create('ะดะฒะฐ ะผะตััั†ะฐ ะฝะฐะทะฐะด'), getRelativeDate(null, -2), 'Date#create | Russian | two months ago');
+  dateEqual(Date.create('ะดะฒะฐ ะณะพะดะฐ ะฝะฐะทะฐะด'), getRelativeDate(-2), 'Date#create | Russian | two years ago');
+
+  dateEqual(Date.create('ะฒะพัะตะผัŒ ะผะธะปะปะธัะตะบัƒะฝะด ะฝะฐะทะฐะด'), getRelativeDate(null, null, null, null, null, null,-8), 'Date#create | Russian | eight milliseconds ago');
+  dateEqual(Date.create('ะฒะพัะตะผัŒ ัะตะบัƒะฝะด ะฝะฐะทะฐะด'), getRelativeDate(null, null, null, null, null, -8), 'Date#create | Russian | eight seconds ago');
+  dateEqual(Date.create('ะฒะพัะตะผัŒ ะผะธะฝัƒั‚ ะฝะฐะทะฐะด'), getRelativeDate(null, null, null, null, -8), 'Date#create | Russian | eight minutes ago');
+  dateEqual(Date.create('ะฒะพัะตะผัŒ ั‡ะฐัะพะฒ ะฝะฐะทะฐะด'), getRelativeDate(null, null, null, -8), 'Date#create | Russian | eight hours ago');
+  dateEqual(Date.create('ะฒะพัะตะผัŒ ะดะฝะตะน ะฝะฐะทะฐะด'), getRelativeDate(null, null, -8), 'Date#create | Russian | eight days ago');
+  dateEqual(Date.create('ะฒะพัะตะผัŒ ะฝะตะดะตะปัŒ ะฝะฐะทะฐะด'), getRelativeDate(null, null, -56), 'Date#create | Russian | eight weeks ago');
+  dateEqual(Date.create('ะฒะพัะตะผัŒ ะผะตััั†ะตะฒ ะฝะฐะทะฐะด'), getRelativeDate(null, -8), 'Date#create | Russian | eight months ago');
+  dateEqual(Date.create('ะฒะพัะตะผัŒ ะปะตั‚ ะฝะฐะทะฐะด'), getRelativeDate(-8), 'Date#create | Russian | eight years ago');
+
+  dateEqual(Date.create('ั‡ะตั€ะตะท 5 ะผะธะปะปะธัะตะบัƒะฝะด'), getRelativeDate(null, null, null, null, null, null,5), 'Date#create | Russian | five milliseconds from now');
+  dateEqual(Date.create('ั‡ะตั€ะตะท 5 ัะตะบัƒะฝะด'), getRelativeDate(null, null, null, null, null, 5), 'Date#create | Russian | five second from now');
+  dateEqual(Date.create('ั‡ะตั€ะตะท 5 ะผะธะฝัƒั‚'), getRelativeDate(null, null, null, null, 5), 'Date#create | Russian | five minute from now');
+  dateEqual(Date.create('ั‡ะตั€ะตะท 5 ั‡ะฐัะพะฒ'), getRelativeDate(null, null, null, 5), 'Date#create | Russian | five hour from now');
+  dateEqual(Date.create('ั‡ะตั€ะตะท 5 ะดะฝะตะน'), getRelativeDate(null, null, 5), 'Date#create | Russian | five days from now');
+  dateEqual(Date.create('ั‡ะตั€ะตะท 5 ะฝะตะดะตะปัŒ'), getRelativeDate(null, null, 35), 'Date#create | Russian | five weeks from now');
+  dateEqual(Date.create('ั‡ะตั€ะตะท 5 ะผะตััั†ะตะฒ'), getRelativeDate(null, 5), 'Date#create | Russian | five months from now');
+  dateEqual(Date.create('ั‡ะตั€ะตะท 5 ะปะตั‚'), getRelativeDate(5), 'Date#create | Russian | five years from now');
+
+  dateEqual(Date.create('ะฟะพะทะฐะฒั‡ะตั€ะฐ'), getRelativeDate(null, null, -2).reset(), 'Date#create | Russian | day before yesterday');
+  dateEqual(Date.create('ะ’ั‡ะตั€ะฐ'), getRelativeDate(null, null, -1).reset(), 'Date#create | Russian | yesterday');
+  dateEqual(Date.create('ะกะตะณะพะดะฝั'), getRelativeDate(null, null, 0).reset(), 'Date#create | Russian | today');
+  dateEqual(Date.create('ะ—ะฐะฒั‚ั€ะฐ'), getRelativeDate(null, null, 1).reset(), 'Date#create | Russian | tomorrow');
+  dateEqual(Date.create('ะฟะพัะปะตะทะฐะฒั‚ั€ะฐ'), getRelativeDate(null, null, 2).reset(), 'Date#create | Russian | day after tomorrow');
+
+  dateEqual(Date.create('ะฝะฐ ะฟั€ะพัˆะปะพะน ะฝะตะดะตะปะต'), getRelativeDate(null, null, -7), 'Date#create | Russian | Last week');
+  dateEqual(Date.create('ะฝะฐ ัะปะตะดัƒัŽั‰ะตะน ะฝะตะดะตะปะต'), getRelativeDate(null, null, 7), 'Date#create | Russian | Next week');
+
+  dateEqual(Date.create('ะฒ ะฟั€ะพัˆะปะพะผ ะผะตััั†ะต'), getRelativeDate(null, -1), 'Date#create | Russian | last month');
+  dateEqual(Date.create('ะฒ ัะปะตะดัƒัŽั‰ะตะผ ะผะตััั†ะต'), getRelativeDate(null, 1), 'Date#create | Russian | Next month');
+
+  dateEqual(Date.create('ะฒ ะฟั€ะพัˆะปะพะผ ะณะพะดัƒ'), getRelativeDate(-1), 'Date#create | Russian | Last year');
+  dateEqual(Date.create('ะฒ ัะปะตะดัƒัŽั‰ะตะผ ะณะพะดัƒ'), getRelativeDate(1), 'Date#create | Russian | Next year');
+
+
+  dateEqual(Date.create('ัะปะตะดัƒัŽั‰ะธะน ะฟะพะฝะตะดะตะปัŒะฝะธะบ'), getDateWithWeekdayAndOffset(1,  7), 'Date#create | Russian | next monday');
+  dateEqual(Date.create('ะฒ ะฟั€ะพัˆะปั‹ะน ะฒั‚ะพั€ะฝะธะบ'), getDateWithWeekdayAndOffset(2, -7), 'Date#create | Russian | last tuesday');
+
+  dateEqual(Date.create('ัะปะตะดัƒัŽั‰ะธะน ะฟะพะฝะตะดะตะปัŒะฝะธะบ 3:45 ะฒะตั‡ะตั€ะฐ'), getDateWithWeekdayAndOffset(1,7).set({ hour: 15, minute: 45 }, true), 'Date#create | Russian | next monday');
+
+  equal(then.format(), '25 ะฐะฒะณัƒัั‚ะฐ 2011 ะณะพะดะฐ 15:45', 'Date#create | Russian | standard format');
+  equal(then.format(), '25 ะฐะฒะณัƒัั‚ะฐ 2011 ะณะพะดะฐ 15:45', 'Date#create | Russian | standard format');
+  equal(then.format('{dd} {month} {yyyy}'), '25 ะฐะฒะณัƒัั‚ะฐ 2011', 'Date#create | Russian | format');
+  equal(then.format('{dd} {month2} {yyyy}'), '25 ะฐะฒะณัƒัั‚ 2011', 'Date#create | Russian | format allows alternates');
+
+
+  // Format shortcuts
+
+  equal(then.format('full'), 'ะงะตั‚ะฒะตั€ะณ 25 ะฐะฒะณัƒัั‚ะฐ 2011 ะณะพะดะฐ 15:45:50', 'Date#create | Russian | full format');
+  equal(then.full(), 'ะงะตั‚ะฒะตั€ะณ 25 ะฐะฒะณัƒัั‚ะฐ 2011 ะณะพะดะฐ 15:45:50', 'Date#create | Russian | full format');
+  equal(then.format('long'), '25 ะฐะฒะณัƒัั‚ะฐ 2011 ะณะพะดะฐ 15:45', 'Date#create | Russian | long format');
+  equal(then.long(), '25 ะฐะฒะณัƒัั‚ะฐ 2011 ะณะพะดะฐ 15:45', 'Date#create | Russian | long shortcut');
+  equal(then.format('short'), '25 ะฐะฒะณัƒัั‚ะฐ 2011 ะณะพะดะฐ', 'Date#create | Russian | short format');
+  equal(then.short(), '25 ะฐะฒะณัƒัั‚ะฐ 2011 ะณะพะดะฐ', 'Date#create | Russian | short shortcut');
+
+
+  equal(Date.create('1 second ago', 'en').relative(), '1 ัะตะบัƒะฝะดัƒ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('1 minute ago', 'en').relative(), '1 ะผะธะฝัƒั‚ัƒ ะฝะฐะทะฐะด',  'Date#relative | Russian | relative format past');
+  equal(Date.create('1 hour ago', 'en').relative(),   '1 ั‡ะฐั ะฝะฐะทะฐะด',     'Date#relative | Russian | relative format past');
+  equal(Date.create('1 day ago', 'en').relative(),    '1 ะดะตะฝัŒ ะฝะฐะทะฐะด',    'Date#relative | Russian | relative format past');
+  equal(Date.create('1 week ago', 'en').relative(),   '1 ะฝะตะดะตะปัŽ ะฝะฐะทะฐะด',  'Date#relative | Russian | relative format past');
+  equal(Date.create('1 month ago', 'en').relative(),  '1 ะผะตััั† ะฝะฐะทะฐะด',   'Date#relative | Russian | relative format past');
+  equal(Date.create('1 year ago', 'en').relative(),   '1 ะณะพะด ะฝะฐะทะฐะด',     'Date#relative | Russian | relative format past');
+
+  equal(Date.create('2 seconds ago', 'en').relative(), '2 ัะตะบัƒะฝะดั‹ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('2 minutes ago', 'en').relative(), '2 ะผะธะฝัƒั‚ั‹ ะฝะฐะทะฐะด',  'Date#relative | Russian | relative format past');
+  equal(Date.create('2 hours ago', 'en').relative(),   '2 ั‡ะฐัะฐ ะฝะฐะทะฐะด',     'Date#relative | Russian | relative format past');
+  equal(Date.create('2 days ago', 'en').relative(),    '2 ะดะฝั ะฝะฐะทะฐะด',    'Date#relative | Russian | relative format past');
+  equal(Date.create('2 weeks ago', 'en').relative(),   '2 ะฝะตะดะตะปะธ ะฝะฐะทะฐะด',  'Date#relative | Russian | relative format past');
+  equal(Date.create('2 months ago', 'en').relative(),  '2 ะผะตััั†ะฐ ะฝะฐะทะฐะด',   'Date#relative | Russian | relative format past');
+  equal(Date.create('2 years ago', 'en').relative(),   '2 ะณะพะดะฐ ะฝะฐะทะฐะด',     'Date#relative | Russian | relative format past');
+
+  equal(Date.create('3 seconds ago', 'en').relative(), '3 ัะตะบัƒะฝะดั‹ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('3 minutes ago', 'en').relative(), '3 ะผะธะฝัƒั‚ั‹ ะฝะฐะทะฐะด',  'Date#relative | Russian | relative format past');
+  equal(Date.create('3 hours ago', 'en').relative(),   '3 ั‡ะฐัะฐ ะฝะฐะทะฐะด',     'Date#relative | Russian | relative format past');
+  equal(Date.create('3 days ago', 'en').relative(),    '3 ะดะฝั ะฝะฐะทะฐะด',    'Date#relative | Russian | relative format past');
+  equal(Date.create('3 weeks ago', 'en').relative(),   '3 ะฝะตะดะตะปะธ ะฝะฐะทะฐะด',  'Date#relative | Russian | relative format past');
+  equal(Date.create('3 months ago', 'en').relative(),  '3 ะผะตััั†ะฐ ะฝะฐะทะฐะด',   'Date#relative | Russian | relative format past');
+  equal(Date.create('3 years ago', 'en').relative(),   '3 ะณะพะดะฐ ะฝะฐะทะฐะด',     'Date#relative | Russian | relative format past');
+
+  equal(Date.create('4 seconds ago', 'en').relative(), '4 ัะตะบัƒะฝะดั‹ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('4 minutes ago', 'en').relative(), '4 ะผะธะฝัƒั‚ั‹ ะฝะฐะทะฐะด',  'Date#relative | Russian | relative format past');
+  equal(Date.create('4 hours ago', 'en').relative(),   '4 ั‡ะฐัะฐ ะฝะฐะทะฐะด',     'Date#relative | Russian | relative format past');
+  equal(Date.create('4 days ago', 'en').relative(),    '4 ะดะฝั ะฝะฐะทะฐะด',    'Date#relative | Russian | relative format past');
+  equal(Date.create('4 weeks ago', 'en').relative(),   '4 ะฝะตะดะตะปะธ ะฝะฐะทะฐะด',  'Date#relative | Russian | relative format past');
+  equal(Date.create('4 months ago', 'en').relative(),  '4 ะผะตััั†ะฐ ะฝะฐะทะฐะด',   'Date#relative | Russian | relative format past');
+  equal(Date.create('4 years ago', 'en').relative(),   '4 ะณะพะดะฐ ะฝะฐะทะฐะด',     'Date#relative | Russian | relative format past');
+
+  equal(Date.create('5 seconds ago', 'en').relative(), '5 ัะตะบัƒะฝะด ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('5 minutes ago', 'en').relative(), '5 ะผะธะฝัƒั‚ ะฝะฐะทะฐะด',  'Date#relative | Russian | relative format past');
+  equal(Date.create('5 hours ago', 'en').relative(),   '5 ั‡ะฐัะพะฒ ะฝะฐะทะฐะด',     'Date#relative | Russian | relative format past');
+  equal(Date.create('5 days ago', 'en').relative(),    '5 ะดะฝะตะน ะฝะฐะทะฐะด',    'Date#relative | Russian | relative format past');
+  equal(Date.create('5 weeks ago', 'en').relative(),   '1 ะผะตััั† ะฝะฐะทะฐะด',  'Date#relative | Russian | relative format past');
+  equal(Date.create('5 months ago', 'en').relative(),  '5 ะผะตััั†ะตะฒ ะฝะฐะทะฐะด',   'Date#relative | Russian | relative format past');
+  equal(Date.create('5 years ago', 'en').relative(),   '5 ะปะตั‚ ะฝะฐะทะฐะด',     'Date#relative | Russian | relative format past');
+
+  equal(Date.create('7 seconds ago', 'en').relative(), '7 ัะตะบัƒะฝะด ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('7 minutes ago', 'en').relative(), '7 ะผะธะฝัƒั‚ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('7 hours ago', 'en').relative(),   '7 ั‡ะฐัะพะฒ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('7 days ago', 'en').relative(),    '1 ะฝะตะดะตะปัŽ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('7 weeks ago', 'en').relative(),   '1 ะผะตััั† ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('7 months ago', 'en').relative(),  '7 ะผะตััั†ะตะฒ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('7 years ago', 'en').relative(),   '7 ะปะตั‚ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+
+  equal(Date.create('21 seconds ago', 'en').relative(), '21 ัะตะบัƒะฝะดัƒ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('21 minutes ago', 'en').relative(), '21 ะผะธะฝัƒั‚ัƒ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('21 hours ago', 'en').relative(),   '21 ั‡ะฐั ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('21 days ago', 'en').relative(),    '3 ะฝะตะดะตะปะธ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('21 years ago', 'en').relative(),   '21 ะณะพะด ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+
+  equal(Date.create('22 seconds ago', 'en').relative(), '22 ัะตะบัƒะฝะดั‹ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('22 minutes ago', 'en').relative(), '22 ะผะธะฝัƒั‚ั‹ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('22 hours ago', 'en').relative(),   '22 ั‡ะฐัะฐ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('22 days ago', 'en').relative(),    '3 ะฝะตะดะตะปะธ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('22 years ago', 'en').relative(),   '22 ะณะพะดะฐ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+
+  equal(Date.create('25 seconds ago', 'en').relative(), '25 ัะตะบัƒะฝะด ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('25 minutes ago', 'en').relative(), '25 ะผะธะฝัƒั‚ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('25 hours ago', 'en').relative(),   '1 ะดะตะฝัŒ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('25 days ago', 'en').relative(),    '3 ะฝะตะดะตะปะธ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('25 years ago', 'en').relative(),   '25 ะปะตั‚ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+
+  equal(Date.create('1 second ago', 'en').relative(), '1 ัะตะบัƒะฝะดัƒ ะฝะฐะทะฐะด', 'Date#relative | Russian | relative format past');
+  equal(Date.create('1 minute ago', 'en').relative(), '1 ะผะธะฝัƒั‚ัƒ ะฝะฐะทะฐะด',  'Date#relative | Russian | relative format past');
+  equal(Date.create('1 hour ago', 'en').relative(),   '1 ั‡ะฐั ะฝะฐะทะฐะด',     'Date#relative | Russian | relative format past');
+  equal(Date.create('1 day ago', 'en').relative(),    '1 ะดะตะฝัŒ ะฝะฐะทะฐะด',    'Date#relative | Russian | relative format past');
+  equal(Date.create('1 week ago', 'en').relative(),   '1 ะฝะตะดะตะปัŽ ะฝะฐะทะฐะด',  'Date#relative | Russian | relative format past');
+  equal(Date.create('1 month ago', 'en').relative(),  '1 ะผะตััั† ะฝะฐะทะฐะด',   'Date#relative | Russian | relative format past');
+  equal(Date.create('1 year ago', 'en').relative(),   '1 ะณะพะด ะฝะฐะทะฐะด',     'Date#relative | Russian | relative format past');
+
+
+  equal(Date.create('1 second from now', 'en').relative(), 'ั‡ะตั€ะตะท 1 ัะตะบัƒะฝะดัƒ', 'Date#relative | Russian | relative format future');
+  equal(Date.create('1 minute from now', 'en').relative(), 'ั‡ะตั€ะตะท 1 ะผะธะฝัƒั‚ัƒ',  'Date#relative | Russian | relative format future');
+  equal(Date.create('1 hour from now', 'en').relative(),   'ั‡ะตั€ะตะท 1 ั‡ะฐั',     'Date#relative | Russian | relative format future');
+  equal(Date.create('1 day from now', 'en').relative(),    'ั‡ะตั€ะตะท 1 ะดะตะฝัŒ',    'Date#relative | Russian | relative format future');
+  equal(Date.create('1 week from now', 'en').relative(),   'ั‡ะตั€ะตะท 1 ะฝะตะดะตะปัŽ',  'Date#relative | Russian | relative format future');
+  equal(Date.create('1 month from now', 'en').relative(),  'ั‡ะตั€ะตะท 1 ะผะตััั†',   'Date#relative | Russian | relative format future');
+  equal(Date.create('1 year from now', 'en').relative(),   'ั‡ะตั€ะตะท 1 ะณะพะด',     'Date#relative | Russian | relative format future');
+
+  equal(Date.create('2 seconds from now', 'en').relative(), 'ั‡ะตั€ะตะท 2 ัะตะบัƒะฝะดั‹', 'Date#relative | Russian | relative format future');
+  equal(Date.create('2 minutes from now', 'en').relative(), 'ั‡ะตั€ะตะท 2 ะผะธะฝัƒั‚ั‹',  'Date#relative | Russian | relative format future');
+  equal(Date.create('2 hours from now', 'en').relative(),   'ั‡ะตั€ะตะท 2 ั‡ะฐัะฐ',     'Date#relative | Russian | relative format future');
+  equal(Date.create('2 days from now', 'en').relative(),    'ั‡ะตั€ะตะท 2 ะดะฝั',    'Date#relative | Russian | relative format future');
+  equal(Date.create('2 weeks from now', 'en').relative(),   'ั‡ะตั€ะตะท 2 ะฝะตะดะตะปะธ',  'Date#relative | Russian | relative format future');
+  equal(Date.create('2 months from now', 'en').relative(),  'ั‡ะตั€ะตะท 2 ะผะตััั†ะฐ',   'Date#relative | Russian | relative format future');
+  equal(Date.create('2 years from now', 'en').relative(),   'ั‡ะตั€ะตะท 2 ะณะพะดะฐ',     'Date#relative | Russian | relative format future');
+
+  equal(Date.create('5 seconds from now', 'en').relative(), 'ั‡ะตั€ะตะท 5 ัะตะบัƒะฝะด', 'Date#relative | Russian | relative format future');
+  equal(Date.create('5 minutes from now', 'en').relative(), 'ั‡ะตั€ะตะท 5 ะผะธะฝัƒั‚',  'Date#relative | Russian | relative format future');
+  equal(Date.create('5 hours from now', 'en').relative(),   'ั‡ะตั€ะตะท 5 ั‡ะฐัะพะฒ',     'Date#relative | Russian | relative format future');
+  equal(Date.create('5 days from now', 'en').relative(),    'ั‡ะตั€ะตะท 5 ะดะฝะตะน',    'Date#relative | Russian | relative format future');
+  equal(Date.create('5 weeks from now', 'en').relative(),   'ั‡ะตั€ะตะท 1 ะผะตััั†',  'Date#relative | Russian | relative format future');
+  equal(Date.create('5 months from now', 'en').relative(),  'ั‡ะตั€ะตะท 5 ะผะตััั†ะตะฒ',   'Date#relative | Russian | relative format future');
+  equal(Date.create('5 years from now', 'en').relative(),   'ั‡ะตั€ะตะท 5 ะปะตั‚',     'Date#relative | Russian | relative format future');
+
+  dateEqual(Date.create('ะ—ะฐะฒั‚ั€ะฐ ะฒ 3:30 ัƒั‚ั€ะฐ'), getRelativeDate(null, null, 1).set({hours:3,minutes:30}, true), 'Date#create | Russian | tomorrow at 3:30');
+
+  equal((5).hours().duration('ru'), '5 ั‡ะฐัะพะฒ', 'Date#create | Russian | simple duration');
+
+
+  equal(Date.create('11 hours ago', 'en').relative(), '11 ั‡ะฐัะพะฒ ะฝะฐะทะฐะด', 'Date#relative | Russian | 11 hours ago');
+  equal(Date.create('12 hours ago', 'en').relative(), '12 ั‡ะฐัะพะฒ ะฝะฐะทะฐะด', 'Date#relative | Russian | 12 hours ago');
+  equal(Date.create('13 hours ago', 'en').relative(), '13 ั‡ะฐัะพะฒ ะฝะฐะทะฐะด', 'Date#relative | Russian | 13 hours ago');
+  equal(Date.create('14 hours ago', 'en').relative(), '14 ั‡ะฐัะพะฒ ะฝะฐะทะฐะด', 'Date#relative | Russian | 14 hours ago');
+  equal(Date.create('15 hours ago', 'en').relative(), '15 ั‡ะฐัะพะฒ ะฝะฐะทะฐะด', 'Date#relative | Russian | 15 hours ago');
+  equal(Date.create('16 hours ago', 'en').relative(), '16 ั‡ะฐัะพะฒ ะฝะฐะทะฐะด', 'Date#relative | Russian | 16 hours ago');
+  equal(Date.create('17 hours ago', 'en').relative(), '17 ั‡ะฐัะพะฒ ะฝะฐะทะฐะด', 'Date#relative | Russian | 17 hours ago');
+  equal(Date.create('18 hours ago', 'en').relative(), '18 ั‡ะฐัะพะฒ ะฝะฐะทะฐะด', 'Date#relative | Russian | 18 hours ago');
+  equal(Date.create('19 hours ago', 'en').relative(), '19 ั‡ะฐัะพะฒ ะฝะฐะทะฐะด', 'Date#relative | Russian | 19 hours ago');
+  equal(Date.create('20 hours ago', 'en').relative(), '20 ั‡ะฐัะพะฒ ะฝะฐะทะฐะด', 'Date#relative | Russian | 20 hours ago');
+
+  equal(Date.create('21 hours ago', 'en').relative(), '21 ั‡ะฐั ะฝะฐะทะฐะด', 'Date#relative | Russian | 21 hours ago');
+  equal(Date.create('22 hours ago', 'en').relative(), '22 ั‡ะฐัะฐ ะฝะฐะทะฐะด', 'Date#relative | Russian | 22 hours ago');
+
+  equal(Date.create('11 hours from now', 'en').relative(), 'ั‡ะตั€ะตะท 11 ั‡ะฐัะพะฒ', 'Date#relative | Russian | 11 hours ago');
+  equal(Date.create('12 hours from now', 'en').relative(), 'ั‡ะตั€ะตะท 12 ั‡ะฐัะพะฒ', 'Date#relative | Russian | 12 hours ago');
+  equal(Date.create('13 hours from now', 'en').relative(), 'ั‡ะตั€ะตะท 13 ั‡ะฐัะพะฒ', 'Date#relative | Russian | 13 hours ago');
+  equal(Date.create('14 hours from now', 'en').relative(), 'ั‡ะตั€ะตะท 14 ั‡ะฐัะพะฒ', 'Date#relative | Russian | 14 hours ago');
+  equal(Date.create('15 hours from now', 'en').relative(), 'ั‡ะตั€ะตะท 15 ั‡ะฐัะพะฒ', 'Date#relative | Russian | 15 hours ago');
+  equal(Date.create('16 hours from now', 'en').relative(), 'ั‡ะตั€ะตะท 16 ั‡ะฐัะพะฒ', 'Date#relative | Russian | 16 hours ago');
+  equal(Date.create('17 hours from now', 'en').relative(), 'ั‡ะตั€ะตะท 17 ั‡ะฐัะพะฒ', 'Date#relative | Russian | 17 hours ago');
+  equal(Date.create('18 hours from now', 'en').relative(), 'ั‡ะตั€ะตะท 18 ั‡ะฐัะพะฒ', 'Date#relative | Russian | 18 hours ago');
+  equal(Date.create('19 hours from now', 'en').relative(), 'ั‡ะตั€ะตะท 19 ั‡ะฐัะพะฒ', 'Date#relative | Russian | 19 hours ago');
+  equal(Date.create('20 hours from now', 'en').relative(), 'ั‡ะตั€ะตะท 20 ั‡ะฐัะพะฒ', 'Date#relative | Russian | 20 hours ago');
+
+  equal(Date.create('21 hours from now', 'en').relative(), 'ั‡ะตั€ะตะท 21 ั‡ะฐั', 'Date#relative | Russian | 21 hours ago');
+  equal(Date.create('22 hours from now', 'en').relative(), 'ั‡ะตั€ะตะท 22 ั‡ะฐัะฐ', 'Date#relative | Russian | 22 hours ago');
+
+});
level2/node_modules/sugar/test/environments/sugar/date_sv.js
@@ -0,0 +1,138 @@
+test('Dates | Swedish', function () {
+
+  var now = new Date();
+  Date.setLocale('sv');
+
+
+  dateEqual(Date.create('den 15 maj 2011'), new Date(2011, 4, 15), 'Date#create | basic Swedish date');
+  dateEqual(Date.create('15 maj 2011'), new Date(2011, 4, 15), 'Date#create | basic Swedish date');
+  dateEqual(Date.create('tisdag 5 januari 2012'), new Date(2012, 0, 5), 'Date#create | Swedish | 2012-01-05');
+  dateEqual(Date.create('tisdag, 5 januari 2012'), new Date(2012, 0, 5), 'Date#create | Swedish | 2012-01-05');
+  dateEqual(Date.create('maj 2011'), new Date(2011, 4), 'Date#create | Swedish | year and month');
+  dateEqual(Date.create('15 maj'), new Date(now.getFullYear(), 4, 15), 'Date#create | Swedish | month and date');
+  dateEqual(Date.create('2011'), new Date(2011, 0), 'Date#create | Swedish | year');
+  dateEqual(Date.create('maj'), new Date(now.getFullYear(), 4), 'Date#create | Swedish | month');
+  dateEqual(Date.create('mรฅndag'), getDateWithWeekdayAndOffset(1), 'Date#create | Swedish | Monday');
+
+  dateEqual(Date.create('15 maj 2011 3:45'), new Date(2011, 4, 15, 3, 45), 'Date#create | basic Swedish date 3:45');
+  dateEqual(Date.create('15 maj 2011 3:45pm'), new Date(2011, 4, 15, 15, 45), 'Date#create | basic Swedish date 3:45pm');
+
+  dateEqual(Date.create('fรถr en millisekund sedan'), getRelativeDate(null, null, null, null, null, null,-1), 'Date#create | Swedish | one millisecond ago');
+  dateEqual(Date.create('fรถr en sekund sedan'), getRelativeDate(null, null, null, null, null, -1), 'Date#create | Swedish | one second ago');
+  dateEqual(Date.create('fรถr en minut sedan'), getRelativeDate(null, null, null, null, -1), 'Date#create | Swedish | one minute ago');
+  dateEqual(Date.create('fรถr en timme sedan'), getRelativeDate(null, null, null, -1), 'Date#create | Swedish | one hour ago');
+  dateEqual(Date.create('fรถr en dag sedan'), getRelativeDate(null, null, -1), 'Date#create | Swedish | one day ago');
+  dateEqual(Date.create('fรถr en vecka sedan'), getRelativeDate(null, null, -7), 'Date#create | Swedish | one week ago');
+  dateEqual(Date.create('fรถr en mรฅnad sedan'), getRelativeDate(null, -1), 'Date#create | Swedish | one month ago');
+  dateEqual(Date.create('fรถr ett รฅr sedan'), getRelativeDate(-1), 'Date#create | Swedish | one year ago');
+  dateEqual(Date.create('ett รฅr sen'), getRelativeDate(-1), 'Date#create | Swedish | one year ago');
+  
+  dateEqual(Date.create('ett ar sen'), getRelativeDate(-1), 'Date#create | Swedish | one year ago');
+
+  dateEqual(Date.create('om 5 millisekunder'), getRelativeDate(null, null, null, null, null, null,5), 'Date#create | Swedish | dans | five milliseconds from now');
+  dateEqual(Date.create('om 5 sekunder'), getRelativeDate(null, null, null, null, null, 5), 'Date#create | Swedish | dans | five second from now');
+  dateEqual(Date.create('om 5 minuter'), getRelativeDate(null, null, null, null, 5), 'Date#create | Swedish | dans | five minute from now');
+  dateEqual(Date.create('om 5 timmar'), getRelativeDate(null, null, null, 5), 'Date#create | Swedish | dans | five hour from now');
+  dateEqual(Date.create('om 5 dagar'), getRelativeDate(null, null, 5), 'Date#create | Swedish | dans | five day from now');
+  dateEqual(Date.create('om 5 veckor'), getRelativeDate(null, null, 35), 'Date#create | Swedish | dans | five weeks from now');
+  dateEqual(Date.create('om 5 mรฅnader'), getRelativeDate(null, 5), 'Date#create | Swedish | dans | five months from now');
+  dateEqual(Date.create('om 5 รฅr'), getRelativeDate(5), 'Date#create | Swedish | dans | five years from now');
+
+
+  dateEqual(Date.create('i fรถrrgรฅr'), getRelativeDate(null, null, -2).reset(), 'Date#create | Swedish | day before yesterday');
+  dateEqual(Date.create('fรถrrgรฅr'), getRelativeDate(null, null, -2).reset(), 'Date#create | Swedish | day before yesterday');
+  dateEqual(Date.create('i gรฅr'), getRelativeDate(null, null, -1).reset(), 'Date#create | Swedish | yesterday');
+  dateEqual(Date.create('igรฅr'), getRelativeDate(null, null, -1).reset(), 'Date#create | Swedish | yesterday');
+  dateEqual(Date.create('i dag'), getRelativeDate(null, null, 0).reset(), 'Date#create | Swedish | today');
+  dateEqual(Date.create('idag'), getRelativeDate(null, null, 0).reset(), 'Date#create | Swedish | today');
+  dateEqual(Date.create('imorgon'), getRelativeDate(null, null, 1).reset(), 'Date#create | Swedish | tomorrow');
+  dateEqual(Date.create('i morgon'), getRelativeDate(null, null, 1).reset(), 'Date#create | Swedish | tomorrow');
+  dateEqual(Date.create('i รถvermorgon'), getRelativeDate(null, null, 2).reset(), 'Date#create | Swedish | day after tomorrow');
+  dateEqual(Date.create('i รถver morgon'), getRelativeDate(null, null, 2).reset(), 'Date#create | Swedish | day after tomorrow');
+
+  dateEqual(Date.create('fรถrra veckan'), getRelativeDate(null, null, -7), 'Date#create | Swedish | Last week');
+  dateEqual(Date.create('i fรถrra veckan'), getRelativeDate(null, null, -7), 'Date#create | Swedish | Last week');
+  dateEqual(Date.create('nรคsta vecka'), getRelativeDate(null, null, 7), 'Date#create | Swedish | Next week');
+  dateEqual(Date.create('nasta vecka'), getRelativeDate(null, null, 7), 'Date#create | Swedish | Next week');
+
+  dateEqual(Date.create('fรถrra mรฅnaden'), getRelativeDate(null, -1), 'Date#create | Swedish | last month');
+  dateEqual(Date.create('nรคsta mรฅnad'), getRelativeDate(null, 1), 'Date#create | Swedish | Next month');
+
+  dateEqual(Date.create('fรถrra รฅret'), getRelativeDate(-1), 'Date#create | Swedish | Last year');
+  dateEqual(Date.create('nรคsta รฅr'), getRelativeDate(1), 'Date#create | Swedish | Next year');
+
+  dateEqual(Date.create('fรถrra mรฅndagen'), getDateWithWeekdayAndOffset(1,  -7), 'Date#create | Swedish | last monday');
+  dateEqual(Date.create('nรคsta mรฅndag'), getDateWithWeekdayAndOffset(1, 7), 'Date#create | Swedish | next monday');
+
+
+  // no accents
+  dateEqual(Date.create('mandag'), getDateWithWeekdayAndOffset(1), 'Date#create | Swedish | Monday');
+  dateEqual(Date.create('for en millisekund sedan'), getRelativeDate(null, null, null, null, null, null,-1), 'Date#create | Swedish | one millisecond ago');
+  dateEqual(Date.create('for en sekund sedan'), getRelativeDate(null, null, null, null, null, -1), 'Date#create | Swedish | one second ago');
+  dateEqual(Date.create('for en minut sedan'), getRelativeDate(null, null, null, null, -1), 'Date#create | Swedish | one minute ago');
+  dateEqual(Date.create('for en timme sedan'), getRelativeDate(null, null, null, -1), 'Date#create | Swedish | one hour ago');
+  dateEqual(Date.create('for en dag sedan'), getRelativeDate(null, null, -1), 'Date#create | Swedish | one day ago');
+  dateEqual(Date.create('for en vecka sedan'), getRelativeDate(null, null, -7), 'Date#create | Swedish | one week ago');
+  dateEqual(Date.create('for en manad sedan'), getRelativeDate(null, -1), 'Date#create | Swedish | one month ago');
+  dateEqual(Date.create('for ett ar sedan'), getRelativeDate(-1), 'Date#create | Swedish | one year ago');
+  dateEqual(Date.create('ett ar sen'), getRelativeDate(-1), 'Date#create | Swedish | one year ago');
+  
+  dateEqual(Date.create('om 5 manader'), getRelativeDate(null, 5), 'Date#create | Swedish | dans | five months from now');
+  dateEqual(Date.create('om 5 ar'), getRelativeDate(5), 'Date#create | Swedish | dans | five years from now');
+  
+  dateEqual(Date.create('i forrgar'), getRelativeDate(null, null, -2).reset(), 'Date#create | Swedish | day before yesterday');
+  dateEqual(Date.create('fรถrrgรฅr'), getRelativeDate(null, null, -2).reset(), 'Date#create | Swedish | day before yesterday');
+  dateEqual(Date.create('i gar'), getRelativeDate(null, null, -1).reset(), 'Date#create | Swedish | yesterday');
+  dateEqual(Date.create('igar'), getRelativeDate(null, null, -1).reset(), 'Date#create | Swedish | yesterday');
+  dateEqual(Date.create('i overmorgon'), getRelativeDate(null, null, 2).reset(), 'Date#create | Swedish | day after tomorrow');
+  dateEqual(Date.create('i over morgon'), getRelativeDate(null, null, 2).reset(), 'Date#create | Swedish | day after tomorrow');
+  
+  dateEqual(Date.create('forra veckan'), getRelativeDate(null, null, -7), 'Date#create | Swedish | Last week');
+  dateEqual(Date.create('i forra veckan'), getRelativeDate(null, null, -7), 'Date#create | Swedish | Last week');
+  dateEqual(Date.create('nasta vecka'), getRelativeDate(null, null, 7), 'Date#create | Swedish | Next week');
+  dateEqual(Date.create('forra manaden'), getRelativeDate(null, -1), 'Date#create | Swedish | last month');
+  dateEqual(Date.create('nasta manad'), getRelativeDate(null, 1), 'Date#create | Swedish | Next month');
+  dateEqual(Date.create('forra aret'), getRelativeDate(-1), 'Date#create | Swedish | Last year');
+  dateEqual(Date.create('nasta ar'), getRelativeDate(1), 'Date#create | Swedish | Next year');
+
+
+  equal(Date.create('2001-06-14 3:45pm').format(), 'den 14 juni 2001 15:45', 'Date#create | Swedish | format');
+  equal(Date.create('2011-08-25').format('{dd} {month} {yyyy}'), '25 augusti 2011', 'Date#create | Swedish | format');
+
+  equal(Date.create('1 second ago', 'en').relative(), '1 sekund sedan', 'Date#create | Swedish | relative format past');
+  equal(Date.create('1 minute ago', 'en').relative(), '1 minut sedan',  'Date#create | Swedish | relative format past');
+  equal(Date.create('1 hour ago', 'en').relative(),   '1 timme sedan',     'Date#create | Swedish | relative format past');
+  equal(Date.create('1 day ago', 'en').relative(),    '1 dag sedan',    'Date#create | Swedish | relative format past');
+  equal(Date.create('1 week ago', 'en').relative(),   '1 vecka sedan',  'Date#create | Swedish | relative format past');
+  equal(Date.create('1 month ago', 'en').relative(),  '1 mรฅnad sedan',   'Date#create | Swedish | relative format past');
+  equal(Date.create('1 year ago', 'en').relative(),   '1 รฅr sedan',     'Date#create | Swedish | relative format past');
+
+  equal(Date.create('5 seconds ago', 'en').relative(), '5 sekunder sedan', 'Date#create | Swedish | relative format past');
+  equal(Date.create('5 minutes ago', 'en').relative(), '5 minuter sedan',  'Date#create | Swedish | relative format past');
+  equal(Date.create('5 hours ago', 'en').relative(),   '5 timmar sedan',     'Date#create | Swedish | relative format past');
+  equal(Date.create('5 days ago', 'en').relative(),    '5 dagar sedan',    'Date#create | Swedish | relative format past');
+  equal(Date.create('3 weeks ago', 'en').relative(),   '3 veckor sedan',  'Date#create | Swedish | relative format past');
+  equal(Date.create('5 weeks ago', 'en').relative(),   '1 mรฅnad sedan',  'Date#create | Swedish | relative format past');
+  equal(Date.create('5 months ago', 'en').relative(),  '5 mรฅnader sedan',   'Date#create | Swedish | relative format past');
+  equal(Date.create('5 years ago', 'en').relative(),   '5 รฅr sedan',     'Date#create | Swedish | relative format past');
+
+  equal(Date.create('1 second from now', 'en').relative(), 'om 1 sekund', 'Date#create | Swedish | relative format future');
+  equal(Date.create('1 minute from now', 'en').relative(), 'om 1 minut',  'Date#create | Swedish | relative format future');
+  equal(Date.create('1 hour from now', 'en').relative(),   'om 1 timme',     'Date#create | Swedish | relative format future');
+  equal(Date.create('1 day from now', 'en').relative(),    'om 1 dag',    'Date#create | Swedish | relative format future');
+  equal(Date.create('1 week from now', 'en').relative(),   'om 1 vecka',  'Date#create | Swedish | relative format future');
+  equal(Date.create('1 month from now', 'en').relative(),  'om 1 mรฅnad',   'Date#create | Swedish | relative format future');
+  equal(Date.create('1 year from now', 'en').relative(),   'om 1 รฅr',     'Date#create | Swedish | relative format future');
+
+  equal(Date.create('5 second from now', 'en').relative(), 'om 5 sekunder', 'Date#create | Swedish | relative format future');
+  equal(Date.create('5 minutes from now', 'en').relative(),'om 5 minuter',  'Date#create | Swedish | relative format future');
+  equal(Date.create('5 hour from now', 'en').relative(),   'om 5 timmar',     'Date#create | Swedish | relative format future');
+  equal(Date.create('5 day from now', 'en').relative(),    'om 5 dagar',    'Date#create | Swedish | relative format future');
+  equal(Date.create('3 weeks from now', 'en').relative(),   'om 3 veckor',  'Date#create | Swedish | relative format future');
+  equal(Date.create('5 weeks from now', 'en').relative(),   'om 1 mรฅnad',  'Date#create | Swedish | relative format future');
+  equal(Date.create('5 month from now', 'en').relative(),  'om 5 mรฅnader',   'Date#create | Swedish | relative format future');
+  equal(Date.create('5 year from now', 'en').relative(),   'om 5 รฅr',     'Date#create | Swedish | relative format future');
+
+  equal((5).hours().duration('sv'), '5 timmar', 'Date#create | Swedish | simple duration');
+
+});
level2/node_modules/sugar/test/environments/sugar/date_zh_cn.js
@@ -0,0 +1,174 @@
+test('Dates | Simplified Chinese', function () {
+
+  var now = new Date();
+  var then = new Date(2011, 7, 25, 15, 45, 50);
+  Date.setLocale('zh-CN');
+
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ'), new Date(2011, 4, 15), 'Date#create | basic Simplified Chinese date');
+  dateEqual(Date.create('2011ๅนด5ๆœˆ'), new Date(2011, 4), 'Date#create | Simplified Chinese | year and month');
+  dateEqual(Date.create('5ๆœˆ15ๆ—ฅ'), new Date(now.getFullYear(), 4, 15), 'Date#create | Simplified Chinese | month and date');
+  dateEqual(Date.create('2011ๅนด'), new Date(2011, 0), 'Date#create | Simplified Chinese | year');
+  dateEqual(Date.create('5ๆœˆ'), new Date(now.getFullYear(), 4), 'Date#create | Simplified Chinese | month');
+  dateEqual(Date.create('15ๆ—ฅ'), new Date(now.getFullYear(), now.getMonth(), 15), 'Date#create | Simplified Chinese | date');
+  dateEqual(Date.create('ๆ˜ŸๆœŸไธ€'), getDateWithWeekdayAndOffset(1), 'Date#create | Simplified Chinese | Monday');
+  dateEqual(Date.create('ไนๆ—ฅ'), new Date(now.getFullYear(), now.getMonth(), 9), 'Date#create | Simplified Chinese | the 9th');
+  dateEqual(Date.create('ไบŒๅไบ”ๆ—ฅ'), new Date(now.getFullYear(), now.getMonth(), 25), 'Date#create | Simplified Chinese | the 25th');
+  dateEqual(Date.create('ไบŒๅไบ”ๅท'), new Date(now.getFullYear(), now.getMonth(), 25), 'Date#create | Simplified Chinese | ๅท should be understood as well');
+  dateEqual(Date.create('ไนๆœˆไบŒๅไบ”ๅท'), new Date(now.getFullYear(), 8, 25), 'Date#create | Simplified Chinese | 9.25');
+
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ 3:45'), new Date(2011, 4, 15, 3, 45), 'Date#create | basic Simplified Chinese date 3:45');
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ ไธ‹ๅˆ3:45'), new Date(2011, 4, 15, 15, 45), 'Date#create | basic Simplified Chinese date 3:45pm');
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ 3็‚น45ๅˆ†้’Ÿ'), new Date(2011, 4, 15, 3, 45), 'Date#create | basic Simplified Chinese date 3:45pm kanji');
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ ไธ‹ๅˆ3็‚น45ๅˆ†้’Ÿ'), new Date(2011, 4, 15, 15, 45), 'Date#create | basic Simplified Chinese date 3:45pm kanji afternoon');
+
+  dateEqual(Date.create('ไธ€ๆฏซ็ง’ๅ‰'), getRelativeDate(null, null, null, null, null, null,-1), 'Date#create | Simplified Chinese | one millisecond ago');
+  dateEqual(Date.create('ไธ€็ง’้’Ÿๅ‰'), getRelativeDate(null, null, null, null, null, -1), 'Date#create | Simplified Chinese | one second ago');
+  dateEqual(Date.create('ไธ€ๅˆ†้’Ÿๅ‰'), getRelativeDate(null, null, null, null, -1), 'Date#create | Simplified Chinese | one minute ago');
+  dateEqual(Date.create('ไธ€ๅฐๆ—ถๅ‰'), getRelativeDate(null, null, null, -1), 'Date#create | Simplified Chinese | one hour ago');
+  dateEqual(Date.create('ไธ€ๅคฉๅ‰'), getRelativeDate(null, null, -1), 'Date#create | Simplified Chinese | one day ago');
+  dateEqual(Date.create('ไธ€ๅ‘จๅ‰'), getRelativeDate(null, null, -7), 'Date#create | Simplified Chinese | one week ๅ‘จ');
+  dateEqual(Date.create('ไธ€ไธชๆ˜ŸๆœŸๅ‰'), getRelativeDate(null, null, -7), 'Date#create | Simplified Chinese | one week ไธชๆ˜ŸๆœŸ');
+  dateEqual(Date.create('ไธ€ไธชๆœˆๅ‰'), getRelativeDate(null, -1), 'Date#create | Simplified Chinese | one month ago');
+  dateEqual(Date.create('ไธ€ๅนดๅ‰'), getRelativeDate(-1), 'Date#create | Simplified Chinese | one year ago');
+
+
+  dateEqual(Date.create('5ๆฏซ็ง’ๅŽ'), getRelativeDate(null, null, null, null, null, null,5), 'Date#create | Simplified Chinese | five millisecond from now');
+  dateEqual(Date.create('5็ง’้’ŸๅŽ'), getRelativeDate(null, null, null, null, null, 5), 'Date#create | Simplified Chinese | five second from now');
+  dateEqual(Date.create('5ๅˆ†้’ŸๅŽ'), getRelativeDate(null, null, null, null, 5), 'Date#create | Simplified Chinese | five minute from now');
+  dateEqual(Date.create('5ๅฐๆ—ถๅŽ'), getRelativeDate(null, null, null, 5), 'Date#create | Simplified Chinese | five hour from now');
+  dateEqual(Date.create('5ๅคฉๅŽ'), getRelativeDate(null, null, 5), 'Date#create | Simplified Chinese | five day from now');
+  dateEqual(Date.create('5ๅ‘จๅŽ'), getRelativeDate(null, null, 35), 'Date#create | Simplified Chinese | five weeks from now ๅ‘จ');
+  dateEqual(Date.create('5ไธชๆ˜ŸๆœŸๅŽ'), getRelativeDate(null, null, 35), 'Date#create | Simplified Chinese | five weeks from now ไธชๆ˜ŸๆœŸ');
+  dateEqual(Date.create('5ไธชๆœˆๅŽ'), getRelativeDate(null, 5), 'Date#create | Simplified Chinese | five months');
+  dateEqual(Date.create('5ๅนดๅŽ'), getRelativeDate(5), 'Date#create | Simplified Chinese | five years from now');
+
+  dateEqual(Date.create('๏ผ’๏ผ๏ผ‘๏ผ‘ๅนด'), new Date(2011, 0), 'Date#create | Simplified Chinese | full-width year');
+  dateEqual(Date.create('ๆ˜ŸๆœŸไธ‰'), getDateWithWeekdayAndOffset(3, 0), 'Date#create | Simplified Chinese | ๆ˜ŸๆœŸ Wednesday');
+
+  dateEqual(Date.create('ๅ‰ๅคฉ'), getRelativeDate(null, null, -2).reset(), 'Date#create | Simplified Chinese | ไธ€ๆ˜จๆ—ฅ');
+  dateEqual(Date.create('ๆ˜จๅคฉ'), getRelativeDate(null, null, -1).reset(), 'Date#create | Simplified Chinese | yesterday');
+  dateEqual(Date.create('ไปŠๅคฉ'), getRelativeDate(null, null, 0).reset(), 'Date#create | Simplified Chinese | today');
+  dateEqual(Date.create('ๆ˜Žๅคฉ'), getRelativeDate(null, null, 1).reset(), 'Date#create | Simplified Chinese | tomorrow');
+  dateEqual(Date.create('ๅŽๅคฉ'), getRelativeDate(null, null, 2).reset(), 'Date#create | Simplified Chinese | tomorrow');
+
+  dateEqual(Date.create('ไธŠๅ‘จ'), getRelativeDate(null, null, -7), 'Date#create | Simplified Chinese | Last week');
+  dateEqual(Date.create('่ฟ™ๅ‘จ'), getRelativeDate(null, null, 0), 'Date#create | Simplified Chinese | This week');
+  dateEqual(Date.create('ไธ‹ๅ‘จ'), getRelativeDate(null, null, 7), 'Date#create | Simplified Chinese | Next week');
+
+  dateEqual(Date.create('ไธŠไธชๆœˆ'), getRelativeDate(null, -1), 'Date#create | Simplified Chinese | last month');
+  dateEqual(Date.create('่ฟ™ไธชๆœˆ'), getRelativeDate(null, 0), 'Date#create | Simplified Chinese | this month');
+  dateEqual(Date.create('ไธ‹ไธชๆœˆ'), getRelativeDate(null, 1), 'Date#create | Simplified Chinese | Next month');
+
+  dateEqual(Date.create('ๅŽปๅนด'), getRelativeDate(-1), 'Date#create | Simplified Chinese | Last year');
+  dateEqual(Date.create('ๆ˜Žๅนด'), getRelativeDate(1), 'Date#create | Simplified Chinese | Next year');
+
+  dateEqual(Date.create('ไธŠๅ‘จไธ‰'), getDateWithWeekdayAndOffset(3, -7), 'Date#create | Simplified Chinese | Last wednesday');
+  dateEqual(Date.create('่ฟ™ๅ‘จๅ…ญ'), getDateWithWeekdayAndOffset(6), 'Date#create | Simplified Chinese | this Saturday');
+  dateEqual(Date.create('ไธ‹ๅ‘จไบ”'), getDateWithWeekdayAndOffset(5, 7), 'Date#create | Simplified Chinese | Next friday');
+
+  equal(then.format(), '2011ๅนด8ๆœˆ25ๆ—ฅ ไธ‹ๅˆ3:45', 'Date#create | Simplified Chinese | standard format');
+  equal(then.format('{yyyy}ๅนด{MM}ๆœˆ{dd}ๆ—ฅ'), '2011ๅนด08ๆœˆ25ๆ—ฅ', 'Date#create | Simplified Chinese | format');
+
+  // Format shortcuts
+  equal(then.format('long'), '2011ๅนด8ๆœˆ25ๆ—ฅ ไธ‹ๅˆ3:45', 'Date#create | Simplified Chinese | long format');
+  equal(then.long(), '2011ๅนด8ๆœˆ25ๆ—ฅ ไธ‹ๅˆ3:45', 'Date#create | Simplified Chinese | long shortcut');
+  equal(then.format('full'), '2011ๅนด8ๆœˆ25ๆ—ฅ ๆ˜ŸๆœŸๅ›› ไธ‹ๅˆ3:45:50', 'Date#create | Simplified Chinese | full format');
+  equal(then.full(), '2011ๅนด8ๆœˆ25ๆ—ฅ ๆ˜ŸๆœŸๅ›› ไธ‹ๅˆ3:45:50', 'Date#create | Simplified Chinese | full format');
+  equal(then.format('short'), '2011ๅนด8ๆœˆ25ๆ—ฅ', 'Date#create | Simplified Chinese | short format');
+  equal(then.short(), '2011ๅนด8ๆœˆ25ๆ—ฅ', 'Date#create | Simplified Chinese | short format');
+
+  equal(Date.create('1 second ago', 'en').relative(), '1็ง’้’Ÿๅ‰', 'Date#create | Simplified Chinese | relative format past');
+  equal(Date.create('1 minute ago', 'en').relative(), '1ๅˆ†้’Ÿๅ‰',  'Date#create | Simplified Chinese | relative format past');
+  equal(Date.create('1 hour ago', 'en').relative(),   '1ๅฐๆ—ถๅ‰',     'Date#create | Simplified Chinese | relative format past');
+  equal(Date.create('1 day ago', 'en').relative(),    '1ๅคฉๅ‰',    'Date#create | Simplified Chinese | relative format past');
+  equal(Date.create('1 week ago', 'en').relative(),   '1ไธชๆ˜ŸๆœŸๅ‰',  'Date#create | Simplified Chinese | relative format past');
+  equal(Date.create('1 month ago', 'en').relative(),  '1ไธชๆœˆๅ‰',   'Date#create | Simplified Chinese | relative format past');
+  equal(Date.create('1 year ago', 'en').relative(),   '1ๅนดๅ‰',     'Date#create | Simplified Chinese | relative format past');
+
+  equal(Date.create('2 seconds ago', 'en').relative(), '2็ง’้’Ÿๅ‰', 'Date#create | Simplified Chinese | relative format past');
+  equal(Date.create('2 minutes ago', 'en').relative(), '2ๅˆ†้’Ÿๅ‰',  'Date#create | Simplified Chinese | relative format past');
+  equal(Date.create('2 hours ago', 'en').relative(),   '2ๅฐๆ—ถๅ‰',     'Date#create | Simplified Chinese | relative format past');
+  equal(Date.create('2 days ago', 'en').relative(),    '2ๅคฉๅ‰',    'Date#create | Simplified Chinese | relative format past');
+  equal(Date.create('2 weeks ago', 'en').relative(),   '2ไธชๆ˜ŸๆœŸๅ‰',  'Date#create | Simplified Chinese | relative format past');
+  equal(Date.create('2 months ago', 'en').relative(),  '2ไธชๆœˆๅ‰',   'Date#create | Simplified Chinese | relative format past');
+  equal(Date.create('2 years ago', 'en').relative(),   '2ๅนดๅ‰',     'Date#create | Simplified Chinese | relative format past');
+
+  equal(Date.create('1 second from now', 'en').relative(), '1็ง’้’ŸๅŽ', 'Date#create | Simplified Chinese | relative format future');
+  equal(Date.create('1 minute from now', 'en').relative(), '1ๅˆ†้’ŸๅŽ',  'Date#create | Simplified Chinese | relative format future');
+  equal(Date.create('1 hour from now', 'en').relative(),   '1ๅฐๆ—ถๅŽ',     'Date#create | Simplified Chinese | relative format future');
+  equal(Date.create('1 day from now', 'en').relative(),    '1ๅคฉๅŽ',    'Date#create | Simplified Chinese | relative format future');
+  equal(Date.create('1 week from now', 'en').relative(),   '1ไธชๆ˜ŸๆœŸๅŽ',  'Date#create | Simplified Chinese | relative format future');
+  equal(Date.create('1 month from now', 'en').relative(),  '1ไธชๆœˆๅŽ',   'Date#create | Simplified Chinese | relative format future');
+  equal(Date.create('1 year from now', 'en').relative(),   '1ๅนดๅŽ',     'Date#create | Simplified Chinese | relative format future');
+
+  equal(Date.create('5 second from now', 'en').relative(), '5็ง’้’ŸๅŽ', 'Date#create | Simplified Chinese | relative format future');
+  equal(Date.create('5 minute from now', 'en').relative(), '5ๅˆ†้’ŸๅŽ',  'Date#create | Simplified Chinese | relative format future');
+  equal(Date.create('5 hour from now', 'en').relative(),   '5ๅฐๆ—ถๅŽ',     'Date#create | Simplified Chinese | relative format future');
+  equal(Date.create('5 day from now', 'en').relative(),    '5ๅคฉๅŽ',    'Date#create | Simplified Chinese | relative format future');
+  equal(Date.create('5 week from now', 'en').relative(),   '1ไธชๆœˆๅŽ',  'Date#create | Simplified Chinese | relative format future');
+  equal(Date.create('5 month from now', 'en').relative(),  '5ไธชๆœˆๅŽ',   'Date#create | Simplified Chinese | relative format future');
+  equal(Date.create('5 year from now', 'en').relative(),   '5ๅนดๅŽ',     'Date#create | Simplified Chinese | relative format future');
+
+
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ ไธ‹ๅˆ3:45'), new Date(2011, 4, 15, 15, 45), 'Date#create | Simplified Chinese | pm still works');
+
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ 3:45:59'), new Date(2011, 4, 15, 3, 45, 59), 'Date#create | Simplified Chinese | full date with time');
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ 3็‚น45ๅˆ†'), new Date(2011, 4, 15, 3, 45, 0), 'Date#create | Simplified Chinese | full date with kanji markers');
+
+  dateEqual(Date.create('ไบŒใ€‡ใ€‡ๅ…ซๅนดๅไธ€ๆœˆๅๅ››ๆ—ฅ ไธ‰็‚นๅ››ๅไบ”ๅˆ†'), new Date(2008, 10, 14, 3, 45), 'Date#create | Simplified Chinese | full date with full kanji');
+  dateEqual(Date.create('ไบŒใ€‡ใ€‡ๅ…ซๅนดๅไธ€ๆœˆๅๅ››ๆ—ฅ ไธ‰็‚นๅ››ๅไบ”ๅˆ†้’Ÿ'), new Date(2008, 10, 14, 3, 45), 'Date#create | Simplified Chinese | full date with full kanji and zhong');
+
+  dateEqual(Date.create('ไบŒใ€‡ใ€‡ๅ…ซๅนดๅไธ€ๆœˆๅๅ››ๆ—ฅ ไธ‰็‚นๅ››ๅไบ”ๅˆ†้’Ÿ'), new Date(2008, 10, 14, 3, 45), 'Date#create | Simplified Chinese | full date with full kanji and zhong');
+
+
+
+  // Kanji conversion tests
+
+  dateEqual(Date.create('ไบŒใ€‡ไธ€ไบŒๅนดไบ”ๆœˆ'), new Date(2012, 4), 'Date#create | Simplified Chinese | ไบŒใ€‡ไธ€ไบŒๅนดไบ”ๆœˆ');
+  dateEqual(Date.create('ไบŒใ€‡ไธ€ไบŒๅนด'), new Date(2012, 0), 'Date#create | Simplified Chinese | ไบŒใ€‡ไธ€ไบŒๅนด');
+  dateEqual(Date.create('ไบ”ๆœˆ'), new Date(now.getFullYear(), 4), 'Date#create | Simplified Chinese | ไบ”ๆœˆ');
+  dateEqual(Date.create('ๅไบŒๆœˆ'), new Date(now.getFullYear(), 11), 'Date#create | Simplified Chinese | ๅไบŒๆœˆ');
+  dateEqual(Date.create('ๅไธ€ๆœˆ'), new Date(now.getFullYear(), 10), 'Date#create | Simplified Chinese | ๅไธ€ๆœˆ');
+  dateEqual(Date.create('ๅๆœˆ'), new Date(now.getFullYear(), 9), 'Date#create | Simplified Chinese | ๅๆœˆ');
+  dateEqual(Date.create('ไบŒใ€‡ไธ€ไบŒๅนด'), new Date(2012, 0), 'Date#create | Simplified Chinese | ไบŒใ€‡ไธ€ไบŒๅนด');
+
+  dateEqual(Date.create('ไบŒๅƒไบŒ็™พไบŒๅไบŒๅนด'), new Date(2222, 0), 'Date#create | Simplified Chinese | ไบŒๅƒไบŒ็™พไบŒๅไบŒๅนด');
+  dateEqual(Date.create('ไบŒๅƒไบŒๅไบŒๅนด'), new Date(2022, 0), 'Date#create | Simplified Chinese | ไบŒๅƒไบŒๅไบŒๅนด');
+  dateEqual(Date.create('ไบŒๅƒไบŒๅนด'), new Date(2002, 0), 'Date#create | Simplified Chinese | ไบŒๅƒไบŒๅนด');
+  dateEqual(Date.create('ไบŒๅƒๅนด'), new Date(2000, 0), 'Date#create | Simplified Chinese | ไบŒๅƒๅนด');
+  dateEqual(Date.create('ๅƒๅนด'), new Date(1000, 0), 'Date#create | Simplified Chinese | ๅƒๅนด');
+
+  dateEqual(Date.create('ไบŒๅƒไบŒ็™พไบŒๅๅนด'), new Date(2220, 0), 'Date#create | Simplified Chinese | ไบŒๅƒไบŒ็™พไบŒๅๅนด');
+  dateEqual(Date.create('ไบŒๅƒไบŒ็™พๅนด'), new Date(2200, 0), 'Date#create | Simplified Chinese | ไบŒๅƒไบŒ็™พๅนด');
+  dateEqual(Date.create('ไบŒๅƒไบŒๅนด'), new Date(2002, 0), 'Date#create | Simplified Chinese | ไบŒๅƒไบŒๅนด');
+
+  dateEqual(Date.create('ๅƒไบŒ็™พไบŒๅไบŒๅนด'), new Date(1222, 0), 'Date#create | Simplified Chinese | ๅƒไบŒ็™พไบŒๅไบŒๅนด');
+  dateEqual(Date.create('ๅƒไบŒ็™พไบŒๅไบŒๅนด'), new Date(1222, 0), 'Date#create | Simplified Chinese | ๅƒไบŒ็™พไบŒๅไบŒๅนด');
+  dateEqual(Date.create('ๅƒ็™พไบŒๅไบŒๅนด'), new Date(1122, 0), 'Date#create | Simplified Chinese | ๅƒ็™พไบŒๅไบŒๅนด');
+  dateEqual(Date.create('ๅƒไบŒๅไบŒๅนด'), new Date(1022, 0), 'Date#create | Simplified Chinese | ๅƒไบŒๅไบŒๅนด');
+  dateEqual(Date.create('ๅƒๅไบŒๅนด'), new Date(1012, 0), 'Date#create | Simplified Chinese | ๅƒๅไบŒๅนด');
+
+  dateEqual(Date.create('ไบŒใ€‡ไบŒไธ€ๅนด'), new Date(2021, 0), 'Date#create | Simplified Chinese | ไบŒใ€‡ไบŒไธ€ๅนด');
+  dateEqual(Date.create('ไบŒไธ‰ไบŒไธ€ๅนด'), new Date(2321, 0), 'Date#create | Simplified Chinese | ไบŒไธ‰ไบŒไธ€ๅนด');
+  dateEqual(Date.create('ๅ››ไธ‰ไบŒไธ€ๅนด'), new Date(4321, 0), 'Date#create | Simplified Chinese | ๅ››ไธ‰ไบŒไธ€ๅนด');
+
+  dateEqual(Date.create('1/2/13'), new Date(2013, 0, 2), 'Date#create | Simplified Chinese | uses American style ambiguity');
+
+  // Issue #148 various Chinese dates
+
+  dateEqual(Date.create('ๆ˜ŸๆœŸๆ—ฅ ไธ‹ๅˆ2:00'), getDateWithWeekdayAndOffset(0).set({ hour: 14 }), 'Date#create | Simplified Chinese | ๆ˜ŸๆœŸๆ—ฅ 2:00pm');
+  dateEqual(Date.create('12/31/2012'), new Date(2012, 11, 31), 'Date#create | Simplified Chinese | 12/31/2012');
+  dateEqual(Date.create('ไธ‹ๆ˜ŸๆœŸๅ…ญ 3็‚น12ๅˆ†'), getDateWithWeekdayAndOffset(6, 7, 3, 12), 'Date#create | Simplified Chinese | Saturday 3:12');
+
+  dateEqual(Date.create('ไธŠๅˆ3็‚น12ๅˆ†'), new Date().set({ hour: 3, minute: 12 }, true), 'Date#create | Simplified Chinese | 3:12am');
+  dateEqual(Date.create('ไธŠๅˆ3็‚น'), new Date().set({ hour: 3 }, true), 'Date#create | Simplified Chinese | 3am');
+
+  dateEqual(Date.create('ไธŠๅˆ3ๆ—ถ12ๅˆ†'), new Date().set({ hour: 3, minute: 12 }, true), 'Date#create | Simplified Chinese | ๆ—ถ | 3:12am');
+  dateEqual(Date.create('ไธŠๅˆ3ๆ—ถ'), new Date().set({ hour: 3 }, true), 'Date#create | Simplified Chinese | ๆ—ถ | 3am');
+
+  equal((5).hours().duration('zh-CN'),   '5ๅฐๆ—ถ',     'Date#create | Simplified Chinese | simple duration');
+
+  equal(Date.create('18:00', 'zh-CN').getHours(), 18, 'Date#create | Simplified Chinese | hour:minute only');
+
+});
+
level2/node_modules/sugar/test/environments/sugar/date_zh_tw.js
@@ -0,0 +1,117 @@
+test('Dates | Traditional Chinese', function () {
+
+  var now = new Date();
+  var then = new Date(2011, 7, 25, 15, 45, 50);
+  Date.setLocale('zh-TW');
+
+
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ'), new Date(2011, 4, 15), 'Date#create | basic Traditional Chinese date');
+  dateEqual(Date.create('2011ๅนด5ๆœˆ'), new Date(2011, 4), 'Date#create | Traditional Chinese | year and month');
+  dateEqual(Date.create('5ๆœˆ15ๆ—ฅ'), new Date(now.getFullYear(), 4, 15), 'Date#create | Traditional Chinese | month and date');
+  dateEqual(Date.create('2011ๅนด'), new Date(2011, 0), 'Date#create | Traditional Chinese | year');
+  dateEqual(Date.create('5ๆœˆ'), new Date(now.getFullYear(), 4), 'Date#create | Traditional Chinese | month');
+  dateEqual(Date.create('15ๆ—ฅ'), new Date(now.getFullYear(), now.getMonth(), 15), 'Date#create | Traditional Chinese | date');
+  dateEqual(Date.create('ๆ˜ŸๆœŸไธ€'), getDateWithWeekdayAndOffset(1), 'Date#create | Traditional Chinese | Monday');
+  dateEqual(Date.create('ไนๆ—ฅ'), new Date(now.getFullYear(), now.getMonth(), 9), 'Date#create | Traditional Chinese | the 9th');
+  dateEqual(Date.create('ไบŒๅไบ”ๆ—ฅ'), new Date(now.getFullYear(), now.getMonth(), 25), 'Date#create | Traditional Chinese | the 25th');
+  dateEqual(Date.create('ไบŒๅไบ”่™Ÿ'), new Date(now.getFullYear(), now.getMonth(), 25), 'Date#create | Traditional Chinese | ่™Ÿ should be understood as well');
+
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ 3:45'), new Date(2011, 4, 15, 3, 45), 'Date#create | basic Traditional Chinese date 3:45');
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ ไธ‹ๅˆ3:45'), new Date(2011, 4, 15, 15, 45), 'Date#create | basic Traditional Chinese date 3:45pm');
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ 3้ปž45ๅˆ†้˜'), new Date(2011, 4, 15, 3, 45), 'Date#create | basic Traditional Chinese date 3:45pm kanji');
+  dateEqual(Date.create('2011ๅนด5ๆœˆ15ๆ—ฅ ไธ‹ๅˆ3้ปž45ๅˆ†้˜'), new Date(2011, 4, 15, 15, 45), 'Date#create | basic Traditional Chinese date 3:45pm kanji afternoon');
+
+  dateEqual(Date.create('ไธ€ๆฏซ็ง’ๅ‰'), getRelativeDate(null, null, null, null, null, null,-1), 'Date#create | Traditional Chinese | one millisecond ago');
+  dateEqual(Date.create('ไธ€็ง’้˜ๅ‰'), getRelativeDate(null, null, null, null, null, -1), 'Date#create | Traditional Chinese | one second ago');
+  dateEqual(Date.create('ไธ€ๅˆ†้˜ๅ‰'), getRelativeDate(null, null, null, null, -1), 'Date#create | Traditional Chinese | one minute ago');
+  dateEqual(Date.create('ไธ€ๅฐๆ™‚ๅ‰'), getRelativeDate(null, null, null, -1), 'Date#create | Traditional Chinese | one hour ago');
+  dateEqual(Date.create('ไธ€ๅคฉๅ‰'), getRelativeDate(null, null, -1), 'Date#create | Traditional Chinese | one day ago');
+  dateEqual(Date.create('ไธ€้€ฑๅ‰'), getRelativeDate(null, null, -7), 'Date#create | Traditional Chinese | one week ้€ฑ');
+  dateEqual(Date.create('ไธ€ๅ€‹ๆ˜ŸๆœŸๅ‰'), getRelativeDate(null, null, -7), 'Date#create | Traditional Chinese | one week ๅ€‹ๆ˜ŸๆœŸ');
+  dateEqual(Date.create('ไธ€ๅ€‹ๆœˆๅ‰'), getRelativeDate(null, -1), 'Date#create | Traditional Chinese | one month ago');
+  dateEqual(Date.create('ไธ€ๅนดๅ‰'), getRelativeDate(-1), 'Date#create | Traditional Chinese | one year ago');
+
+  dateEqual(Date.create('5ๆฏซ็ง’ๅพŒ'), getRelativeDate(null, null, null, null, null, null, 5), 'Date#create | Traditional Chinese | five millisecond from now');
+  dateEqual(Date.create('5็ง’้˜ๅพŒ'), getRelativeDate(null, null, null, null, null, 5), 'Date#create | Traditional Chinese | five second from now');
+  dateEqual(Date.create('5ๅˆ†้˜ๅพŒ'), getRelativeDate(null, null, null, null, 5), 'Date#create | Traditional Chinese | five minute from now');
+  dateEqual(Date.create('5ๅฐๆ™‚ๅพŒ'), getRelativeDate(null, null, null, 5), 'Date#create | Traditional Chinese | five hour from now');
+  dateEqual(Date.create('5ๅคฉๅพŒ'), getRelativeDate(null, null, 5), 'Date#create | Traditional Chinese | five day from now');
+  dateEqual(Date.create('5้€ฑๅพŒ'), getRelativeDate(null, null, 35), 'Date#create | Traditional Chinese | five weeks from now ้€ฑ');
+  dateEqual(Date.create('5ๅ€‹ๆ˜ŸๆœŸๅพŒ'), getRelativeDate(null, null, 35), 'Date#create | Traditional Chinese | five weeks from now ๅ€‹ๆ˜ŸๆœŸ');
+  dateEqual(Date.create('5ๅ€‹ๆœˆๅพŒ'), getRelativeDate(null, 5), 'Date#create | Traditional Chinese | five months');
+  dateEqual(Date.create('5ๅนดๅพŒ'), getRelativeDate(5), 'Date#create | Traditional Chinese | five years from now');
+
+  dateEqual(Date.create('๏ผ’๏ผ๏ผ‘๏ผ‘ๅนด'), new Date(2011, 0), 'Date#create | Traditional Chinese | full-width year');
+
+  dateEqual(Date.create('ๅ‰ๅคฉ'), getRelativeDate(null, null, -2).reset(), 'Date#create | Traditional Chinese | ไธ€ๆ˜จๆ—ฅ');
+  dateEqual(Date.create('ๆ˜จๅคฉ'), getRelativeDate(null, null, -1).reset(), 'Date#create | Traditional Chinese | yesterday');
+  dateEqual(Date.create('ไปŠๅคฉ'), getRelativeDate(null, null, 0).reset(), 'Date#create | Traditional Chinese | today');
+  dateEqual(Date.create('ๆ˜Žๅคฉ'), getRelativeDate(null, null, 1).reset(), 'Date#create | Traditional Chinese | tomorrow');
+  dateEqual(Date.create('ๅพŒๅคฉ'), getRelativeDate(null, null, 2).reset(), 'Date#create | Traditional Chinese | ๆ˜ŽๅพŒๆ—ฅ');
+
+  dateEqual(Date.create('ไธŠ้€ฑ'), getRelativeDate(null, null, -7), 'Date#create | Traditional Chinese | Last week');
+  dateEqual(Date.create('้€™้€ฑ'), getRelativeDate(null, null, 0), 'Date#create | Traditional Chinese | This week');
+  dateEqual(Date.create('ไธ‹้€ฑ'), getRelativeDate(null, null, 7), 'Date#create | Traditional Chinese | Next week');
+
+  dateEqual(Date.create('ไธŠๅ€‹ๆœˆ'), getRelativeDate(null, -1), 'Date#create | Traditional Chinese | last month');
+  dateEqual(Date.create('้€™ๅ€‹ๆœˆ'), getRelativeDate(null, 0), 'Date#create | Traditional Chinese | this month');
+  dateEqual(Date.create('ไธ‹ๅ€‹ๆœˆ'), getRelativeDate(null, 1), 'Date#create | Traditional Chinese | Next month');
+
+  dateEqual(Date.create('ๅŽปๅนด'), getRelativeDate(-1), 'Date#create | Traditional Chinese | Last year');
+  dateEqual(Date.create('ๆ˜Žๅนด'), getRelativeDate(1), 'Date#create | Traditional Chinese | Next year');
+
+  dateEqual(Date.create('ไธŠ้€ฑไธ‰'), getDateWithWeekdayAndOffset(3, -7), 'Date#create | Traditional Chinese | Last wednesday');
+  dateEqual(Date.create('้€™ๅ€‹ๆœˆ'), getRelativeDate(null, 0), 'Date#create | Traditional Chinese | this month');
+  dateEqual(Date.create('ไธ‹้€ฑไบ”'), getDateWithWeekdayAndOffset(5, 7), 'Date#create | Traditional Chinese | Next friday');
+
+  equal(then.format(), '2011ๅนด8ๆœˆ25ๆ—ฅ ไธ‹ๅˆ3:45', 'Date#create | Traditional Chinese | standard format');
+  equal(then.format(), '2011ๅนด8ๆœˆ25ๆ—ฅ ไธ‹ๅˆ3:45', 'Date#create | Traditional Chinese | standard format');
+  equal(then.format('{yyyy}ๅนด{MM}ๆœˆ{dd}ๆ—ฅ'), '2011ๅนด08ๆœˆ25ๆ—ฅ', 'Date#create | Traditional Chinese | format');
+
+  // Format shortcuts
+  equal(then.format('long'), '2011ๅนด8ๆœˆ25ๆ—ฅ ไธ‹ๅˆ3:45', 'Date#create | Traditional Chinese | long format');
+  equal(then.long(), '2011ๅนด8ๆœˆ25ๆ—ฅ ไธ‹ๅˆ3:45', 'Date#create | Traditional Chinese | long shortcut');
+  equal(then.format('full'), '2011ๅนด8ๆœˆ25ๆ—ฅ ๆ˜ŸๆœŸๅ›› ไธ‹ๅˆ3:45:50', 'Date#create | Traditional Chinese | full format');
+  equal(then.full(), '2011ๅนด8ๆœˆ25ๆ—ฅ ๆ˜ŸๆœŸๅ›› ไธ‹ๅˆ3:45:50', 'Date#create | Traditional Chinese | full format');
+  equal(then.format('short'), '2011ๅนด8ๆœˆ25ๆ—ฅ', 'Date#create | Traditional Chinese | short format');
+  equal(then.short(), '2011ๅนด8ๆœˆ25ๆ—ฅ', 'Date#create | Traditional Chinese | short format');
+
+
+
+  equal(Date.create('1 second ago', 'en').relative(), '1็ง’้˜ๅ‰', 'Date#create | Traditional Chinese | relative format past');
+  equal(Date.create('1 minute ago', 'en').relative(), '1ๅˆ†้˜ๅ‰',  'Date#create | Traditional Chinese | relative format past');
+  equal(Date.create('1 hour ago', 'en').relative(),   '1ๅฐๆ™‚ๅ‰',     'Date#create | Traditional Chinese | relative format past');
+  equal(Date.create('1 day ago', 'en').relative(),    '1ๅคฉๅ‰',    'Date#create | Traditional Chinese | relative format past');
+  equal(Date.create('1 week ago', 'en').relative(),   '1ๅ€‹ๆ˜ŸๆœŸๅ‰',  'Date#create | Traditional Chinese | relative format past');
+  equal(Date.create('1 month ago', 'en').relative(),  '1ๅ€‹ๆœˆๅ‰',   'Date#create | Traditional Chinese | relative format past');
+  equal(Date.create('1 year ago', 'en').relative(),   '1ๅนดๅ‰',     'Date#create | Traditional Chinese | relative format past');
+
+  equal(Date.create('2 seconds ago', 'en').relative(), '2็ง’้˜ๅ‰', 'Date#create | Traditional Chinese | relative format past');
+  equal(Date.create('2 minutes ago', 'en').relative(), '2ๅˆ†้˜ๅ‰',  'Date#create | Traditional Chinese | relative format past');
+  equal(Date.create('2 hours ago', 'en').relative(),   '2ๅฐๆ™‚ๅ‰',     'Date#create | Traditional Chinese | relative format past');
+  equal(Date.create('2 days ago', 'en').relative(),    '2ๅคฉๅ‰',    'Date#create | Traditional Chinese | relative format past');
+  equal(Date.create('2 weeks ago', 'en').relative(),   '2ๅ€‹ๆ˜ŸๆœŸๅ‰',  'Date#create | Traditional Chinese | relative format past');
+  equal(Date.create('2 months ago', 'en').relative(),  '2ๅ€‹ๆœˆๅ‰',   'Date#create | Traditional Chinese | relative format past');
+  equal(Date.create('2 years ago', 'en').relative(),   '2ๅนดๅ‰',     'Date#create | Traditional Chinese | relative format past');
+
+  equal(Date.create('1 second from now', 'en').relative(), '1็ง’้˜ๅพŒ', 'Date#create | Traditional Chinese | relative format future');
+  equal(Date.create('1 minute from now', 'en').relative(), '1ๅˆ†้˜ๅพŒ',  'Date#create | Traditional Chinese | relative format future');
+  equal(Date.create('1 hour from now', 'en').relative(),   '1ๅฐๆ™‚ๅพŒ',     'Date#create | Traditional Chinese | relative format future');
+  equal(Date.create('1 day from now', 'en').relative(),    '1ๅคฉๅพŒ',    'Date#create | Traditional Chinese | relative format future');
+  equal(Date.create('1 week from now', 'en').relative(),   '1ๅ€‹ๆ˜ŸๆœŸๅพŒ',  'Date#create | Traditional Chinese | relative format future');
+  equal(Date.create('1 month from now', 'en').relative(),  '1ๅ€‹ๆœˆๅพŒ',   'Date#create | Traditional Chinese | relative format future');
+  equal(Date.create('1 year from now', 'en').relative(),   '1ๅนดๅพŒ',     'Date#create | Traditional Chinese | relative format future');
+
+  equal(Date.create('5 second from now', 'en').relative(), '5็ง’้˜ๅพŒ', 'Date#create | Traditional Chinese | relative format future');
+  equal(Date.create('5 minute from now', 'en').relative(), '5ๅˆ†้˜ๅพŒ',  'Date#create | Traditional Chinese | relative format future');
+  equal(Date.create('5 hour from now', 'en').relative(),   '5ๅฐๆ™‚ๅพŒ',     'Date#create | Traditional Chinese | relative format future');
+  equal(Date.create('5 day from now', 'en').relative(),    '5ๅคฉๅพŒ',    'Date#create | Traditional Chinese | relative format future');
+  equal(Date.create('5 week from now', 'en').relative(),   '1ๅ€‹ๆœˆๅพŒ',  'Date#create | Traditional Chinese | relative format future');
+  equal(Date.create('5 month from now', 'en').relative(),  '5ๅ€‹ๆœˆๅพŒ',   'Date#create | Traditional Chinese | relative format future');
+  equal(Date.create('5 year from now', 'en').relative(),   '5ๅนดๅพŒ',     'Date#create | Traditional Chinese | relative format future');
+
+  equal((5).hours().duration('zh-TW'),   '5ๅฐๆ™‚',     'Date#create | Traditional Chinese | simple duration');
+
+  equal(Date.create('18:00', 'zh-TW').getHours(), 18, 'Date#create | Traditional Chinese | hour:minute only');
+
+});
level2/node_modules/sugar/test/environments/sugar/dom.js
@@ -0,0 +1,61 @@
+
+test('DOM', function () {
+
+  // Can convert special host objects if they exist.
+  if(document.body.classList) {
+    document.body.className += ' woot';
+    equal(Array.create(document.body.classList).any('woot'), true, 'Array.create | handles array-like objects');
+  }
+
+  var tags = 'a,abbr,acronym,address,applet,area,article,aside,audio,b,base,basefont,bdi,bdo,big,blockquote,body,br,button,canvas,caption,center,cite,code,col,colgroup,command,datalist,dd,del,details,dfn,dir,div,dl,dt,em,embed,fieldset,figcaption,figure,font,footer,form,frame,frameset,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,i,iframe,img,input,ins,kbd,keygen,label,legend,li,link,map,mark,menu,meta,meter,nav,noframes,noscript,object,ol,optgroup,option,output,p,param,pre,progress,q,rp,rt,ruby,s,samp,script,section,select,small,source,span,strike,strong,style,sub,summary,sup,table,tbody,td,textarea,tfoot,th,thead,time,title,tr,track,tt,u,ul,var,video,wbr'.split(',');
+
+  tags.forEach(function(tag) {
+
+    var el1 = document.createElement(tag);
+    var el2 = document.createElement(tag);
+
+    equal([el1].unique(), [el1], 'Array#unique | DOM Elements | 1 element identical by reference');
+    equal([el1,el1].unique(), [el1], 'Array#unique | DOM Elements | 2 elements identical by reference');
+    equal([el1,el1,el1].unique(), [el1], 'Array#unique | DOM Elements | 3 elements identical by reference');
+
+    equal([el1,el2].unique(), [el1,el2], 'Array#unique | DOM Elements | 2 elements different by reference');
+
+    equal([el1].subtract([el1]), [], 'Array#subtract | DOM Elements | [a] - [a]');
+    equal([el1].subtract([el2]), [el1], 'Array#subtract | DOM Elements | [a] - [b]');
+    equal([el1,el2].subtract([el2]), [el1], 'Array#subtract | DOM Elements | [a,b] - [b]');
+    equal([el1,el2].subtract([el1,el2]), [], 'Array#subtract | DOM Elements | [a,b] - [a,b]');
+
+    equal([el1].intersect([el2]), [], 'Array#intersect | DOM Elements | [a] & [b]');
+    equal([el1].intersect([el1]), [el1], 'Array#intersect | DOM Elements | [a] & [a]');
+    equal([el1,el2].intersect([el2]), [el2], 'Array#intersect | DOM Elements | [a,b] & [b]');
+    equal([el1,el2].intersect([el1,el2]), [el1,el2], 'Array#intersect | DOM Elements | [a,b] & [b]');
+
+    equal([el1,el2].any(el1), true, 'Array#any | DOM Elements | a in [a,b]');
+    equal([el1,el2].any(el2), true, 'Array#any | DOM Elements | b in [a,b]');
+    equal([el1].any(el1), true, 'Array#any | DOM Elements | a in [a]');
+    equal([el1].any(el2), false, 'Array#any | DOM Elements | b in [a]');
+
+    equal([el1,el2].every(el1), false, 'Array#every | DOM Elements | a in [a,b]');
+    equal([el1,el2].every(el2), false, 'Array#every | DOM Elements | b in [a,b]');
+    equal([el1].every(el1), true, 'Array#every | DOM Elements | a in [a]');
+    equal([el1].every(el2), false, 'Array#every | DOM Elements | b in [a]');
+
+  });
+
+  if(Object.isObject) {
+    equal(Object.isObject(document), false, 'Object.isObject | document');
+  }
+
+  if(Object.clone) {
+    // Issue #307  Object.clone should error when cloning unknown types.
+    raisesError(function(){ Object.clone(document.body); }, 'Object.clone | raises an error if trying to clone a DOM element');
+    raisesError(function(){ Object.clone(new MouseEvent('click')); }, 'Object.clone | raises an error if trying to a browser event');
+  }
+
+  if(Object.isFunction) {
+    equal(Object.isFunction(document.createElement('embed')), false, 'Object.isFunction | not true for embed objects');
+  }
+
+
+
+});
level2/node_modules/sugar/test/environments/sugar/equals.js
@@ -0,0 +1,291 @@
+
+// These tests also shamefully stolen from the Underscore.js test suite.
+// Careful checks for cyclical references, equality between primitives and
+// wrappers, and more. Sugar's Object.equal should now be considered "egal".
+
+test('Equality', function() {
+
+  function First() {
+    this.value = 1;
+  }
+  First.prototype.value = 1;
+  function Second() {
+    this.value = 1;
+  }
+  Second.prototype.value = 2;
+
+  // Basic equality and identity comparisons.
+  equal(Object.equal(null, null), true, "`null` is equal to `null`");
+  equal(Object.equal(), true, "`undefined` is equal to `undefined`");
+
+  equal(Object.equal(0, -0), false, "`0` is not equal to `-0`");
+  equal(Object.equal(-0, 0), false, "Commutative equality is implemented for `0` and `-0`");
+
+  equal(Object.equal(null, undefined), false, "`null` is not equal to `undefined`");
+  equal(Object.equal(undefined, null), false, "Commutative equality is implemented for `null` and `undefined`");
+
+  // String object and primitive comparisons.
+  equal(Object.equal("Curly", "Curly"), true, "Identical string primitives are equal");
+  equal(Object.equal(new String("Curly"), new String("Curly")), true, "String objects with identical primitive values are equal");
+
+  equal(Object.equal("Curly", "Larry"), false, "String primitives with different values are not equal");
+  equal(Object.equal(new String("Curly"), "Curly"), false, "String primitives and their corresponding object wrappers are not equal");
+  equal(Object.equal("Curly", new String("Curly")), false, "Commutative equality is implemented for string objects and primitives");
+  equal(Object.equal(new String("Curly"), new String("Larry")), false, "String objects with different primitive values are not equal");
+  equal(Object.equal(new String("Curly"), {toString: function(){ return "Curly"; }}), false, "String objects and objects with a custom `toString` method are not equal");
+
+  // Number object and primitive comparisons.
+  equal(Object.equal(75, 75), true, "Identical number primitives are equal");
+  equal(Object.equal(new Number(75), new Number(75)), true, "Number objects with identical primitive values are equal");
+
+  equal(Object.equal(75, new Number(75)), false, "Number primitives and their corresponding object wrappers are not equal");
+  equal(Object.equal(new Number(75), 75), false, "Commutative equality is implemented for number objects and primitives");
+  equal(Object.equal(new Number(75), new Number(63)), false, "Number objects with different primitive values are not equal");
+  equal(Object.equal(new Number(63), {valueOf: function(){ return 63; }}), false, "Number objects and objects with a `valueOf` method are not equal");
+
+
+  // Comparisons involving `NaN`.
+  equal(Object.equal(NaN, NaN), true, "`NaN` is equal to `NaN`");
+  equal(Object.equal(61, NaN), false, "A number primitive is not equal to `NaN`");
+  equal(Object.equal(new Number(79), NaN), false, "A number object is not equal to `NaN`");
+  equal(Object.equal(Infinity, NaN), false, "`Infinity` is not equal to `NaN`");
+
+  // Boolean object and primitive comparisons.
+  equal(Object.equal(true, true), true, "Identical boolean primitives are equal");
+  equal(Object.equal(new Boolean, new Boolean), true, "Boolean objects with identical primitive values are equal");
+  equal(Object.equal(true, new Boolean(true)), false, "Boolean primitives and their corresponding object wrappers are not equal");
+  equal(Object.equal(new Boolean(true), true), false, "Commutative equality is implemented for booleans");
+  equal(Object.equal(new Boolean(true), new Boolean), false, "Boolean objects with different primitive values are not equal");
+
+  // Common type coercions.
+  equal(Object.equal(true, new Boolean(false)), false, "Boolean objects are not equal to the boolean primitive `true`");
+  equal(Object.equal("75", 75), false, "String and number primitives with like values are not equal");
+  equal(Object.equal(new Number(63), new String(63)), false, "String and number objects with like values are not equal");
+  equal(Object.equal(75, "75"), false, "Commutative equality is implemented for like string and number values");
+  equal(Object.equal(0, ""), false, "Number and string primitives with like values are not equal");
+  equal(Object.equal(1, true), false, "Number and boolean primitives with like values are not equal");
+  equal(Object.equal(new Boolean(false), new Number(0)), false, "Boolean and number objects with like values are not equal");
+  equal(Object.equal(false, new String("")), false, "Boolean primitives and string objects with like values are not equal");
+  equal(Object.equal(1256428800000, new Date(Date.UTC(2009, 9, 25))), false, "Dates and their corresponding numeric primitive values are not equal");
+
+  // Dates.
+  equal(Object.equal(new Date(2009, 9, 25), new Date(2009, 9, 25)), true, "Date objects referencing identical times are equal");
+  equal(Object.equal(new Date(2009, 9, 25), new Date(2009, 9, 25, 0, 0, 0, 1)), false, "Date objects 1ms apart");
+  equal(Object.equal(new Date(2009, 9, 25), new Date(2009, 11, 13)), false, "Date objects referencing different times are not equal");
+  equal(Object.equal(new Date(2009, 11, 13), {
+    getTime: function(){
+      return 12606876e5;
+    }
+  }), false, "Date objects and objects with a `getTime` method are not equal");
+  equal(Object.equal(new Date("Curly"), new Date("Curly")), true, "Invalid dates are equal");
+
+  // Functions.
+  equal(Object.equal(First, Second), false, "Different functions with identical bodies and source code representations are not equal");
+
+  // RegExps.
+  equal(Object.equal(/(?:)/gim, /(?:)/gim), true, "RegExps with equivalent patterns and flags are equal");
+  equal(Object.equal(/(?:)/g, /(?:)/gi), false, "RegExps with equivalent patterns and different flags are not equal");
+  equal(Object.equal(/Moe/gim, /Curly/gim), false, "RegExps with different patterns and equivalent flags are not equal");
+  equal(Object.equal(/(?:)/gi, /(?:)/g), false, "Commutative equality is implemented for RegExps");
+  equal(Object.equal(/Curly/g, {source: "Larry", global: true, ignoreCase: false, multiline: false}), false, "RegExps and RegExp-like objects are not equal");
+
+  // Empty arrays, array-like objects, and object literals.
+  equal(Object.equal({}, {}), true, "Empty object literals are equal");
+  equal(Object.equal([], []), true, "Empty array literals are equal");
+  equal(Object.equal([{}], [{}]), true, "Empty nested arrays and objects are equal");
+  equal(Object.equal({length: 0}, []), false, "Array-like objects and arrays are not equal.");
+  equal(Object.equal([], {length: 0}), false, "Commutative equality is implemented for array-like objects");
+
+  equal(Object.equal({}, []), false, "Object literals and array literals are not equal");
+  equal(Object.equal([], {}), false, "Commutative equality is implemented for objects and arrays");
+  equal(Object.equal((function(){ return arguments; })(), (function(){ return arguments; })()), true, "Empty arguments objects are equal");
+
+  // Arrays with primitive and object values.
+  equal(Object.equal([1, true, "Larry", true], [1, true, "Larry", true]), true, "Arrays containing identical primitives are equal");
+  equal(Object.equal([/Moe/g, new Date(2009, 9, 25)], [/Moe/g, new Date(2009, 9, 25)]), true, "Arrays containing equivalent elements are equal");
+
+  // Multi-dimensional arrays.
+  var a = [new Number(47), false, true, "Larry", /Moe/, new Date(2009, 11, 13), ['running', 'biking', new String('programming')], {a: 47}];
+  var b = [new Number(47), false, true, "Larry", /Moe/, new Date(2009, 11, 13), ['running', 'biking', new String('programming')], {a: 47}];
+  equal(Object.equal(a, b), true, "Arrays containing nested arrays and objects are recursively compared");
+
+  // Overwrite the methods defined in ES 5.1 section 15.4.4.
+  a.forEach = a.map = a.filter = a.every = a.indexOf = a.lastIndexOf = a.some = a.reduce = a.reduceRight = null;
+  b.pop = b.reverse = b.shift = b.slice = b.splice = b.concat = b.sort = b.unshift = null;
+
+  // Array elements and properties.
+  equal(Object.equal(a, b), true, "Arrays containing equivalent elements and different non-numeric properties are equal");
+  a.push("White Rocks");
+  equal(Object.equal(a, b), false, "Arrays of different lengths are not equal");
+  a.push("East Boulder");
+  b.push("Gunbarrel Ranch", true, "Teller Farm");
+  equal(Object.equal(a, b), false, "Arrays of identical lengths containing different elements are not equal");
+
+  // Sparse arrays.
+  equal(Object.equal(Array(3), Array(3)), true, "Sparse arrays of identical lengths are equal");
+  equal(Object.equal(Array(3), Array(6)), false, "Sparse arrays of different lengths are not equal when both are empty");
+
+  // According to the Microsoft deviations spec, section 2.1.26, JScript 5.x treats `undefined`
+  // elements in arrays as elisions. Thus, sparse arrays and dense arrays containing `undefined`
+  // values are equivalent.
+  if (0 in [undefined]) {
+    equal(Object.equal(Array(3), [undefined, undefined, undefined]), true, "Sparse and dense arrays are equal");
+    equal(Object.equal([undefined, undefined, undefined], Array(3)), true, "Commutative equality is implemented for sparse and dense arrays");
+  }
+
+  // Simple objects.
+  equal(Object.equal({a: "Curly", b: 1, c: true}, {a: "Curly", b: 1, c: true}), true, "Objects containing identical primitives are equal");
+  equal(Object.equal({a: /Curly/g, b: new Date(2009, 11, 13)}, {a: /Curly/g, b: new Date(2009, 11, 13)}), true, "Objects containing equivalent members are equal");
+  equal(Object.equal({a: 63, b: 75}, {a: 61, b: 55}), false, "Objects of identical sizes with different values are not equal");
+  equal(Object.equal({a: 63, b: 75}, {a: 61, c: 55}), false, "Objects of identical sizes with different property names are not equal");
+  equal(Object.equal({a: 1, b: 2}, {a: 1}), false, "Objects of different sizes are not equal");
+  equal(Object.equal({a: 1}, {a: 1, b: 2}), false, "Commutative equality is implemented for objects");
+  equal(Object.equal({x: 1, y: undefined}, {x: 1, z: 2}), false, "Objects with identical keys and different values are not equivalent");
+
+  // `A` contains nested objects and arrays.
+  a = {
+    name: new String("Moe Howard"),
+    age: new Number(77),
+    stooge: true,
+    hobbies: ["acting"],
+    film: {
+      name: "Sing a Song of Six Pants",
+      release: new Date(1947, 9, 30),
+      stars: [new String("Larry Fine"), true, "Shemp Howard"],
+      minutes: new Number(16),
+      seconds: 54
+    }
+  };
+
+  // `B` contains equivalent nested objects and arrays.
+  b = {
+    name: new String("Moe Howard"),
+    age: new Number(77),
+    stooge: true,
+    hobbies: ["acting"],
+    film: {
+      name: "Sing a Song of Six Pants",
+      release: new Date(1947, 9, 30),
+      stars: [new String("Larry Fine"), true, "Shemp Howard"],
+      minutes: new Number(16),
+      seconds: 54
+    }
+  };
+  equal(Object.equal(a, b), true, "Objects with nested equivalent members are recursively compared");
+
+  // Instances.
+  equal(Object.equal(new First, new First), false, "Object instances are equal");
+  equal(Object.equal(new First, new Second), false, "Objects with different constructors and identical own properties are not equal");
+  equal(Object.equal({value: 1}, new First), false, "Object instances and objects sharing equivalent properties are not identical");
+  equal(Object.equal({value: 2}, new Second), false, "The prototype chain of objects should not be examined");
+
+  // Circular Arrays.
+  (a = []).push(a);
+  (b = []).push(b);
+  equal(Object.equal(a, b), true, "Arrays containing circular references are equal");
+  a.push(new String("Larry"));
+  b.push(new String("Larry"));
+  equal(Object.equal(a, b), true, "Arrays containing circular references and equivalent properties are equal");
+  a.push("Shemp");
+  b.push("Curly");
+  equal(Object.equal(a, b), false, "Arrays containing circular references and different properties are not equal");
+
+  // Circular Objects.
+  a = {abc: null};
+  b = {abc: null};
+  a.abc = a;
+  b.abc = b;
+  equal(Object.equal(a, b), true, "Objects containing circular references are equal");
+  a.def = 75;
+  b.def = 75;
+  equal(Object.equal(a, b), true, "Objects containing circular references and equivalent properties are equal");
+  a.def = new Number(75);
+  b.def = new Number(63);
+  equal(Object.equal(a, b), false, "Objects containing circular references and different properties are not equal");
+
+  // Cyclic Structures.
+  a = [{abc: null}];
+  b = [{abc: null}];
+  (a[0].abc = a).push(a);
+  (b[0].abc = b).push(b);
+  equal(Object.equal(a, b), true, "Cyclic structures are equal");
+  a[0].def = "Larry";
+  b[0].def = "Larry";
+  equal(Object.equal(a, b), true, "Cyclic structures containing equivalent properties are equal");
+  a[0].def = new String("Larry");
+  b[0].def = new String("Curly");
+  equal(Object.equal(a, b), false, "Cyclic structures containing different properties are not equal");
+
+  // Complex Circular References.
+  a = {foo: {b: {foo: {c: {foo: null}}}}};
+  b = {foo: {b: {foo: {c: {foo: null}}}}};
+  a.foo.b.foo.c.foo = a;
+  b.foo.b.foo.c.foo = b;
+  equal(Object.equal(a, b), true, "Cyclic structures with nested and identically-named properties are equal");
+
+
+  // Custom `isEqual` methods.
+  var isEqualObj = {isEqual: function (o) { return o.isEqual == this.isEqual; }, unique: {}};
+  var isEqualObjClone = {isEqual: isEqualObj.isEqual, unique: {}};
+
+  equal(Object.equal(isEqualObj, isEqualObjClone), true, 'Both objects implement identical `isEqual` methods');
+  equal(Object.equal(isEqualObjClone, isEqualObj), true, 'Commutative equality is implemented for objects with custom `isEqual` methods');
+  equal(Object.equal(isEqualObj, {}), false, 'Objects that do not implement equivalent `isEqual` methods are not equal');
+  equal(Object.equal({}, isEqualObj), false, 'Commutative equality is implemented for objects with different `isEqual` methods');
+
+  // Custom `isEqual` methods - comparing different types
+  /* CHANGED: Leaving the alone off for now
+  LocalizedString = (function() {
+    function LocalizedString(id) { this.id = id; this.string = (this.id===10)? 'Bonjour': ''; }
+    LocalizedString.prototype.isEqual = function(that) {
+      if (_.isString(that)) return this.string == that;
+      else if (that instanceof LocalizedString) return this.id == that.id;
+      return false;
+    };
+    return LocalizedString;
+  })();
+  var localized_string1 = new LocalizedString(10), localized_string2 = new LocalizedString(10), localized_string3 = new LocalizedString(11);
+  equal(Object.equal(localized_string1, localized_string2), true, 'comparing same typed instances with same ids');
+  equal(Object.equal(localized_string1, localized_string3), false, 'comparing same typed instances with different ids');
+  equal(Object.equal(localized_string1, 'Bonjour'), true, 'comparing different typed instances with same values');
+  equal(Object.equal('Bonjour', localized_string1), true, 'comparing different typed instances with same values');
+  equal(Object.equal('Bonjour', localized_string3), false, 'comparing two localized strings with different ids');
+  equal(Object.equal(localized_string1, 'Au revoir'), false, 'comparing different typed instances with different values');
+  equal(Object.equal('Au revoir', localized_string1), false, 'comparing different typed instances with different values');
+
+  // Custom `isEqual` methods - comparing with serialized data
+  Date.prototype.toJSON = function() {
+    return {
+      _type:'Date',
+      year:this.getUTCFullYear(),
+      month:this.getUTCMonth(),
+      day:this.getUTCDate(),
+      hours:this.getUTCHours(),
+      minutes:this.getUTCMinutes(),
+      seconds:this.getUTCSeconds()
+    };
+  };
+  Date.prototype.isEqual = function(that) {
+    var this_date_components = this.toJSON();
+    var that_date_components = (that instanceof Date) ? that.toJSON() : that;
+    delete this_date_components['_type']; delete that_date_components['_type']
+    return Object.equal(this_date_components, that_date_components);
+  };
+
+  var date = new Date();
+  var date_json = {
+    _type:'Date',
+    year:date.getUTCFullYear(),
+    month:date.getUTCMonth(),
+    day:date.getUTCDate(),
+    hours:date.getUTCHours(),
+    minutes:date.getUTCMinutes(),
+    seconds:date.getUTCSeconds()
+  };
+
+  equal(Object.equal(date_json, date), 'serialized date matches date');
+  equal(Object.equal(date, date_json), 'date matches serialized date');
+  */
+
+});
+
level2/node_modules/sugar/test/environments/sugar/es5.js
@@ -0,0 +1,838 @@
+
+test('ES5', function () {
+
+  var arr, count, expected, result, previous, current, fn, reg, obj, Person;
+
+  arr = [];
+  //arr[4294967295] = 'd';
+  // This will reset the array in < IE8. Modern browsers will ignore the element.
+  equal(arr.length, 0, 'Array internals will not allow more than a 32bit integer as a key. Anything higher will be ignored');
+
+
+
+  // Array.isArray
+
+
+  // all following calls return true
+  equal(Array.isArray([]), true, 'Array.isArray | empty array');
+  equal(Array.isArray([1]), true, 'Array.nArray | simple array');
+  equal(Array.isArray(new Array()), true, 'Array.nArray | new array with constructor');
+  equal(Array.isArray(Array.prototype), true, 'Array.nArray | Array.prototype'); // Little known fact: Array.prototype is itself an array.
+
+  // all following calls return false
+  equal(Array.isArray(), false, 'Array.nArray | no param');
+  equal(Array.isArray({}), false, 'Array.nArray | object');
+  equal(Array.isArray(null), false, 'Array.nArray | null');
+  equal(Array.isArray(undefined), false, 'Array.nArray | undefined');
+  equal(Array.isArray(17), false, 'Array.nArray | number');
+  equal(Array.isArray("Array"), false, 'Array.nArray | string');
+  equal(Array.isArray(true), false, 'Array.nArray | true');
+  equal(Array.isArray(false), false, 'Array.nArray | false');
+
+
+  // Array#forEach
+
+  arr = ['a','b','c'];
+
+  raisesError(function(){ [].forEach(); }, 'Array#forEach | should raise an error when no fn given');
+  result = arr.forEach(function(){
+    equal(this, nullScope, 'Array#forEach | scope should be undefined when not passed');
+  });
+  result = arr.forEach(function(){
+    equal(this.toString(), 'wasabi', 'Array#forEach | scope can be passed');
+  }, 'wasabi');
+  equal(result, undefined, 'Array#forEach | returns undefined');
+
+  arr[234] = 'd';
+  count = 0;
+  expected = ['a','b','c','d'];
+  arr.forEach(function(el, i, arr){
+    arr.push(3)
+    equal(el, expected[count], 'Array#forEach | elements should be as expected');
+    equal(typeof i, 'number', 'Array#forEach | i must be a number');
+    count++;
+  }, 'wasabi');
+
+  equal(count, 4, 'Array#forEach | will not visit elements that were added since beginning the loop or visit missing elements');
+
+  arr = ['a'];
+  arr[-3] = 'b';
+
+
+  // This will lock browsers, including native implementations. Sparse array
+  // optimizations are NOT in the ECMA spec, it would seem.
+  // arr[4294967294] = 'c';
+
+
+  arr[256] = 'd';
+  count = 0;
+  arr.forEach(function(el, i){
+    count++;
+  });
+
+  equal(count, 2, 'Array#forEach | will only visit elements with valid indexes');
+  equal(arr.length, 257, 'Array#forEach | "numerically greater than the name of every property whose name is an array index"');
+
+  arr.length = 50;
+  arr.forEach(function() {
+    count++;
+  });
+  equal(arr[4294967294], undefined, 'Array#forEach | setting the length property will delete all elements above that index');
+
+  arr = ['a','b','c'];
+  expected = ['a','x'];
+  count = 0;
+  arr.forEach(function(el, i){
+    if(i == 0) {
+      arr[1] = 'x';
+      delete arr[2];
+    }
+    equal(el, expected[count], 'Array#forEach | elements should be as expected');
+    count++;
+  });
+  equal(count, 2, 'Array#forEach | elements deleted after the loop begins should not be visited');
+
+  arr = [];
+  expected = ['moo'];
+  count = 0;
+  arr[2] = 'two';
+  arr['2'] = 'moo';
+  arr.forEach(function(el, i){
+    equal(el, expected[count], 'Array#forEach | strings and numbers are both the same for accessing array elements');
+    count++;
+  });
+
+  equal(count, 1, 'Array#forEach | setting array elements with a string is the same as with a number');
+
+
+  arr = [];
+  arr[2] = 'c';
+  arr[1] = 'b';
+  arr[0] = 'a';
+
+  result = [];
+  arr.forEach(function(el) {
+    result.push(el);
+  });
+  equal(result, ['a','b','c'], 'Array#forEach | walks array in order');
+
+
+
+  count = 0;
+  [1,2,3].forEach(function(el) {
+    count++;
+    return false;
+  });
+  equal(count, 3, 'Array#forEach | returning false will not break the loop');
+
+
+  count = 0;
+  arr = [1,2];
+  arr.push(undefined);
+  arr.push(3);
+  arr.forEach(function(el) {
+    count++;
+    return false;
+  });
+  equal(count, 4, 'Array#forEach | undefined members are counted');
+
+
+
+  // Array#indexOf
+
+  arr = [1,2,3];
+  arr[-2] = 4; // Throw a wrench in the gears by assigning a non-valid array index as object property.
+
+  equal(arr.indexOf(1), 0, 'Array#indexOf | finds 1');
+  equal(arr.indexOf(1) === 0, true, 'Array#indexOf | finds 1 and is result strictly equal');
+  equal(arr.indexOf(4), -1, 'Array#indexOf | does not find 4');
+  equal(arr.indexOf('1'), -1, 'Array#indexOf | Uses strict equality');
+  equal(arr.indexOf(2, 1), 1, 'Array#indexOf | from index 1');
+  equal(arr.indexOf(2, 2), -1, 'Array#indexOf | from index 2');
+  equal(arr.indexOf(2, 3), -1, 'Array#indexOf | from index 3');
+  equal(arr.indexOf(2, 4), -1, 'Array#indexOf | from index 4');
+  equal(arr.indexOf(3, -1), 2, 'Array#indexOf | from index -1');
+  equal(arr.indexOf(3, -2), 2, 'Array#indexOf | from index -2');
+  equal(arr.indexOf(3, -3), 2, 'Array#indexOf | from index -3');
+  equal(arr.indexOf(3, -4), 2, 'Array#indexOf | from index -4');
+
+  // These tests will by proxy be stress testing the toInteger internal private function.
+  equal(arr.indexOf(1, NaN), 0, 'Array#indexOf | index NaN becomes 0');
+  equal(arr.indexOf(1, true), -1, 'Array#indexOf | index true becomes 1');
+  equal(arr.indexOf(1, false), 0, 'Array#indexOf | index false becomes 0');
+  equal(arr.indexOf(1, 0.1), 0, 'Array#indexOf | index 0.1 becomes 0');
+  equal(arr.indexOf(1, 1.1), -1, 'Array#indexOf | index 1.1 becomes 1');
+  equal(arr.indexOf(3, -0.1), 2, 'Array#indexOf | index -0.1 becomes 0');
+  equal(arr.indexOf(3, -1.1), 2, 'Array#indexOf | index -1.1 becomes -1');
+  equal(arr.indexOf(1, 1.7), -1, 'Array#indexOf | index 1.7 becomes 1');
+  equal(arr.indexOf(3, -1.7), 2, 'Array#indexOf | index -1.7 becomes -1');
+
+
+  fn  = function(){};
+  reg = /arf/;
+  obj = { moo: 'cow' };
+
+  equal([fn].indexOf(fn), 0, 'Array#indexOf | finds function references');
+  equal([reg].indexOf(reg), 0, 'Array#indexOf | finds regex references');
+  equal([obj].indexOf(obj), 0, 'Array#indexOf | finds object references');
+
+  arr = [];
+  arr[2] = 'c';
+  arr[1] = 'c';
+  arr[0] = 'c';
+
+  equal(arr.indexOf('c'), 0, 'Array#indexOf | walks array in order');
+  equal(Array.prototype.indexOf.call('moo', 'o'), 1, 'Array#indexOf | should work on strings as well');
+
+  arr = [];
+  arr[3] = 'a';
+
+  equal(arr.indexOf('a'), 3, 'Array#indexOf | must work on sparse arrays as well');
+
+  // Although Infinity appears to be allowable in the ECMA spec, both of these cases
+  // would appear to kill all modern browsers.
+  // equal(arr.indexOf(1, Infinity), -1, 'Array#indexOf | infinity is valid');  This locks the browser... should it??
+  // equal(arr.indexOf(1, -Infinity), 0, 'Array#indexOf | -infinity is valid');
+
+
+  // Array#lastIndexOf
+
+  arr = ['a', 1, 'a'];
+  arr[-2] = 'a'; // Throw a wrench in the gears by assigning a non-valid array index as object property.
+
+  equal(arr.lastIndexOf('a'), 2, 'Array#lastIndexOf | finds a');
+  equal(arr.lastIndexOf('a') === 2, true, 'Array#lastIndexOf | finds a and is result strictly equal');
+  equal(arr.lastIndexOf('c'), -1, 'Array#lastIndexOf | does not find c');
+  equal(arr.lastIndexOf('1'), -1, 'Array#lastIndexOf | Uses strict equality');
+  equal(arr.lastIndexOf('a', 1), 0, 'Array#lastIndexOf | from index 1');
+  equal(arr.lastIndexOf('a', 2), 2, 'Array#lastIndexOf | from index 2');
+  equal(arr.lastIndexOf('a', 3), 2, 'Array#lastIndexOf | from index 3');
+  equal(arr.lastIndexOf('a', 4), 2, 'Array#lastIndexOf | from index 4');
+  equal(arr.lastIndexOf('a', 0), 0, 'Array#lastIndexOf | from index 0');
+  equal(arr.lastIndexOf('a', -1), 2, 'Array#lastIndexOf | from index -1');
+  equal(arr.lastIndexOf('a', -2), 0, 'Array#lastIndexOf | from index -2');
+  equal(arr.lastIndexOf('a', -3), 0, 'Array#lastIndexOf | from index -3');
+  equal(arr.lastIndexOf('a', -4), -1, 'Array#lastIndexOf | from index -4');
+
+  fn  = function(){};
+  reg = /arf/;
+  obj = { moo: 'cow' };
+
+  equal([fn].lastIndexOf(fn), 0, 'Array#lastIndexOf | finds function references');
+  equal([reg].lastIndexOf(reg), 0, 'Array#lastIndexOf | finds regex references');
+  equal([obj].lastIndexOf(obj), 0, 'Array#lastIndexOf | finds object references');
+
+  arr = [];
+  arr[2] = 'c';
+  arr[1] = 'c';
+  arr[0] = 'c';
+
+  equal(arr.lastIndexOf('c'), 2, 'Array#lastIndexOf | walks array in order');
+  equal(Array.prototype.lastIndexOf.call('moo', 'o'), 2, 'Array#lastIndexOf | should work on strings as well');
+
+  arr = ['c'];
+  arr[3] = 'a';
+
+  equal(arr.lastIndexOf('c'), 0, 'Array#lastIndexOf | must work on sparse arrays as well');
+
+
+   // Array#every
+
+  raisesError(function(){ [].every(); }, 'Array#every | should raise an error when no first param');
+  result = arr.every(function(){
+    equal(this, nullScope, 'Array#every | scope should be undefined when not passed');
+  });
+  [1].every(function(){
+    equal(this.toString(), 'wasabi', 'Array#every | scope can be passed');
+  }, 'wasabi');
+  [1].every(function(){
+    equal(this.toString(), '', 'Array#every | scope can be falsy');
+  }, '');
+  equal([].every(function(){ return true; }), true, 'Array#every | empty arrays will always be true');
+  equal([].every(function(){ return false; }), true, 'Array#every | empty arrays will always be true even when false returned');
+  equal([1].every(function(){ return 1; }), true, 'Array#every | 1 coerced to true');
+  equal([1].every(function(){ return 0; }), false, 'Array#every | 0 coerced to false');
+  equal([1].every(function(){ return 'blah'; }), true, 'Array#every | non-null string coerced to true');
+  equal([1].every(function(){ return ''; }), false, 'Array#every | blank string coerced to false');
+
+  arr = ['c','c','c'];
+  count = 0;
+  result = arr.every(function(el, i, a){
+    equal(el, 'c', 'Array#every | first argument is element');
+    equal(i, count, 'Array#every | second argument is index');
+    equal(a, arr, 'Array#every | third argument is the array');
+    count++;
+    return el == 'c';
+  });
+  equal(result, true, 'Array#every | all are c');
+  equal(count, 3, 'Array#every | should have been called 3 times');
+
+
+  arr = ['a','b','c'];
+  count = 0;
+  result = arr.every(function(el){
+    count++;
+    return el == 'c';
+  });
+  equal(result, false, 'Array#every | not all are c');
+  equal(count, 1, 'Array#every | should stop once it can return false');
+
+
+  arr = [];
+  arr[247] = 'a';
+  count = 0;
+  result = arr.every(function(el){
+    count++;
+    return el == 'a';
+  });
+  equal(result, true, 'Array#every | sparse arrays should not count missing elements');
+  equal(count, 1, 'Array#every | sparse arrays should have called once only');
+
+
+  arr = ['a','b','c'];
+  expected = ['a','x'];
+  count = 0;
+  arr.every(function(el, i){
+    if(i == 0) {
+      arr[1] = 'x';
+      delete arr[2];
+    }
+    equal(el, expected[count], 'Array#every | elements should be as expected');
+    count++;
+    return true;
+  });
+  equal(count, 2, 'Array#every | elements deleted after the loop begins should not be visited');
+
+
+
+
+
+  // Array#some
+
+  raisesError(function(){ [].some(); }, 'Array#some | should raise an error when no first param');
+  result = arr.some(function(){
+    equal(this, nullScope, 'Array#some | scope should be undefined when not passed');
+  });
+  [1].some(function(){
+    equal(this.toString(), 'wasabi', 'Array#some | scope can be passed');
+  }, 'wasabi');
+  [1].some(function(){
+    equal(this.toString(), '', 'Array#some | scope can be falsy');
+  }, '');
+  equal([].some(function(){ return true; }), false, 'Array#some | empty arrays will always be false');
+  equal([].some(function(){ return false; }), false, 'Array#some | empty arrays will always be false even when false returned');
+  equal([1].some(function(){ return 1; }), true, 'Array#some | 1 coerced to true');
+  equal([1].some(function(){ return 0; }), false, 'Array#some | 0 coerced to false');
+  equal([1].some(function(){ return 'blah'; }), true, 'Array#some | non-null string coerced to true');
+  equal([1].some(function(){ return ''; }), false, 'Array#some | blank string coerced to false');
+
+  arr = ['c','c','c'];
+  count = 0;
+  result = arr.some(function(el, i, a){
+    equal(el, 'c', 'Array#some | first argument is element');
+    equal(i, count, 'Array#some | second argument is index');
+    equal(a, arr, 'Array#some | third argument is the array');
+    count++;
+    return el == 'c';
+  });
+  equal(result, true, 'Array#some | some are c');
+  equal(count, 1, 'Array#some | should stop as soon as it finds an element');
+
+
+  arr = ['a','b','c'];
+  count = 0;
+  result = arr.some(function(el){
+    count++;
+    return el == 'd';
+  });
+  equal(result, false, 'Array#some | none are d');
+  equal(count, 3, 'Array#some | should have been called 3 times');
+
+
+  arr = [];
+  arr[247] = 'a';
+  count = 0;
+  result = arr.some(function(el){
+    count++;
+    return el == 'a';
+  });
+
+  equal(result, true, 'Array#some | sparse arrays should not count missing elements');
+  equal(count, 1, 'Array#some | sparse arrays should have called once only');
+
+
+  arr = ['a','b','c'];
+  expected = ['a','x'];
+  count = 0;
+  arr.some(function(el, i){
+    if(i == 0) {
+      arr[1] = 'x';
+      delete arr[2];
+    }
+    equal(el, expected[count], 'Array#some | elements should be as expected');
+    count++;
+    return false;
+  });
+  equal(count, 2, 'Array#some | elements deleted after the loop begins should not be visited');
+
+
+
+
+  // Array#map
+
+  raisesError(function(){ [].map(); }, 'Array#map | should raise an error when no first param');
+  result = arr.map(function(){
+    equal(this, nullScope, 'Array#map | scope should be undefined when not passed');
+  });
+  [1].map(function(){
+    equal(this.toString(), 'wasabi', 'Array#map | scope can be passed');
+  }, 'wasabi');
+  [1].map(function(){
+    equal(this.toString(), '', 'Array#map | scope can be falsy');
+  }, '');
+  [1].map(function(){
+    equal(Number(this), 0, 'Array#map | scope can be a number');
+  }, 0);
+
+  arr = ['c','c','c'];
+  count = 0;
+  result = arr.map(function(el, i, a){
+    equal(el, 'c', 'Array#map | first argument is element');
+    equal(i, count, 'Array#map | second argument is index');
+    equal(a, arr, 'Array#map | third argument is the array');
+    count++;
+    return 'a';
+  });
+  equal(result, ['a','a','a'], 'Array#map | mapped to a');
+  equal(count, 3, 'Array#map | should have run 3 times');
+
+
+  arr = [1,2,3];
+  count = 0;
+  result = arr.map(function(el){
+    return Math.pow(el, 2);
+  });
+  equal(result, [1,4,9], 'Array#map | n^2');
+
+
+  arr = [];
+  arr[247] = 'a';
+  count = 0;
+  result = arr.map(function(el){
+    count++;
+    return 'c';
+  });
+  equal(result.length, 248, 'Array#map | resulting array should also be sparse if source was');
+  equal(count, 1, 'Array#map | callback should only have been called once');
+
+
+  arr = ['a','b','c'];
+  expected = ['a','x'];
+  count = 0;
+  arr.map(function(el, i){
+    if(i == 0) {
+      arr[1] = 'x';
+      delete arr[2];
+    }
+    equal(el, expected[count], 'Array#map | elements should be as expected');
+    count++;
+  });
+  equal(count, 2, 'Array#map | elements deleted after the loop begins should not be visited');
+
+
+
+
+
+  // Array#filter
+
+  raisesError(function(){ [].filter(); }, 'Array#filter | should raise an error when no first param');
+  result = arr.filter(function(){
+    equal(this, nullScope, 'Array#filter | scope should be undefined when not passed');
+  });
+  [1].filter(function(){
+    equal(this.toString(), 'wasabi', 'Array#filter | scope can be passed');
+  }, 'wasabi');
+  [1].filter(function(){
+    equal(this.toString(), '', 'Array#filter | scope can be falsy');
+  }, '');
+  equal([].filter(function(){ return true; }), [], 'Array#filter | empty arrays will always be []');
+  equal([].filter(function(){ return false; }), [], 'Array#filter | empty arrays will always be [] even when false returned');
+  equal([1].filter(function(){ return 1; }), [1], 'Array#filter | 1 coerced to true');
+  equal([1].filter(function(){ return 0; }), [], 'Array#filter | 0 coerced to false');
+  equal([1].filter(function(){ return 'blah'; }), [1], 'Array#filter | non-null string coerced to true');
+  equal([1].filter(function(){ return ''; }), [], 'Array#filter | blank string coerced to false');
+
+  arr = ['c','c','c'];
+  count = 0;
+  result = arr.filter(function(el, i, a){
+    equal(el, 'c', 'Array#filter | first argument is element');
+    equal(i, count, 'Array#filter | second argument is index');
+    equal(a, arr, 'Array#filter | third argument is the array');
+    count++;
+    return el == 'c';
+  });
+  equal(result, ['c','c','c'], 'Array#filter | filter are c');
+  equal(count, 3, 'Array#filter | should have executed 3 times');
+
+
+  arr = ['a','b','c'];
+  count = 0;
+  result = arr.filter(function(el){
+    count++;
+    return el == 'b';
+  });
+  equal(result, ['b'], 'Array#filter | returns [b]');
+  equal(count, 3, 'Array#filter | should have been called 3 times');
+
+
+  arr = [];
+  arr[247] = 'a';
+  count = 0;
+  result = arr.filter(function(el){
+    count++;
+    return true;
+  });
+  equal(result, ['a'], 'Array#filter | sparse arrays should not count missing elements');
+  equal(count, 1, 'Array#filter | sparse arrays should have called once only');
+
+
+  arr = ['a','b','c'];
+  expected = ['a','x'];
+  count = 0;
+  result = arr.filter(function(el, i){
+    if(i == 0) {
+      arr[1] = 'x';
+      delete arr[2];
+    }
+    equal(el, expected[count], 'Array#filter | elements should be as expected');
+    count++;
+    return true;
+  });
+  equal(result, ['a','x'], 'Array#filter | modified array should appear as the result');
+  equal(count, 2, 'Array#filter | elements deleted after the loop begins should not be visited');
+
+
+  // Array#reduce
+
+  raisesError(function(){ [1].reduce(); }, 'Array#reduce | should raise an error when no callback provided');
+  raisesError(function(){ [].reduce(function(){}); }, 'Array#reduce | should raise an error on an empty array with no initial value');
+  [1].reduce(function(){
+    equal(this, nullScope, 'Array#reduce | scope should be undefined');
+  }, 1);
+
+
+  arr = [1,2,3];
+  previous = [1,3];
+  current = [2,3];
+  count = 0;
+
+  result = arr.reduce(function(prev, el, i, o){
+    equal(prev, previous[count], 'Array#reduce | first argument is the prev value');
+    equal(el, current[count], 'Array#reduce | second argument is element');
+    equal(i, count + 1, 'Array#reduce | third argument is index');
+    equal(o, arr, 'Array#reduce | fourth argument is the array');
+    count++;
+    return prev + el;
+  });
+
+  equal(result, 6, 'Array#reduce | result is correct');
+  equal(count, 2, 'Array#reduce | should have been called 3 times');
+
+
+  equal([1].reduce(function(){ return 324242; }), 1, 'Array#reduce | function is not called and returns 1');
+
+  count = 0;
+  [1].reduce(function(prev, current, i) {
+    equal(prev, 5, 'Array#reduce | prev is equal to the inital value if it is provided');
+    equal(current, 1, 'Array#reduce | current is equal to the first value in the array if no intial value provided');
+    equal(i, 0, 'Array#reduce | i is 0 when an initial value is passed');
+    count++;
+  }, 5);
+  equal(count, 1, 'Array#reduce | should have been called once');
+
+  arr = ['a','b','c'];
+  previous = ['a','ab'];
+  current  = ['b','c'];
+  count = 0;
+  result = arr.reduce(function(prev, el, i){
+    if(i == 0) {
+      arr[1] = 'x';
+      delete arr[2];
+    }
+    equal(prev, previous[count], 'Array#reduce | previous should be as expected');
+    equal(el, current[count], 'Array#reduce | current should be as expected');
+    count++;
+    return prev + el;
+  });
+  equal(count, 2, 'Array#reduce | elements deleted after the loop begins should not be visited');
+
+  equal([1,2,3].reduce(function(a, n){ return a + n; }, 0), 6, 'Array#reduce | can handle initial value of 0');
+
+
+
+
+
+  // Array#reduceRight
+
+  raisesError(function(){ [1].reduceRight(); }, 'Array#reduceRight | should raise an error when no callback provided');
+  raisesError(function(){ [].reduceRight(function(){}); }, 'Array#reduceRight | should raise an error on an empty array with no initial value');
+  [1].reduceRight(function(){
+    equal(this, nullScope, 'Array#reduceRight | scope should be undefined');
+  }, 1);
+
+
+  arr = [1,2,3];
+  previous = [3,5];
+  current = [2,1];
+  count = 0;
+
+  result = arr.reduceRight(function(prev, el, i, o){
+    equal(prev, previous[count], 'Array#reduceRight | first argument is the prev value');
+    equal(el, current[count], 'Array#reduceRight | second argument is element');
+    equal(i, 1 - count, 'Array#reduceRight | third argument is index');
+    equal(o, arr, 'Array#reduceRight | fourth argument is the array');
+    count++;
+    return prev + el;
+  });
+
+  equal(result, 6, 'Array#reduceRight | result is correct');
+  equal(count, 2, 'Array#reduceRight | should have been called 3 times');
+
+
+  equal([1].reduceRight(function(){ return 324242; }), 1, 'Array#reduceRight | function is not called and returns 1');
+
+  count = 0;
+  [1].reduceRight(function(prev, current, i) {
+    equal(prev, 5, 'Array#reduceRight | prev is equal to the inital value if it is provided');
+    equal(current, 1, 'Array#reduceRight | current is equal to the first value in the array if no intial value provided');
+    equal(i, 0, 'Array#reduceRight | i is 0 when an initial value is passed');
+    count++;
+  }, 5);
+  equal(count, 1, 'Array#reduceRight | should have been called once');
+
+  arr = ['a','b','c'];
+  previous = ['c','cb'];
+  current  = ['b','a'];
+  count = 0;
+  result = arr.reduceRight(function(prev, el, i){
+    if(i == 0) {
+      arr[1] = 'x';
+      delete arr[2];
+    }
+    equal(prev, previous[count], 'Array#reduceRight | previous should be as expected');
+    equal(el, current[count], 'Array#reduceRight | current should be as expected');
+    count++;
+    return prev + el;
+  });
+  equal(count, 2, 'Array#reduceRight | elements deleted after the loop begins should not be visited');
+
+  equal([1,2,3].reduceRight(function(a, n){ return a + n; }, 0), 6, 'Array#reduceRight | can handle initial value of 0');
+
+
+
+  // Polyfills can be run on objects that inherit from Arrays
+
+  var Soup = function(){};
+  Soup.prototype = [1,2,3];
+
+  var x = new Soup();
+
+  count = 0;
+  x.every(function() {
+    count++;
+    return true;
+  });
+  x.some(function() {
+    count++;
+  });
+  x.map(function() {
+    count++;
+  });
+  x.filter(function() {
+    count++;
+  });
+  x.forEach(function() {
+    count++;
+  });
+  x.reduce(function() {
+    count++;
+  });
+  x.reduceRight(function() {
+    count++;
+  });
+
+  equal(count, 19, 'Array | array elements in the prototype chain are also properly iterated');
+  equal(x.indexOf(2), 1, 'Array | indexOf | array elements in the prototype chain are also properly iterated');
+  equal(x.lastIndexOf(2), 1, 'Array | lastIndexOf | array elements in the prototype chain are also properly iterated');
+
+
+  // String#trim
+
+  var whiteSpace = '\u0009\u000B\u000C\u0020\u00A0\uFEFF\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000';
+  var lineTerminators = '\u000A\u000D\u2028\u2029';
+
+  equal(whiteSpace.trim(), '', 'String#trim | should trim all WhiteSpace characters defined in 7.2 and Unicode "space, separator and Unicode "space, separator""');
+  equal(lineTerminators.trim(), '', 'String#trim | should trim all LineTerminator characters defined in 7.3');
+
+
+
+  // String#trimLeft (non standard)
+
+  equal(whiteSpace.trimLeft(), '', 'String#trimLeft | should trim all WhiteSpace characters defined in 7.2 and Unicode "space, separator"');
+  equal(lineTerminators.trimLeft(), '', 'String#trimLeft | should trim all LineTerminator characters defined in 7.3');
+
+
+
+  // String#trimRight (non standard)
+
+  equal(whiteSpace.trimRight(), '', 'String#trimRight | should trim all WhiteSpace characters defined in 7.2 and Unicode "space, separator"');
+  equal(lineTerminators.trimRight(), '', 'String#trimRight | should trim all LineTerminator characters defined in 7.3');
+
+
+
+  equal(String.prototype.trim.call([1]), '1', 'String#trim | should handle objects as well');
+
+
+
+
+
+  // Object#keys
+
+  raisesError(function(){ Object.keys(undefined); }, 'Object#keys | raises a TypeError for undefined');
+  raisesError(function(){ Object.keys(null); }, 'Object#keys | raises a TypeError for null');
+  raisesError(function(){ Object.keys(true); }, 'Object#keys | raises a TypeError for booleans');
+  raisesError(function(){ Object.keys(NaN); }, 'Object#keys | raises a TypeError for NaN');
+  raisesError(function(){ Object.keys(3); }, 'Object#keys | raises a TypeError for numbers');
+  raisesError(function(){ Object.keys('moofa'); }, 'Object#keys | raises a TypeError for strings');
+
+
+  equal(Object.keys({ moo:'bar', broken:'wear' }), ['moo','broken'], 'Object#keys | returns keys of an object');
+  equal(Object.keys(['a','b','c']), ['0','1','2'], 'Object#keys | returns indexes of an array');
+  equal(Object.keys(/foobar/), [], 'Object#keys | regexes return a blank array');
+  equal(Object.keys(function(){}), [], 'Object#keys | functions return a blank array');
+  equal(Object.keys(new Date), [], 'Object#keys | dates return a blank array');
+
+  Person = function() {
+    this.broken = 'wear';
+  };
+  Person.prototype = { cat: 'dog' };
+
+  equal(Object.keys(new Person), ['broken'], 'Object#keys | will get instance properties but not inherited properties');
+
+
+  // Date.now (support may be missing if only core is included)
+
+  if(Date.now) {
+    equalWithMargin(Date.now(), new Date().getTime(), 5, 'Date#now | basic functionality');
+  }
+
+
+  // Date.parse
+
+  // Returns 807937200000 in time zone GMT-0300, and other values in other
+  // timezones, since the argument does not specify a time zone.
+  equal(Date.parse("Aug 9, 1995"), new Date(1995, 7, 9).getTime(), 'Date#parse | No timezone');
+  // Returns 807926400000 no matter the local time zone.
+  equal(Date.parse("Wed, 09 Aug 1995 00:00:00 GMT"), new Date(807926400000).getTime(), 'Date#parse | GMT');
+  // Returns 807937200000 in timezone GMT-0300, and other values in other
+  // timezones, since there is no time zone specifier in the argument.
+  equal(Date.parse("Wed, 09 Aug 1995 00:00:00"), new Date(1995, 7, 9).getTime(), 'Date#parse | No timezone with time');
+  equal(Date.parse("Thu, 09 Aug 1995 00:00:00 GMT-0400"), 807940800000, 'Date#parse | 1995/7/9 GMT-04:00');
+  // Returns 0 no matter the local time zone.
+  equal(Date.parse("Thu, 01 Jan 1970 00:00:00 GMT"), 0, 'Date#parse | 1970/1/1 GMT');
+
+  // Note: Avoiding non GMT dates around the epoch as they tend to be unreliable.
+  // Returns 14400000 in timezone GMT-0400, and other values in other
+  // timezones, since there is no time zone specifier in the argument.
+  // equal(Date.parse("Thu, 01 Jan 1970 00:00:00"), (new Date).getTimezoneOffset().minutes(), 'Date#parse | 1970/1/1 Local');
+  // Returns 14400000 no matter the local time zone.
+  // equal(Date.parse("Thu, 01 Jan 1970 00:00:00 GMT-0400"), new Date(1995, 7, 9).getTime(), 'Date#parse | 1970/1/1 GMT-04:00');
+
+
+
+
+  if(Date.prototype.toISOString) {
+
+    // Date#toISOString (support may be missing if only core is included)
+
+    equal(new Date(Date.UTC(2000, 0, 1)).toISOString(), '2000-01-01T00:00:00.000Z', 'Date#toISOString | new millenium!');
+    equal(new Date(Date.UTC(1978, 7, 25)).toISOString(), '1978-08-25T00:00:00.000Z', 'Date#toISOString | happy birthday!');
+    equal(new Date(Date.UTC(1978, 7, 25, 11, 45, 33, 456)).toISOString(), '1978-08-25T11:45:33.456Z', 'Date#toISOString | with time');
+
+
+    // Date#toJSON
+    // Essentially just an ISO string. Add more tests as needed.
+
+    equal(new Date(2002, 7, 25).toJSON(), new Date(2002, 7, 25).toISOString(), 'Date#toJSON | output');
+
+  }
+
+  // Function#bind
+
+  var instance, BoundPerson;
+
+  raisesError(function(){ Function.prototype.bind.call('mooo'); }, 'Function#bind | Raises an error when used on anything un-callable');
+  raisesError(function(){ Function.prototype.bind.call(/mooo/); }, 'Function#bind | Regexes are functions in chrome');
+
+  equal((function(){ return this; }).bind('yellow')().toString(), 'yellow', 'Function#bind | basic binding of this arg');
+  equal((function(){ return arguments[0]; }).bind('yellow', 'mellow')(), 'mellow', 'Function#bind | currying argument 1');
+  equal((function(){ return arguments[1]; }).bind('yellow', 'mellow', 'fellow')(), 'fellow', 'Function#bind | currying argument 2');
+  equal((function(){ return this; }).bind(undefined)(), nullScope, 'Function#bind | passing undefined as the scope');
+
+  (function(a, b){
+    equal(this.toString(), 'yellow', 'Function#bind | ensure only one call | this object');
+    equal(a, 'mellow', 'Function#bind | ensure only one call | argument 1');
+    equal(b, 'fellow', 'Function#bind | ensure only one call | argument 2');
+  }).bind('yellow', 'mellow', 'fellow')();
+
+  // It seems this functionality can't be achieved in a JS polyfill...
+  // equal((function(){}).bind().prototype, undefined, 'Function#bind | currying argument 2'); 
+
+  Person = function(a, b) {
+    this.first = a;
+    this.second = b;
+  };
+
+  BoundPerson = Person.bind({ mellow: 'yellow' }, 'jump');
+  instance = new BoundPerson('jumpy');
+
+  equal(instance.mellow, undefined, 'Function#bind | passed scope is ignored when used with the new keyword');
+  equal(instance.first, 'jump', 'Function#bind | curried argument makes it to the constructor');
+  equal(instance.second, 'jumpy', 'Function#bind | argument passed to the constructor makes it in as the second arg');
+  equal(instance instanceof Person, true, 'Function#bind | instance of the class');
+
+
+  // Node has a discrepancy in its implementation of instanceof, where it will throw a
+  // TypeError when the RHS has no prototype object. Looking at the spec it appears that
+  // this may in fact be correct behavior, but no other environment does this. Also note
+  // that the ONLY way I can see to create a function (instanceof will throw an error in
+  // any case on anything else) with no prototype is through the bind method. All other
+  // environments properly have an undefined prototype, but do not raise an error on instanceof.
+
+  skipEnvironments(['node'], function() {
+
+    equal(instance instanceof BoundPerson, true, 'Function#bind | instance of the bound class');
+
+    // Note that this spec appears to be wrong in the MDN docs:
+    // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
+    // Changing this test to assert true as native implementations all function this way.
+    equal(new Person() instanceof BoundPerson, true, 'Function#bind | instance of unbound class is not an instance of the bound class');
+
+  });
+
+
+
+  // Binding functions without a prototype should not explode.
+  Object.prototype.toString.bind('hooha')();
+
+
+
+  // Ensure that all prototype methods affected by Sugar are still overwriteable.
+
+  var storedEach = Array.prototype.each;
+  var a = [];
+  a.each = 'OH PLEASE';
+  equal(a.each, 'OH PLEASE', 'Sugar methods can ALL be overwritten!');
+  Array.prototype.each = storedEach;
+
+});
level2/node_modules/sugar/test/environments/sugar/es6.js
@@ -0,0 +1,146 @@
+
+test('ES6', function () {
+
+  var arr, result, visited, count;
+  var undefinedContextObj = (function(){ return this; }).call(undefined);
+
+
+  equal([1,2,3].find(function(el){ return el === 3; }), 3, 'Array#find | should find by predicate');
+  equal([1,2,3].find(function(el){ return el === 7; }), undefined, 'Array#find | should return undefined when not matched');
+
+  arr = ['a','b','c'];
+  count = 0;
+
+  arr.find(function(el) {
+    count++;
+    return el === 'a';
+  });
+
+  equal(count, 1, 'Array#find only visits until the first element is found');
+
+  arr   = ['a','b','c'];
+  count = 0;
+  var expectedValues  = ['a', 'b', 'c'];
+  var expectedIndexes = [0, 1, 2];
+
+  arr.find(function(el, i, a) {
+    equal(el, expectedValues[count], 'Array#find | first argument should be element');
+    equal(i, expectedIndexes[count], 'Array#find | second argument should be index');
+    equal(a, arr, 'Array#find | third argument should be the array');
+    equal(this, undefinedContextObj, 'Array#find | context should be undefined');
+    count++;
+  });
+  equal(count, 3, 'Array#find | should have iterated 3 times');
+
+  var scope = {};
+  [1].find(function() {
+    equal(this, scope, 'Array#find | this object should be passable');
+  }, scope);
+
+
+
+  arr = { 0:'a',1:'b',2:'c',length: 3 };
+  result = Array.prototype.find.call(arr, function(el) {
+    return el === 'a';
+  });
+  equal(result, 'a', 'Array#find | should work on array-like objects');
+
+  arr = { 0:'a',1:'b',2:'c',length: '3' };
+  result = Array.prototype.find.call(arr, function(el) {
+    return el === 'a';
+  });
+  equal(result, 'a', 'Array#find | should work on array-like objects with length of type string');
+
+
+  arr = ['a'];
+  arr[2] = 'b';
+  visited = [];
+  arr.find(function(el) {
+    visited.push(el);
+  });
+
+  equal(visited, ['a','b'], 'Array#find | should not visit undefined indexes');
+
+
+  arr = ['a'];
+  visited = [];
+  arr.find(function(el) {
+    visited.push(el);
+    arr.push('b');
+  });
+
+  equal(visited, ['a'], 'Array#find | does not visit elements mutated after being called');
+
+
+  equal([1,2,3].findIndex(function(el){ return el === 2; }), 1, 'Array#findIndex | should findIndex by predicate');
+  equal([1,2,3].findIndex(function(el){ return el === 7; }), -1, 'Array#findIndex | should return -1 when not matched');
+
+  arr = ['a','b','c'];
+  count = 0;
+
+  arr.findIndex(function(el) {
+    count++;
+    return el === 'a';
+  });
+
+  equal(count, 1, 'Array#findIndex only visits until the first element is found');
+
+  arr   = ['a','b','c'];
+  count = 0;
+  var expectedValues  = ['a', 'b', 'c'];
+  var expectedIndexes = [0, 1, 2];
+
+  arr.findIndex(function(el, i, a) {
+    equal(el, expectedValues[count], 'Array#findIndex | first argument should be element');
+    equal(i, expectedIndexes[count], 'Array#findIndex | second argument should be index');
+    equal(a, arr, 'Array#findIndex | third argument should be the array');
+    equal(this, undefinedContextObj, 'Array#findIndex | context should be undefined');
+    count++;
+  });
+  equal(count, 3, 'Array#findIndex | should have iterated 3 times');
+
+  var scope = {};
+  [1].findIndex(function() {
+    equal(this, scope, 'Array#findIndex | this object should be passable');
+  }, scope);
+
+
+
+  arr = { 0:'a',1:'b',2:'c',length: 3 };
+  result = Array.prototype.findIndex.call(arr, function(el) {
+    return el === 'a';
+  });
+  equal(result, 0, 'Array#findIndex | should work on array-like objects');
+
+  arr = { 0:'a',1:'b',2:'c',length: '3' };
+  result = Array.prototype.findIndex.call(arr, function(el) {
+    return el === 'a';
+  });
+  equal(result, 0, 'Array#findIndex | should work on array-like objects with length of type string');
+
+
+  arr = ['a'];
+  arr[2] = 'b';
+  visited = [];
+  arr.findIndex(function(el) {
+    visited.push(el);
+  });
+
+  equal(visited, ['a','b'], 'Array#findIndex | should not visit undefined indexes');
+
+
+  arr = ['a'];
+  visited = [];
+  arr.findIndex(function(el) {
+    visited.push(el);
+    arr.push('b');
+  });
+
+  equal(visited, ['a'], 'Array#findIndex | does not visit elements mutated after being called');
+
+  equal(Array.prototype.find.length,        1, 'Array#find | should have argument length of 1');
+  equal(Array.prototype.findIndex.length,   1, 'Array#findIndex | should have argument length of 1');
+  equal(String.prototype.startsWith.length, 1, 'String#startsWith | arg length should be 1');
+  equal(String.prototype.endsWith.length,   1, 'String#startsWith | arg length should be 1');
+
+});
level2/node_modules/sugar/test/environments/sugar/function.js
@@ -0,0 +1,477 @@
+test('Function', function () {
+
+  var bound,obj,result;
+
+  obj = { foo: 'bar' };
+
+  bound = (function(num, bool, str, fourth, fifth) {
+    equal(this === obj, true, 'Function#bind | Bound object is strictly equal');
+    equal(num, 1, 'Function#bind | first parameter');
+    equal(bool, true, 'Function#bind | second parameter', { mootools: undefined });
+    equal(str, 'wasabi', 'Function#bind | third parameter', { mootools: undefined });
+    equal(fourth, 'fourth', 'Function#bind | fourth parameter', { mootools: undefined });
+    equal(fifth, 'fifth', 'Function#bind | fifth parameter', { mootools: undefined });
+    return 'howdy';
+  }).bind(obj, 1, true, 'wasabi');
+
+  result = bound('fourth','fifth');
+  equal(result, 'howdy', 'Function#bind | result is correctly returned');
+
+  (function(first) {
+    equal(Array.prototype.slice.call(arguments), [], 'Function#bind | arguments array is empty');
+    equal(first, undefined, 'Function#bind | first argument is undefined');
+  }).bind('foo')();
+
+  bound = (function(num, bool, str) {}).bind('wasabi', 'moo')();
+
+
+  // Prototype's delay function takes the value in seconds, so 20 makes the tests
+  // take at least 20 seconds to finish!
+  var delayTime = environment === 'prototype' ? 0.02 : 20;
+
+  async(function(){
+    var fn, ref;
+    fn = function(one, two) {
+      equal(this, fn, 'Function#delay | this object should be the function');
+      equal(one, 'one', 'Function#delay | first parameter', { mootools: 'two' });
+      equal(two, 'two', 'Function#delay | second parameter', { mootools: undefined });
+      asyncFinished();
+    };
+    ref = fn.delay(delayTime, 'one', 'two');
+    equal(typeof ref, 'function', 'Function#delay | returns the function', { prototype: 'number', mootools: 'number' });
+  });
+
+  async(function(){
+    var fn, ref, shouldBeFalse = false;
+    fn = function() {
+      shouldBeFalse = true;
+    };
+    fn.delay(delayTime / 4);
+    fn.delay(delayTime / 5);
+    fn.delay(delayTime / 6);
+    ref = fn.cancel();
+    equal(typeof fn.timers, 'object', 'Function#delay | timers object should be exposed');
+    equal(ref, fn, 'Function#cancel | returns a reference to the function');
+    setTimeout(function() {
+      equal(shouldBeFalse, false, 'Function#delay | cancel is working', { prototype: true, mootools: true });
+      asyncFinished();
+    }, 60);
+  });
+
+  skipEnvironments(['prototype'], function() {
+    async(function(){
+
+      var counter = 0;
+      var fn = function(){ counter++; }
+
+      fn.delay(50);
+      fn.delay(10);
+
+      setTimeout(function() {
+        fn.cancel();
+      }, 30);
+
+      setTimeout(function() {
+        equal(counter, 1, 'Function#cancel | should be able to find the correct timers', { prototype: 0 });
+        fn.cancel();
+        asyncFinished();
+      }, 60);
+
+    });
+  });
+
+  // Function#lazy
+
+  async(function() {
+    var counter = 0;
+    var expected = [['maybe','a',1],['baby','b',2],['you lazy','c',3],['biotch','d',4]];
+    var fn = (function(one, two) {
+      equal([this.toString(), one, two], expected[counter], 'Function#lazy | scope and arguments are correct');
+      counter++;
+    }).lazy();
+    fn.call('maybe', 'a', 1);
+    fn.call('baby', 'b', 2);
+    fn.call('you lazy', 'c', 3);
+    equal(counter, 0, 'Function#lazy | not immediate by default');
+    setTimeout(function() {
+      equal(counter, 3, 'Function#lazy | was executed by 10ms');
+      fn.call('biotch', 'd', 4);
+      equal(counter, 3, 'Function#lazy | counter should still be 3');
+      setTimeout(function() {
+        equal(counter, 4, 'Function#lazy | final call');
+        asyncFinished();
+      }, 10);
+    }, 100);
+  });
+
+
+  async(function() {
+    var counter = 0;
+    var expected = [['maybe','a',1],['baby','b',2],['you lazy','c',3],['biotch','d',4]];
+    var fn = (function(one, two) {
+      equal([this.toString(), one, two], expected[counter], 'Function#lazy | scope and arguments are correct');
+      counter++;
+    }).lazy(1, true);
+    fn.call('maybe', 'a', 1);
+    fn.call('baby', 'b', 2);
+    fn.call('you lazy', 'c', 3);
+    equal(counter, 1, "Function#lazy | should have executed once");
+    setTimeout(function() {
+      equal(counter, 3, 'Function#lazy | was executed by 10ms');
+      fn.call('biotch', 'd', 4);
+      equal(counter, 4, 'Function#lazy | next execution should be immediate');
+      asyncFinished();
+    }, 100);
+  });
+
+  async(function() {
+    var counter = 0;
+    var fn = (function() { counter++; }).lazy();
+    fn();
+    fn();
+    fn();
+    fn.cancel();
+    setTimeout(function() {
+      equal(counter, 0, 'Function#lazy | lazy functions can also be canceled');
+      asyncFinished();
+    }, 10);
+  });
+
+  async(function() {
+    var counter = 0;
+    var fn = (function() { counter++; }).lazy(1, true);
+    fn();
+    fn();
+    fn();
+    fn.cancel();
+    setTimeout(function() {
+      equal(counter, 1, 'Function#lazy | immediate | lazy functions can also be canceled');
+      asyncFinished();
+    }, 10);
+  });
+
+
+  async(function() {
+    var counter = 0;
+    var fn = (function() { counter++; }).lazy(0.1);
+    for(var i = 0; i < 20; i++) {
+      fn();
+    }
+    setTimeout(function() {
+      equal(counter, 20, 'Function#lazy | lazy (throttled) functions can have a [wait] value of < 1ms');
+      asyncFinished();
+    }, 100);
+  });
+
+
+  async(function() {
+    var counter = 0;
+    var fn = (function() { counter++; }).lazy(0.1, false, 10);
+    for(var i = 0; i < 50; i++) {
+      fn();
+    }
+    setTimeout(function() {
+      equal(counter, 10, 'Function#lazy | lazy functions have an upper threshold');
+      asyncFinished();
+    }, 50);
+  });
+
+  async(function() {
+    var counter = 0;
+    var fn = (function() { counter++; }).lazy(0.1, true, 10);
+    for(var i = 0; i < 50; i++) {
+      fn();
+    }
+    setTimeout(function() {
+      equal(counter, 10, 'Function#lazy | immediate | should have same upper threshold as non-immediate');
+      asyncFinished();
+    }, 50);
+  });
+
+
+  async(function() {
+    var counter = 0;
+    var fn = (function() { counter++; }).lazy(0.1, false, 1);
+    for(var i = 0; i < 50; i++) {
+      fn();
+    }
+    setTimeout(function() {
+      equal(counter, 1, 'Function#lazy | lazy functions with a limit of 1 WILL still execute');
+      asyncFinished();
+    }, 50);
+  });
+
+  async(function() {
+    var counter = 0;
+    var fn = (function() { counter++; }).lazy(0.1, true, 1);
+    for(var i = 0; i < 50; i++) {
+      fn();
+    }
+    setTimeout(function() {
+      equal(counter, 1, 'Function#lazy | immediate | lazy functions with a limit of 1 WILL still execute');
+      asyncFinished();
+    }, 50);
+  });
+
+
+
+  // Function#debounce
+
+  async(function(){
+    var fn, ret, counter = 0, expected = [['leia', 5],['han solo', 7]];
+    var fn = (function(one){
+      equal([this.toString(), one], expected[counter], 'Function#debounce | scope and arguments are correct');
+      counter++;
+    }).debounce(30);
+
+    fn.call('3p0', 1);
+    fn.call('r2d2', 2);
+    fn.call('chewie', 3);
+
+    setTimeout(function() {
+      fn.call('leia', 5);
+    }, 10);
+
+    setTimeout(function() {
+      fn.call('luke', 6);
+      fn.call('han solo', 7);
+    }, 200);
+
+    ret = fn.call('vader', 4);
+
+    equal(ret, undefined, 'Function#debounce | calls to a debounced function return undefined');
+
+    setTimeout(function() {
+      equal(counter, 2, 'Function#debounce | counter is correct');
+      asyncFinished();
+    }, 500);
+  });
+
+
+  async(function() {
+    var counter = 0;
+    var fn = (function() { counter++; }).debounce(50);
+    fn();
+    fn();
+    fn();
+    fn.cancel();
+    equal(counter, 0, 'Function#debounce | debounced functions can also be canceled | immediate');
+    setTimeout(function() {
+      equal(counter, 0, 'Function#debounce | debounced functions can also be canceled | after delay');
+      asyncFinished();
+    }, 100);
+  });
+
+  // Function#throttle
+
+  async(function(){
+    var fn, ret, counter = 0, expected = [['3p0', 1],['luke', 6]];
+    fn = (function(one){
+      equal([this.toString(), one], expected[counter], 'Function#throttle | immediate execution | scope and arguments are correct');
+      counter++;
+      return counter;
+    }).throttle(50);
+
+    equal(fn.call('3p0', 1), 1, 'Function#throttle | first run, gets value');
+    equal(fn.call('r2d2', 2), 1, 'Function#thrttle | second run, return value is caching');
+    equal(fn.call('chewie', 3), 1, 'Function#throttle | third run, return value is caching');
+
+    setTimeout(function() {
+      equal(fn.call('leia', 5), 1, 'Function#throttle | fifth run, return value is caching');
+    }, 10);
+
+    setTimeout(function() {
+      equal(fn.call('luke', 6), 2, 'Function#throttle | sixth run, gets value');
+      equal(fn.call('han solo', 7), 2, 'Function#throttle | seventh run, return value is caching');
+    }, 100);
+
+    equal(fn.call('vader', 4), 1, 'Function#throttle | fourth run, return value is caching');
+
+    setTimeout(function() {
+      equal(counter, 2, 'Function#throttle | counter is correct');
+      asyncFinished();
+    }, 200);
+  });
+
+  async(function(){
+
+    var n = 1;
+
+    var fn = (function() { return ++n; }).throttle(50);
+
+    equal(fn(), 2, 'Function#throttle | memoize | iteration 1');
+    equal(fn(), 2, 'Function#throttle | memoize | iteration 2');
+    equal(fn(), 2, 'Function#throttle | memoize | iteration 3');
+
+    setTimeout(function() {
+      equal(fn(), 3, 'Function#throttle | memoize | result expires after 200 ms');
+      asyncFinished();
+    }, 200);
+  });
+
+
+
+  // Function#after
+
+  async(function() {
+    var fn, ret, counter = 0, i = 1;
+    var expectedArguments = [[[1,'bop'],[2,'bop'],[3,'bop'],[4,'bop'],[5,'bop']],[[6,'bop'],[7,'bop'],[8,'bop'],[9,'bop'],[10,'bop']]];
+    fn = (function(args) {
+      equal(args, expectedArguments[counter], 'Function#after | collects arguments called');
+      equal(!!args[0].slice, true, 'Function#after | arguments are converted to actual arrays');
+      counter++;
+      return 'hooha';
+    }).after(5);
+    while(i <= 10) {
+      ret = fn(i, 'bop');
+      equal(ret, (i % 5 == 0 ? 'hooha' : undefined), 'Function#after | collects return value as well');
+      i++;
+    }
+    equal(counter, 2, 'Function#after | calls a function only after a certain number of calls');
+    asyncFinished();
+  });
+
+  async(function() {
+    var fn, counter = 0;
+    var fn = (function(args) { counter++; }).after(0);
+    equal(counter, 1, 'Function#after | 0 should fire the function immediately');
+    equal(typeof fn, 'function', 'Function#after | 0 should still return a function');
+    asyncFinished();
+  });
+
+
+  // Function#once
+
+  async(function() {
+    var fn, obj = { foo:'bar' }, counter = 0;
+    fn = (function(one, two) {
+      counter++;
+      equal(this, obj, 'Function#once | scope is properly set');
+      equal(one, 'one', 'Function#once | first argument is passed');
+      equal(two, 'two', 'Function#once | second argument is passed');
+      return counter * 30;
+    }).once();
+
+    equal(fn.call(obj, 'one', 'two'), 30, 'Function#once | first call calculates the result');
+    equal(fn.call(obj, 'one', 'two'), 30, 'Function#once | second call memoizes the result');
+    equal(fn.call(obj, 'one', 'two'), 30, 'Function#once | third call memoizes the result');
+    equal(fn.call(obj, 'one', 'two'), 30, 'Function#once | fourth call memoizes the result');
+    equal(fn.call(obj, 'one', 'two'), 30, 'Function#once | fifth call memoizes the result');
+
+    equal(counter, 1, 'Function#once | counter is only incremented once');
+    asyncFinished();
+  });
+
+
+  async(function() {
+    var fn, counter = 0;
+    fn = (function(one, two) {
+      counter++;
+    }).once();
+
+    fn.call();
+    fn.call();
+    fn.call();
+
+    equal(counter, 1, 'Function#once | returning undefined will not affect the number of calls');
+    asyncFinished();
+  });
+
+
+  // Function#fill
+
+  // Now that core is being split up, Number#format may not exist, so replicating it here.
+
+  var format = function(place, last){
+      return (last || '') + this.toFixed(place);
+    }
+
+  Number.prototype.two = format.fill(2);
+
+  equal((18).two(), '18.00', 'Function#fill | two | 18');
+  equal((9999).two(), '9999.00', 'Function#fill | two | 9999.00');
+  equal((9999).two('$'), '$9999.00', 'Function#fill | two | $9999.00');
+
+
+  Number.prototype.euro = format.fill(undefined, 'โ‚ฌ');
+
+  equal((9999.77).euro(), 'โ‚ฌ10000', 'Function#fill | euro | no params | 9.999,77');
+  equal((9999.77).euro(0), 'โ‚ฌ10000', 'Function#fill | euro | 0 | 9.999');
+  equal((9999.77).euro(1), 'โ‚ฌ9999.8', 'Function#fill | euro | 1 | 9.999,8');
+  equal((9999.77).euro(2), 'โ‚ฌ9999.77', 'Function#fill | euro | 2 | 9.999,77');
+  equal((9999.77).euro(3), 'โ‚ฌ9999.770', 'Function#fill | euro | 3 | 9.999,777');
+
+
+  Number.prototype.noop = format.fill();
+
+  equal((1000).noop(3, '$'), '$1000.000', 'Function#fill | noop | noop | 1 000,000');
+  equal((1000).noop(4, '$'), '$1000.0000', 'Function#fill | noop | noop | 1 000,0000');
+  equal((1000).noop(5, '$'), '$1000.00000', 'Function#fill | noop | noop | 1 000,00000');
+
+  equal((function(first){ return first; }).fill(['a','b'])(), ['a','b'], 'Function#fill | can be passed arrays');
+
+  equal((function(){ return Array.prototype.slice.call(arguments); }).fill(0)('a'), [0, 'a'], 'Function#fill | falsy values can be passed');
+
+
+  // Issue #346
+
+  async(function() {
+    var counter = 0;
+    var fn = function() {
+      counter++;
+      fn.cancel();
+    };
+    fn.delay(5);
+    fn.delay(20);
+    fn.delay(20);
+    fn.delay(20);
+    fn.delay(20);
+    fn.delay(20);
+    setTimeout(function() {
+      equal(counter, 1, 'Function#cancel | delays should have been canceled after 1');
+      asyncFinished();
+    }, 50);
+  });
+
+  async(function() {
+    var counter = 0;
+    var fn = function() {
+      counter++;
+      if (counter === 2) {
+        fn.cancel();
+      }
+    };
+    // Note that IE seems unable to clear timeouts that are too close
+    // together, so spacing them out a bit.
+    fn.delay(20);
+    fn.delay(20);
+    fn.delay(2);
+    fn.delay(5);
+    fn.delay(20);
+    fn.delay(20);
+    setTimeout(function() {
+      equal(counter, 2, 'function#cancel | delays should have been canceled after 2');
+      asyncFinished();
+    }, 50);
+  });
+
+  // Issue #348
+
+  async(function() {
+    var counter = 0;
+    var fn = function(one, two) {
+      equal(this, fn, 'Function#every | this object should be the function');
+      equal(one, 'one', 'function#every | first argument should be curried');
+      equal(two, 'two', 'function#every | second argument should be curried');
+      counter++;
+    };
+    fn.every(10, 'one', 'two');
+    setTimeout(function() {
+      fn.cancel();
+      equal(counter > 6, true, 'function#every | should have been called at least 7 times');
+      asyncFinished();
+    }, 100);
+  });
+
+});
+
level2/node_modules/sugar/test/environments/sugar/inflections.js
@@ -0,0 +1,586 @@
+test('Inflections', function () {
+
+  /* Note that the following methods are not implemented now and may not be:
+   *
+   *   String#demodulize
+   *   String#deconstantize
+   *   String#foreign_key
+   *   String#tableize
+   *   String#classify
+   */
+
+
+  var SingularToPlural = {
+    "search"      : "searches",
+    "switch"      : "switches",
+    "fix"         : "fixes",
+    "box"         : "boxes",
+    "process"     : "processes",
+    "address"     : "addresses",
+    "case"        : "cases",
+    "stack"       : "stacks",
+    "wish"        : "wishes",
+    "fish"        : "fish",
+    "jeans"       : "jeans",
+    "funky jeans" : "funky jeans",
+
+    "my money"    : "my money",
+
+    "category"    : "categories",
+    "query"       : "queries",
+    "ability"     : "abilities",
+    "agency"      : "agencies",
+    "movie"       : "movies",
+
+    "archive"     : "archives",
+
+    "index"       : "indices",
+
+    "wife"        : "wives",
+    "save"        : "saves",
+    "half"        : "halves",
+
+    "move"        : "moves",
+
+    "salesperson" : "salespeople",
+    "person"      : "people",
+
+    "spokesman"   : "spokesmen",
+    "man"         : "men",
+    "woman"       : "women",
+
+    "basis"       : "bases",
+    "diagnosis"   : "diagnoses",
+    "diagnosis_a" : "diagnosis_as",
+
+    "datum"       : "data",
+    "medium"      : "media",
+    "stadium"     : "stadia",
+    "analysis"    : "analyses",
+
+    "node_child"  : "node_children",
+    "child"       : "children",
+
+    "experience"  : "experiences",
+    "day"         : "days",
+
+    "comment"     : "comments",
+    "foobar"      : "foobars",
+    "newsletter"  : "newsletters",
+
+    "old_news"    : "old_news",
+    "news"        : "news",
+
+    "series"      : "series",
+    "species"     : "species",
+
+    "quiz"        : "quizzes",
+
+    "perspective" : "perspectives",
+
+    "ox"          : "oxen",
+    "photo"       : "photos",
+    "buffalo"     : "buffaloes",
+    "tomato"      : "tomatoes",
+    "dwarf"       : "dwarves",
+    "elf"         : "elves",
+    "information" : "information",
+    "equipment"   : "equipment",
+    "bus"         : "buses",
+    "status"      : "statuses",
+    "status_code" : "status_codes",
+    "mouse"       : "mice",
+
+    "louse"       : "lice",
+    "house"       : "houses",
+    "octopus"     : "octopi",
+    "virus"       : "viri",
+    "alias"       : "aliases",
+    "portfolio"   : "portfolios",
+
+    "vertex"      : "vertices",
+    "matrix"      : "matrices",
+    "matrix_fu"   : "matrix_fus",
+
+    "axis"        : "axes",
+    "testis"      : "testes",
+    "crisis"      : "crises",
+
+    "rice"        : "rice",
+    "shoe"        : "shoes",
+
+    "horse"       : "horses",
+    "prize"       : "prizes",
+    "edge"        : "edges",
+
+    "cow"         : "kine",
+    "database"    : "databases",
+
+    // regression tests against improper inflection regexes
+    "|ice"        : "|ices",
+    "|ouse"       : "|ouses",
+
+
+    // Taken from Wikipedia
+
+    "kiss"    : "kisses",
+    "ass"     : "asses",
+    "mess"    : "messes",
+    "fuss"    : "fusses",
+    "phase"   : "phases",
+    "dish"    : "dishes",
+    "massage" : "massages",
+    "witch"   : "witches",
+    "judge"   : "judges",
+
+    "lap"     : "laps",
+    "cat"     : "cats",
+    "clock"   : "clocks",
+    "cuff"    : "cuffs",
+    "death"   : "deaths",
+
+    "boy"     : "boys",
+    "girl"    : "girls",
+    "chair"   : "chairs",
+
+    "hero"    : "heroes",
+    "potato"  : "potatoes",
+    "volcano" : "volcanoes",
+
+    "cherry"  : "cherries",
+    "lady"    : "ladies",
+
+
+    "day"     : "days",
+    "monkey"  : "monkeys",
+    "canto"   : "cantos",
+    "homo"    : "homos",
+    "photo"   : "photos",
+    "zero"    : "zeros",
+    "piano"   : "pianos",
+    "portico" : "porticos",
+    "pro"     : "pros",
+    "quarto"  : "quartos",
+    "kimono"  : "kimonos",
+
+
+    "bath"   : "baths",
+    "mouth"  : "mouths",
+    "calf"   : "calves",
+    "leaf"   : "leaves",
+    "knife"  : "knives",
+    "life"   : "lives",
+    "house"  : "houses",
+    "moth"   : "moths",
+    "proof"  : "proofs",
+    "dwarf"  : "dwarves",
+    "hoof"   : "hooves",
+    "elf"    : "elves",
+    "roof"   : "roofs",
+
+    "aircraft"   : "aircraft",
+    "watercraft" : "watercraft",
+    "spacecraft" : "spacecraft",
+    "hovercraft" : "hovercraft",
+
+    "information" : "information",
+    "ox"          : "oxen",
+    "child"       : "children",
+    "foot"        : "feet",
+    "goose"       : "geese",
+    "louse"       : "lice",
+    "man"         : "men",
+    "mouse"       : "mice",
+    "tooth"       : "teeth",
+    "woman"       : "women",
+    "alumnus"     : "alumni",
+    "census"      : "censuses",
+    "focus"       : "foci",
+    "radius"      : "radii",
+    "fungus"      : "fungi",
+    "status"      : "statuses",
+    "syllabus"    : "syllabuses"
+
+  };
+
+
+  var Irregulars = {
+    'person' : 'people',
+    'man'    : 'men',
+    'child'  : 'children',
+    'sex'    : 'sexes',
+    'move'   : 'moves'
+  };
+
+  var Uncountables = [
+    'equipment',
+    'information',
+    'rice',
+    'money',
+    'species',
+    'series',
+    'fish',
+    'sheep',
+    'jeans'
+  ];
+
+  var MixtureToTitleCase = {
+    'active_record'       : 'Active Record',
+    'ActiveRecord'        : 'Active Record',
+    'action web service'  : 'Action Web Service',
+    'Action Web Service'  : 'Action Web Service',
+    'Action web service'  : 'Action Web Service',
+    'actionwebservice'    : 'Actionwebservice',
+    'Actionwebservice'    : 'Actionwebservice',
+    "david's code"        : "David's Code",
+    "David's code"        : "David's Code",
+    "david's Code"        : "David's Code",
+
+    // Added test cases for non-titleized words
+
+    "the day of the jackal"        : "The Day of the Jackal",
+    "what color is your parachute?": "What Color Is Your Parachute?",
+    "a tale of two cities"         : "A Tale of Two Cities",
+    "where am i going to"          : "Where Am I Going To",
+
+    // From the titleize docs
+    "man from the boondocks"       :  "Man from the Boondocks",
+    "x-men: the last stand"        :  "X Men: The Last Stand",
+    "i am a sentence. and so am i.":  "I Am a Sentence. And so Am I.",
+    "hello! and goodbye!"          :  "Hello! And Goodbye!",
+    "hello, and goodbye"           :  "Hello, and Goodbye",
+    "hello; and goodbye"           :  "Hello; And Goodbye",
+    'about "you" and "me"'         :  'About "You" and "Me"',
+    "TheManWithoutAPast"           :  "The Man Without a Past",
+    "raiders_of_the_lost_ark"      :  "Raiders of the Lost Ark"
+  }
+
+  var CamelToUnderscore = {
+    "Product"               : "product",
+    "SpecialGuest"          : "special_guest",
+    "ApplicationController" : "application_controller",
+    "Area51Controller"      : "area51_controller"
+  }
+
+  var UnderscoreToLowerCamel = {
+    "product"                : "product",
+    "special_guest"          : "specialGuest",
+    "application_controller" : "applicationController",
+    "area51_controller"      : "area51Controller"
+  }
+
+  var CamelToUnderscoreWithoutReverse = {
+    "HTMLTidy"              : "html_tidy",
+    "HTMLTidyGenerator"     : "html_tidy_generator",
+    "FreeBSD"               : "free_bsd",
+    "HTML"                  : "html"
+  }
+
+  var StringToParameterized = {
+    "Donald E. Knuth"                     : "donald-e-knuth",
+    "Random text with *(bad)* characters" : "random-text-with-bad-characters",
+    "Allow_Under_Scores"                  : "allow_under_scores",
+    "Trailing bad characters!@#"          : "trailing-bad-characters",
+    "!@#Leading bad characters"           : "leading-bad-characters",
+    "Squeeze   separators"                : "squeeze-separators",
+    "Test with + sign"                    : "test-with-sign",
+    "Test with malformed utf8 \251"       : "test-with-malformed-utf8"
+  }
+
+  var StringToParameterizeWithNoSeparator = {
+    "Donald E. Knuth"                     : "donaldeknuth",
+    "With-some-dashes"                    : "with-some-dashes",
+    "Random text with *(bad)* characters" : "randomtextwithbadcharacters",
+    "Trailing bad characters!@#"          : "trailingbadcharacters",
+    "!@#Leading bad characters"           : "leadingbadcharacters",
+    "Squeeze   separators"                : "squeezeseparators",
+    "Test with + sign"                    : "testwithsign",
+    "Test with malformed utf8 \251"       : "testwithmalformedutf8"
+  }
+
+  var StringToParameterizeWithUnderscore = {
+    "Donald E. Knuth"                     : "donald_e_knuth",
+    "Random text with *(bad)* characters" : "random_text_with_bad_characters",
+    "With-some-dashes"                    : "with-some-dashes",
+    "Retain_underscore"                   : "retain_underscore",
+    "Trailing bad characters!@#"          : "trailing_bad_characters",
+    "!@#Leading bad characters"           : "leading_bad_characters",
+    "Squeeze   separators"                : "squeeze_separators",
+    "Test with + sign"                    : "test_with_sign",
+    "Test with malformed utf8 \251"       : "test_with_malformed_utf8"
+  }
+
+  var StringToParameterizedAndNormalized = {
+    "Malmรถ"                               : "malmo",
+    "Garรงons"                             : "garcons",
+    "Ops\331"                             : "opsu",
+    "ร†rรธskรธbing"                          : "aeroskobing",
+    "AรŸlar"                               : "asslar",
+    "Japanese: ๆ—ฅๆœฌ่ชž"                    : "japanese"
+  }
+
+  var UnderscoreToHuman = {
+    "employee_salary" : "Employee salary",
+    "employee_id"     : "Employee",
+    "underground"     : "Underground"
+  }
+
+  var UnderscoresToDashes = {
+    "street"                : "street",
+    "street_address"        : "street-address",
+    "person_street_address" : "person-street-address"
+  }
+
+
+  // Test pluralize plurals
+  equal("plurals".pluralize(), "plurals", "String#pluralize | plurals")
+  equal("Plurals".pluralize(), "Plurals", "String#pluralize | Plurals")
+
+
+  // Test pluralize empty string
+  equal("".pluralize(), "", 'String#pluralize | ""');
+
+
+  // Test uncountability of words
+  Uncountables.forEach(function(word) {
+    equal(word.singularize(), word, 'String#singularize | uncountables');
+    equal(word.pluralize(), word, 'String#pluralize | uncountables');
+    equal(word.singularize(), word.pluralize(), 'String#singularize | uncountables | same as pluralize');
+  });
+
+
+  // Test uncountable word is not greedy
+  var uncountable_word = "ors";
+  var countable_word = "sponsor";
+
+  String.Inflector.uncountable(uncountable_word);
+
+  equal(uncountable_word.singularize(), uncountable_word, 'String#singularize | uncountable | ors');
+  equal(uncountable_word.pluralize(), uncountable_word, 'String#pluralize | uncountable | ors');
+  equal(uncountable_word.pluralize(), uncountable_word.singularize(), 'String#singularize | uncountable | both are same');
+  equal(countable_word.singularize(), 'sponsor', 'String#singularize | countable | sponsor');
+  equal(countable_word.pluralize(), 'sponsors', 'String#pluralize | countable | sponsors');
+  equal(countable_word.pluralize().singularize(), 'sponsor', 'String#pluralize | countable | both are same');
+
+
+  // Test pluralize singular
+  testIterateOverObject(SingularToPlural, function(singular, plural) {
+    equal(singular.pluralize(), plural, 'String#pluralize | singular > plural');
+    equal(singular.capitalize().pluralize(), plural.capitalize(), 'String#pluralize | singular > capitalize > plural == plural > capitalize');
+  });
+
+  // Test singularize plural
+  testIterateOverObject(SingularToPlural, function(singular, plural) {
+    equal(plural.singularize(), singular, 'String#singularize | plural > singular');
+    equal(plural.capitalize().singularize(), singular.capitalize(), 'String#singularize | plural > capitalize > singular == singular > capitalize');
+  });
+
+  // Test singularize singular
+  testIterateOverObject(SingularToPlural, function(singular, plural) {
+    equal(singular.singularize(), singular, 'String#singularize | singular > singular');
+    equal(singular.capitalize().singularize(), singular.capitalize(), 'String#singularize | singular > capitalize > singular == singular > capitalize');
+  });
+
+  // Test pluralize plural
+  testIterateOverObject(SingularToPlural, function(singular, plural) {
+    equal(plural.pluralize(), plural, 'String#pluralize | plural > plural');
+    equal(plural.capitalize().pluralize(), plural.capitalize(), 'String#singularize | plural > capitalize > plural == plural > capitalize');
+  });
+
+
+  // Test overwrite previous inflectors
+  equal('series'.singularize(), 'series', 'String#singularize | series');
+  String.Inflector.singular('series', 'serie');
+  equal('series'.singularize(), 'serie', 'String#singularize | serie');
+  String.Inflector.singular('series'); // Return to normal
+
+
+  // Test irregulars
+
+  testIterateOverObject(Irregulars, function(singular, plural) {
+    equal(plural.singularize(), singular, 'String#singularize | irregulars');
+    equal(singular.pluralize(), plural, 'String#pluralize | irregulars | pluralized singular is plural');
+  });
+
+  testIterateOverObject(Irregulars, function(singular, plural) {
+    equal(plural.pluralize(), plural, 'String#singularize | irregulars | pluralized plural id pluralized');
+  });
+
+
+  // Test titleize
+  testIterateOverObject(MixtureToTitleCase, function(before, titleized) {
+    equal(before.titleize(), titleized, 'String#titleize | mixed cases')
+  });
+
+
+  // Test camelize
+  testIterateOverObject(CamelToUnderscore, function(camel, underscore) {
+    equal(underscore.camelize(), camel, 'String#camelize | mixed cases')
+  });
+
+  testIterateOverObject(UnderscoreToLowerCamel, function(under, lowerCamel) {
+    // Sugar differs from ActiveSupport here in that the first character is upcased by default
+    equal(under.camelize(false), lowerCamel, 'String#camelize | lower camel')
+  });
+
+  // Test with lower downcases the first letter
+  equal('Capital'.camelize(false), 'capital', 'String#camelize | downcases the first letter');
+
+  // Test camelize with underscores
+  equal('Camel_Case'.camelize(), 'CamelCase', 'String#camelize | handles underscores');
+
+
+  // Test acronyms
+
+  String.Inflector.acronym("API");
+  String.Inflector.acronym("HTML");
+  String.Inflector.acronym("HTTP");
+  String.Inflector.acronym("RESTful");
+  String.Inflector.acronym("W3C");
+  String.Inflector.acronym("PhD");
+  String.Inflector.acronym("RoR");
+  String.Inflector.acronym("SSL");
+
+  // camelize             underscore            humanize              titleize
+  [
+    ["API",               "api",                "API",                "API"],
+    ["APIController",     "api_controller",     "API controller",     "API Controller"],
+
+    // Ruby specific inflections don't make sense here.
+    // ["Nokogiri::HTML",    "nokogiri/html",      "Nokogiri/HTML",      "Nokogiri/HTML"],
+    // ["HTTP::Get",         "http/get",           "HTTP/get",           "HTTP/Get"],
+
+    ["HTTPAPI",           "http_api",           "HTTP API",           "HTTP API"],
+    ["SSLError",          "ssl_error",          "SSL error",          "SSL Error"],
+    ["RESTful",           "restful",            "RESTful",            "RESTful"],
+    ["RESTfulController", "restful_controller", "RESTful controller", "RESTful Controller"],
+    ["IHeartW3C",         "i_heart_w3c",        "I heart W3C",        "I Heart W3C"],
+    ["PhDRequired",       "phd_required",       "PhD required",       "PhD Required"],
+    ["IRoRU",             "i_ror_u",            "I RoR u",            "I RoR U"],
+    ["RESTfulHTTPAPI",    "restful_http_api",   "RESTful HTTP API",   "RESTful HTTP API"],
+
+    // misdirection
+    ["Capistrano",        "capistrano",         "Capistrano",       "Capistrano"],
+    ["CapiController",    "capi_controller",    "Capi controller",  "Capi Controller"],
+    ["HttpsApis",         "https_apis",         "Https apis",       "Https Apis"],
+    ["Html5",             "html5",              "Html5",            "Html5"],
+    ["Restfully",         "restfully",          "Restfully",        "Restfully"]
+    // This one confounds the JS implementation, but I argue that it isn't correct anyway.
+    // ["RoRails",           "ro_rails",           "Ro rails",         "Ro Rails"]
+  ].forEach(function(set) {
+    var camel = set[0], under = set[1], human = set[2], title = set[3];
+    equal(under.camelize(), camel, 'String#camelize | under.camelize()')
+    equal(camel.camelize(), camel, 'String#camelize | camel.camelize()')
+    equal(under.underscore(), under, 'String#underscore | under.underscore()')
+    equal(camel.underscore(), under, 'String#underscore | camel.underscore()')
+    equal(under.titleize(), title, 'String#titleize | under.titleize()')
+    equal(camel.titleize(), title, 'String#titleize | camel.titleize()')
+    equal(under.humanize(), human, 'String#humanize | under.humanize()')
+  });
+
+
+
+  // Test acronym override
+  String.Inflector.acronym("LegacyApi")
+
+  equal('legacyapi'.camelize(), "LegacyApi", 'String#camelize | LegacyApi')
+  equal('legacy_api'.camelize(), "LegacyAPI", 'String#camelize | LegacyAPI')
+  equal('some_legacyapi'.camelize(), "SomeLegacyApi", 'String#camelize | SomeLegacyApi')
+  equal('nonlegacyapi'.camelize(), "Nonlegacyapi", 'String#camelize | Nonlegacyapi')
+
+
+  // Test acronyms camelize lower
+
+  equal('html_api'.camelize(false), 'htmlAPI', 'String#camelize | html_api')
+  equal('htmlAPI'.camelize(false), 'htmlAPI', 'String#camelize | htmlAPI')
+  equal('HTMLAPI'.camelize(false), 'htmlAPI', 'String#camelize | HTMLAPI')
+
+
+  // Test underscore acronym sequence
+
+  String.Inflector.acronym("HTML5");
+
+  equal('HTML5HTMLAPI'.underscore(), 'html5_html_api', 'String#underscore | HTML5HTMLAPI')
+
+
+
+
+  // Test underscore
+  testIterateOverObject(CamelToUnderscore, function(camel, underscore) {
+      equal(camel.underscore(), underscore, 'String#underscore | mixed cases')
+  });
+
+  testIterateOverObject(CamelToUnderscoreWithoutReverse, function(camel, underscore) {
+      equal(camel.underscore(), underscore, 'String#underscore | mixed cases')
+  });
+
+
+  // Test parameterize
+
+  testIterateOverObject(StringToParameterized, function(str, parameterized) {
+      equal(str.parameterize(), parameterized, 'String#parameterized')
+  });
+
+  testIterateOverObject(StringToParameterizedAndNormalized, function(str, parameterized) {
+      equal(str.parameterize(), parameterized, 'String#parameterized | and normalized')
+  });
+
+  testIterateOverObject(StringToParameterizeWithUnderscore, function(str, parameterized) {
+      equal(str.parameterize('_'), parameterized, 'String#parameterized | with underscore')
+  });
+
+  testIterateOverObject(StringToParameterized, function(str, parameterized) {
+      equal(str.parameterize('__sep__'), parameterized.replace(/-/g, '__sep__'), 'String#parameterized | with underscore')
+  });
+
+
+
+
+
+  // Test humanize
+
+  testIterateOverObject(UnderscoreToHuman, function(under, human) {
+      equal(under.humanize(), human, 'String#humanize | underscore')
+  });
+
+  String.Inflector.human(/_cnt$/i, '_count');
+  String.Inflector.human(/^prefx_/i, '')
+
+  equal('jargon_cnt'.humanize(), 'Jargon count', 'String#humanize | Jargon count')
+  equal('prefx_request'.humanize(), 'Request', 'String#humanize | Request')
+
+  String.Inflector.human("col_rpted_bugs", "Reported bugs")
+
+  equal('col_rpted_bugs'.humanize(), 'Reported bugs', 'String#humanize | Reported bugs')
+  equal('COL_rpted_bugs'.humanize(), 'Col rpted bugs', 'String#humanize | Col rpted bugs')
+
+
+
+  // Test dasherize
+  testIterateOverObject(UnderscoresToDashes, function(under, dasherized) {
+      equal(under.dasherize(), dasherized, 'String#dasherize')
+  });
+
+  testIterateOverObject(UnderscoresToDashes, function(under, dasherized) {
+      equal(under.dasherize().underscore(), under, 'String#dasherize | reverse')
+  });
+
+  // More irregulars
+  equal('street'.pluralize(), 'streets', 'String.Inflector | street > streets');
+
+  // Test clearing inflectors KEEP ME AT THE BOTTOM
+  equal('foo'.pluralize(), 'foos', 'String.Inflector.clear | foo is foos');
+  String.Inflector.clear('plurals');
+  equal('foo'.pluralize(), 'foo', 'String.Inflector.clear | clear purals');
+  equal('foos'.singularize(), 'foo', 'String.Inflector.clear | singulars are not cleared');
+  String.Inflector.plural(/$/, 's');
+  equal('foo'.pluralize(), 'foos', 'String.Inflector.plural | re-add');
+  String.Inflector.clear('all');
+  equal('foo'.pluralize(), 'foo', 'String.Inflector.plural | clear all with "all"');
+  String.Inflector.plural(/$/, 's');
+  equal('foo'.pluralize(), 'foos', 'String.Inflector.plural | re-add again');
+  String.Inflector.clear();
+  equal('foo'.pluralize(), 'foo', 'String.Inflector.plural | clear all with undefined');
+
+
+});
level2/node_modules/sugar/test/environments/sugar/language.js
@@ -0,0 +1,266 @@
+test('Language', function () {
+
+
+
+
+  // String#is/has[Script]
+
+  equal('ใ‚ข'.isKatakana(), true, 'String#isKatakana | ใ‚ข');
+  equal('๏ฝฑ'.isKatakana(), true, 'String#isKatakana | ๏ฝฑ');
+  equal('ใ‚ก'.isKatakana(), true, 'String#isKatakana | ใ‚ก');
+  equal('ah'.isKatakana(), false, 'String#isKatakana | ah');
+  equal('ใ‚ขใ‚คใ‚ซใƒ ใ‚คใƒณใƒ”ใƒผใ‚น'.isKatakana(), true, 'String#isKatakana | full katakana');
+  equal('ใ‚ขใ‚คใ‚ซใƒ inใƒ”ใƒผใ‚น'.isKatakana(), false, 'String#isKatakana | full katakana with romaji');
+  equal('ใ‚ขใ‚คใ‚ซใƒ  ใ‚คใƒณ ใƒ”ใƒผใ‚น'.isKatakana(), true, 'String#isKatakana | full katakana with spaces');
+
+  equal('ใ‚ข'.hasKatakana(), true, 'String#hasKatakana | ใ‚ข');
+  equal('๏ฝฑ'.hasKatakana(), true, 'String#hasKatakana | ๏ฝฑ');
+  equal('ah'.hasKatakana(), false, 'String#hasKatakana | ah');
+  equal('aใ‚ขh'.hasKatakana(), true, 'String#hasKatakana | aใ‚ขh');
+  equal('a๏ฝฑh'.hasKatakana(), true, 'String#hasKatakana | a๏ฝฑh');
+  equal('ใ‚ขใ‚คใ‚ซใƒ ใ‚คใƒณใƒ”ใƒผใ‚น'.hasKatakana(), true, 'String#hasKatakana | full katakana');
+  equal('ใ‚ขใ‚คใ‚ซใƒ inใƒ”ใƒผใ‚น'.hasKatakana(), true, 'String#hasKatakana | full katakana with romaji');
+
+
+  equal('ใ‚'.isHiragana(), true, 'String#isHiragana | ใ‚');
+  equal('ใ'.isHiragana(), true, 'String#isHiragana | ใ');
+  equal('ah'.isHiragana(), false, 'String#isHiragana | ah');
+  equal('ใ‚ใ„ใ‹ใ‚€ใ„ใ‚“ใดใƒผใ™'.isHiragana(), true, 'String#isHiragana | full hiragana');
+  equal('ใ‚ใ„ใ‹ใ‚€inใดใƒผใ™'.isHiragana(), false, 'String#isHiragana | full hiragana with romaji');
+  equal('ใ‚ใ„ใ‹ใ‚€ in ใดใƒผใ™'.isHiragana(), false, 'String#isHiragana | full hiragana with romaji and spaces');
+  equal('ใ‚ขใ‚คใ‚ซใƒ  ใ‚คใƒณ ใƒ”ใƒผใ‚น'.isHiragana(), false, 'String#isHiragana | full hiragana with spaces');
+
+
+  equal('ใ‚'.hasHiragana(), true, 'String#hasHiragana | ใ‚');
+  equal('ใ'.hasHiragana(), true, 'String#hasHiragana | ใ');
+  equal('ah'.hasHiragana(), false, 'String#hasHiragana | ah');
+  equal('aใ‚h'.hasHiragana(), true, 'String#hasHiragana | aใ‚h');
+  equal('aใh'.hasHiragana(), true, 'String#hasHiragana | aใh');
+  equal('ใ‚ใ„ใ‹ใ‚€ใ„ใ‚“ใดใƒผใ™'.hasHiragana(), true, 'String#hasHiragana | full hiragana');
+  equal('ใ‚ใ„ใ‹ใ‚€inใดใƒผใ™'.hasHiragana(), true, 'String#hasHiragana | full hiragana with romaji');
+
+
+
+
+  equal(''.isKana(), false, 'String#isKana | blank');
+  equal('ใ‚ใ„ใ†ใˆใŠ'.isKana(), true, 'String#isKana | hiragana');
+  equal('ใ‚ขใ‚คใ‚ฆใ‚จใ‚ช'.isKana(), true, 'String#isKana | katakana');
+  equal('ใ‚ใ†ใˆใŠใ‚ขใ‚คใ‚ฆใ‚จใ‚ช'.isKana(), true, 'String#isKana | hiragana and katakan');
+  equal('ใ‚ใ†ใˆใŠaeiouใ‚ขใ‚คใ‚ฆใ‚จใ‚ช'.isKana(), false, 'String#isKana | hiragana, katakana, and romaji');
+  equal('  ใ‚ใ„ใ†ใˆใŠ  '.isKana(), true, 'String#isKana | hiragana with whitespace');
+  equal('  ใ‚ขใ‚คใ‚ฆใ‚จใ‚ช \n '.isKana(), true, 'String#isKana | katakana with whitespace and a newline');
+
+
+
+
+
+  equal(''.hasKana(), false, 'String#hasKana | blank');
+  equal('aeiou'.hasKana(), false, 'String#hasKana | romaji');
+  equal('ใ‚ใ„ใ†ใˆใŠ'.hasKana(), true, 'String#hasKana | hiragana');
+  equal('ใ‚ขใ‚คใ‚ฆใ‚จใ‚ช'.hasKana(), true, 'String#hasKana | katakana');
+  equal('ใ‚ใ†ใˆใŠใ‚ขใ‚คใ‚ฆใ‚จใ‚ช'.hasKana(), true, 'String#hasKana | hiragana and katakana');
+  equal('ใ‚ใ†ใˆใŠaeiouใ‚ขใ‚คใ‚ฆใ‚จใ‚ช'.hasKana(), true, 'String#hasKana | hiragana, katakana, and romaji');
+  equal('aeiouใ‚ขaeiou'.hasKana(), true, 'String#hasKana | katakana with romaji outside');
+  equal('aeiouaeiou'.hasKana(), false, 'String#hasKana | romaji all the way');
+
+
+
+  equal(''.isHan(), false, 'String#isHan | blank');
+  equal('aeiou'.isHan(), false, 'String#isHan | romaji');
+  equal('ใ‚ใ„ใ†ใˆใŠ'.isHan(), false, 'String#isHan | hiragana');
+  equal('ใ‚ขใ‚คใ‚ฆใ‚จใ‚ช'.isHan(), false, 'String#isHan | katakana');
+  equal('ใ‚ใ†ใˆใŠaeiouใ‚ขใ‚คใ‚ฆใ‚จใ‚ช'.isHan(), false, 'String#isHan | hiragana, katakana, and romaji');
+  equal('ๅˆใ‚ณใƒณ'.isHan(), false, 'String#isHan | mixed kanji and katakana');
+  equal('่ชžๅญฆ'.isHan(), true, 'String#isHan | kango');
+  equal('ๅบญใซใฏไบŒ็พฝ้ถใŒใ„ใ‚‹ใ€‚'.isHan(), false, 'String#isHan | full sentence');
+  equal(' ่ชžๅญฆ '.isHan(), true, 'String#isHan | kango with whitespace');
+  equal(' ่ชžๅญฆ\t '.isHan(), true, 'String#isHan | kango with whitespace and tabs');
+
+
+
+  equal(''.hasHan(), false, 'String#hasHan | blank');
+  equal('aeiou'.hasHan(), false, 'String#hasHan | romaji');
+  equal('ใ‚ใ„ใ†ใˆใŠ'.hasHan(), false, 'String#hasHan | hiragana');
+  equal('ใ‚ขใ‚คใ‚ฆใ‚จใ‚ช'.hasHan(), false, 'String#hasHan | katakana');
+  equal('ใ‚ใ†ใˆใŠaeiouใ‚ขใ‚คใ‚ฆใ‚จใ‚ช'.hasHan(), false, 'String#hasHan | hiragana, katakana, and romaji');
+  equal('ๅˆใ‚ณใƒณ'.hasHan(), true, 'String#hasHan | mixed kanji and katakana');
+  equal('่ชžๅญฆ'.hasHan(), true, 'String#hasHan | kango');
+  equal('ๅบญใซใฏไบŒ็พฝ้ถใŒใ„ใ‚‹ใ€‚'.hasHan(), true, 'String#hasHan | full sentence');
+  equal(' ่ชžๅญฆ '.hasHan(), true, 'String#hasHan | kango with whitespace');
+  equal(' ่ชžๅญฆ\t '.hasHan(), true, 'String#hasHan | kango with whitespace and tabs');
+
+
+
+
+
+  equal(''.isKanji(), false, 'String#isKanji | blank');
+  equal('aeiou'.isKanji(), false, 'String#isKanji | romaji');
+  equal('ใ‚ใ„ใ†ใˆใŠ'.isKanji(), false, 'String#isKanji | hiragana');
+  equal('ใ‚ขใ‚คใ‚ฆใ‚จใ‚ช'.isKanji(), false, 'String#isKanji | katakana');
+  equal('ใ‚ใ†ใˆใŠaeiouใ‚ขใ‚คใ‚ฆใ‚จใ‚ช'.isKanji(), false, 'String#isKanji | hiragana, katakana, and romaji');
+  equal('ๅˆใ‚ณใƒณ'.isKanji(), false, 'String#isKanji | mixed kanji and katakana');
+  equal('่ชžๅญฆ'.isKanji(), true, 'String#isKanji | kango');
+  equal('ๅบญใซใฏไบŒ็พฝ้ถใŒใ„ใ‚‹ใ€‚'.isKanji(), false, 'String#isKanji | full sentence');
+  equal(' ่ชžๅญฆ '.isKanji(), true, 'String#isKanji | kango with whitespace');
+  equal(' ่ชžๅญฆ\t '.isKanji(), true, 'String#isKanji | kango with whitespace and tabs');
+  equal(' ่ชž ๅญฆ\t '.isKanji(), true, 'String#isKanji | middle whitespace is also not counted');
+
+
+
+
+
+  equal(''.hasKanji(), false, 'String#hasKanji | blank');
+  equal('aeiou'.hasKanji(), false, 'String#hasKanji | romaji');
+  equal('ใ‚ใ„ใ†ใˆใŠ'.hasKanji(), false, 'String#hasKanji | hiragana');
+  equal('ใ‚ขใ‚คใ‚ฆใ‚จใ‚ช'.hasKanji(), false, 'String#hasKanji | katakana');
+  equal('ใ‚ใ†ใˆใŠaeiouใ‚ขใ‚คใ‚ฆใ‚จใ‚ช'.hasKanji(), false, 'String#hasKanji | hiragana, katakana, and romaji');
+  equal('ๅˆใ‚ณใƒณ'.hasKanji(), true, 'String#hasKanji | mixed kanji and katakana');
+  equal('่ชžๅญฆ'.hasKanji(), true, 'String#hasKanji | kango');
+  equal('ๅบญใซใฏไบŒ็พฝ้ถใŒใ„ใ‚‹ใ€‚'.hasKanji(), true, 'String#hasKanji | full sentence');
+  equal(' ่ชžๅญฆ '.hasKanji(), true, 'String#hasKanji | kango with whitespace');
+  equal(' ่ชžๅญฆ\t '.hasKanji(), true, 'String#hasKanji | kango with whitespace and tabs');
+
+
+  equal('๋ชจ'.isHangul(), true, 'String#isHangul | character');
+  equal('๋‚œ ๋ป”๋ฐ๊ธฐ๋ฅผ ์‹ซ์–ด ํ•œ ์‚ฌ๋žŒ ์ด๋‹ค...๋„ˆ๋Š”?'.isHangul(), false, 'String#isHangul | full sentence');
+  equal('์•ˆ๋…• ํ•˜์„ธ์š”'.isHangul(), true, 'String#isHangul | how are you?');
+  equal('ใ… ใƒ–ใƒฉใ˜ใ‚ƒใชใ„๏ผ'.isHangul(), false, 'String#isHangul | mixed with kana');
+  equal('์ด๊ฒƒ๋„ ํ•œ๊ตด์ด์•ผ'.isHangul(), true, 'String#isHangul | spaces do not count');
+
+  equal('๋ชจ'.hasHangul(), true, 'String#hasHangul | character');
+  equal('๋‚œ ๋ป”๋ฐ๊ธฐ๋ฅผ ์‹ซ์–ด ํ•œ ์‚ฌ๋žŒ ์ด๋‹ค...๋„ˆ๋Š”?'.hasHangul(), true, 'String#hasHangul | full sentence');
+  equal('์•ˆ๋…• ํ•˜์„ธ์š”.'.hasHangul(), true, 'String#hasHangul | how are you?');
+  equal('ใ… ใƒ–ใƒฉใ˜ใ‚ƒใชใ„๏ผ'.hasHangul(), false, 'String#hasHangul | mixed with kana');
+
+  equal('ืฉึฐื‚ืจึธืึตืœ'.isHebrew(), true, 'String#isHebrew');
+  equal('ืฉึฐื‚ืจึธืึตืœ'.hasHebrew(), true, 'String#hasHebrew');
+
+  equal('เคธเคญเฅ€ เคฎเคจเฅเคทเฅเคฏเฅ‹เค‚'.hasDevanagari(), true, 'String#hasDevanagari');
+  equal('เคธเคญเฅ€ เคฎเคจเฅเคทเฅเคฏเฅ‹เค‚'.isDevanagari(), true, 'String#isDevanagari');
+
+  equal("l'annรฉe derniรจre".hasLatin(), true, 'String#hasLatin | French');
+  equal("l'annรฉe derniรจre".isLatin(), true, 'String#isLatin | French');
+  equal('ใ“ใ‚Œใฏ one big mix ใงใ™ใ‚ˆใญใ€‚'.isLatin(), false, 'String#isLatin | Hiragana romaji mix');
+  equal('ใ“ใ‚Œใฏ one big mix ใงใ™ใ‚ˆใญใ€‚'.hasLatin(), true, 'String#isLatin | Hiragana romaji mix');
+  equal('ฤ'.isLatin(), true, 'String#isLatin | Extended set A');
+  equal('ฤ'.hasLatin(), true, 'String#isLatin | Extended set A');
+  equal('ฦ‰'.isLatin(), true, 'String#isLatin | Extended set B');
+  equal('ฦ‰'.hasLatin(), true, 'String#isLatin | Extended set B');
+  equal('ใ“ใ‚ŒใฏใƒŸใƒƒใ‚ฏใ‚นใงใ™ใ‚ˆใญใ€‚'.isLatin(), false, 'String#isLatin | Katakana hiragana mix');
+  equal('ใ“ใ‚ŒใฏใƒŸใƒƒใ‚ฏใ‚นใงใ™ใ‚ˆใญใ€‚'.hasLatin(), false, 'String#hasLatin | Katakana hiragana mix');
+
+
+
+
+
+  equal('ใ‚ซใ‚ฟใ‚ซใƒŠ'.hankaku(), '๏ฝถ๏พ€๏ฝถ๏พ…', 'String#hankaku | katakana');
+  equal('ใ“ใ‚“ใซใกใฏใ€‚ใƒคใƒžใƒ€ใ‚ฟใƒญใ‚ฆใงใ™ใ€‚'.hankaku(), 'ใ“ใ‚“ใซใกใฏ๏ฝก๏พ”๏พ๏พ€๏พž๏พ€๏พ›๏ฝณใงใ™๏ฝก', 'String#hankaku |  hankaku katakana inside a string');
+  equal('ใ“ใ‚“ใซใกใฏใ€‚๏ผด๏ผก๏ผฒ๏ผฏใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™ใ€‚'.hankaku(), 'ใ“ใ‚“ใซใกใฏ๏ฝกTARO YAMADAใงใ™๏ฝก', 'String#hankaku | hankaku romaji inside a string');
+  equal('ใ€€'.hankaku(), ' ', 'String#hankaku | spaces');
+  equal('ใ€€'.hankaku('p'), 'ใ€€', 'String#hankaku | punctuation | spaces');
+  equal('ใ€€'.hankaku('s'), ' ', 'String#hankaku | spaces');
+
+
+  var barabara = 'ใ“ใ‚“ใซใกใฏใ€‚ใ‚ฟใƒญใ‚ฆใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™ใ€‚๏ผ‘๏ผ˜ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰';
+  equal(barabara.hankaku(), 'ใ“ใ‚“ใซใกใฏ๏ฝก๏พ€๏พ›๏ฝณ YAMADAใงใ™๏ฝก18ๆ‰ใงใ™!(็ฌ‘)', 'String#hankaku | modes | full conversion');
+  equal(barabara.hankaku('all'), 'ใ“ใ‚“ใซใกใฏ๏ฝก๏พ€๏พ›๏ฝณ YAMADAใงใ™๏ฝก18ๆ‰ใงใ™!(็ฌ‘)', 'String#hankaku | modes all | full conversion');
+  equal(barabara.hankaku('a'), 'ใ“ใ‚“ใซใกใฏใ€‚ใ‚ฟใƒญใ‚ฆใ€€YAMADAใงใ™ใ€‚๏ผ‘๏ผ˜ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#hankaku | modes | romaji only');
+  equal(barabara.hankaku('n'), 'ใ“ใ‚“ใซใกใฏใ€‚ใ‚ฟใƒญใ‚ฆใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™ใ€‚18ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#hankaku | modes | numbers only');
+  equal(barabara.hankaku('k'), 'ใ“ใ‚“ใซใกใฏใ€‚๏พ€๏พ›๏ฝณใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™ใ€‚๏ผ‘๏ผ˜ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#hankaku | modes | katakana only');
+  equal(barabara.hankaku('p'), 'ใ“ใ‚“ใซใกใฏ๏ฝกใ‚ฟใƒญใ‚ฆใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™๏ฝก๏ผ‘๏ผ˜ๆ‰ใงใ™!(็ฌ‘)', 'String#hankaku | modes | punctuation only');
+  equal(barabara.hankaku('s'), 'ใ“ใ‚“ใซใกใฏใ€‚ใ‚ฟใƒญใ‚ฆ ๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™ใ€‚๏ผ‘๏ผ˜ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#hankaku | modes | spaces only');
+
+  equal(barabara.hankaku('an'), 'ใ“ใ‚“ใซใกใฏใ€‚ใ‚ฟใƒญใ‚ฆใ€€YAMADAใงใ™ใ€‚18ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#hankaku | modes | alphabet and numbers');
+  equal(barabara.hankaku('ak'), 'ใ“ใ‚“ใซใกใฏใ€‚๏พ€๏พ›๏ฝณใ€€YAMADAใงใ™ใ€‚๏ผ‘๏ผ˜ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#hankaku | modes | alphabet and katakana');
+  equal(barabara.hankaku('as'), 'ใ“ใ‚“ใซใกใฏใ€‚ใ‚ฟใƒญใ‚ฆ YAMADAใงใ™ใ€‚๏ผ‘๏ผ˜ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#hankaku | modes | alphabet and spaces');
+  equal(barabara.hankaku('ap'), 'ใ“ใ‚“ใซใกใฏ๏ฝกใ‚ฟใƒญใ‚ฆใ€€YAMADAใงใ™๏ฝก๏ผ‘๏ผ˜ๆ‰ใงใ™!(็ฌ‘)', 'String#hankaku | modes | alphabet and punctuation');
+
+  equal(barabara.hankaku('na'), 'ใ“ใ‚“ใซใกใฏใ€‚ใ‚ฟใƒญใ‚ฆใ€€YAMADAใงใ™ใ€‚18ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#hankaku | modes reverse | alphabet and numbers');
+  equal(barabara.hankaku('ka'), 'ใ“ใ‚“ใซใกใฏใ€‚๏พ€๏พ›๏ฝณใ€€YAMADAใงใ™ใ€‚๏ผ‘๏ผ˜ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#hankaku | modes reverse | alphabet and katakana');
+  equal(barabara.hankaku('sa'), 'ใ“ใ‚“ใซใกใฏใ€‚ใ‚ฟใƒญใ‚ฆ YAMADAใงใ™ใ€‚๏ผ‘๏ผ˜ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#hankaku | modes reverse | alphabet and spaces');
+  equal(barabara.hankaku('pa'), 'ใ“ใ‚“ใซใกใฏ๏ฝกใ‚ฟใƒญใ‚ฆใ€€YAMADAใงใ™๏ฝก๏ผ‘๏ผ˜ๆ‰ใงใ™!(็ฌ‘)', 'String#hankaku | modes reverse | alphabet and punctuation');
+
+  equal(barabara.hankaku('alphabet'), 'ใ“ใ‚“ใซใกใฏใ€‚ใ‚ฟใƒญใ‚ฆใ€€YAMADAใงใ™ใ€‚๏ผ‘๏ผ˜ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#hankaku | modes full | alphabet');
+  equal(barabara.hankaku('numbers'), 'ใ“ใ‚“ใซใกใฏใ€‚ใ‚ฟใƒญใ‚ฆใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™ใ€‚18ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#hankaku | modes full | numbers');
+  equal(barabara.hankaku('katakana'), 'ใ“ใ‚“ใซใกใฏใ€‚๏พ€๏พ›๏ฝณใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™ใ€‚๏ผ‘๏ผ˜ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#hankaku | modes full | katakana');
+  equal(barabara.hankaku('punctuation'), 'ใ“ใ‚“ใซใกใฏ๏ฝกใ‚ฟใƒญใ‚ฆใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™๏ฝก๏ผ‘๏ผ˜ๆ‰ใงใ™!(็ฌ‘)', 'String#hankaku | modes full | punctuation');
+  equal(barabara.hankaku('spaces'), 'ใ“ใ‚“ใซใกใฏใ€‚ใ‚ฟใƒญใ‚ฆ ๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™ใ€‚๏ผ‘๏ผ˜ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#hankaku | modes full | spaces');
+
+  var allZenkakuChars = 'ใ€€ใ€ใ€‚๏ผŒ๏ผŽใƒป๏ผš๏ผ›๏ผŸ๏ผใƒผ๏ฝž๏ผ๏ฝœ๏ผˆ๏ผ‰๏ผป๏ผฝ๏ฝ›๏ฝใ€Œใ€๏ผ‹๏ผ๏ผ๏ผœ๏ผž๏ฟฅ๏ผ„๏ฟ ๏ฟก๏ผ…๏ผƒ๏ผ†๏ผŠ๏ผ ๏ผ๏ผ‘๏ผ’๏ผ“๏ผ”๏ผ•๏ผ–๏ผ—๏ผ˜๏ผ™๏ผก๏ผข๏ผฃ๏ผค๏ผฅ๏ผฆ๏ผง๏ผจ๏ผฉ๏ผช๏ผซ๏ผฌ๏ผญ๏ผฎ๏ผฏ๏ผฐ๏ผฑ๏ผฒ๏ผณ๏ผด๏ผต๏ผถ๏ผท๏ผธ๏ผน๏ผบ๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝšใ‚กใ‚ขใ‚ฃใ‚คใ‚ฅใ‚ฆใ‚งใ‚จใ‚ฉใ‚ชใ‚ซใ‚ฌใ‚ญใ‚ฎใ‚ฏใ‚ฐใ‚ฑใ‚ฒใ‚ณใ‚ดใ‚ตใ‚ถใ‚ทใ‚ธใ‚นใ‚บใ‚ปใ‚ผใ‚ฝใ‚พใ‚ฟใƒ€ใƒใƒ‚ใƒƒใƒ„ใƒ…ใƒ†ใƒ‡ใƒˆใƒ‰ใƒŠใƒ‹ใƒŒใƒใƒŽใƒใƒใƒ‘ใƒ’ใƒ“ใƒ”ใƒ•ใƒ–ใƒ—ใƒ˜ใƒ™ใƒšใƒ›ใƒœใƒใƒžใƒŸใƒ ใƒกใƒขใƒฃใƒคใƒฅใƒฆใƒงใƒจใƒฉใƒชใƒซใƒฌใƒญใƒฏใƒฒใƒณ';
+  var allHankakuChars = ' ๏ฝค๏ฝก,.๏ฝฅ:;?!๏ฝฐ~/|()[]{}๏ฝข๏ฝฃ+-=<>ยฅ$ยขยฃ%#&*@0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz๏ฝง๏ฝฑ๏ฝจ๏ฝฒ๏ฝฉ๏ฝณ๏ฝช๏ฝด๏ฝซ๏ฝต๏ฝถ๏ฝถ๏พž๏ฝท๏ฝท๏พž๏ฝธ๏ฝธ๏พž๏ฝน๏ฝน๏พž๏ฝบ๏ฝบ๏พž๏ฝป๏ฝป๏พž๏ฝผ๏ฝผ๏พž๏ฝฝ๏ฝฝ๏พž๏ฝพ๏ฝพ๏พž๏ฝฟ๏ฝฟ๏พž๏พ€๏พ€๏พž๏พ๏พ๏พž๏ฝฏ๏พ‚๏พ‚๏พž๏พƒ๏พƒ๏พž๏พ„๏พ„๏พž๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พŠ๏พž๏พŠ๏พŸ๏พ‹๏พ‹๏พž๏พ‹๏พŸ๏พŒ๏พŒ๏พž๏พŒ๏พŸ๏พ๏พ๏พž๏พ๏พŸ๏พŽ๏พŽ๏พž๏พŽ๏พŸ๏พ๏พ๏พ‘๏พ’๏พ“๏ฝฌ๏พ”๏ฝญ๏พ•๏ฝฎ๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏ฝฆ๏พ';
+
+
+  equal(allZenkakuChars.hankaku(), allHankakuChars, 'String#hankaku | everything');
+  equal(allHankakuChars.zenkaku(), allZenkakuChars, 'String#zenkaku | everything');
+
+
+  equal('๏ฝถ๏พ€๏ฝถ๏พ…'.zenkaku(), 'ใ‚ซใ‚ฟใ‚ซใƒŠ', 'String#zenkaku | katakana');
+  equal(' '.zenkaku(), 'ใ€€', 'String#zenkaku | spaces | all');
+  equal(' '.zenkaku('s'), 'ใ€€', 'String#zenkaku | spaces | s');
+  equal(' '.zenkaku('p'), ' ', 'String#zenkaku | spaces | p');
+
+
+  barabara = 'ใ“ใ‚“ใซใกใฏ๏ฝก๏พ€๏พ›๏ฝณ YAMADAใงใ™๏ฝก18ๆ‰ใงใ™!(็ฌ‘)';
+
+  equal(barabara.zenkaku(), 'ใ“ใ‚“ใซใกใฏใ€‚ใ‚ฟใƒญใ‚ฆใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™ใ€‚๏ผ‘๏ผ˜ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#zenkaku | modes | full conversion');
+  equal(barabara.zenkaku('a'), 'ใ“ใ‚“ใซใกใฏ๏ฝก๏พ€๏พ›๏ฝณ ๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™๏ฝก18ๆ‰ใงใ™!(็ฌ‘)', 'String#zenkaku | modes | alphabet');
+  equal(barabara.zenkaku('n'), 'ใ“ใ‚“ใซใกใฏ๏ฝก๏พ€๏พ›๏ฝณ YAMADAใงใ™๏ฝก๏ผ‘๏ผ˜ๆ‰ใงใ™!(็ฌ‘)', 'String#zenkaku | modes | number');
+  equal(barabara.zenkaku('k'), 'ใ“ใ‚“ใซใกใฏ๏ฝกใ‚ฟใƒญใ‚ฆ YAMADAใงใ™๏ฝก18ๆ‰ใงใ™!(็ฌ‘)', 'String#zenkaku | modes | katakana');
+  equal(barabara.zenkaku('p'), 'ใ“ใ‚“ใซใกใฏใ€‚๏พ€๏พ›๏ฝณ YAMADAใงใ™ใ€‚18ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#zenkaku | modes | punctuation');
+  equal(barabara.zenkaku('s'), 'ใ“ใ‚“ใซใกใฏ๏ฝก๏พ€๏พ›๏ฝณใ€€YAMADAใงใ™๏ฝก18ๆ‰ใงใ™!(็ฌ‘)', 'String#zenkaku | modes | spaces');
+
+  equal(barabara.zenkaku('an'), 'ใ“ใ‚“ใซใกใฏ๏ฝก๏พ€๏พ›๏ฝณ ๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™๏ฝก๏ผ‘๏ผ˜ๆ‰ใงใ™!(็ฌ‘)', 'String#zenkaku | modes | alphabet and numbers');
+  equal(barabara.zenkaku('ak'), 'ใ“ใ‚“ใซใกใฏ๏ฝกใ‚ฟใƒญใ‚ฆ ๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™๏ฝก18ๆ‰ใงใ™!(็ฌ‘)', 'String#zenkaku | modes | alphabet and katakana');
+  equal(barabara.zenkaku('as'), 'ใ“ใ‚“ใซใกใฏ๏ฝก๏พ€๏พ›๏ฝณใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™๏ฝก18ๆ‰ใงใ™!(็ฌ‘)', 'String#zenkaku | modes | alphabet and spaces');
+  equal(barabara.zenkaku('ap'), 'ใ“ใ‚“ใซใกใฏใ€‚๏พ€๏พ›๏ฝณ ๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™ใ€‚18ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#zenkaku | modes | alphabet and punctuation');
+
+  equal(barabara.zenkaku('na'), 'ใ“ใ‚“ใซใกใฏ๏ฝก๏พ€๏พ›๏ฝณ ๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™๏ฝก๏ผ‘๏ผ˜ๆ‰ใงใ™!(็ฌ‘)', 'String#zenkaku | modes reverse | alphabet and numbers');
+  equal(barabara.zenkaku('ka'), 'ใ“ใ‚“ใซใกใฏ๏ฝกใ‚ฟใƒญใ‚ฆ ๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™๏ฝก18ๆ‰ใงใ™!(็ฌ‘)', 'String#zenkaku | modes reverse | alphabet and katakana');
+  equal(barabara.zenkaku('sa'), 'ใ“ใ‚“ใซใกใฏ๏ฝก๏พ€๏พ›๏ฝณใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™๏ฝก18ๆ‰ใงใ™!(็ฌ‘)', 'String#zenkaku | modes reverse | alphabet and spaces');
+  equal(barabara.zenkaku('pa'), 'ใ“ใ‚“ใซใกใฏใ€‚๏พ€๏พ›๏ฝณ ๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™ใ€‚18ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#zenkaku | modes reverse | alphabet and punctuation');
+
+  equal(barabara.zenkaku('alphabet'), 'ใ“ใ‚“ใซใกใฏ๏ฝก๏พ€๏พ›๏ฝณ ๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™๏ฝก18ๆ‰ใงใ™!(็ฌ‘)', 'String#zenkaku | modes full | alphabet');
+  equal(barabara.zenkaku('numbers'), 'ใ“ใ‚“ใซใกใฏ๏ฝก๏พ€๏พ›๏ฝณ YAMADAใงใ™๏ฝก๏ผ‘๏ผ˜ๆ‰ใงใ™!(็ฌ‘)', 'String#zenkaku | modes full | numbers');
+  equal(barabara.zenkaku('katakana'), 'ใ“ใ‚“ใซใกใฏ๏ฝกใ‚ฟใƒญใ‚ฆ YAMADAใงใ™๏ฝก18ๆ‰ใงใ™!(็ฌ‘)', 'String#zenkaku | modes full | katakana');
+  equal(barabara.zenkaku('spaces'), 'ใ“ใ‚“ใซใกใฏ๏ฝก๏พ€๏พ›๏ฝณใ€€YAMADAใงใ™๏ฝก18ๆ‰ใงใ™!(็ฌ‘)', 'String#zenkaku | modes full | spaces');
+  equal(barabara.zenkaku('punctuation'), 'ใ“ใ‚“ใซใกใฏใ€‚๏พ€๏พ›๏ฝณ YAMADAใงใ™ใ€‚18ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#zenkaku | modes full | punctuation');
+
+
+  equal('ใ‚ฌ'.hankaku(), '๏ฝถ๏พž', 'String#hankaku | dakuten | ใ‚ฌ');
+  equal('๏ฝถ๏พž'.zenkaku(), 'ใ‚ฌ', 'String#zenkaku | dakuten | ๏ฝถ');
+  equal('๏ฝถ๏พž'.hiragana(), 'ใŒ', 'String#hiragana | dakuten | ๏ฝถ');
+
+
+  equal('ใ‚ซใ‚ฟใ‚ซใƒŠ'.hiragana(), 'ใ‹ใŸใ‹ใช', 'String#hiragana | from katakana');
+  equal('๏ฝถ๏พ€๏ฝถ๏พ…'.hiragana(), 'ใ‹ใŸใ‹ใช', 'String#hiragana | convert from hankaku katakana');
+  equal('๏ฝถ๏พ€๏ฝถ๏พ…'.hiragana(false), '๏ฝถ๏พ€๏ฝถ๏พ…', 'String#hiragana | no widths |convert from hankaku katakana');
+  equal(barabara.hiragana(), 'ใ“ใ‚“ใซใกใฏ๏ฝกใŸใ‚ใ† YAMADAใงใ™๏ฝก18ๆ‰ใงใ™!(็ฌ‘)', 'String#hiragana | full string');
+  equal(barabara.zenkaku().hiragana(), 'ใ“ใ‚“ใซใกใฏใ€‚ใŸใ‚ใ†ใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™ใ€‚๏ผ‘๏ผ˜ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#hiragana | full string to zenkaku');
+  equal(barabara.hiragana(false), 'ใ“ใ‚“ใซใกใฏ๏ฝก๏พ€๏พ›๏ฝณ YAMADAใงใ™๏ฝก18ๆ‰ใงใ™!(็ฌ‘)', 'String#hiragana | no widths | full string');
+
+
+  equal('ใฒใ‚‰ใŒใช'.katakana(), 'ใƒ’ใƒฉใ‚ฌใƒŠ', 'String#katakana | from hiragana');
+  equal(barabara.katakana(), 'ใ‚ณใƒณใƒ‹ใƒใƒ๏ฝก๏พ€๏พ›๏ฝณ YAMADAใƒ‡ใ‚น๏ฝก18ๆ‰ใƒ‡ใ‚น!(็ฌ‘)', 'String#katakana | full string');
+  equal(barabara.zenkaku().katakana(), 'ใ‚ณใƒณใƒ‹ใƒใƒใ€‚ใ‚ฟใƒญใ‚ฆใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใƒ‡ใ‚นใ€‚๏ผ‘๏ผ˜ๆ‰ใƒ‡ใ‚น๏ผ๏ผˆ็ฌ‘๏ผ‰', 'String#katakana full string to zenkaku');
+
+
+  equal('ใ“ใ‚“ใซใกใฏใ€‚ใ‚ฟใƒญใ‚ฆใ€€๏ผน๏ผก๏ผญ๏ผก๏ผค๏ผกใงใ™ใ€‚๏ผ‘๏ผ˜ๆ‰ใงใ™๏ผ๏ผˆ็ฌ‘๏ผ‰'.katakana().hankaku(), '๏ฝบ๏พ๏พ†๏พ๏พŠ๏ฝก๏พ€๏พ›๏ฝณ YAMADA๏พƒ๏พž๏ฝฝ๏ฝก18ๆ‰๏พƒ๏พž๏ฝฝ!(็ฌ‘)', 'String#katakana | full string to katakana and hankaku');
+
+  var allHiragana = 'ใใ‚ใƒใ„ใ…ใ†ใ‡ใˆใ‰ใŠใ‹ใŒใใŽใใใ‘ใ’ใ“ใ”ใ•ใ–ใ—ใ˜ใ™ใšใ›ใœใใžใŸใ ใกใขใฃใคใฅใฆใงใจใฉใชใซใฌใญใฎใฏใฐใฑใฒใณใดใตใถใทใธในใบใปใผใฝใพใฟใ‚€ใ‚ใ‚‚ใ‚ƒใ‚„ใ‚…ใ‚†ใ‚‡ใ‚ˆใ‚‰ใ‚Šใ‚‹ใ‚Œใ‚ใ‚Žใ‚ใ‚ใ‚‘ใ‚’ใ‚“ใ‚”ใ‚•ใ‚–';
+  var allKatakana = 'ใ‚กใ‚ขใ‚ฃใ‚คใ‚ฅใ‚ฆใ‚งใ‚จใ‚ฉใ‚ชใ‚ซใ‚ฌใ‚ญใ‚ฎใ‚ฏใ‚ฐใ‚ฑใ‚ฒใ‚ณใ‚ดใ‚ตใ‚ถใ‚ทใ‚ธใ‚นใ‚บใ‚ปใ‚ผใ‚ฝใ‚พใ‚ฟใƒ€ใƒใƒ‚ใƒƒใƒ„ใƒ…ใƒ†ใƒ‡ใƒˆใƒ‰ใƒŠใƒ‹ใƒŒใƒใƒŽใƒใƒใƒ‘ใƒ’ใƒ“ใƒ”ใƒ•ใƒ–ใƒ—ใƒ˜ใƒ™ใƒšใƒ›ใƒœใƒใƒžใƒŸใƒ ใƒกใƒขใƒฃใƒคใƒฅใƒฆใƒงใƒจใƒฉใƒชใƒซใƒฌใƒญใƒฎใƒฏใƒฐใƒฑใƒฒใƒณใƒดใƒตใƒถ';
+
+  equal(allKatakana.hiragana(), allHiragana, 'String#hiragana | all');
+  equal(allHiragana.katakana(), allKatakana, 'String#katakana | all');
+
+
+  equal(''.hankaku(), '', 'String#hankaku | blank');
+  equal('ใ‚ซ'.hankaku(), '๏ฝถ', 'String#hankaku | ใ‚ซ');
+  equal(''.zenkaku(), '', 'String#zenkaku | blank');
+  equal('๏ฝถ'.zenkaku(), 'ใ‚ซ', 'String#zenkaku | ๏ฝถ');
+  equal(''.hiragana(), '', 'String#hiragana | blank');
+  equal('ใ‚ซ'.hiragana(), 'ใ‹', 'String#hiragana | ใ‚ซ');
+  equal(''.katakana(), '', 'String#katakana | blank');
+  equal('ใ‹'.katakana(), 'ใ‚ซ', 'String#katakana | ใ‹');
+
+
+});
level2/node_modules/sugar/test/environments/sugar/number.js
@@ -0,0 +1,864 @@
+
+test('Number', function () {
+
+  var counter;
+  var ret;
+
+  var rand = Number.random();
+  equal(rand == 0 || rand == 1, true, 'Number.random | no params = 0 or 1', { mootools: false });
+
+  rand = Number.random(10);
+  equal(rand >= 0 && rand <= 10, true, 'Number.random | min not defined, max is 10', { mootools: false });
+  equal(rand % 1, 0, 'Number.random | number is whole', { mootools: NaN });
+
+  rand = Number.random(536224, 536280);
+  equal(rand >= 536224 && rand <= 536280, true, 'Number.random | min and max defined');
+
+  rand = Number.random(6, -5);
+  equal(rand >= -5 && rand <= 6, true, 'Number.random | min and max can be reversed');
+
+  equal(Number.random(0, 0), 0, 'Number.random | 0 should always remain 0');
+  equal(Number.random(0, 0), 0, 'Number.random | 0 should always remain 0');
+  equal(Number.random(0, 0), 0, 'Number.random | 0 should always remain 0');
+  equal(Number.random(0, 0), 0, 'Number.random | 0 should always remain 0');
+  equal(Number.random(0, 0), 0, 'Number.random | 0 should always remain 0');
+  equal(Number.random(0, 0), 0, 'Number.random | 0 should always remain 0');
+
+
+
+  equal((4).toNumber(), 4, 'Number#toNumber | 4 is 4');
+  equal((10000).toNumber(), 10000, 'Number#toNumber | 10000 is 10000');
+  equal((5.2345).toNumber(), 5.2345, 'Number#toNumber | 5.2345 is 5.2345');
+
+
+
+  equal((5.5).ceil(), 6, 'Number#ceil | 5.5');
+  equal((5.14).ceil(), 6, 'Number#ceil | 5.14');
+  equal((5).ceil(), 5, 'Number#ceil | 5');
+  equal((-5.5).ceil(), -5, 'Number#ceil | -5.5');
+  equal((-5.14).ceil(), -5, 'Number#ceil | -5.14');
+  equal((-5).ceil(), -5, 'Number#ceil | -5');
+  equal((4417.1318).ceil(0), 4418, 'Number#ceil | 0');
+  equal((4417.1318).ceil(1), 4417.2, 'Number#ceil | 1', { prototype: 4418, mootools: 4418 });
+  equal((4417.1318).ceil(2), 4417.14, 'Number#ceil | 2', { prototype: 4418, mootools: 4418 });
+  equal((4417.1318).ceil(3), 4417.132, 'Number#ceil | 3', { prototype: 4418, mootools: 4418 });
+  equal((4417.1318).ceil(-1), 4420, 'Number#ceil | -1', { prototype: 4418, mootools: 4418 });
+  equal((4417.1318).ceil(-2), 4500, 'Number#ceil | -2', { prototype: 4418, mootools: 4418 });
+  equal((4417.1318).ceil(-3), 5000, 'Number#ceil | -3', { prototype: 4418, mootools: 4418 });
+
+  equal((5.5).floor(), 5, 'Number#floor | 5.5');
+  equal((5.14).floor(), 5, 'Number#floor | 5.14');
+  equal((5.9).floor(), 5, 'Number#floor | 5.9');
+  equal((5).floor(), 5, 'Number#floor | 5');
+  equal((-5.5).floor(), -6, 'Number#floor | -5.5');
+  equal((-5.14).floor(), -6, 'Number#floor | -5.14');
+  equal((-5).floor(), -5, 'Number#floor | -5');
+  equal((4417.1318).floor(0), 4417, 'Number#floor | 0');
+  equal((4417.1318).floor(1), 4417.1, 'Number#floor | 1', { prototype: 4417, mootools: 4417 });
+  equal((4417.1318).floor(2), 4417.13, 'Number#floor | 2', { prototype: 4417, mootools: 4417 });
+  equal((4417.1318).floor(3), 4417.131, 'Number#floor | 3', { prototype: 4417, mootools: 4417 });
+  equal((4417.1318).floor(-1), 4410, 'Number#floor | -1', { prototype: 4417, mootools: 4417 });
+  equal((4417.1318).floor(-2), 4400, 'Number#floor | -2', { prototype: 4417, mootools: 4417 });
+  equal((4417.1318).floor(-3), 4000, 'Number#floor | -3', { prototype: 4417, mootools: 4417 });
+
+
+  equal((-5).abs(), 5, 'Number#abs | -5');
+  equal((5).abs(), 5, 'Number#abs | 5');
+  equal((-3.324).abs(), 3.324, 'Number#abs | -3.324');
+  equal((3.324).abs(), 3.324, 'Number#abs | 3.324');
+
+
+  equal((3).pow(2), 9, 'Number#pow | 3 ^ 2');
+  equal((3).pow(1), 3, 'Number#pow | 3 ^ 1');
+  equal((12).pow(2), 144, 'Number#pow | 12 ^ 2');
+  equal((3).pow(3), 27, 'Number#pow | 3 ^ 3');
+  equal((3).pow(0), 1, 'Number#pow | zero is allowed');
+  equal((3).pow(), NaN, 'Number#pow | defaults to no number');
+
+
+  equal((1).sin(), 0.8414709848078965, 'Number#sin | 1');
+  equal((0).sin(), 0, 'Number#sin | 0');
+  equal((Math.PI/2).sin(), 1, 'Number#sin | PI/2');
+  equal((0).cos(), 1, 'Number#cos | 0');
+  equal((Math.PI).cos(), -1, 'Number#cos | PI');
+  equal((Math.PI*2).cos(), 1, 'Number#cos | PI*2');
+  equal((0).tan(), 0, 'Number#tan | 0');
+  equal((45).tan(), 1.6197751905438615, 'Number#tan | 45');
+  equal((90).tan(), -1.995200412208242, 'Number#tan | 90');
+  equal((0).asin(), 0, 'Number#asin | 0');
+  equal((1).asin(), Math.PI/2, 'Number#asin | 1');
+  equal((0).acos(), Math.PI/2, 'Number#acos | 0');
+  equal((1).acos(), 0, 'Number#acos | 1');
+  equal((0).atan(), 0, 'Number#atan | 0');
+  equal((45).atan(), 1.5485777614681775, 'Number#atan | 45');
+
+
+  equal((64).log(2), 6, 'Number#log | 64 with base 2');
+  equal((9).log(3), 2, 'Number#log | 9 with base 3');
+  equal((5).log(), 1.6094379124341003, 'Number#log | 5');
+  equal((Math.E).log(), 1, 'Number#log | E');
+  equal((1).exp(), Math.E, 'Number#exp | 1');
+  equal((0).exp(), 1, 'Number#exp | 0');
+
+
+  equal((9).sqrt(), 3, 'Number#sqrt | 9');
+  equal((1024).sqrt(), 32, 'Number#sqrt | 1024');
+
+
+
+  equal((3).round(), 3, 'Number#round | 3');
+  equal((3.241).round(), 3, 'Number#round | 3.241');
+  equal((3.752).round(), 4, 'Number#round | 3.752');
+  equal((-3.241).round(), -3, 'Number#round | -3.241');
+  equal((-3.752).round(), -4, 'Number#round | -3.752');
+  equal((3.241).round(1), 3.2, 'Number#round | 3.241 to 1 place', { prototype: 3 });
+
+  equal((3.752).round(1), 3.8, 'Number#round | 3.752 to 1 place', { prototype: 4 });
+  equal((3.241).round(2), 3.24,  'Number#round | 3.241 to 2 places', { prototype: 3 });
+  equal((3.752).round(2), 3.75,  'Number#round | 3.752 to 2 places', { prototype: 4 });
+
+  equal((322855.241).round(-2), 322900, 'Number#round | 322855.241 to -2 places', { prototype: 322855 });
+  equal((322855.241).round(-3), 323000, 'Number#round | 322855.241 to -3 places', { prototype: 322855 });
+  equal((322855.241).round(-4), 320000, 'Number#round | 322855.241 to -4 places', { prototype: 322855 });
+  equal((322855.241).round(-6), 0, 'Number#round | 322855.241 to -6 places', { prototype: 322855 });
+  equal((722855.241).round(-6), 1000000, 'Number#round | 722855.241 to -6 places', { prototype: 722855 });
+  equal((722855.241).round(-8), 0, 'Number#round | 722855.241 to -8 places', { prototype: 722855 });
+
+
+  equal((65).chr(), 'A', 'Number#chr | 65');
+  equal((24536).chr(), 'ๅฟ˜', 'Number#chr | 24536');
+  equal((20294).chr(), 'ไฝ†', 'Number#chr | 20294');
+
+  counter = 0;
+  var dCounter = 5;
+  ret = (5).downto(1, function(n, i) {
+    equal(n, dCounter, 'Number#downto | n is set');
+    equal(i, counter, 'Number#downto | index is set');
+    counter++;
+    dCounter--;
+  });
+  equal(counter, 5, 'Number#downto | iterated 5 times');
+  equal(ret, [5,4,3,2,1], 'Number#downto | returns array');
+
+
+  counter = 0;
+  var dCounter = 1;
+  ret = (1).upto(5, function(n, i) {
+    equal(n, dCounter, 'Number#upto | n is set');
+    equal(i, counter, 'Number#upto | index is set');
+    counter++;
+    dCounter++;
+  });
+  equal(counter, 5, 'Number#upto | iterated 5 times');
+  equal(ret, [1,2,3,4,5], 'Number#upto | returns array');
+
+  counter = 0;
+  var dCounter = 5;
+  ret = (5).downto(10, function(n, i) {
+    equal(n, dCounter, 'Number#downto | n is set');
+    equal(i, counter, 'Number#downto | index is set');
+    counter++;
+    dCounter++;
+  });
+  equal(counter, 6, 'Number#downto | 5 downto 10 | iterates 6 times');
+  equal(ret, [5,6,7,8,9,10], 'Number#downto | 5 downto 10 | returns equivalent of Number#upto');
+
+  counter = 0;
+  var dCounter = 5;
+  ret = (5).upto(1, function(n, i) {
+    equal(n, dCounter, 'Number#downto | n is set');
+    equal(i, counter, 'Number#downto | index is set');
+    counter++;
+    dCounter--;
+  });
+  equal(counter, 5, 'Number#upto | 5 up to 1 | iterates 5 times');
+  equal(ret, [5,4,3,2,1], 'Number#upto | 5 up to 1 | returns equivalent of Number#downto');
+
+  equal((3).upto(9, null, 3), [3,6,9], 'Number#upto | can handle multiples');
+  equal((3).upto(10, null, 3), [3,6,9], 'Number#upto | can handle multiples stops at 9');
+  equal((3).upto(8, null, 3), [3,6], 'Number#upto | can handle multiples stops at 8');
+  equal((9).downto(3, null, 3), [9,6,3], 'Number#downto | can handle multiples');
+  equal((9).downto(4, null, 3), [9,6], 'Number#downto | can handle multiples stops at 4');
+  equal((9).downto(2, null, 3), [9,6,3], 'Number#downto | can handle multiples stops at 2');
+
+
+
+  counter = 0;
+  (5).times(function(first) {
+    equal(first, counter, 'Number#times | first parameter is the index');
+    counter++;
+  }, 'wasabi');
+  equal(counter, 5, 'Number#times | iterated 5 times');
+
+
+
+  equal((2).isMultipleOf(2), true, 'Number#multipleOf | 2 is a multiple of 2');
+  equal((6).isMultipleOf(2), true, 'Number#multipleOf | 6 is a multiple of 2');
+  equal((100).isMultipleOf(2), true, 'Number#multipleOf | 100 is a multiple of 2');
+  equal((2).isMultipleOf(100), false, 'Number#multipleOf | 2 is a multiple of 100');
+  equal((100).isMultipleOf(-2), true, 'Number#multipleOf | 100 is a multiple of -2');
+  equal((6).isMultipleOf(-2), true, 'Number#multipleOf | 6 is a multiple of -2');
+  equal((6).isMultipleOf(3), true, 'Number#multipleOf | 6 is a multiple of 3');
+  equal((7).isMultipleOf(3), false, 'Number#multipleOf | 7 is a multiple of 3');
+  equal((2.5).isMultipleOf(1.25), true, 'Number#multipleOf | 2.5 is a multiple of 1.25');
+  equal((2).isMultipleOf('a'), false, 'Number#multipleOf | string arguments');
+  equal((2).isMultipleOf(/af/), false, 'Number#multipleOf | other random arguments');
+  equal((2).isMultipleOf(null), false, 'Number#multipleOf | null');
+  equal((2).isMultipleOf(), false, 'Number#multipleOf | no argument passed');
+
+
+
+  equal((0).isOdd(), false, 'Number#isOdd | 0');
+  equal((1).isOdd(), true, 'Number#isOdd | 1');
+  equal((2).isOdd(), false, 'Number#isOdd | 2');
+  equal((24).isOdd(), false, 'Number#isOdd | 24');
+  equal((200).isOdd(), false, 'Number#isOdd | 200');
+  equal((NaN).isOdd(), false, 'Number#isOdd | NaN');
+
+
+
+
+  equal((0).isEven(), true, 'Number#isEven | 0');
+  equal((1).isEven(), false, 'Number#isEven | 1');
+  equal((2).isEven(), true, 'Number#isEven | 2');
+  equal((24).isEven(), true, 'Number#isEven | 24');
+  equal((200).isEven(), true, 'Number#isEven | 200');
+  equal((NaN).isEven(), false, 'Number#isEven | NaN');
+
+
+
+  equal((1).ordinalize(), '1st', 'Number#ordinalize | 1');
+  equal((2).ordinalize(), '2nd', 'Number#ordinalize | 2');
+  equal((3).ordinalize(), '3rd', 'Number#ordinalize | 3');
+  equal((4).ordinalize(), '4th', 'Number#ordinalize | 4');
+  equal((5).ordinalize(), '5th', 'Number#ordinalize | 5');
+  equal((6).ordinalize(), '6th', 'Number#ordinalize | 6');
+  equal((7).ordinalize(), '7th', 'Number#ordinalize | 7');
+  equal((8).ordinalize(), '8th', 'Number#ordinalize | 8');
+  equal((9).ordinalize(), '9th', 'Number#ordinalize | 9');
+  equal((10).ordinalize(), '10th', 'Number#ordinalize | 10');
+  equal((11).ordinalize(), '11th', 'Number#ordinalize | 11');
+  equal((12).ordinalize(), '12th', 'Number#ordinalize | 12');
+  equal((13).ordinalize(), '13th', 'Number#ordinalize | 13');
+  equal((14).ordinalize(), '14th', 'Number#ordinalize | 14');
+  equal((15).ordinalize(), '15th', 'Number#ordinalize | 15');
+  equal((20).ordinalize(), '20th', 'Number#ordinalize | 20');
+  equal((21).ordinalize(), '21st', 'Number#ordinalize | 21');
+  equal((22).ordinalize(), '22nd', 'Number#ordinalize | 22');
+  equal((23).ordinalize(), '23rd', 'Number#ordinalize | 23');
+  equal((24).ordinalize(), '24th', 'Number#ordinalize | 24');
+  equal((25).ordinalize(), '25th', 'Number#ordinalize | 25');
+  equal((100).ordinalize(), '100th', 'Number#ordinalize | 100');
+  equal((101).ordinalize(), '101st', 'Number#ordinalize | 101');
+  equal((102).ordinalize(), '102nd', 'Number#ordinalize | 102');
+  equal((103).ordinalize(), '103rd', 'Number#ordinalize | 103');
+  equal((104).ordinalize(), '104th', 'Number#ordinalize | 104');
+  equal((105).ordinalize(), '105th', 'Number#ordinalize | 105');
+  equal((111).ordinalize(), '111th', 'Number#ordinalize | 111');
+  equal((112).ordinalize(), '112th', 'Number#ordinalize | 112');
+  equal((113).ordinalize(), '113th', 'Number#ordinalize | 113');
+  equal((114).ordinalize(), '114th', 'Number#ordinalize | 114');
+  equal((-1).ordinalize(), '-1st', 'Number#ordinalize | -1');
+
+
+  // Moved from inflections tests
+
+  var OrdinalNumbers = {
+    "-1"    : "-1st",
+    "-2"    : "-2nd",
+    "-3"    : "-3rd",
+    "-4"    : "-4th",
+    "-5"    : "-5th",
+    "-6"    : "-6th",
+    "-7"    : "-7th",
+    "-8"    : "-8th",
+    "-9"    : "-9th",
+    "-10"   : "-10th",
+    "-11"   : "-11th",
+    "-12"   : "-12th",
+    "-13"   : "-13th",
+    "-14"   : "-14th",
+    "-20"   : "-20th",
+    "-21"   : "-21st",
+    "-22"   : "-22nd",
+    "-23"   : "-23rd",
+    "-24"   : "-24th",
+    "-100"  : "-100th",
+    "-101"  : "-101st",
+    "-102"  : "-102nd",
+    "-103"  : "-103rd",
+    "-104"  : "-104th",
+    "-110"  : "-110th",
+    "-111"  : "-111th",
+    "-112"  : "-112th",
+    "-113"  : "-113th",
+    "-1000" : "-1000th",
+    "-1001" : "-1001st",
+    "0"     : "0th",
+    "1"     : "1st",
+    "2"     : "2nd",
+    "3"     : "3rd",
+    "4"     : "4th",
+    "5"     : "5th",
+    "6"     : "6th",
+    "7"     : "7th",
+    "8"     : "8th",
+    "9"     : "9th",
+    "10"    : "10th",
+    "11"    : "11th",
+    "12"    : "12th",
+    "13"    : "13th",
+    "14"    : "14th",
+    "20"    : "20th",
+    "21"    : "21st",
+    "22"    : "22nd",
+    "23"    : "23rd",
+    "24"    : "24th",
+    "100"   : "100th",
+    "101"   : "101st",
+    "102"   : "102nd",
+    "103"   : "103rd",
+    "104"   : "104th",
+    "110"   : "110th",
+    "111"   : "111th",
+    "112"   : "112th",
+    "113"   : "113th",
+    "1000"  : "1000th",
+    "1001"  : "1001st"
+  }
+
+  // Test ordinalize
+  testIterateOverObject(OrdinalNumbers, function(str, ordinalized) {
+      equal(parseInt(str).ordinalize(), ordinalized, 'String#ordinalize')
+  });
+
+
+
+
+
+
+
+
+
+
+  equal((100).format(), '100', 'Number#format | 100')
+  equal((1).format(), '1', 'Number#format | 1')
+  equal((10).format(), '10', 'Number#format | 10')
+  equal((100).format(), '100', 'Number#format | 100')
+  equal((1000).format(), '1,000', 'Number#format | 1,000')
+  equal((10000).format(), '10,000', 'Number#format | 10,000')
+  equal((100000).format(), '100,000', 'Number#format | 100,000')
+  equal((1000000).format(), '1,000,000', 'Number#format | 1,000,000')
+  equal((1000000.01).format(), '1,000,000.01', 'Number#format | 1,000,000.01')
+  equal((-100).format(), '-100', 'Number#format | -100')
+  equal((-1).format(), '-1', 'Number#format | -1')
+  equal((-1000).format(), '-1,000', 'Number#format | -1,000')
+  equal((-1000000.01).format(), '-1,000,000.01', 'Number#format | -1,000,000.01')
+
+  equal((0.52).format(), '0.52', 'Number#format | 0.52')
+
+  // These discrepancies are due to floating point variable limitations.
+  equal((100046546510000.022435451).format().replace(/\.\d+$/, ''), '100,046,546,510,000', 'Number#format | 100,046,546,510,000')
+  equal((-100046546510000.022435451).format().replace(/\.\d+$/, ''), '-100,046,546,510,000', 'Number#format | -100,046,546,510,000')
+
+  equal((1000).format(null, ' '), '1 000', 'Number#format | 1000')
+  equal((1532587).format(null, ' '), '1 532 587', 'Number#format | larger number')
+  equal((1532587.5752).format(null, ' ', ','), '1 532 587,5752', 'Number#format | larger number with decimal')
+  equal((9999999.99).format(), '9,999,999.99', 'Number#format | Standard');
+  equal((9999999.99).format(null, '.',','), '9.999.999,99', 'Number#format | Euro style!');
+  equal((9999999.99).format(null, ''), '9999999.99', 'Number#format | empty string');
+  equal((9999999.99).format(null, '', ''), '999999999', 'Number#format | no punctuation');
+
+  equal((1).format(2), '1.00', 'Number#format | to 2 places')
+  equal((10).format(2), '10.00', 'Number#format | to 2 places')
+  equal((100).format(2), '100.00', 'Number#format | to 2 places')
+  equal((1000).format(2), '1,000.00', 'Number#format | to 2 places')
+  equal((10000).format(2), '10,000.00', 'Number#format | to 2 places')
+  equal((100000).format(2), '100,000.00', 'Number#format | to 2 places')
+  equal((1000000).format(2), '1,000,000.00', 'Number#format | to 2 places')
+  equal((1).format(4), '1.0000', 'Number#format | to 4 places')
+  equal((10).format(4), '10.0000', 'Number#format | to 4 places')
+  equal((100).format(4), '100.0000', 'Number#format | to 4 places')
+  equal((1000).format(4), '1,000.0000', 'Number#format | to 4 places')
+  equal((10000).format(4), '10,000.0000', 'Number#format | to 4 places')
+  equal((100000).format(4), '100,000.0000', 'Number#format | to 4 places')
+  equal((1000000).format(4), '1,000,000.0000', 'Number#format | to 4 places')
+  equal((-1).format(2), '-1.00', 'Number#format | to 2 places')
+  equal((-10).format(2), '-10.00', 'Number#format | to 2 places')
+  equal((-100).format(2), '-100.00', 'Number#format | to 2 places')
+  equal((-1000).format(2), '-1,000.00', 'Number#format | to 2 places')
+  equal((-10000).format(2), '-10,000.00', 'Number#format | to 2 places')
+  equal((-100000).format(2), '-100,000.00', 'Number#format | to 2 places')
+  equal((-1000000).format(2), '-1,000,000.00', 'Number#format | to 2 places')
+  equal((-1).format(4), '-1.0000', 'Number#format | to 4 places')
+  equal((-10).format(4), '-10.0000', 'Number#format | to 4 places')
+  equal((-100).format(4), '-100.0000', 'Number#format | to 4 places')
+  equal((-1000).format(4), '-1,000.0000', 'Number#format | to 4 places')
+  equal((-10000).format(4), '-10,000.0000', 'Number#format | to 4 places')
+  equal((-100000).format(4), '-100,000.0000', 'Number#format | to 4 places')
+  equal((-1000000).format(4), '-1,000,000.0000', 'Number#format | to 4 places')
+
+  equal((2.435).format(2), '2.44', 'Number#format | 2.44')
+  equal((553599.435).format(2), '553,599.44', 'Number#format | 553,599.44')
+  equal((553599.435).format(1), '553,599.4', 'Number#format | 553,599.4')
+  equal((553599.435).format(0), '553,599', 'Number#format | 553,599')
+  equal((553599.435).format(-1), '553,600', 'Number#format | 553,600')
+  equal((553599.435).format(-2), '553,600', 'Number#format | 553,600')
+  equal((553599.435).format(-3), '554,000', 'Number#format | 553,600')
+  equal((553599.435).format(-4), '550,000', 'Number#format | 550,000')
+  equal((553599.435).format(-5), '600,000', 'Number#format | 600,000')
+
+
+  equal((1).pad(0), '1', 'Number#pad | 1 no padding')
+  equal((1).pad(1), '1', 'Number#pad | 1 padded to 1 place')
+  equal((1).pad(2), '01', 'Number#pad | 1 padded to 2 places')
+  equal((1).pad(3), '001', 'Number#pad | 1 padded to 3 places')
+  equal((1).pad(4), '0001', 'Number#pad | 1 padded to 4 places')
+  equal((547).pad(0), '547', 'Number#pad | 547 no padding')
+  equal((547).pad(1), '547', 'Number#pad | 547 padded to 1 place')
+  equal((547).pad(2), '547', 'Number#pad | 547 padded to 2 places')
+  equal((547).pad(3), '547', 'Number#pad | 547 padded to 3 places')
+  equal((547).pad(4), '0547', 'Number#pad | 547 padded to 4 places')
+  equal((0).pad(0), '0', 'Number#pad | 0 no padding')
+  equal((0).pad(1), '0', 'Number#pad | 0 padded to 1 place')
+  equal((0).pad(2), '00', 'Number#pad | 0 padded to 2 places')
+  equal((0).pad(3), '000', 'Number#pad | 0 padded to 3 places')
+  equal((0).pad(4), '0000', 'Number#pad | 0 padded to 4 places')
+  equal((-1).pad(1), '-1', 'Number#pad | -1 padded to 1 places')
+  equal((-1).pad(2), '-01', 'Number#pad | -1 padded to 2 places')
+  equal((-1).pad(3), '-001', 'Number#pad | -1 padded to 3 places')
+  equal((-1).pad(4), '-0001', 'Number#pad | -1 padded to 4 places')
+  equal((1).pad(1, true), '+1', 'Number#pad | 1 padded to 1 places and sign')
+  equal((1).pad(2, true), '+01', 'Number#pad | 1 padded to 2 places and sign')
+  equal((1).pad(3, true), '+001', 'Number#pad | 1 padded to 3 places and sign')
+  equal((1).pad(4, true), '+0001', 'Number#pad | 1 padded to 4 places and sign')
+  equal((0).pad(1, true), '+0', 'Number#pad | 0 padded to 1 place and sign')
+  equal((547.528).pad(4), '0547.528', 'Number#pad | does not take decimal places into account')
+
+  equal((255).pad(4, false, 16), '00ff', 'Number#pad | handles hex')
+  equal((2).pad(4, false, 2), '0010', 'Number#pad | handles binary')
+
+
+  equal((0).hex(), '0', 'Number#hex | 0')
+  equal((10).hex(), 'a', 'Number#hex | 10')
+  equal((255).hex(), 'ff', 'Number#hex | 255')
+  equal((0.5).hex(), '0.8', 'Number#hex | 0.5')
+  equal((2.5).hex(), '2.8', 'Number#hex | 2.8')
+  equal((2553423).hex(), '26f64f', 'Number#hex | 2553423')
+
+  equal((0).hex(2), '00', 'Number#hex | padding 2 places | 0')
+  equal((10).hex(2), '0a', 'Number#hex | padding 2 places | 10')
+  equal((255).hex(2), 'ff', 'Number#hex | padding 2 places | 10')
+  equal((0.5).hex(2), '00.8', 'Number#hex | padding 2 places | 0.5')
+  equal((2.5).hex(2), '02.8', 'Number#hex | padding 2 places | 2.8')
+
+  equal((0).hex(4), '0000', 'Number#hex | padding 4 places | 0')
+  equal((10).hex(4), '000a', 'Number#hex | padding 4 places | 10')
+  equal((255).hex(4), '00ff', 'Number#hex | padding 4 places | 10')
+  equal((0.5).hex(4), '0000.8', 'Number#hex | padding 4 places | 0.5')
+  equal((2.5).hex(4), '0002.8', 'Number#hex | padding 4 places | 2.8')
+
+
+  // Number#isInteger
+
+  equal((15).isInteger(), true, 'Number#isInteger | 15');
+  equal((15.2).isInteger(), false, 'Number#isInteger | 15.2');
+  equal((15.2668).isInteger(), false, 'Number#isInteger | 15.2668');
+  equal((15.0).isInteger(), true, 'Number#isInteger | 15.0');
+  equal(Number.prototype.isInteger.call('15'), true, 'Number#isInteger | "15"');
+  equal(Number.prototype.isInteger.call('15.8'), false, 'Number#isInteger | "15.8"');
+
+
+  // Number#abbr
+
+  equal((1).abbr(), '1', 'Number#abbr | 1');
+  equal((10).abbr(), '10', 'Number#abbr | 10');
+  equal((100).abbr(), '100', 'Number#abbr | 100');
+  equal((1000).abbr(), '1k', 'Number#abbr | 1,000');
+  equal((10000).abbr(), '10k', 'Number#abbr | 10,000');
+  equal((100000).abbr(), '100k', 'Number#abbr | 100,000');
+  equal((1000000).abbr(), '1m', 'Number#abbr | 1,000,000');
+  equal((10000000).abbr(), '10m', 'Number#abbr | 10,000,000');
+  equal((100000000).abbr(), '100m', 'Number#abbr | 100,000,000');
+  equal((1000000000).abbr(), '1b', 'Number#abbr | 1,000,000,000');
+  equal((10000000000).abbr(), '10b', 'Number#abbr | 10,000,000,000');
+  equal((100000000000).abbr(), '100b', 'Number#abbr | 100,000,000,000');
+  equal((1000000000000).abbr(), '1t', 'Number#abbr | 1,000,000,000,000');
+  equal((1000000000000000000).abbr(), '1,000,000t', 'Number#abbr | 1,000,000,000,000,000,000');
+
+  equal((1).abbr(), '1', 'Number#abbr | decimal | 1');
+  equal((12).abbr(), '12', 'Number#abbr | decimal | 12');
+  equal((124).abbr(), '124', 'Number#abbr | decimal | 124');
+  equal((1249).abbr(), '1k', 'Number#abbr | decimal | 1,249');
+  equal((1749).abbr(), '2k', 'Number#abbr | decimal | 1,749');
+  equal((12495).abbr(), '12k', 'Number#abbr | decimal | 12,495');
+  equal((17495).abbr(), '17k', 'Number#abbr | decimal | 17,495');
+  equal((124958).abbr(), '125k', 'Number#abbr | decimal | 124,958');
+  equal((174958).abbr(), '175k', 'Number#abbr | decimal | 174,958');
+  equal((1249584).abbr(), '1m', 'Number#abbr | decimal | 1,249,584');
+  equal((1749584).abbr(), '2m', 'Number#abbr | decimal | 1,749,584');
+
+  equal((1).abbr(1), '1', 'Number#abbr | decimal 1 place | 1');
+  equal((12).abbr(1), '12', 'Number#abbr | decimal 1 place | 12');
+  equal((124).abbr(1), '124', 'Number#abbr | decimal 1 place | 124');
+  equal((1249).abbr(1), '1.2k', 'Number#abbr | decimal 1 place | 1,249');
+  equal((1749).abbr(1), '1.7k', 'Number#abbr | decimal 1 place | 1,749');
+  equal((12495).abbr(1), '12.5k', 'Number#abbr | decimal 1 place | 12,495');
+  equal((17495).abbr(1), '17.5k', 'Number#abbr | decimal 1 place | 17,495');
+  equal((124958).abbr(1), '125k', 'Number#abbr | decimal 1 place | 124,958');
+  equal((174958).abbr(1), '175k', 'Number#abbr | decimal 1 place | 174,958');
+  equal((1249584).abbr(1), '1.2m', 'Number#abbr | decimal 1 place | 1,249,584');
+  equal((1749584).abbr(1), '1.7m', 'Number#abbr | decimal 1 place | 1,749,584');
+
+  equal((1).abbr(2), '1', 'Number#abbr | decimal 2 places | 1');
+  equal((12).abbr(2), '12', 'Number#abbr | decimal 2 places | 12');
+  equal((124).abbr(2), '124', 'Number#abbr | decimal 2 places | 124');
+  equal((1249).abbr(2), '1.25k', 'Number#abbr | decimal 2 places | 1,249');
+  equal((1749).abbr(2), '1.75k', 'Number#abbr | decimal 2 places | 1,749');
+  equal((12495).abbr(2), '12.5k', 'Number#abbr | decimal 2 places | 12,495');
+  equal((17495).abbr(2), '17.5k', 'Number#abbr | decimal 2 places | 17,495');
+  equal((124958).abbr(2), '124.96k', 'Number#abbr | decimal 2 places | 124,958');
+  equal((174958).abbr(2), '174.96k', 'Number#abbr | decimal 2 places | 174,958');
+  equal((1249584).abbr(2), '1.25m', 'Number#abbr | decimal 2 places | 1,249,584');
+  equal((1749584).abbr(2), '1.75m', 'Number#abbr | decimal 2 places | 1,749,584');
+
+  equal((1).abbr(3), '1', 'Number#abbr | decimal 3 places | 1');
+  equal((12).abbr(3), '12', 'Number#abbr | decimal 3 places | 12');
+  equal((124).abbr(3), '124', 'Number#abbr | decimal 3 places | 124');
+  equal((1249).abbr(3), '1.249k', 'Number#abbr | decimal 3 places | 1,249');
+  equal((1749).abbr(3), '1.749k', 'Number#abbr | decimal 3 places | 1,749');
+  equal((12495).abbr(3), '12.495k', 'Number#abbr | decimal 3 places | 12,495');
+  equal((17495).abbr(3), '17.495k', 'Number#abbr | decimal 3 places | 17,495');
+  equal((124958).abbr(3), '124.958k', 'Number#abbr | decimal 3 places | 124,958');
+  equal((174958).abbr(3), '174.958k', 'Number#abbr | decimal 3 places | 174,958');
+  equal((1249584).abbr(3), '1.25m', 'Number#abbr | decimal 3 places | 1,249,584');
+  equal((1749584).abbr(3), '1.75m', 'Number#abbr | decimal 3 places | 1,749,584');
+
+  equal((1).abbr(-1), '0', 'Number#abbr | decimal -1 places | 1');
+  equal((12).abbr(-1), '10', 'Number#abbr | decimal -1 places | 12');
+  equal((124).abbr(-1), '120', 'Number#abbr | decimal -1 places | 124');
+  equal((1249).abbr(-1), '0k', 'Number#abbr | decimal -1 places | 1,249');
+  equal((1749).abbr(-1), '0k', 'Number#abbr | decimal -1 places | 1,749');
+  equal((12495).abbr(-1), '10k', 'Number#abbr | decimal -1 places | 12,495');
+  equal((17495).abbr(-1), '20k', 'Number#abbr | decimal -1 places | 17,495');
+  equal((124958).abbr(-1), '120k', 'Number#abbr | decimal -1 places | 124,958');
+  equal((174958).abbr(-1), '170k', 'Number#abbr | decimal -1 places | 174,958');
+  equal((1249584).abbr(-1), '0m', 'Number#abbr | decimal -1 places | 1,249,584');
+  equal((1749584).abbr(-1), '0m', 'Number#abbr | decimal -1 places | 1,749,584');
+
+  equal((0.1).abbr(), '0', 'Number#abbr | 0.1');
+  equal((0.01).abbr(), '0', 'Number#abbr | 0.01');
+  equal((0.001).abbr(), '0', 'Number#abbr | 0.001');
+  equal((0.0001).abbr(), '0', 'Number#abbr | 0.00001');
+  equal((0.00001).abbr(), '0', 'Number#abbr | 0.000001');
+  equal((0.000001).abbr(), '0', 'Number#abbr | 0.0000001');
+  equal((0.0000001).abbr(), '0', 'Number#abbr | 0.00000001');
+  equal((0.00000001).abbr(), '0', 'Number#abbr | 0.000000001');
+
+  equal((1.1).abbr(), '1', 'Number#abbr | 1.1');
+  equal((1.01).abbr(), '1', 'Number#abbr | 1.01');
+  equal((1.001).abbr(), '1', 'Number#abbr | 1.001');
+  equal((1.0001).abbr(), '1', 'Number#abbr | 1.00001');
+  equal((1.00001).abbr(), '1', 'Number#abbr | 1.000001');
+  equal((1.000001).abbr(), '1', 'Number#abbr | 1.0000001');
+  equal((1.0000001).abbr(), '1', 'Number#abbr | 1.00000001');
+  equal((1.00000001).abbr(), '1', 'Number#abbr | 1.000000001');
+
+  equal((1000.004).abbr(), '1k', 'Number#abbr | 1000.004');
+  equal((10000.004).abbr(), '10k', 'Number#abbr | 10,000.004');
+  equal((100000.004).abbr(), '100k', 'Number#abbr | 100,000.004');
+  equal((1000000.004).abbr(), '1m', 'Number#abbr | 1,000,000.004');
+
+  equal((1000.004).abbr(2), '1k', 'Number#abbr | 2 places | 1000.004');
+  equal((10000.004).abbr(2), '10k', 'Number#abbr | 2 places | 10,000.004');
+  equal((100000.004).abbr(2), '100k', 'Number#abbr | 2 places | 100,000.004');
+  equal((1000000.004).abbr(2), '1m', 'Number#abbr | 2 places | 1,000,000.004');
+
+  // Number#metric
+
+  equal((1).metric(0, false), '1', 'Number#metric | 1');
+  equal((10).metric(0, false), '10', 'Number#metric | 10');
+  equal((100).metric(0, false), '100', 'Number#metric | 100');
+  equal((1000).metric(0, false), '1k', 'Number#metric | 1,000');
+  equal((10000).metric(0, false), '10k', 'Number#metric | 10,000');
+  equal((100000).metric(0, false), '100k', 'Number#metric | 100,000');
+  equal((1000000).metric(0, false), '1M', 'Number#metric | 1,000,000');
+  equal((10000000).metric(0, false), '10M', 'Number#metric | 10,000,000');
+  equal((100000000).metric(0, false), '100M', 'Number#metric | 100,000,000');
+  equal((1000000000).metric(0, false), '1G', 'Number#metric | 1,000,000,000');
+  equal((10000000000).metric(0, false), '10G', 'Number#metric | 10,000,000,000');
+  equal((100000000000).metric(0, false), '100G', 'Number#metric | 100,000,000,000');
+  equal((1000000000000).metric(0, false), '1T', 'Number#metric | 10,000,000,000,000');
+  equal((10000000000000).metric(0, false), '10T', 'Number#metric | 100,000,000,000,000');
+  equal((100000000000000).metric(0, false), '100T', 'Number#metric | 1,000,000,000,000,000');
+  equal((1000000000000000).metric(0, false), '1P', 'Number#metric | 10,000,000,000,000,000');
+  equal((10000000000000000).metric(0, false), '10P', 'Number#metric | 100,000,000,000,000,000');
+  equal((100000000000000000).metric(0, false), '100P', 'Number#metric | 1,000,000,000,000,000,000');
+
+  equal((1).metric(0, false), '1', 'Number#metric | decimal | 1');
+  equal((12).metric(0, false), '12', 'Number#metric | decimal | 12');
+  equal((124).metric(0, false), '124', 'Number#metric | decimal | 124');
+  equal((1249).metric(0, false), '1k', 'Number#metric | decimal | 1,249');
+  equal((1749).metric(0, false), '2k', 'Number#metric | decimal | 1,749');
+  equal((12495).metric(0, false), '12k', 'Number#metric | decimal | 12,495');
+  equal((17495).metric(0, false), '17k', 'Number#metric | decimal | 17,495');
+  equal((124958).metric(0, false), '125k', 'Number#metric | decimal | 124,958');
+  equal((174958).metric(0, false), '175k', 'Number#metric | decimal | 174,958');
+  equal((1249584).metric(0, false), '1M', 'Number#metric | decimal | 1,249,584');
+  equal((1749584).metric(0, false), '2M', 'Number#metric | decimal | 1,749,584');
+  equal((1249584000).metric(0, false), '1G', 'Number#metric | decimal | 1,249,584,000');
+  equal((1749584000).metric(0, false), '2G', 'Number#metric | decimal | 1,749,584,000');
+
+  equal((1).metric(1, false), '1', 'Number#metric | decimal 1 place | 1');
+  equal((12).metric(1, false), '12', 'Number#metric | decimal 1 place | 12');
+  equal((124).metric(1, false), '124', 'Number#metric | decimal 1 place | 124');
+  equal((1249).metric(1, false), '1.2k', 'Number#metric | decimal 1 place | 1,249');
+  equal((1749).metric(1, false), '1.7k', 'Number#metric | decimal 1 place | 1,749');
+  equal((12495).metric(1, false), '12.5k', 'Number#metric | decimal 1 place | 12,495');
+  equal((17495).metric(1, false), '17.5k', 'Number#metric | decimal 1 place | 17,495');
+  equal((124958).metric(1, false), '125k', 'Number#metric | decimal 1 place | 124,958');
+  equal((174958).metric(1, false), '175k', 'Number#metric | decimal 1 place | 174,958');
+  equal((1249584).metric(1, false), '1.2M', 'Number#metric | decimal 1 place | 1,249,584');
+  equal((1749584).metric(1, false), '1.7M', 'Number#metric | decimal 1 place | 1,749,584');
+  equal((1249584000).metric(1, false), '1.2G', 'Number#metric | decimal 1 place | 1,249,584,000');
+  equal((1749584000).metric(1, false), '1.7G', 'Number#metric | decimal 1 place | 1,749,584,000');
+
+  equal((1).metric(2, false), '1', 'Number#metric | decimal 2 places | 1');
+  equal((12).metric(2, false), '12', 'Number#metric | decimal 2 places | 12');
+  equal((124).metric(2, false), '124', 'Number#metric | decimal 2 places | 124');
+  equal((1249).metric(2, false), '1.25k', 'Number#metric | decimal 2 places | 1,249');
+  equal((1749).metric(2, false), '1.75k', 'Number#metric | decimal 2 places | 1,749');
+  equal((12495).metric(2, false), '12.5k', 'Number#metric | decimal 2 places | 12,495');
+  equal((17495).metric(2, false), '17.5k', 'Number#metric | decimal 2 places | 17,495');
+  equal((124958).metric(2, false), '124.96k', 'Number#metric | decimal 2 places | 124,958');
+  equal((174958).metric(2, false), '174.96k', 'Number#metric | decimal 2 places | 174,958');
+  equal((1249584).metric(2, false), '1.25M', 'Number#metric | decimal 2 places | 1,249,584');
+  equal((1749584).metric(2, false), '1.75M', 'Number#metric | decimal 2 places | 1,749,584');
+  equal((1249584000).metric(2, false), '1.25G', 'Number#metric | decimal 2 places | 1,249,584,000');
+  equal((1749584000).metric(2, false), '1.75G', 'Number#metric | decimal 2 places | 1,749,584,000');
+
+  equal((1).metric(3, false), '1', 'Number#metric | decimal 3 places | 1');
+  equal((12).metric(3, false), '12', 'Number#metric | decimal 3 places | 12');
+  equal((124).metric(3, false), '124', 'Number#metric | decimal 3 places | 124');
+  equal((1249).metric(3, false), '1.249k', 'Number#metric | decimal 3 places | 1,249');
+  equal((1749).metric(3, false), '1.749k', 'Number#metric | decimal 3 places | 1,749');
+  equal((12495).metric(3, false), '12.495k', 'Number#metric | decimal 3 places | 12,495');
+  equal((17495).metric(3, false), '17.495k', 'Number#metric | decimal 3 places | 17,495');
+  equal((124958).metric(3, false), '124.958k', 'Number#metric | decimal 3 places | 124,958');
+  equal((174958).metric(3, false), '174.958k', 'Number#metric | decimal 3 places | 174,958');
+  equal((1249584).metric(3, false), '1.25M', 'Number#metric | decimal 3 places | 1,249,584');
+  equal((1749584).metric(3, false), '1.75M', 'Number#metric | decimal 3 places | 1,749,584');
+  equal((1249584000).metric(3, false), '1.25G', 'Number#metric | decimal 3 places | 1,249,584,000');
+  equal((1749584000).metric(3, false), '1.75G', 'Number#metric | decimal 3 places | 1,749,584,000');
+
+
+  equal((1).metric(-1, false), '0', 'Number#metric | decimal -1 places | 1');
+  equal((12).metric(-1, false), '10', 'Number#metric | decimal -1 places | 12');
+  equal((124).metric(-1, false), '120', 'Number#metric | decimal -1 places | 124');
+  equal((1249).metric(-1, false), '0k', 'Number#metric | decimal -1 places | 1,249');
+  equal((1749).metric(-1, false), '0k', 'Number#metric | decimal -1 places | 1,749');
+  equal((12495).metric(-1, false), '10k', 'Number#metric | decimal -1 places | 12,495');
+  equal((17495).metric(-1, false), '20k', 'Number#metric | decimal -1 places | 17,495');
+  equal((124958).metric(-1, false), '120k', 'Number#metric | decimal -1 places | 124,958');
+  equal((174958).metric(-1, false), '170k', 'Number#metric | decimal -1 places | 174,958');
+  equal((1249584).metric(-1, false), '0M', 'Number#metric | decimal -1 places | 1,249,584');
+  equal((1749584).metric(-1, false), '0M', 'Number#metric | decimal -1 places | 1,749,584');
+  equal((1249584000).metric(-1, false), '0G', 'Number#metric | decimal -1 places | 1,249,584,000');
+  equal((1749584000).metric(-1, false), '0G', 'Number#metric | decimal -1 places | 1,749,584,000');
+
+  equal((0.1000000000000).metric(), '100m',    'Number#metric | fractional | 0.1');
+  equal((0.0100000000000).metric(), '10m',     'Number#metric | fractional | 0.01');
+  equal((0.0010000000000).metric(), '1m',      'Number#metric | fractional | 0.001');
+  equal((0.0001000000000).metric(), '100ฮผ',    'Number#metric | fractional | 0.0001');
+  equal((0.0000100000000).metric(), '10ฮผ',     'Number#metric | fractional | 0.00001');
+  equal((0.0000010000000).metric(), '1ฮผ',      'Number#metric | fractional | 0.000001');
+  equal((0.0000001000000).metric(), '100n',    'Number#metric | fractional | 0.0000001');
+  equal((0.0000000100000).metric(), '10n',     'Number#metric | fractional | 0.00000001');
+  equal((0.0000000010000).metric(), '1n',      'Number#metric | fractional | 0.000000001');
+  equal((0.0000000001000).metric(), '0.1n',    'Number#metric | fractional | 0.0000000001');
+  equal((0.0000000000100).metric(), '0.01n',   'Number#metric | fractional | 0.00000000001');
+  equal((0.0000000000010).metric(), '0.001n',  'Number#metric | fractional | 0.000000000001');
+  equal((0.0000000000001).metric(), '0.0001n', 'Number#metric | fractional | 0.0000000000001');
+
+  equal((0.1111111111111).metric(), '111m',    'Number#metric | fractional | 0 places | 0.1111111111111');
+  equal((0.0111111111111).metric(), '11m',     'Number#metric | fractional | 0 places | 0.0111111111111');
+  equal((0.0011111111111).metric(), '1m',      'Number#metric | fractional | 0 places | 0.0011111111111');
+  equal((0.0001111111111).metric(), '111ฮผ',    'Number#metric | fractional | 0 places | 0.0001111111111');
+  equal((0.0000111111111).metric(), '11ฮผ',     'Number#metric | fractional | 0 places | 0.0000111111111');
+  equal((0.0000011111111).metric(), '1ฮผ',      'Number#metric | fractional | 0 places | 0.0000011111111');
+  equal((0.0000001111111).metric(), '111n',    'Number#metric | fractional | 0 places | 0.0000001111111');
+  equal((0.0000000111111).metric(), '11n',     'Number#metric | fractional | 0 places | 0.0000000111111');
+  equal((0.0000000011111).metric(), '1n',      'Number#metric | fractional | 0 places | 0.0000000011111');
+  equal((0.0000000001111).metric(), '0.1n',    'Number#metric | fractional | 0 places | 0.0000000001111');
+  equal((0.0000000000111).metric(), '0.01n',   'Number#metric | fractional | 0 places | 0.0000000000111');
+  equal((0.0000000000011).metric(), '0.001n',  'Number#metric | fractional | 0 places | 0.0000000000011');
+  equal((0.0000000000001).metric(), '0.0001n', 'Number#metric | fractional | 0 places | 0.0000000000001');
+
+  equal((0.1111111111111).metric(2, false), '111.11m', 'Number#metric | fractional | 2 places | 0.1111111111111');
+  equal((0.0111111111111).metric(2, false), '11.11m',  'Number#metric | fractional | 2 places | 0.0111111111111');
+  equal((0.0011111111111).metric(2, false), '1.11m',   'Number#metric | fractional | 2 places | 0.0011111111111');
+  equal((0.0001111111111).metric(2, false), '111.11ฮผ', 'Number#metric | fractional | 2 places | 0.0001111111111');
+  equal((0.0000111111111).metric(2, false), '11.11ฮผ',  'Number#metric | fractional | 2 places | 0.0000111111111');
+  equal((0.0000011111111).metric(2, false), '1.11ฮผ',   'Number#metric | fractional | 2 places | 0.0000011111111');
+  equal((0.0000001111111).metric(2, false), '111.11n', 'Number#metric | fractional | 2 places | 0.0000001111111');
+  equal((0.0000000111111).metric(2, false), '11.11n',  'Number#metric | fractional | 2 places | 0.0000000111111');
+  equal((0.0000000011111).metric(2, false), '1.11n',   'Number#metric | fractional | 2 places | 0.0000000011111');
+  equal((0.0000000001111).metric(2, false), '0.1n',    'Number#metric | fractional | 2 places | 0.0000000001111');
+  equal((0.0000000000111).metric(2, false), '0.01n',   'Number#metric | fractional | 2 places | 0.0000000000111');
+  equal((0.0000000000011).metric(2, false), '0.001n',  'Number#metric | fractional | 2 places | 0.0000000000011');
+  equal((0.0000000000001).metric(2, false), '0.0001n', 'Number#metric | fractional | 2 places | 0.0000000000001');
+
+  equal((1.1111111111111).metric(), '1', 'Number#metric | fractional | 0 places | 1.1111111111111');
+  equal((1.0111111111111).metric(), '1', 'Number#metric | fractional | 0 places | 1.0111111111111');
+  equal((1.0011111111111).metric(), '1', 'Number#metric | fractional | 0 places | 1.0011111111111');
+  equal((1.0001111111111).metric(), '1', 'Number#metric | fractional | 0 places | 1.0001111111111');
+  equal((1.0000111111111).metric(), '1', 'Number#metric | fractional | 0 places | 1.0000111111111');
+  equal((1.0000011111111).metric(), '1', 'Number#metric | fractional | 0 places | 1.0000011111111');
+  equal((1.0000001111111).metric(), '1', 'Number#metric | fractional | 0 places | 1.0000001111111');
+  equal((1.0000000111111).metric(), '1', 'Number#metric | fractional | 0 places | 1.0000000111111');
+  equal((1.0000000011111).metric(), '1', 'Number#metric | fractional | 0 places | 1.0000000011111');
+  equal((1.0000000001111).metric(), '1', 'Number#metric | fractional | 0 places | 1.0000000001111');
+  equal((1.0000000000111).metric(), '1', 'Number#metric | fractional | 0 places | 1.0000000000111');
+  equal((1.0000000000011).metric(), '1', 'Number#metric | fractional | 0 places | 1.0000000000011');
+  equal((1.0000000000001).metric(), '1', 'Number#metric | fractional | 0 places | 1.0000000000001');
+
+  equal((1.1111111111111).metric(2, false), '1.11', 'Number#metric | fractional | 2 places | 1.1111111111111');
+  equal((1.0111111111111).metric(2, false), '1.01', 'Number#metric | fractional | 2 places | 1.0111111111111');
+  equal((1.0011111111111).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0011111111111');
+  equal((1.0001111111111).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0001111111111');
+  equal((1.0000111111111).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0000111111111');
+  equal((1.0000011111111).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0000011111111');
+  equal((1.0000001111111).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0000001111111');
+  equal((1.0000000111111).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0000000111111');
+  equal((1.0000000011111).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0000000011111');
+  equal((1.0000000001111).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0000000001111');
+  equal((1.0000000000111).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0000000000111');
+  equal((1.0000000000011).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0000000000011');
+  equal((1.0000000000001).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0000000000001');
+
+  equal((1.1000000000001).metric(), '1', 'Number#metric | fractional | 0 places | 1.1000000000001');
+  equal((1.0100000000001).metric(), '1', 'Number#metric | fractional | 0 places | 1.0100000000001');
+  equal((1.0010000000001).metric(), '1', 'Number#metric | fractional | 0 places | 1.0010000000001');
+  equal((1.0001000000001).metric(), '1', 'Number#metric | fractional | 0 places | 1.0001000000001');
+  equal((1.0000100000001).metric(), '1', 'Number#metric | fractional | 0 places | 1.0000100000001');
+  equal((1.0000010000001).metric(), '1', 'Number#metric | fractional | 0 places | 1.0000010000001');
+  equal((1.0000001000001).metric(), '1', 'Number#metric | fractional | 0 places | 1.0000001000001');
+  equal((1.0000000100001).metric(), '1', 'Number#metric | fractional | 0 places | 1.0000000100001');
+  equal((1.0000000010001).metric(), '1', 'Number#metric | fractional | 0 places | 1.0000000010001');
+  equal((1.0000000001001).metric(), '1', 'Number#metric | fractional | 0 places | 1.0000000001001');
+  equal((1.0000000000101).metric(), '1', 'Number#metric | fractional | 0 places | 1.0000000000101');
+  equal((1.0000000000011).metric(), '1', 'Number#metric | fractional | 0 places | 1.0000000000011');
+  equal((1.0000000000001).metric(), '1', 'Number#metric | fractional | 0 places | 1.0000000000001');
+
+  equal((1.1000000000001).metric(2, false), '1.1',  'Number#metric | fractional | 2 places | 1.1000000000001');
+  equal((1.0100000000001).metric(2, false), '1.01', 'Number#metric | fractional | 2 places | 1.0100000000001');
+  equal((1.0010000000001).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0010000000001');
+  equal((1.0001000000001).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0001000000001');
+  equal((1.0000100000001).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0000100000001');
+  equal((1.0000010000001).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0000010000001');
+  equal((1.0000001000001).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0000001000001');
+  equal((1.0000000100001).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0000000100001');
+  equal((1.0000000010001).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0000000010001');
+  equal((1.0000000001001).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0000000001001');
+  equal((1.0000000000101).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0000000000101');
+  equal((1.0000000000011).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0000000000011');
+  equal((1.0000000000001).metric(2, false), '1',    'Number#metric | fractional | 2 places | 1.0000000000001');
+
+  equal((12334.5880).metric(3, false), '12.335k', 'Number#metric | fractional | 3 places | 12334.5880');
+  equal((12334.5880).metric(), '12k', 'Number#metric | fractional | 0 places | 12334.5880');
+  equal((.588500).metric(9, false), '588.5m', 'Number#metric | fractional | 9 places | .5885');
+  equal((.580085).metric(9, false), '580.085m', 'Number#metric | fractional | 9 places | .580085');
+  equal((.580085).metric(7, false), '580.085m', 'Number#metric | fractional | 7 places | .580085');
+  equal((.580085).metric(5, false), '580.085m', 'Number#metric | fractional | 5 places | .580085');
+  equal((.580085).metric(3, false), '580.085m', 'Number#metric | fractional | 3 places | .580085');
+  equal((.580085).metric(1, false), '580.1m', 'Number#metric | fractional | 1 places | .580085');
+
+
+  equal((0.0001).metric()     + 'm', '100ฮผm',       'Number#metric | 100ฮผm');
+  equal((0.001).metric()      + 'm', '1mm',         'Number#metric | 1mm');
+  equal((0.01).metric()       + 'm', '10mm',        'Number#metric | 10mm');
+  equal((0.1).metric()        + 'm', '100mm',       'Number#metric | 100mm');
+  equal((1).metric()          + 'm', '1m',          'Number#metric | 1m');
+  equal((1000).metric()       + 'm', '1km',         'Number#metric | 1km');
+  equal((1000000).metric()    + 'm', '1,000km',     'Number#metric | 1,000km');
+  equal((1000000000).metric() + 'm', '1,000,000km', 'Number#metric | 1,000,000km');
+
+  equal((1000000000).metric(0, 0) + 'm', '1,000,000,000m', 'Number#metric | limited to meters | 1,000,000,000m');
+  equal((1000000).metric(0, 0)    + 'm', '1,000,000m',     'Number#metric | limited to meters | 1,000,000m');
+  equal((1000).metric(0, 0)       + 'm', '1,000m',         'Number#metric | limited to meters | 1,000m');
+  equal((1).metric(0, 0)          + 'm', '1m',             'Number#metric | limited to meters | 1m');
+
+  equal((12323.424558).metric(3, 0), '12,323.425', 'Number#metric | limited and 3 decimals');
+  equal((1).metric(0, -1) + 'm', '1,000mm', 'Number#metric | 1 meter is 1,000mm');
+
+
+
+  // Number#bytes
+
+  equal((1).bytes(),                  '1B' ,       'Number#bytes | 1B       ');
+  equal((10).bytes(),                 '10B' ,      'Number#bytes | 10B      ');
+  equal((100).bytes(),                '100B' ,     'Number#bytes | 100B     ');
+  equal((1000).bytes(),               '1kB' ,      'Number#bytes | 1kB      ');
+  equal((10000).bytes(),              '10kB' ,     'Number#bytes | 10kB     ');
+  equal((100000).bytes(),             '98kB' ,     'Number#bytes | 100kB    ');
+  equal((1000000).bytes(),            '1MB' ,      'Number#bytes | 1MB      ');
+  equal((10000000).bytes(),           '10MB' ,     'Number#bytes | 10MB     ');
+  equal((100000000).bytes(),          '95MB' ,     'Number#bytes | 100MB    ');
+  equal((1000000000).bytes(),         '1GB' ,      'Number#bytes | 1GB      ');
+  equal((10000000000).bytes(),        '9GB' ,      'Number#bytes | 10GB     ');
+  equal((100000000000).bytes(),       '93GB' ,     'Number#bytes | 100GB    ');
+  equal((1000000000000).bytes(),      '1TB' ,      'Number#bytes | 1TB      ');
+  equal((10000000000000).bytes(),     '9TB' ,      'Number#bytes | 10TB     ');
+  equal((100000000000000).bytes(),    '91TB' ,     'Number#bytes | 100TB    ');
+  equal((1000000000000000).bytes(),   '909TB' ,    'Number#bytes | 1,000TB  ');
+  equal((10000000000000000).bytes(),  '9,095TB' ,  'Number#bytes | 10,000TB ');
+  equal((100000000000000000).bytes(), '90,949TB' , 'Number#bytes | 10,000TB ');
+
+  equal((1).bytes(0, false),                  '1B' ,   'Number#bytes | no limit | 1B       ');
+  equal((10).bytes(0, false),                 '10B' ,  'Number#bytes | no limit | 10B      ');
+  equal((100).bytes(0, false),                '100B' , 'Number#bytes | no limit | 100B     ');
+  equal((1000).bytes(0, false),               '1kB' ,  'Number#bytes | no limit | 1kB      ');
+  equal((10000).bytes(0, false),              '10kB' , 'Number#bytes | no limit | 10kB     ');
+  equal((100000).bytes(0, false),             '98kB' , 'Number#bytes | no limit | 100kB    ');
+  equal((1000000).bytes(0, false),            '1MB' ,  'Number#bytes | no limit | 1MB      ');
+  equal((10000000).bytes(0, false),           '10MB' , 'Number#bytes | no limit | 10MB     ');
+  equal((100000000).bytes(0, false),          '95MB' , 'Number#bytes | no limit | 100MB    ');
+  equal((1000000000).bytes(0, false),         '1GB' ,  'Number#bytes | no limit | 1GB      ');
+  equal((10000000000).bytes(0, false),        '9GB' ,  'Number#bytes | no limit | 10GB     ');
+  equal((100000000000).bytes(0, false),       '93GB' , 'Number#bytes | no limit | 100GB    ');
+  equal((1000000000000).bytes(0, false),      '1TB' ,  'Number#bytes | no limit | 1TB      ');
+  equal((10000000000000).bytes(0, false),     '9TB' ,  'Number#bytes | no limit | 10TB     ');
+  equal((100000000000000).bytes(0, false),    '91TB' , 'Number#bytes | no limit | 100TB    ');
+  equal((1000000000000000).bytes(0, false),   '1PB' ,  'Number#bytes | no limit | 1,000TB  ');
+  equal((10000000000000000).bytes(0, false),  '9PB' ,  'Number#bytes | no limit | 10,000TB ');
+  equal((100000000000000000).bytes(0, false), '89PB' , 'Number#bytes | no limit | 10,000TB ');
+
+  equal((1).bytes(2, false),                  '1B' ,      'Number#bytes | no limit, 2 places | 1B       ');
+  equal((10).bytes(2, false),                 '10B' ,     'Number#bytes | no limit, 2 places | 10B      ');
+  equal((100).bytes(2, false),                '100B' ,    'Number#bytes | no limit, 2 places | 100B     ');
+  equal((1000).bytes(2, false),               '0.98kB' ,  'Number#bytes | no limit, 2 places | 1kB      ');
+  equal((10000).bytes(2, false),              '9.77kB' ,  'Number#bytes | no limit, 2 places | 10kB     ');
+  equal((100000).bytes(2, false),             '97.66kB' , 'Number#bytes | no limit, 2 places | 100kB    ');
+  equal((1000000).bytes(2, false),            '0.95MB' ,  'Number#bytes | no limit, 2 places | 1MB      ');
+  equal((10000000).bytes(2, false),           '9.54MB' ,  'Number#bytes | no limit, 2 places | 10MB     ');
+  equal((100000000).bytes(2, false),          '95.37MB' , 'Number#bytes | no limit, 2 places | 100MB    ');
+  equal((1000000000).bytes(2, false),         '0.93GB' ,  'Number#bytes | no limit, 2 places | 1GB      ');
+  equal((10000000000).bytes(2, false),        '9.31GB' ,  'Number#bytes | no limit, 2 places | 10GB     ');
+  equal((100000000000).bytes(2, false),       '93.13GB' , 'Number#bytes | no limit, 2 places | 100GB    ');
+  equal((1000000000000).bytes(2, false),      '0.91TB' ,  'Number#bytes | no limit, 2 places | 1TB      ');
+  equal((10000000000000).bytes(2, false),     '9.09TB' ,  'Number#bytes | no limit, 2 places | 10TB     ');
+  equal((100000000000000).bytes(2, false),    '90.95TB' , 'Number#bytes | no limit, 2 places | 100TB    ');
+  equal((1000000000000000).bytes(2, false),   '0.89PB' ,  'Number#bytes | no limit, 2 places | 1,000TB  ');
+  equal((10000000000000000).bytes(2, false),  '8.88PB' ,  'Number#bytes | no limit, 2 places | 10,000TB ');
+  equal((100000000000000000).bytes(2, false), '88.82PB' , 'Number#bytes | no limit, 2 places | 10,000TB ');
+
+  equal((1024).bytes(),     '1kB', 'Number#bytes | 1024 bytes is 1kB');
+  equal((1024).bytes(2),    '1kB', 'Number#bytes | 2 places | 1024 bytes is 1kB');
+  equal((1048576).bytes(),  '1MB', 'Number#bytes | 2 places | 1048576 bytes is 1MB');
+  equal((1048576).bytes(2), '1MB', 'Number#bytes | 2 places | 1048576 bytes is 1MB');
+
+  equal(((10).pow(16)).bytes(), '9,095TB', 'Number#bytes | 10 ^ 16 bytes');
+  equal(((10).pow(16)).bytes(-2), '9,100TB', 'Number#bytes | 10 ^ 16 bytes | -2 places');
+
+});
+
level2/node_modules/sugar/test/environments/sugar/number_range.js
@@ -0,0 +1,257 @@
+
+test('Number Ranges', function () {
+
+  var range;
+  var mergedRange;
+  var clonedRange;
+  var result;
+  var count;
+
+  range = Number.range(5, 10);
+
+  equal(range.toString(), '5..10', 'Number | Range | toString');
+  equal(range.isValid(), true, 'Number | Range | isValid');
+  equal(range.span(), 6, 'Number | Range | span');
+  equal(range.contains(), false, 'Number | Range | contains undefined');
+  equal(range.contains(1), false, 'Number | Range | contains 1');
+  equal(range.contains(4), false, 'Number | Range | contains 4');
+  equal(range.contains(5), true, 'Number | Range | contains 5');
+  equal(range.contains(6), true, 'Number | Range | contains 6');
+  equal(range.contains(7), true, 'Number | Range | contains 7');
+  equal(range.contains(8), true, 'Number | Range | contains 8');
+  equal(range.contains(9), true, 'Number | Range | contains 9');
+  equal(range.contains(10), true, 'Number | Range | contains 10');
+  equal(range.contains(11), false, 'Number | Range | contains 11');
+
+  mergedRange = range.union(Number.range(1, 5));
+  equal(mergedRange.start, 1, 'Number | 1..5 | 5..10 | start');
+  equal(mergedRange.end, 10, 'Number | 1..5 | 5..10 | end');
+
+  mergedRange = range.union(Number.range(1, 4));
+  equal(mergedRange.start, 1, 'Number | 1..4 | 5..10 | start');
+  equal(mergedRange.end, 10, 'Number | 1..4 | 5..10 | end');
+
+  mergedRange = range.union(Number.range(1, 3));
+  equal(mergedRange.start, 1, 'Number | 1..3 | 5..10 | start');
+  equal(mergedRange.end, 10, 'Number | 1..3 | 5..10 | end');
+
+  mergedRange = range.union(Number.range(1, 1));
+  equal(mergedRange.start, 1, 'Number | 1..1 | 5..10 | start');
+  equal(mergedRange.end, 10, 'Number | 1..1 | 5..10 | end');
+
+  mergedRange = range.union(Number.range(1, 20));
+  equal(mergedRange.start, 1, 'Number | 1..20 | 5..10 | start');
+  equal(mergedRange.end, 20, 'Number | 1..20 | 5..10 | end');
+
+  mergedRange = range.union(Number.range(-5, 7));
+  equal(mergedRange.start, -5, 'Number | -5..7 | 5..10 | start');
+  equal(mergedRange.end, 10, 'Number | -5..7 | 5..10 | end');
+
+  mergedRange = range.union(Number.range(-5, 50));
+  equal(mergedRange.start, -5, 'Number | -5..50 | 5..10 | start');
+  equal(mergedRange.end, 50, 'Number | -5..50 | 5..10 | end');
+
+  mergedRange = range.intersect(Number.range(1, 5));
+  equal(mergedRange.start, 5, 'Number | 1..5 & 5..10 | start');
+  equal(mergedRange.end, 5, 'Number | 1..5 & 5..10 | end');
+
+  mergedRange = range.intersect(Number.range(7, 8));
+  equal(mergedRange.start, 7, 'Number | 7..8 & 5..10 | start');
+  equal(mergedRange.end, 8, 'Number | 7..8 & 5..10 | end');
+
+  mergedRange = range.intersect(Number.range(1, 4));
+  equal(mergedRange.isValid(), false, 'Number | 1..4 & 5..10 | isValid');
+
+  mergedRange = range.intersect(Number.range(1, 3));
+  equal(mergedRange.isValid(), false, 'Number | 1..3 & 5..10 | isValid');
+
+  mergedRange = range.intersect(Number.range(1, 1));
+  equal(mergedRange.isValid(), false, 'Number | 1..1 & 5..10 | isValid');
+
+  mergedRange = range.intersect(Number.range(1, 20));
+  equal(mergedRange.start, 5, 'Number | 1..20 & 5..10 | start');
+  equal(mergedRange.end, 10, 'Number | 1..20 & 5..10 | end');
+
+  mergedRange = range.intersect(Number.range(-5, 7));
+  equal(mergedRange.start, 5, 'Number | -5..7 & 5..10 | start');
+  equal(mergedRange.end, 7, 'Number | -5..7 & 5..10 | end');
+
+  mergedRange = range.intersect(Number.range(-5, 50));
+  equal(mergedRange.start, 5, 'Number | -5..50 & 5..10 | start');
+  equal(mergedRange.end, 10, 'Number | -5..50 & 5..10 | end');
+
+  mergedRange = Number.range(-5, 5).intersect(Number.range(-20, 0));
+  equal(mergedRange.start, -5, 'Number | -5..5 & -20..0 | start');
+  equal(mergedRange.end, 0, 'Number | -5..5 & -20..0 | end');
+
+
+  clonedRange = range.clone();
+
+  equal(clonedRange.start, 5, 'Number | Range | cloned range start');
+  equal(clonedRange.end, 10, 'Number | Range | cloned range end');
+  equal(clonedRange === range, false, 'Number | Range | cloned range should not be strictly equal');
+
+
+  count = 0;
+
+  result = range.every(1, function() {
+    count++;
+  });
+
+  equal(result, [5,6,7,8,9,10], 'Number | Range | result should be an array');
+  equal(count, 6, 'Number | Range | every 1');
+
+  count = 0;
+
+  result = range.every(2, function() {
+    count++;
+  });
+
+  equal(result, [5,7,9], 'Number | Range every 2 | result should be an array');
+  equal(count, 3, 'Number | Range every 2 | count');
+
+  count = 0;
+
+  result = range.every(function() {
+    count++;
+  });
+
+  equal(result, [5,6,7,8,9,10], 'Number | Range | result should be an array');
+  equal(count, 6, 'Number | Range | every 1');
+
+
+  equal(range.clamp(25), 10, 'Number | Range#clamp | 25');
+  equal(range.clamp(10), 10, 'Number | Range#clamp | 10');
+  equal(range.clamp(9),   9, 'Number | Range#clamp |  9');
+  equal(range.clamp(8),   8, 'Number | Range#clamp |  8');
+  equal(range.clamp(7),   7, 'Number | Range#clamp |  7');
+  equal(range.clamp(6),   6, 'Number | Range#clamp |  6');
+  equal(range.clamp(5),   5, 'Number | Range#clamp |  5');
+  equal(range.clamp(4),   5, 'Number | Range#clamp |  4');
+  equal(range.clamp(1),   5, 'Number | Range#clamp |  1');
+  equal(range.clamp(0),   5, 'Number | Range#clamp |  0');
+  equal(range.clamp(-1),  5, 'Number | Range#clamp | -1');
+
+  equal((25).clamp(5, 10), 10, 'Number#clamp | 25');
+  equal((10).clamp(5, 10), 10, 'Number#clamp | 10');
+  equal((9).clamp(5, 10), 9, 'Number#clamp | 9');
+  equal((8).clamp(5, 10), 8, 'Number#clamp | 8');
+  equal((7).clamp(5, 10), 7, 'Number#clamp | 7');
+  equal((6).clamp(5, 10), 6, 'Number#clamp | 6');
+  equal((5).clamp(5, 10), 5, 'Number#clamp | 5');
+  equal((4).clamp(5, 10), 5, 'Number#clamp | 4');
+  equal((1).clamp(5, 10), 5, 'Number#clamp | 1');
+  equal((0).clamp(5, 10), 5, 'Number#clamp | 0');
+  equal((-1).clamp(5, 10), 5, 'Number#clamp | -1');
+
+  equal((25).clamp(10, 5), 10, 'Number#clamp | inverted | 25');
+  equal((10).clamp(10, 5), 10, 'Number#clamp | inverted | 10');
+  equal((9).clamp(10, 5), 9, 'Number#clamp | inverted | 9');
+  equal((8).clamp(10, 5), 8, 'Number#clamp | inverted | 8');
+  equal((7).clamp(10, 5), 7, 'Number#clamp | inverted | 7');
+  equal((6).clamp(10, 5), 6, 'Number#clamp | inverted | 6');
+  equal((5).clamp(10, 5), 5, 'Number#clamp | inverted | 5');
+  equal((4).clamp(10, 5), 5, 'Number#clamp | inverted | 4');
+  equal((1).clamp(10, 5), 5, 'Number#clamp | inverted | 1');
+  equal((0).clamp(10, 5), 5, 'Number#clamp | inverted | 0');
+  equal((-1).clamp(10, 5), 5, 'Number#clamp | inverted | -1');
+
+  equal((5).cap(6), 5, 'Number#cap | 5 capped to 6');
+  equal((5).cap(5), 5, 'Number#cap | 5 capped to 5');
+  equal((5).cap(4), 4, 'Number#cap | 5 capped to 4');
+  equal((5).cap(1), 1, 'Number#cap | 5 capped to 1');
+  equal((5).cap(0), 0, 'Number#cap | 5 capped to 0');
+  equal((5).cap(-1), -1, 'Number#cap | 5 capped to -1');
+  equal((5).cap(-5), -5, 'Number#cap | 5 capped to -5');
+  equal((5).cap(-10), -10, 'Number#cap | 5 capped to -10');
+
+  equal((0).cap(6), 0, 'Number#cap | 0 capped to 6');
+  equal((0).cap(5), 0, 'Number#cap | 0 capped to 5');
+  equal((0).cap(4), 0, 'Number#cap | 0 capped to 4');
+  equal((0).cap(1), 0, 'Number#cap | 0 capped to 1');
+  equal((0).cap(0), 0, 'Number#cap | 0 capped to 0');
+  equal((0).cap(-1), -1, 'Number#cap | 0 capped to -1');
+  equal((0).cap(-5), -5, 'Number#cap | 0 capped to -5');
+  equal((0).cap(-10), -10, 'Number#cap | 0 capped to -10');
+
+  equal((-5).cap(6), -5, 'Number#cap | -5 capped to 6');
+  equal((-5).cap(5), -5, 'Number#cap | -5 capped to 5');
+  equal((-5).cap(4), -5, 'Number#cap | -5 capped to 4');
+  equal((-5).cap(1), -5, 'Number#cap | -5 capped to 1');
+  equal((-5).cap(0), -5, 'Number#cap | -5 capped to 0');
+  equal((-5).cap(-1), -5, 'Number#cap | -5 capped to -1');
+  equal((-5).cap(-5), -5, 'Number#cap | -5 capped to -5');
+  equal((-5).cap(-10), -10, 'Number#cap | -5 capped to -10');
+
+  range = Number.range(4, 1);
+
+  equal(range.toString(), '4..1', 'Number | Range | inverse | toString');
+  equal(range.isValid(), true, 'Number | Range | inverse | isValid');
+  equal(range.every(), [4,3,2,1], 'Number | Range | inverse | every');
+
+  equal(Number.range(NaN, NaN).toString(), 'Invalid Range', 'Number | Range | invalid | toString');
+
+
+  range = Number.range(1, Infinity);
+  equal(range.contains(1), true, 'Number | 1..Infinity | contains 1');
+  equal(range.contains(10), true, 'Number | 1..Infinity | contains 10');
+  equal(range.contains(100), true, 'Number | 1..Infinity | contains 100');
+  equal(range.contains(Infinity), true, 'Number | 1..Infinity | contains 100');
+  equal(range.contains(0), false, 'Number | 1..Infinity | contains 0');
+  equal(range.contains(-1), false, 'Number | 1..Infinity | contains -1');
+  equal(range.contains(-10), false, 'Number | 1..Infinity | contains -10');
+  equal(range.contains(-100), false, 'Number | 1..Infinity | contains -100');
+  equal(range.contains(-Infinity), false, 'Number | 1..Infinity | contains -Infinity');
+
+  range = Number.range(-Infinity, 1);
+  equal(range.contains(1), true, 'Number | -Infinity..1 | contains 1');
+  equal(range.contains(10), false, 'Number | -Infinity..1 | contains 10');
+  equal(range.contains(100), false, 'Number | -Infinity..1 | contains 100');
+  equal(range.contains(Infinity), false, 'Number | -Infinity..1 | contains 100');
+  equal(range.contains(0), true, 'Number | -Infinity..1 | contains 0');
+  equal(range.contains(-1), true, 'Number | -Infinity..1 | contains -1');
+  equal(range.contains(-10), true, 'Number | -Infinity..1 | contains -10');
+  equal(range.contains(-100), true, 'Number | -Infinity..1 | contains -100');
+  equal(range.contains(-Infinity), true, 'Number | -Infinity..1 | contains -Infinity');
+
+  range = Number.range(-Infinity, Infinity);
+  equal(range.contains(1), true, 'Number | -Infinity..Infinity | contains 1');
+  equal(range.contains(10), true, 'Number | -Infinity..Infinity | contains 10');
+  equal(range.contains(100), true, 'Number | -Infinity..Infinity | contains 100');
+  equal(range.contains(Infinity), true, 'Number | -Infinity..Infinity | contains 100');
+  equal(range.contains(0), true, 'Number | -Infinity..Infinity | contains 0');
+  equal(range.contains(-1), true, 'Number | -Infinity..Infinity | contains -1');
+  equal(range.contains(-10), true, 'Number | -Infinity..Infinity | contains -10');
+  equal(range.contains(-100), true, 'Number | -Infinity..Infinity | contains -100');
+  equal(range.contains(-Infinity), true, 'Number | -Infinity..Infinity | contains -Infinity');
+
+  range = Number.range(0, 0);
+  equal(range.contains(-1), false, 'Number | 0..0 | contains -1');
+  equal(range.contains(0), true, 'Number | 0..0 | contains 0');
+  equal(range.contains(1), false, 'Number | 0..0 | contains 1');
+
+
+  range = Number.range(null, null);
+  equal(range.contains(-1), false, 'Number | null..null | contains -1');
+  equal(range.contains(0), true, 'Number | null..null | contains 0');
+  equal(range.contains(1), false, 'Number | null..null | contains 1');
+  equal(range.isValid(), false, 'Number | null..null | isValid');
+
+
+  range = Number.range(undefined, undefined);
+  equal(range.contains(-1), false, 'Number | undefined..undefined | contains -1');
+  equal(range.contains(0), false, 'Number | undefined..undefined | contains 0');
+  equal(range.contains(1), false, 'Number | undefined..undefined | contains 1');
+  equal(range.isValid(), false, 'Number | undefined..undefined | isValid');
+
+  equal(Number.range(new Date(2010, 0).getTime(), new Date(2010, 2).getTime()).contains(new Date(2010, 0)), true, 'Number | range | contains different type');
+
+
+  equal(Number.range(1, 5).every(null, function(){}), [1,2,3,4,5], 'Number | 1..5 | null increment defaults to 1');
+
+  if(Array.create) {
+    equal(Array.create(Number.range(1, 5)), [1,2,3,4,5], 'Array.create | should work on number ranges');
+    equal(Array.create(Number.range(5, 1)), [5,4,3,2,1], 'Array.create | should work on inverse number ranges');
+  }
+
+});
level2/node_modules/sugar/test/environments/sugar/object.js
@@ -0,0 +1,990 @@
+test('Object', function () {
+
+
+  var count,result;
+  var Person = function() {};
+  var p = new Person();
+
+
+
+  equal(Object.isObject({}), true, 'Object.isObject | {}');
+  equal(Object.isObject(Object.extended()), true, 'Object.isObject | extended object');
+  equal(Object.isObject(new Object({})), true, 'Object.isObject | new Object()');
+  equal(Object.isObject([]), false, 'Object.isObject | []');
+  equal(Object.isObject(new Array(1,2,3)), false, 'Object.isObject | new Array(1,2,3)');
+  equal(Object.isObject(new RegExp()), false, 'Object.isObject | new RegExp()');
+  equal(Object.isObject(new Date()), false, 'Object.isObject | new Date()');
+  equal(Object.isObject(function() {}), false, 'Object.isObject | function() {}');
+  equal(Object.isObject(1), false, 'Object.isObject | 1');
+  equal(Object.isObject('wasabi'), false, 'Object.isObject | "wasabi"');
+  equal(Object.isObject(null), false, 'Object.isObject | null');
+  equal(Object.isObject(undefined), false, 'Object.isObject | undefined');
+  equal(Object.isObject(NaN), false, 'Object.isObject | NaN');
+  equal(Object.isObject(), false, 'Object.isObject | blank');
+  equal(Object.isObject(false), false, 'Object.isObject | false');
+  equal(Object.isObject(true), false, 'Object.isObject | true');
+  equal(Object.isObject(p), false, 'Object.isObject | instance');
+
+  equal(Object.isArray({}), false, 'Object.isArray | {}');
+  equal(Object.isArray([]), true, 'Object.isArray | []');
+  equal(Object.isArray(new Array(1,2,3)), true, 'Object.isArray | new Array(1,2,3)');
+  equal(Object.isArray(new RegExp()), false, 'Object.isArray | new RegExp()');
+  equal(Object.isArray(new Date()), false, 'Object.isArray | new Date()');
+  equal(Object.isArray(function() {}), false, 'Object.isArray | function() {}');
+  equal(Object.isArray(1), false, 'Object.isArray | 1');
+  equal(Object.isArray('wasabi'), false, 'Object.isArray | "wasabi"');
+  equal(Object.isArray(null), false, 'Object.isArray | null');
+  equal(Object.isArray(undefined), false, 'Object.isArray | undefined');
+  equal(Object.isArray(NaN), false, 'Object.isArray | NaN');
+  equal(Object.isArray(), false, 'Object.isArray | blank');
+  equal(Object.isArray(false), false, 'Object.isArray | false');
+  equal(Object.isArray(true), false, 'Object.isArray | true');
+
+  equal(Object.isBoolean({}), false, 'Object.isBoolean | {}');
+  equal(Object.isBoolean([]), false, 'Object.isBoolean | []');
+  equal(Object.isBoolean(new RegExp()), false, 'Object.isBoolean | new RegExp()');
+  equal(Object.isBoolean(new Date()), false, 'Object.isBoolean | new Date()');
+  equal(Object.isBoolean(function() {}), false, 'Object.isBoolean | function() {}');
+  equal(Object.isBoolean(1), false, 'Object.isBoolean | 1');
+  equal(Object.isBoolean('wasabi'), false, 'Object.isBoolean | "wasabi"');
+  equal(Object.isBoolean(null), false, 'Object.isBoolean | null');
+  equal(Object.isBoolean(undefined), false, 'Object.isBoolean | undefined');
+  equal(Object.isBoolean(NaN), false, 'Object.isBoolean | NaN');
+  equal(Object.isBoolean(), false, 'Object.isBoolean | blank');
+  equal(Object.isBoolean(false), true, 'Object.isBoolean | false');
+  equal(Object.isBoolean(true), true, 'Object.isBoolean | true');
+
+  equal(Object.isDate({}), false, 'Object.isDate | {}');
+  equal(Object.isDate([]), false, 'Object.isDate | []');
+  equal(Object.isDate(new RegExp()), false, 'Object.isDate | new RegExp()');
+  equal(Object.isDate(new Date()), true, 'Object.isDate | new Date()');
+  equal(Object.isDate(function() {}), false, 'Object.isDate | function() {}');
+  equal(Object.isDate(1), false, 'Object.isDate | 1');
+  equal(Object.isDate('wasabi'), false, 'Object.isDate | "wasabi"');
+  equal(Object.isDate(null), false, 'Object.isDate | null');
+  equal(Object.isDate(undefined), false, 'Object.isDate | undefined');
+  equal(Object.isDate(NaN), false, 'Object.isDate | NaN');
+  equal(Object.isDate(), false, 'Object.isDate | blank');
+  equal(Object.isDate(false), false, 'Object.isDate | false');
+  equal(Object.isDate(true), false, 'Object.isDate | true');
+
+  equal(Object.isFunction({}), false, 'Object.isFunction | {}');
+  equal(Object.isFunction([]), false, 'Object.isFunction | []');
+  equal(Object.isFunction(new RegExp()), false, 'Object.isFunction | new RegExp()');
+  equal(Object.isFunction(new Date()), false, 'Object.isFunction | new Date()');
+  equal(Object.isFunction(function() {}), true, 'Object.isFunction | function() {}');
+  equal(Object.isFunction(new Function()), true, 'Object.isFunction | new Function()');
+  equal(Object.isFunction(1), false, 'Object.isFunction | 1');
+  equal(Object.isFunction('wasabi'), false, 'Object.isFunction | "wasabi"');
+  equal(Object.isFunction(null), false, 'Object.isFunction | null');
+  equal(Object.isFunction(undefined), false, 'Object.isFunction | undefined');
+  equal(Object.isFunction(NaN), false, 'Object.isFunction | NaN');
+  equal(Object.isFunction(), false, 'Object.isFunction | blank');
+  equal(Object.isFunction(false), false, 'Object.isFunction | false');
+  equal(Object.isFunction(true), false, 'Object.isFunction | true');
+
+  equal(Object.isNumber({}), false, 'Object.isNumber | {}');
+  equal(Object.isNumber([]), false, 'Object.isNumber | []');
+  equal(Object.isNumber(new RegExp()), false, 'Object.isNumber | new RegExp()');
+  equal(Object.isNumber(new Date()), false, 'Object.isNumber | new Date()');
+  equal(Object.isNumber(function() {}), false, 'Object.isNumber | function() {}');
+  equal(Object.isNumber(new Function()), false, 'Object.isNumber | new Function()');
+  equal(Object.isNumber(1), true, 'Object.isNumber | 1');
+  equal(Object.isNumber(0), true, 'Object.isNumber | 0');
+  equal(Object.isNumber(-1), true, 'Object.isNumber | -1');
+  equal(Object.isNumber(new Number('3')), true, 'Object.isNumber | new Number("3")');
+  equal(Object.isNumber('wasabi'), false, 'Object.isNumber | "wasabi"');
+  equal(Object.isNumber(null), false, 'Object.isNumber | null');
+  equal(Object.isNumber(undefined), false, 'Object.isNumber | undefined');
+  equal(Object.isNumber(NaN), true, 'Object.isNumber | NaN');
+  equal(Object.isNumber(), false, 'Object.isNumber | blank');
+  equal(Object.isNumber(false), false, 'Object.isNumber | false');
+  equal(Object.isNumber(true), false, 'Object.isNumber | true');
+
+  equal(Object.isString({}), false, 'Object.isString | {}');
+  equal(Object.isString([]), false, 'Object.isString | []');
+  equal(Object.isString(new RegExp()), false, 'Object.isString | new RegExp()');
+  equal(Object.isString(new Date()), false, 'Object.isString | new Date()');
+  equal(Object.isString(function() {}), false, 'Object.isString | function() {}');
+  equal(Object.isString(new Function()), false, 'Object.isString | new Function()');
+  equal(Object.isString(1), false, 'Object.isString | 1');
+  equal(Object.isString('wasabi'), true, 'Object.isString | "wasabi"');
+  equal(Object.isString(new String('wasabi')), true, 'Object.isString | new String("wasabi")');
+  equal(Object.isString(null), false, 'Object.isString | null');
+  equal(Object.isString(undefined), false, 'Object.isString | undefined');
+  equal(Object.isString(NaN), false, 'Object.isString | NaN');
+  equal(Object.isString(), false, 'Object.isString | blank');
+  equal(Object.isString(false), false, 'Object.isString | false');
+  equal(Object.isString(true), false, 'Object.isString | true');
+
+  equal(Object.isRegExp({}), false, 'Object.isRegExp | {}');
+  equal(Object.isRegExp([]), false, 'Object.isRegExp | []');
+  equal(Object.isRegExp(new RegExp()), true, 'Object.isRegExp | new RegExp()');
+  equal(Object.isRegExp(/afda/), true, 'Object.isRegExp | /afda/');
+  equal(Object.isRegExp(new Date()), false, 'Object.isRegExp | new Date()');
+  equal(Object.isRegExp(function() {}), false, 'Object.isRegExp | function() {}');
+  equal(Object.isRegExp(new Function()), false, 'Object.isRegExp | new Function()');
+  equal(Object.isRegExp(1), false, 'Object.isRegExp | 1');
+  equal(Object.isRegExp('wasabi'), false, 'Object.isRegExp | "wasabi"');
+  equal(Object.isRegExp(null), false, 'Object.isRegExp | null');
+  equal(Object.isRegExp(undefined), false, 'Object.isRegExp | undefined');
+  equal(Object.isRegExp(NaN), false, 'Object.isRegExp | NaN');
+  equal(Object.isRegExp(), false, 'Object.isRegExp | blank');
+  equal(Object.isRegExp(false), false, 'Object.isRegExp | false');
+  equal(Object.isRegExp(true), false, 'Object.isRegExp | true');
+
+  equal(Object.isNaN({}), false, 'Object.isNaN | {}');
+  equal(Object.isNaN([]), false, 'Object.isNaN | []');
+  equal(Object.isNaN(new RegExp()), false, 'Object.isNaN | new RegExp()');
+  equal(Object.isNaN(/afda/), false, 'Object.isNaN | /afda/');
+  equal(Object.isNaN(new Date()), false, 'Object.isNaN | new Date()');
+  equal(Object.isNaN(function() {}), false, 'Object.isNaN | function() {}');
+  equal(Object.isNaN(new Function()), false, 'Object.isNaN | new Function()');
+  equal(Object.isNaN(1), false, 'Object.isNaN | 1');
+  equal(Object.isNaN('wasabi'), false, 'Object.isNaN | "wasabi"');
+  equal(Object.isNaN(null), false, 'Object.isNaN | null');
+  equal(Object.isNaN(undefined), false, 'Object.isNaN | undefined');
+  equal(Object.isNaN(NaN), true, 'Object.isNaN | NaN');
+  equal(Object.isNaN(), false, 'Object.isNaN | blank');
+  equal(Object.isNaN(false), false, 'Object.isNaN | false');
+  equal(Object.isNaN(true), false, 'Object.isNaN | true');
+
+
+  equal(({}).keys, undefined, 'Object | native objects are not wrapped by default');
+  equal(Object.extended(), Object.extended({}), 'Object.extended | null argument same as empty object');
+
+  var keys,values;
+  var d = new Date();
+  var obj = Object.extended({
+    number: 3,
+    person: 'jim',
+    date: d
+  });
+
+
+  keys = ['number','person','date'];
+  values = [3,'jim',d];
+  equal(obj.keys(), keys, "Object#keys | returns object's keys");
+  count = 0;
+  obj.keys(function(key, value) {
+    equal(key, keys[count], 'Object#keys | accepts a block');
+    equal(value, values[count], 'Object#keys | value is also passed');
+    equal(this, obj, 'Object#keys | "this" is the object');
+    count++;
+  });
+
+  equal(count, 3, 'Object#keys | accepts a block | iterated properly');
+
+  equal(Object.extended().keys(), [], 'Object#keys | empty object');
+  equal(Object.keys(Object.extended()), [], 'Object#keys | empty object');
+
+  keys = ['number','person','date'];
+  values = [3,'jim',d];
+  equal(Object.keys(obj), keys, "Object.keys | returns object's keys");
+  count = 0;
+  Object.keys(obj, function(key) {
+    equal(key, keys[count], 'Object.keys | accepts a block');
+    count++;
+  });
+  equal(count, 3, 'Object.keys | accepts a block | iterated properly');
+
+
+
+  var strippedValues;
+
+  strippedValues = obj.values().filter(function(m) { return typeof m != 'function'; });
+  equal(strippedValues, values, "Object#values | returns object's values", { prototype: values });
+  count = 0;
+  obj.values(function(value) {
+    equal(value, values[count], 'Object#values | accepts a block');
+    count++;
+  });
+
+  equal(count, 3, 'Object#values | accepts a block | iterated properly', { prototype: 0, mootools: 0 });
+
+  strippedValues = Object.values(obj).filter(function(m) { return typeof m != 'function'; });
+  equal(strippedValues, values, "Object.values | returns object's values", { prototype: values });
+  count = 0;
+  Object.values(obj, function(value) {
+    equal(value, values[count], 'Object.values | accepts a block');
+    count++;
+  });
+  equal(count, 3, 'Object.values | accepts a block | iterated properly', { prototype: 0, mootools: 0 });
+
+  strippedValues = Object.extended().values().filter(function(m) { return typeof m != 'function'; });
+  equal(strippedValues, [], 'Object#values | empty object');
+
+  strippedValues = Object.values(Object.extended()).filter(function(m) { return typeof m != 'function'; });
+  equal(strippedValues, [], 'Object#values | empty object');
+
+
+
+
+
+
+  equal(Object.merge({ foo: 'bar' }, { broken: 'wear' }), { foo: 'bar', broken: 'wear' }, 'Object.merge | basic');
+  equal(Object.merge({ foo: 'bar' }, 'aha'), { foo: 'bar' }, 'Object.merge | will not merge a string', { mootools: { foo: 'bar', aha: undefined } });
+  equal(Object.merge({ foo: 'bar' }, null), { foo: 'bar' }, 'Object.merge | merge null');
+  equal(Object.merge({}, {}), {}, 'Object.merge | merge multi empty');
+
+
+  equal(Object.merge({ foo: 'bar' }, 8), { foo: 'bar' }, 'Object.merge | merge number', { mootools: (function() { var s = Object.clone(8); s.foo = 'bar'; return s; })() });
+
+
+  equal(Object.merge({ foo:'bar' }, 'wear', 8, null), { foo:'bar' }, 'Object.merge | merge multi invalid', { mootools: { foo: 'bar', wear: 7 } });
+  equal(Object.merge([1,2,3,4], [4,5,6]), [4,5,6,4], 'Object.merge | arrays should also be mergeable');
+  equal(Object.merge({ foo: { one: 'two' }}, { foo: { two: 'three' }}, true, true), { foo: { one: 'two', two: 'three' }}, 'Object.merge | accepts deep merges');
+
+  equal(Object.merge('foo', 'bar'), 'foo', 'Object.merge | two strings');
+
+  equal(Object.merge({ a:1 }, { a:2 }), { a:2 }, 'Object.merge | incoming wins');
+  equal(Object.merge({ a:1 }, { a:2 }), { a:2 }, 'Object.merge | incoming wins | params true');
+  equal(Object.merge({ a:1 }, { a:2 }, false, false), { a:1 }, 'Object.merge | target wins');
+  equal(Object.merge({ a:undefined }, { a:2 }), { a:2 }, 'Object.merge | existing but undefined properties are overwritten');
+  equal(Object.merge({ a:null }, { a:2 }), { a:2 }, 'Object.merge | null properties are not overwritten');
+  equal(Object.merge({ a:undefined }, { a:2 }, false, false), { a:2 }, 'Object.merge | false | existing but undefined properties are overwritten');
+  equal(Object.merge({ a:null }, { a:2 }, false, false), { a:null }, 'Object.merge | false | null properties are not overwritten');
+  equal(Object.merge([{ foo:'bar' }], [{ moo:'car' }], true, true), [{ foo:'bar',moo:'car' }], 'Object.merge | can merge arrays as well');
+
+  var fn1 = function() {};
+  fn1.foo = 'bar';
+  equal(Object.merge(function(){}, fn1).foo, 'bar', 'Object.merge | retains properties');
+
+  var fn = function(key, a, b) {
+    equal(key, 'a', 'Object.merge | resolve function | first argument is the key');
+    equal(a, 1, 'Object.merge | resolve function | second argument is the target val');
+    equal(b, 2, 'Object.merge | resolve function | third argument is the source val');
+    equal(this, { a:2 }, 'Object.merge | resolve function | context is the source object');
+    return a + b;
+  };
+
+  equal(Object.merge({ a:1 }, { a:2 }, false, fn), { a:3 }, 'Object.merge | function resolves');
+
+
+  // Issue #335
+
+  equal(Object.merge({a:{b:1}}, {a:{b:2,c:3} },true,false), {a:{b:1,c:3}}, 'Object.merge | two deep properties');
+
+
+  var fn1 = function() { return 'joe' };
+  var fn2 = function() { return 'moe' };
+  var date1 = new Date(2001, 1, 6);
+  var date2 = new Date(2005, 1, 6);
+  var inner1 = { foo: 'bar', hee: 'haw' }
+  var inner2 = { foo: 'car', mee: 'maw' }
+
+  var obj1 = {
+    str: 'oolala',
+    num: 18,
+    fn: fn1,
+    date: date1,
+    prop1: 'next',
+    inner: inner1,
+    arr: [1,2,3,4]
+  }
+
+  var obj2 = {
+    str: 'foofy',
+    num: 67,
+    fn: fn2,
+    date: date2,
+    prop2: 'beebop',
+    inner: inner2,
+    arr: [4,5,6]
+  }
+
+  var fn = function(key, a, b) {
+    if(key == 'str') {
+      return 'conflict!';
+    } else if(key == 'num') {
+      return a + b;
+    } else {
+      return b;
+    }
+  }
+
+  var expected = {
+    str: 'conflict!',
+    num: 85,
+    fn: fn2,
+    date: date2,
+    prop1: 'next',
+    prop2: 'beebop',
+    inner: {
+      foo: 'car',
+      hee: 'haw',
+      mee: 'maw'
+    },
+    arr: [4,5,6,4]
+  }
+
+  equal(Object.merge(obj1, obj2, true, fn), expected, 'Object.merge | complex objects with resolve function');
+  equal(obj1.fn(), 'moe', 'Object.merge | fn conflict resolved');
+  equal(obj1.date.getTime(), new Date(2005, 1, 6).getTime(), 'Object.merge | date conflict resolved');
+
+
+
+  equal(Object.extended({ foo: 'bar' }).merge({ broken: 'wear' }), { foo: 'bar', broken: 'wear' }, 'Object#merge | basic');
+  equal(Object.extended({ foo: 'bar' }).merge('aha'), { foo: 'bar' }, 'Object#merge | will not merge a string', { mootools: { foo: 'bar', aha: undefined } });
+  equal(Object.extended({ foo: 'bar' }).merge(null), { foo: 'bar' }, 'Object#merge | merge null');
+  equal(Object.extended({}).merge({}, {}, {}), {}, 'Object#merge | merge multi empty');
+
+  equal(Object.extended({ foo: 'bar' }).merge('wear', 8, null), { foo:'bar' }, 'Object#merge | merge multi invalid', { mootools: { foo: 'bar', wear: 8 } });
+
+
+  var fn1 = function() {};
+  fn1.foo = 'bar';
+  equal(Object.extended(function(){}).merge(fn1).foo, 'bar', 'Object.merge | retains properties');
+
+
+  equal(Object.extended({ a:1 }).merge({ a:2 }), { a:2 }, 'Object.merge | incoming wins');
+  equal(Object.extended({ a:1 }).merge({ a:2 }, true), { a:2 }, 'Object.merge | incoming wins | params true');
+  equal(Object.extended({ a:1 }).merge({ a:2 }, false, false), { a:1 }, 'Object.merge | target wins');
+  equal(Object.extended({ a:1 }).merge({ a:2 }, false, function(key, a, b){ return a + b; }), { a:3 }, 'Object.merge | function resolves');
+
+
+
+  skipEnvironments(['prototype','mootools'], function() {
+    equal(Object.clone('hardy'), 'hardy', 'Object.clone | clone on a string');
+  });
+  equal(Object.clone(undefined), undefined, 'Object.clone | clone on undefined', { prototype: {} });
+  equal(Object.clone(null), null, 'Object.clone | clone on null', { prototype: {} });
+  equal(Object.clone({ foo: 'bar' }), { foo: 'bar' }, 'Object.clone | basic clone');
+  equal(Object.clone({ foo: 'bar', broken: 1, wear: null }), { foo: 'bar', broken: 1, wear: null }, 'Object.clone | complex clone');
+  equal(Object.clone({ foo: { broken: 'wear' }}), { foo: { broken: 'wear' }}, 'Object.clone | deep clone');
+  equal(Object.clone({ foo: 'bar', broken: 1, wear: /foo/ }) == { foo: 'bar', broken: 1, wear: /foo/ }, false, 'Object.clone | fully cloned');
+  equal(Object.clone([1,2,3]), [1,2,3], 'Object.clone | clone on arrays');
+  equal(Object.clone(['a','b','c']), ['a','b','c'], 'Object.clone | clone on array of strings');
+
+  var arr1    = [1];
+  var arr2    = [2];
+  var arr3    = [3];
+  var shallow = Object.clone([arr1,arr2,arr3]);
+  var deep    = Object.clone([arr1,arr2,arr3], true);
+
+  equal(shallow[0] === arr1, true, 'Object.clone | shallow clone | index 0 is strictly equal');
+  equal(shallow[1] === arr2, true, 'Object.clone | shallow clone | index 1 is strictly equal');
+  equal(shallow[2] === arr3, true, 'Object.clone | shallow clone | index 2 is strictly equal');
+
+  equal(deep[0] === arr1, false, 'Object.clone | deep clone | index 0 is not strictly equal');
+  equal(deep[1] === arr2, false, 'Object.clone | deep clone | index 1 is not strictly equal');
+  equal(deep[2] === arr3, false, 'Object.clone | deep clone | index 2 is not strictly equal');
+
+  var obj1, obj2, obj3;
+
+  obj1 = {
+    broken: 'wear',
+    foo: {
+      jumpy: 'jump',
+      bucket: {
+        reverse: true
+      }
+    }
+  }
+  obj2 = Object.clone(obj1);
+  equal(obj1.foo.jumpy, 'jump', 'Object.clone | cloned object has nested attribute');
+  obj1.foo.jumpy = 'hump';
+  equal(obj1.foo.jumpy, 'hump', 'Object.clone | original object is modified');
+  equal(obj2.foo.jumpy, 'hump', 'Object.clone | clone is shallow', { mootools: 'jump' });
+
+  obj1 = {
+    foo: {
+      bar: [1,2,3]
+    }
+  };
+  obj2 = Object.clone(obj1);
+  obj3 = Object.clone(obj1, true);
+
+  obj1.foo.bar = ['a','b','c'];
+  equal(obj1.foo.bar, ['a','b','c'], 'Object#clone | original object is modified');
+  equal(obj2.foo.bar, ['a','b','c'], 'Object#clone | clone is shallow', { mootools: [1,2,3] });
+
+
+  obj1.foo.bar = ['a','b','c'];
+  equal(obj3.foo.bar, [1,2,3], 'Object#clone | clone is deep', { prototype: ['a','b','c'] });
+
+  var arr1 = [obj1, obj1, obj1];
+  var arr2 = Object.clone(arr1, true);
+
+  equal(arr1.length, arr2.length, 'Object.clone | array deep | lengths should be equal');
+  equal(arr2[0] === obj1, false, 'Object.clone | array deep | obj1 is not equal');
+  equal(arr2[1] === obj2, false, 'Object.clone | array deep | obj2 is not equal');
+  equal(arr2[2] === obj3, false, 'Object.clone | array deep | obj3 is not equal');
+
+
+  // Note here that the need for these complicated syntaxes is that both Prototype and Mootools' Object.clone is incorrectly
+  // cloning properties in the prototype chain directly into the object itself.
+  equal(Object.extended({ foo: 'bar' }).clone(), { foo: 'bar' }, 'Object#clone | basic clone');
+  equal(Object.extended({ foo: 'bar', broken: 1, wear: null }).clone(), { foo: 'bar', broken: 1, wear: null }, 'Object#clone | complex clone');
+  equal(Object.extended({ foo: { broken: 'wear' }}).clone(), { foo: { broken: 'wear' }}, 'Object#clone | deep clone');
+
+  equal(Object.extended({ foo: 'bar', broken: 1, wear: /foo/ }).clone() == { foo: 'bar', broken: 1, wear: /foo/ }, false, 'Object#clone | fully cloned');
+
+  var obj1, obj2, obj3;
+
+  obj1 = Object.extended({
+    broken: 'wear',
+    foo: {
+      jumpy: 'jump',
+      bucket: {
+        reverse: true
+      }
+    }
+  });
+  obj2 = obj1.clone();
+  obj3 = obj1.clone(true);
+
+  equal(obj1.foo.jumpy, 'jump', 'Object#clone | cloned object has nested attribute');
+  obj1.foo.jumpy = 'hump';
+  equal(obj1.foo.jumpy, 'hump', 'Object#clone | original object is modified');
+  equal(obj2.foo.jumpy, 'hump', 'Object#clone | clone is shallow');
+  equal(obj3.foo.jumpy, 'jump', 'Object#clone | clone is deep', { prototype: 'hump' });
+
+  skipEnvironments(['prototype','mootools'], function() {
+    equal(obj2.keys().sort(), ['broken','foo'], 'Object#clone | cloned objects are themselves extended');
+  });
+
+  obj1 = Object.extended({
+    foo: {
+      bar: [1,2,3]
+    }
+  });
+  obj2 = obj1.clone();
+  obj3 = obj1.clone(true);
+
+  obj1.foo.bar[1] = 'b';
+  equal(obj1.foo.bar, [1,'b',3], 'Object#clone | original object is modified');
+  equal(obj3.foo.bar, [1,2,3], 'Object#clone | cloned object is not modified', { prototype: [1,'b',3] });
+
+
+
+  equal(Object.equal({ broken: 'wear' }, { broken: 'wear' }), true, 'Object.equal | objects are equal');
+  equal(Object.equal({ broken: 'wear' }, { broken: 'jumpy' }), false, 'Object.equal | objects are not equal');
+  equal(Object.equal({}, {}), true, 'Object.equal | empty objects are equal');
+  equal(Object.equal({}, { broken: 'wear' }), false, 'Object.equal | 1st empty');
+  equal(Object.equal({ broken: 'wear' }, {}), false, 'Object.equal | 2nd empty');
+
+  equal(Object.equal({x: 1, y: undefined}, {x: 1, z: 2}), false, 'Object.equal | undefined keys');
+
+
+  equal(Object.extended({ broken: 'wear' }).equals({ broken: 'wear' }), true, 'Object#equals | extended objects are equal to plain objects');
+  equal(Object.extended({ broken: 'wear' }).equals({ broken: 'jumpy' }), false, 'Object#equals | objects are not equal');
+  equal(Object.extended({}).equals({}), true, 'Object#equals | empty extended objects are equal to empty plain objects');
+  equal(Object.extended({}).equals({ broken: 'wear' }), false, 'Object#equals | 1st empty');
+  equal(Object.extended({ broken: 'wear' }).equals({}), false, 'Object#equals | 2nd empty');
+
+
+  var obj1 = { foo: 'bar' };
+  equal(Object.equal({ a: obj1, b: obj1 }, { a: obj1, b: obj1 }), true, 'Object.equal | multiple references will not choke');
+
+  var obj1 = { foo: 'bar' };
+  obj1.moo = obj1;
+  equal(Object.equal(obj1, { foo: 'bar', moo: obj1 }), true, 'Object.equal | cyclical references handled');
+
+  equal(Object.equal(undefined, 'one'), false, 'Object.equal | string to undefined');
+  // Enabling native object methods
+
+
+  rememberObjectProtoypeMethods();
+
+  Object.extend();
+
+  var prototypeBaseValues = ({}).values().sort();
+
+  count = 0;
+  equal(({ foo: 'bar' }).keys(function() { count++; }), ['foo'], 'Object#keys | Object.prototype');
+  equal(({ foo: 'bar' }).values(function() { count++; }).sort(), ['bar'], 'Object#values | Object.prototype', { prototype: ['bar'].concat(prototypeBaseValues) });
+
+  equal(count, 2, 'Object | Object.prototype should have correctly called all functions', { prototype: 2, mootools: 2 });
+
+  equal(({ foo: 'bar' }).equals({ foo: 'bar' }), true, 'Object#equals | Object.prototype');
+  equal(({ foo: 'bar' }).merge({ moo: 'car' }), { foo: 'bar', moo: 'car' }, 'Object#merge | Object.prototype', { mootools: Object.clone({ foo: 'bar', moo: 'car' }) });
+
+  obj1 = { foo: 'bar' };
+  obj2 = obj1.clone();
+  obj1.foo = 'mar';
+
+  equal(obj2, { foo: 'bar' }, 'Object#clone | Object.prototype');
+
+  equal(([1,2,3]).isArray(), true, 'Object#isArray | Object.prototype');
+  equal(([1,2,3]).isBoolean(), false, 'Object#isBoolean | Object.prototype');
+  equal(([1,2,3]).isDate(), false, 'Object#isDate | Object.prototype');
+  equal(([1,2,3]).isFunction(), false, 'Object#isFunction | Object.prototype');
+  equal(([1,2,3]).isNumber(), false, 'Object#isNumber | Object.prototype');
+  equal(([1,2,3]).isString(), false, 'Object#isString | Object.prototype');
+  equal(([1,2,3]).isRegExp(), false, 'Object#isRegExp | Object.prototype');
+  equal(([1,2,3]).isNaN(), false, 'Object#isNaN | Object.prototype');
+  equal((true).isArray(), false, 'Object#isArray | Object.prototype');
+  equal((true).isBoolean(), true, 'Object#isBoolean | Object.prototype');
+  equal((true).isDate(), false, 'Object#isDate | Object.prototype');
+  equal((true).isFunction(), false, 'Object#isFunction | Object.prototype');
+  equal((true).isNumber(), false, 'Object#isNumber | Object.prototype');
+  equal((true).isString(), false, 'Object#isString | Object.prototype');
+  equal((true).isRegExp(), false, 'Object#isRegExp | Object.prototype');
+  equal((true).isNaN(), false, 'Object#isNaN | Object.prototype');
+  equal((new Date()).isArray(), false, 'Object#isArray | Object.prototype');
+  equal((new Date()).isBoolean(), false, 'Object#isBoolean | Object.prototype');
+  equal((new Date()).isDate(), true, 'Object#isDate | Object.prototype');
+  equal((new Date()).isFunction(), false, 'Object#isFunction | Object.prototype');
+  equal((new Date()).isNumber(), false, 'Object#isNumber | Object.prototype');
+  equal((new Date()).isString(), false, 'Object#isString | Object.prototype');
+  equal((new Date()).isRegExp(), false, 'Object#isRegExp | Object.prototype');
+  equal((new Date()).isNaN(), false, 'Object#isNaN | Object.prototype');
+  equal((function() {}).isArray(), false, 'Object#isArray | Object.prototype');
+  equal((function() {}).isBoolean(), false, 'Object#isBoolean | Object.prototype');
+  equal((function() {}).isDate(), false, 'Object#isDate | Object.prototype');
+  equal((function() {}).isFunction(), true, 'Object#isFunction | Object.prototype');
+  equal((function() {}).isNumber(), false, 'Object#isNumber | Object.prototype');
+  equal((function() {}).isString(), false, 'Object#isString | Object.prototype');
+  equal((function() {}).isRegExp(), false, 'Object#isRegExp | Object.prototype');
+  equal((function() {}).isNaN(), false, 'Object#isNaN | Object.prototype');
+  equal((3).isArray(), false, 'Object#isArray | Object.prototype');
+  equal((3).isBoolean(), false, 'Object#isBoolean | Object.prototype');
+  equal((3).isDate(), false, 'Object#isDate | Object.prototype');
+  equal((3).isFunction(), false, 'Object#isFunction | Object.prototype');
+  equal((3).isNumber(), true, 'Object#isNumber | Object.prototype');
+  equal((3).isString(), false, 'Object#isString | Object.prototype');
+  equal((3).isRegExp(), false, 'Object#isRegExp | Object.prototype');
+  equal((3).isNaN(), false, 'Object#isNaN | Object.prototype');
+  equal(('wasabi').isArray(), false, 'Object#isArray | Object.prototype');
+  equal(('wasabi').isBoolean(), false, 'Object#isBoolean | Object.prototype');
+  equal(('wasabi').isDate(), false, 'Object#isDate | Object.prototype');
+  equal(('wasabi').isFunction(), false, 'Object#isFunction | Object.prototype');
+  equal(('wasabi').isNumber(), false, 'Object#isNumber | Object.prototype');
+  equal(('wasabi').isString(), true, 'Object#isString | Object.prototype');
+  equal(('wasabi').isRegExp(), false, 'Object#isRegExp | Object.prototype');
+  equal(('wasabi').isNaN(), false, 'Object#isNaN | Object.prototype');
+  equal((/wasabi/).isArray(), false, 'Object#isArray | Object.prototype');
+  equal((/wasabi/).isBoolean(), false, 'Object#isBoolean | Object.prototype');
+  equal((/wasabi/).isDate(), false, 'Object#isDate | Object.prototype');
+  equal((/wasabi/).isFunction(), false, 'Object#isFunction | Object.prototype');
+  equal((/wasabi/).isNumber(), false, 'Object#isNumber | Object.prototype');
+  equal((/wasabi/).isString(), false, 'Object#isString | Object.prototype');
+  equal((/wasabi/).isRegExp(), true, 'Object#isRegExp | Object.prototype');
+  equal((/wasabi/).isNaN(), false, 'Object#isNaN | Object.prototype');
+  equal((NaN).isArray(), false, 'Object#isArray | Object.prototype');
+  equal((NaN).isBoolean(), false, 'Object#isBoolean | Object.prototype');
+  equal((NaN).isDate(), false, 'Object#isDate | Object.prototype');
+  equal((NaN).isFunction(), false, 'Object#isFunction | Object.prototype');
+  equal((NaN).isNumber(), true, 'Object#isNumber | Object.prototype');
+  equal((NaN).isString(), false, 'Object#isString | Object.prototype');
+  equal((NaN).isRegExp(), false, 'Object#isRegExp | Object.prototype');
+  equal((NaN).isNaN(), true, 'Object#isNaN | Object.prototype');
+
+
+
+
+  // Object#watch
+
+  var obj = Object.extended({ foo: 'bar' }), ran = false, counter = 0, key;
+
+  obj.watch('foo', function(prop, oldVal, newVal) {
+    equal(this, obj, 'Object#watch | scope is the object');
+    equal(prop, 'foo', 'Object#watch | first argument is the propety');
+    equal(oldVal, 'bar', 'Object#watch | second argument is the old value');
+    equal(newVal, 'howdy', 'Object#watch | third argument is the new value');
+    ran = true;
+    return newVal;
+  });
+
+  equal(obj.foo, 'bar', 'Object#watch | old property is retained');
+  obj.foo = 'howdy';
+  equal(obj.foo, 'howdy', 'Object#watch | property was set');
+  equal(ran, true, 'Object#watch | setter ran');
+  for(key in obj) counter++;
+  equal(counter, 1, 'Object#watch | property should be enumerable');
+
+
+
+  // Object#tap
+
+  var fn = function(first) {
+    equal(this, [1,2,3,4,5], 'Object#tap | context is the object');
+    equal(first, [1,2,3,4,5], 'Object#tap | first argument is also the object');
+    this.pop();
+  }
+
+  var map = function(n) {
+    return n * 2;
+  }
+
+  var expected = [2,4,6,8];
+
+  equal([1,2,3,4,5].tap(fn).map(map), expected, 'Object#tap | pop the array');
+  equal([1,2,3,4,5].tap('pop').map(map), expected, 'Object#tap | string shortcut | pop the array');
+  equal([1,2].tap(function() { this.push(3, 4); }).map(map), expected, 'Object#tap | push to the array');
+  equal([1,2].tap('push', 3, 4).map(map), [2,4], 'Object#tap | string shortcut | passing arguments is not supported');
+  equal([1,2,3,4].tap(function(){ if(this[this.length - 1] === 5) this.pop(); }).map(map), expected, 'Object#tap | checking last');
+
+
+  var obj = { foo: 'bar' };
+  equal(obj.tap(), obj, 'Object#tap | return value is strictly equal');
+
+
+
+  equal('foo'.fromQueryString, undefined, 'Object.fromQueryString should not be mapped');
+  equal('foo'.extended, undefined, 'Object.extended should not be mapped');
+  equal('foo'.equal, undefined, 'Object.equal should not be mapped (should be "equals" instead)');
+
+
+
+  // Issue #248
+  // Ensure that methods can be reverted
+
+  Object.sugarRevert('isObject');
+  equal('isObject' in {}, false, 'Object.sugarRevert | isObject should be removed');
+
+  Object.prototype.tap = undefined;
+  Object.extend();
+  Object.sugarRevert('tap');
+  equal('tap' in {}, true, 'Object.sugarRevert | previously undefined property should not be deleted');
+  equal(({}).tap === undefined, true, 'Object.sugarRevert | previously undefined property is still undefined');
+  delete Object.prototype.tap;
+
+  restoreObjectPrototypeMethods();
+
+
+  // Object.fromQueryString
+
+  equal(Object.fromQueryString('foo=bar&moo=car'), {foo:'bar',moo:'car'}, 'Object.fromQueryString | basic');
+  equal(Object.fromQueryString('foo=bar&moo=3'), {foo:'bar',moo:'3'}, 'Object.fromQueryString | with numbers');
+
+  equal(Object.fromQueryString('foo=bar&moo=true'), {foo:'bar',moo:'true'}, 'Object.fromQueryString | with true');
+  equal(Object.fromQueryString('foo=bar&moo=false'), {foo:'bar',moo:'false'}, 'Object.fromQueryString | with false');
+
+  equal(Object.fromQueryString('foo=bar&moo=true', true), {foo:'bar',moo:true}, 'Object.fromQueryString | coerced | with true');
+  equal(Object.fromQueryString('foo=bar&moo=false', true), {foo:'bar',moo:false}, 'Object.fromQueryString | coerced | with false');
+
+  equal(Object.fromQueryString('foo=bar3'), { foo: 'bar3' }, 'Object.fromQueryString | number in back');
+  equal(Object.fromQueryString('foo=3bar'), { foo: '3bar' }, 'Object.fromQueryString | number up front');
+  equal(Object.fromQueryString('foo=345'), { foo: '345' }, 'Object.fromQueryString | numbers only');
+  equal(Object.fromQueryString('foo=&bar='), { foo: '', bar: '' }, 'Object.fromQueryString | undefined params');
+  equal(Object.fromQueryString('foo[]=bar&foo[]=car'), { foo: ['bar','car'] }, 'Object.fromQueryString | handles array params');
+  equal(Object.fromQueryString('foo[bar]=tee&foo[car]=hee'), { foo: { bar: 'tee', car: 'hee' } }, 'Object.fromQueryString | handles hash params');
+  equal(Object.fromQueryString('foo[0]=a&foo[1]=b&foo[2]=c'), { foo: ['a','b','c'] }, 'Object.fromQueryString | handles array indexes');
+
+  equal(Object.fromQueryString('foo[cap][map]=3'), { foo: { cap: { map: '3' } } }, 'Object.fromQueryString | handles array indexes');
+  equal(Object.fromQueryString('foo[cap][map][]=3'), { foo: { cap: { map: ['3'] } } }, 'Object.fromQueryString | nested with trailing array');
+  equal(Object.fromQueryString('foo[moo]=1&bar[far]=2'), { foo: { moo: '1' }, bar: { far: '2' }}, 'Object.fromQueryString | sister objects');
+
+  equal(Object.fromQueryString('f[]=a&f[]=b&f[]=c&f[]=d&f[]=e&f[]=f'), { f: ['a','b','c','d','e','f'] }, 'Object.fromQueryString | large array');
+  equal(Object.fromQueryString('foo[0][]=a&foo[1][]=b'), { foo: [['a'],['b']] }, 'Object.fromQueryString | nested arrays separate');
+  equal(Object.fromQueryString('foo[0][0]=3&foo[0][1]=4'), { foo: [['3','4']] }, 'Object.fromQueryString | nested arrays together');
+  equal(Object.fromQueryString('foo[][]=3&foo[][]=4'), { foo: [['3'],['4']] }, 'Object.fromQueryString | nested arrays');
+
+  var qs = 'foo[cap][map]=true&foo[cap][pap]=false';
+  equal(Object.fromQueryString(qs), {foo:{cap:{ map:'true',pap:'false'}}}, 'Object.fromQueryString | nested boolean without coercion');
+  equal(Object.fromQueryString(qs, true), {foo:{cap:{map:true,pap:false}}}, 'Object.fromQueryString | nested boolean with coercion');
+
+
+  var sparse = [];
+  sparse[3] = 'hardy';
+  sparse[10] = 'har har';
+  equal(Object.fromQueryString('foo[3]=hardy&foo[10]=har har'), { foo: sparse }, 'Object.fromQueryString | constructed arrays can be sparse');
+
+  equal(Object.fromQueryString('text=What%20is%20going%20on%20here%3f%3f&url=http://animalsbeingdicks.com/page/2'), { text: 'What is going on here??', url: 'http://animalsbeingdicks.com/page/2' }, 'Object.fromQueryString | handles partially escaped params');
+  equal(Object.fromQueryString('text=What%20is%20going%20on%20here%3f%3f&url=http%3A%2F%2Fanimalsbeingdicks.com%2Fpage%2F2'), { text: 'What is going on here??', url: 'http://animalsbeingdicks.com/page/2' }, 'Object.fromQueryString | handles fully escaped params');
+
+  equal(Object.fromQueryString('url=http%3A%2F%2Fwww.site.com%2Fslug%3Fin%3D%2Fuser%2Fjoeyblake'), { url: 'http://www.site.com/slug?in=/user/joeyblake' }, 'Object.fromQueryString | equal must be escaped as well');
+
+  equal(Object.fromQueryString('http://fake.com?foo=bar'), { foo: 'bar' }, 'Object.fromQueryString | handles whole URLs');
+  equal(Object.fromQueryString('foo=bar&moo=car').keys(), ['foo', 'moo'], 'Object.fromQueryString | should be extended');
+  equal(Object.fromQueryString(), {}, 'Object.fromQueryString | will not die if no arguments');
+
+  if(typeof window !== 'undefined') {
+    equal(Object.isArray(Object.fromQueryString(window.location).keys()), true, 'Object.fromQueryString | can handle just window.location');
+  }
+
+  equal(Object.fromQueryString('foo=3.14156'), { foo: '3.14156' }, 'Object.fromQueryString | float values are not coerced');
+  equal(Object.fromQueryString('foo=127.0.0.1'), { foo: '127.0.0.1' }, 'Object.fromQueryString | IP addresses not treated as numbers');
+  equal(Object.fromQueryString('zip=00165'), { zip: '00165' }, 'Object.fromQueryString | zipcodes are not treated as numbers');
+  equal(Object.fromQueryString('foo[=bar'), { 'foo[': 'bar' }, 'Object.fromQueryString | opening bracket does not trigger deep parameters');
+
+
+
+  // Object.watch
+
+  var obj = { foo: 'bar' }, ran = false, counter = 0, key;
+
+  Object.watch(obj, 'foo', function(prop, oldVal, newVal) {
+    equal(this, obj, 'Object.watch | scope is the object');
+    equal(prop, 'foo', 'Object.watch | first argument is the propety');
+    equal(oldVal, 'bar', 'Object.watch | second argument is the old value');
+    equal(newVal, 'howdy', 'Object.watch | third argument is the new value');
+    ran = true;
+    return newVal;
+  });
+
+  equal(obj.foo, 'bar', 'Object.watch | old property is retained');
+  obj.foo = 'howdy';
+  equal(obj.foo, 'howdy', 'Object.watch | property was set');
+  equal(ran, true, 'Object.watch | setter ran');
+  for(key in obj) counter++;
+  equal(counter, 1, 'Object.watch | property should be enumerable');
+
+
+
+  // Object.tap
+
+  var fn = function(first) {
+    equal(this, [1,2,3,4,5], 'Object.tap | context is the object');
+    equal(first, [1,2,3,4,5], 'Object.tap | first argument is also the object');
+    this.pop();
+  }
+
+  var map = function(n) {
+    return n * 2;
+  }
+
+  var expected = [2,4,6,8];
+
+  equal(Object.tap([1,2,3,4,5], fn).map(map), expected, 'Object.tap | pop the array');
+  equal(Object.tap([1,2,3,4,5], 'pop').map(map), expected, 'Object.tap | string shortcut | pop the array');
+  equal(Object.tap([1,2], function() { this.push(3, 4); }).map(map), expected, 'Object.tap | push to the array');
+  equal(Object.tap([1,2], 'push', 3, 4).map(map), [2,4], 'Object.tap | string shortcut | not supported');
+  equal(Object.tap([1,2,3,4], function(){ if(this[this.length - 1] === 5) this.pop(); }).map(map), expected, 'Object.tap | checking last');
+
+
+  var obj = { foo: 'bar' };
+  equal(Object.tap(obj), obj, 'Object.tap | return value is strictly equal');
+
+  // Class.extend functionality
+
+
+  String.extend({
+    foo: function() {
+      return 'bar';
+    }
+  });
+
+
+  equal('s'.foo(), 'bar', 'Class.extend | basic functionality');
+
+  Number.extend({
+    plus: function(a, b) {
+      return this + a + b;
+    },
+    chr: function() {
+      return String.fromCharCode(this);
+    }
+  });
+
+
+  equal((1).plus(2, 3), 6, 'Class.extend | arguments and scope are correct');
+
+  Number.prototype.chr = function() { return 'F'; };
+
+  equal((69).chr(), 'F', 'Class.extend | should overwrite existing methods');
+
+  Number.sugarRestore('chr');
+
+  equal((69).chr(), 'E', 'Class.extend | simple array of strings should restore Sugar methods');
+  equal((1).plus(2, 3), 6, 'Class.extend | restoring Sugar methods should not override other custom extended methods');
+
+
+  // Object.extended hasOwnProperty issue #97
+  // see: http://www.devthought.com/2012/01/18/an-object-is-not-a-hash/
+
+  var a = Object.extended({ hasOwnProperty: true });
+
+
+  // Object.has
+
+  equal(Object.has({ foo: 'bar' }, 'foo'), true, 'Object.has | finds a property');
+  equal(Object.has({ foo: 'bar' }, 'baz'), false, 'Object.has | does not find a nonexistant property');
+  equal(Object.has({ hasOwnProperty: true, foo: 'bar' }, 'foo'), true, 'Object.has | local hasOwnProperty is ignored');
+
+
+
+  // Object.clone on dates and regexes
+
+  var obj1 = {
+    d: new Date(2000, 5, 25),
+    r: /dasfsa/gi
+  }
+
+  var obj2 = Object.clone(obj1, true);
+
+  obj1.d.setDate(3);
+  obj1.r.source = 'mwahaha';
+
+  equal(obj2.d.getDate(), 25, 'Object.clone | deep cloning also clones dates');
+  equal(obj2.r.source, 'dasfsa', 'Object.clone | deep cloning also clones regexes');
+
+
+  // Object.merge should not merge prototype properties
+
+  var Foo = function(){};
+  Foo.prototype.bar = 3;
+
+  var f = new Foo();
+
+  equal(Object.merge({}, f).bar, undefined, 'Object.merge should not merge inherited properties');
+
+  // Issue #307  Object.clone should error when cloning unknown types.
+
+  raisesError(function(){ Object.clone(f); }, 'Object.clone | raises an error if clone is not a basic object type');
+
+
+
+  // Object.merge should not choke when target and source contain strictly equal objects
+
+  var obj = { foo: 'bar' };
+
+  equal(Object.merge({ one: obj }, { one: obj }), { one: obj }, 'Object.merge should be able to handle identical source/target objects');
+
+  obj.moo = obj;
+
+  equal(typeof Object.merge(obj, { foo: obj }), 'object', 'Object.merge should not choke on cyclic references');
+
+  // Object.merge deep merges should clone regexes
+
+  var obj1 = {
+    reg: /foobar/g
+  }
+
+  equal(Object.merge({}, obj1, true).reg === obj1.reg, false, 'Object.merge | deep merging will clone regexes');
+
+
+  // Object.select
+
+  var obj = {
+    one:    1,
+    two:    2,
+    three:  3,
+    four:   4,
+    five:   5
+  };
+
+  var obj2 = { foo: obj };
+
+  testClassAndInstance('select', obj, ['one'], { one: 1 }, 'Object.select | one key');
+  testClassAndInstance('select', obj, ['foo'], {}, 'Object.select | nonexistent key');
+  testClassAndInstance('select', obj, ['one', 'two'], { one: 1, two: 2 }, 'Object.select | two keys');
+  testClassAndInstance('select', obj, ['one', 'foo'], { one: 1 }, 'Object.select | one existing one non-existing');
+  testClassAndInstance('select', obj, ['four', 'two'], { two: 2, four: 4 }, 'Object.select | keys out of order');
+  testClassAndInstance('select', obj, [['four', 'two']], { two: 2, four: 4 }, 'Object.select | keys in an array');
+  testClassAndInstance('select', obj, [/o/], { one: 1, two: 2, four: 4 }, 'Object.select | regex');
+  testClassAndInstance('select', obj, [/o$/], { two: 2 }, 'Object.select | regex $');
+  testClassAndInstance('select', obj, [/^o/], { one: 1 }, 'Object.select | ^ regex');
+  testClassAndInstance('select', obj, [/z/], {}, 'Object.select | non-matching regex');
+  testClassAndInstance('select', obj, [{ one: 1 }], { one: 1 }, 'Object.select | comparing object');
+  testClassAndInstance('select', obj, [{ one: 'foobar' }], {}, 'Object.select | should not match with different values');
+  testClassAndInstance('select', obj, [{}], {}, 'Object.select | empty object');
+  testClassAndInstance('select', obj, [[/^o/, /^f/]], { one: 1, four: 4, five: 5 }, 'Object.select | complex nested array of regexes');
+
+  equal(Object.select(obj2, 'foo').foo === obj, true, 'Object.select | selected values should be equal by reference');
+
+  var obj3 = Object.extended(obj);
+
+  equal(typeof Object.select(obj,  'one').select, "undefined", 'Object.select | non-Hash should return non Hash');
+  equal(typeof Object.select(obj,  'two', 'three').select, "undefined", 'Object.select | non-Hash should return non Hash');
+  equal(typeof Object.select(obj3, 'one').select, "function", 'Object.select | Hash should return Hash');
+  equal(typeof Object.select(obj3, 'two', 'three').select, "function", 'Object.select | Hash should return Hash');
+
+  testClassAndInstance('reject', obj, ['one'], { two: 2, three: 3, four: 4, five: 5 }, 'Object.reject | one key');
+  testClassAndInstance('reject', obj, ['foo'], obj, 'Object.reject | nonexistent key');
+  testClassAndInstance('reject', obj, ['one', 'two'], { three: 3, four: 4, five: 5 }, 'Object.reject | two keys');
+  testClassAndInstance('reject', obj, ['one', 'foo'], { two: 2, three: 3, four: 4, five: 5 }, 'Object.reject | one existing one non-existing');
+  testClassAndInstance('reject', obj, ['four', 'two'], { one: 1, three: 3, five: 5 }, 'Object.reject | keys out of order');
+  testClassAndInstance('reject', obj, [['four', 'two']], { one: 1, three: 3, five: 5 }, 'Object.reject | keys in an array');
+  testClassAndInstance('reject', obj, [/o/], { three: 3, five: 5 }, 'Object.reject | regex');
+  testClassAndInstance('reject', obj, [/o$/], { one: 1, three: 3, four: 4, five: 5 }, 'Object.reject | regex $');
+  testClassAndInstance('reject', obj, [/^o/], { two: 2, three: 3, four: 4, five: 5 }, 'Object.reject | ^ regex');
+  testClassAndInstance('reject', obj, [/z/], obj, 'Object.reject | non-matching regex');
+  testClassAndInstance('reject', obj, [{ one: 1 }], { two: 2, three: 3, four: 4, five: 5 }, 'Object.reject | comparing object');
+  testClassAndInstance('reject', obj, [{ one: 'foobar' }], obj, 'Object.reject | comparing object with different values');
+  testClassAndInstance('reject', obj, [{}], obj, 'Object.reject | empty object');
+  testClassAndInstance('reject', obj, [[/^o/, /^f/]], { two: 2, three: 3 }, 'Object.reject | complex nested array of regexes');
+
+  equal(Object.reject(obj2, 'moo').foo === obj, true, 'Object.reject | rejected values should be equal by reference');
+
+
+  // Issue #256
+
+  if(Date.prototype.clone) {
+    equal(Object.clone(new Date().utc())._utc, true, 'Object.clone | should preserve utc flag when set');
+  }
+
+
+  var date = new Date(2012, 8, 25);
+
+  assertQueryStringGenerated({foo:'bar'}, [], 'foo=bar', 'Object.toQueryString | basic string');
+  assertQueryStringGenerated({foo:'bar',moo:'car'}, [], 'foo=bar&moo=car', 'Object.toQueryString | two keys');
+  assertQueryStringGenerated({foo:'bar',moo:8}, [], 'foo=bar&moo=8', 'Object.toQueryString | one string one numeric');
+  assertQueryStringGenerated({foo:'bar3'}, [], 'foo=bar3', 'Object.toQueryString | number in back');
+  assertQueryStringGenerated({foo:'3bar'}, [], 'foo=3bar', 'Object.toQueryString | number in front');
+  assertQueryStringGenerated({foo: 3}, [], 'foo=3', 'Object.toQueryString | basic number');
+  assertQueryStringGenerated({foo: true}, [], 'foo=true', 'Object.toQueryString | basic boolean');
+  assertQueryStringGenerated({foo: /reg/}, [], 'foo=%2Freg%2F', 'Object.toQueryString | regexp');
+  assertQueryStringGenerated({foo:'a b'}, [], 'foo=a+b', 'Object.toQueryString | should escape string');
+  assertQueryStringGenerated({foo: date}, [], 'foo=' + date.getTime(), 'Object.toQueryString | should stringify date');
+  assertQueryStringGenerated({foo:['a','b','c']}, [], 'foo[0]=a&foo[1]=b&foo[2]=c', 'Object.toQueryString | basic array');
+  assertQueryStringGenerated({foo:{bar:'tee',car:'hee'}}, [], 'foo[bar]=tee&foo[car]=hee', 'Object.toQueryString | basic object');
+
+  assertQueryStringGenerated({foo:undefined}, [], 'foo=', 'Object.toQueryString | undefined');
+  assertQueryStringGenerated({foo:false}, [], 'foo=false', 'Object.toQueryString | false');
+  assertQueryStringGenerated({foo:null}, [], 'foo=', 'Object.toQueryString | null');
+  assertQueryStringGenerated({foo:NaN}, [], 'foo=', 'Object.toQueryString | NaN');
+  assertQueryStringGenerated({foo:''}, [], 'foo=', 'Object.toQueryString | empty string');
+  assertQueryStringGenerated({foo:0}, [], 'foo=0', 'Object.toQueryString | 0');
+  assertQueryStringGenerated({foo:[['fap','cap']]}, [], 'foo[0][0]=fap&foo[0][1]=cap', 'Object.toQueryString | array double nested');
+  assertQueryStringGenerated({foo:[['fap'],['cap']]}, [], 'foo[0][0]=fap&foo[1][0]=cap', 'Object.toQueryString | array horizonal nested');
+  assertQueryStringGenerated({foo:{bar:{map:'fap'}}}, [], 'foo[bar][map]=fap', 'Object.toQueryString | object double nested');
+
+  assertQueryStringGenerated({foo:'bar'}, ['paw'], 'paw[foo]=bar', 'Object.toQueryString | namespace | basic string');
+  assertQueryStringGenerated({foo:'bar',moo:'car'}, ['paw'], 'paw[foo]=bar&paw[moo]=car', 'Object.toQueryString | namespace | two keys');
+  assertQueryStringGenerated({foo:'bar',moo:8}, ['paw'], 'paw[foo]=bar&paw[moo]=8', 'Object.toQueryString | namespace | one string one numeric');
+  assertQueryStringGenerated({foo:'bar3'}, ['paw'], 'paw[foo]=bar3', 'Object.toQueryString | namespace | number in back');
+  assertQueryStringGenerated({foo:'3bar'}, ['paw'], 'paw[foo]=3bar', 'Object.toQueryString | namespace | number in front');
+  assertQueryStringGenerated({foo: 3}, ['paw'], 'paw[foo]=3', 'Object.toQueryString | namespace | basic number');
+  assertQueryStringGenerated({foo: true}, ['paw'], 'paw[foo]=true', 'Object.toQueryString | namespace | basic boolean');
+  assertQueryStringGenerated({foo: /reg/}, ['paw'], 'paw[foo]=%2Freg%2F', 'Object.toQueryString | namespace | regexp');
+  assertQueryStringGenerated({foo:'a b'}, ['paw'], 'paw[foo]=a+b', 'Object.toQueryString | namespace | should escape string');
+  assertQueryStringGenerated({foo: date}, ['paw'], 'paw[foo]=' + date.getTime(), 'Object.toQueryString | namespace | should stringify date');
+  assertQueryStringGenerated({foo:['a','b','c']}, ['paw'], 'paw[foo][0]=a&paw[foo][1]=b&paw[foo][2]=c', 'Object.toQueryString | namespace | basic array');
+  assertQueryStringGenerated({foo:{bar:'tee',car:'hee'}}, ['paw'], 'paw[foo][bar]=tee&paw[foo][car]=hee', 'Object.toQueryString | namespace | basic object');
+
+  assertQueryStringGenerated({foo:undefined}, ['paw'], 'paw[foo]=', 'Object.toQueryString | namespace | undefined');
+  assertQueryStringGenerated({foo:false}, ['paw'], 'paw[foo]=false', 'Object.toQueryString | namespace | false');
+  assertQueryStringGenerated({foo:null}, ['paw'], 'paw[foo]=', 'Object.toQueryString | namespace | null');
+  assertQueryStringGenerated({foo:NaN}, ['paw'], 'paw[foo]=', 'Object.toQueryString | namespace | NaN');
+  assertQueryStringGenerated({foo:''}, ['paw'], 'paw[foo]=', 'Object.toQueryString | namespace | empty string');
+  assertQueryStringGenerated({foo:0}, ['paw'], 'paw[foo]=0', 'Object.toQueryString | namespace | 0');
+  assertQueryStringGenerated({foo:[['fap','cap']]}, ['paw'], 'paw[foo][0][0]=fap&paw[foo][0][1]=cap', 'Object.toQueryString | namespace | array double nested');
+  assertQueryStringGenerated({foo:[['fap'],['cap']]}, ['paw'], 'paw[foo][0][0]=fap&paw[foo][1][0]=cap', 'Object.toQueryString | namespace | array horizonal nested');
+  assertQueryStringGenerated({foo:{bar:{map:'fap'}}}, ['paw'], 'paw[foo][bar][map]=fap', 'Object.toQueryString | namespace | object double nested');
+
+  assertQueryStringGenerated({'hello there': 'bar'}, [], 'hello+there=bar', 'Object.toQueryString | spaces in key');
+  assertQueryStringGenerated({'"/+': 'bar'}, [], '%22%2F%2B=bar', 'Object.toQueryString | key requires encoding');
+  assertQueryStringGenerated({'ๆ™‚ๅˆป': 'bar'}, [], '%E6%99%82%E5%88%BB=bar', 'Object.toQueryString | Japanese key');
+  assertQueryStringGenerated({'%20': 'bar'}, [], '%2520=bar', 'Object.toQueryString | %20');
+
+  assertQueryStringGenerated(['a','b','c'], [], '0=a&1=b&2=c', 'Object.toQueryString | straight array no namespace');
+  assertQueryStringGenerated(8, [], '', 'Object.toQueryString | straight number no namespace');
+  assertQueryStringGenerated(date, [], '', 'Object.toQueryString | straight date no namespace');
+  assertQueryStringGenerated({foo:'bar'}, ['่ฌ'], '%E8%90%AC[foo]=bar', 'Object.toQueryString | Japanese characters in the namespace');
+  equal(Object.toQueryString('foo'), '', 'Object.toQueryString | straight string no namespace');
+
+  var obj = {
+    toString: function() {
+      return 'hardyhar';
+    }
+  }
+
+  assertQueryStringGenerated({foo: obj}, [], 'foo=hardyhar', 'Object.toQueryString | toString object member');
+
+  var Foo = function() {};
+  Foo.prototype.toString = function() {
+    return 'custom';
+  }
+
+  assertQueryStringGenerated({foo: new Foo}, [], 'foo=custom', 'Object.toQueryString | toString inherited method');
+
+
+  // Issue #365 Object.merge can skip when source is object and target is not.
+
+  equal(Object.merge({a:''}, {a:{b:1}}, true), {a:{b:1}}, 'Object.merge | source object wins with empty string');
+  equal(Object.merge({a:'1'}, {a:{b:1}}, true), {a:{b:1}}, 'Object.merge | source object wins with number as string');
+
+});
level2/node_modules/sugar/test/environments/sugar/regexp.js
@@ -0,0 +1,66 @@
+test('RegExp', function () {
+
+  var r, n;
+
+
+  function flagsEqual(actual, expected, message) {
+    var actualSorted   = actual.split('').sort().join('');
+    var expectedSorted = actual.split('').sort().join('');
+    equal(actualSorted, expectedSorted, message);
+  }
+
+  equal(RegExp.escape('test regexp'), 'test regexp', 'RegExp#escape');
+  equal(RegExp.escape('test reg|exp'), 'test reg\\|exp', 'RegExp#escape');
+  equal(RegExp.escape('hey there (budday)'), 'hey there \\(budday\\)', 'RegExp#escape');
+  equal(RegExp.escape('what a day...'), 'what a day\\.\\.\\.', 'RegExp#escape');
+  equal(RegExp.escape('.'), '\\.', 'RegExp#escape');
+  equal(RegExp.escape('*.+[]{}()?|/\\'), '\\*\\.\\+\\[\\]\\{\\}\\(\\)\\?\\|\\/\\\\', 'RegExp#escape');
+  equal(RegExp.escape('?'), '\\?', 'RegExp#escape | ?');
+  equal(RegExp.escape('\?'), '\\?', 'RegExp#escape | one slash and ?');
+  equal(RegExp.escape('\\?'), '\\\\\\?', 'RegExp#escape | two slashes and ?');
+  equal(RegExp.escape('\\?'), '\\\\\\?', 'RegExp#escape | two slashes and ?');
+
+  r = /foobar/;
+  n = r.setFlags('gim');
+
+  equal(n.global, true, 'RegExp#setFlags');
+  equal(n.ignoreCase, true, 'RegExp#setFlags');
+  equal(n.multiline, true, 'RegExp#setFlags');
+
+  equal(r.global, false, 'RegExp#setFlags | initial regex is untouched');
+  equal(r.ignoreCase, false, 'RegExp#setFlags | initial regex is untouched');
+  equal(r.multiline, false, 'RegExp#setFlags | initial regex is untouched');
+
+  n = r.addFlag('g');
+
+  equal(n.global, true, 'RegExp#addFlag');
+  equal(n.ignoreCase, false, 'RegExp#addFlag');
+  equal(n.multiline, false, 'RegExp#addFlag');
+
+  equal(r.global, false, 'RegExp#addFlag | initial regex is untouched');
+  equal(r.ignoreCase, false, 'RegExp#addFlag | initial regex is untouched');
+  equal(r.multiline, false, 'RegExp#addFlag | initial regex is untouched');
+
+  equal(/foobar/gim.addFlag('d').getFlags().length, 3, 'RegExp#addFlag | unknown flag is ignored');
+
+  r = /foobar/gim;
+  n = r.removeFlag('g');
+
+  equal(n.global, false, 'RegExp#removeFlag | global');
+  equal(n.ignoreCase, true, 'RegExp#removeFlag | ignoreCase');
+  equal(n.multiline, true, 'RegExp#removeFlag | multiline');
+
+  equal(r.global, true, 'RegExp#removeFlag | initial regex is untouched | global');
+  equal(r.ignoreCase, true, 'RegExp#removeFlag | initial regex is untouched | ignoreCase');
+  equal(r.multiline, true, 'RegExp#removeFlag | initial regex is untouched | multiline');
+
+
+  // RegExp#getFlags
+
+  flagsEqual(/foobar/gim.getFlags(), 'gim', 'RegExp#getFlags | gim');
+  flagsEqual(/foobar/im.getFlags(), 'im', 'RegExp#getFlags | gi');
+  flagsEqual(/foobar/i.getFlags(), 'i', 'RegExp#getFlags | i');
+  flagsEqual(/foobar/.getFlags(), '', 'RegExp#getFlags | none');
+
+});
+
level2/node_modules/sugar/test/environments/sugar/string.js
@@ -0,0 +1,1141 @@
+test('String', function () {
+
+  equal('test regexp'.escapeRegExp(), 'test regexp', 'String#escapeRegExp | nothing to escape');
+  equal('test reg|exp'.escapeRegExp(), 'test reg\\|exp', 'String#escapeRegExp | should escape pipe');
+  equal('hey there (budday)'.escapeRegExp(), 'hey there \\(budday\\)', 'String#escapeRegExp | should escape parentheses');
+  equal('.'.escapeRegExp(), '\\.', 'String#escapeRegExp | should escape period');
+  equal('what a day...'.escapeRegExp(), 'what a day\\.\\.\\.', 'String#escapeRegExp | should escape many period');
+  equal('*.+[]{}()?|/'.escapeRegExp(), '\\*\\.\\+\\[\\]\\{\\}\\(\\)\\?\\|\\/', 'String#escapeRegExp | complex regex tokens');
+
+  /* Leaving these tests but this method seems all but totally useless
+   equal('test regexp'.unescapeRegExp(), 'test regexp', 'String#unescapeRegExp | nothing to unescape');
+   equal('test reg\\|exp'.unescapeRegExp(), 'test reg|exp', 'String#unescapeRegExp | should unescape pipe');
+   equal('hey there \\(budday\\)'.unescapeRegExp(), 'hey there (budday)', 'String#unescapeRegExp | should unescape parentheses');
+   equal('\\.'.unescapeRegExp(), '.', 'String#unescapeRegExp | should unescape period');
+   equal('what a day\\.\\.\\.'.unescapeRegExp(), 'what a day...', 'String#unescapeRegExp | should unescape many period');
+   equal('\\*\\.\\+\\[\\]\\{\\}\\(\\)\\?\\|\\/'.unescapeRegExp(), '*.+[]{}()?|/', 'String#unescapeRegExp | complex regex tokens');
+   */
+
+
+  equal('what a day...'.escapeURL(), 'what%20a%20day...', 'String#escapeURL | ...');
+  equal('/?:@&=+$#'.escapeURL(), '/?:@&=+$#', 'String#escapeURL | url chars');
+  equal('!%^*()[]{}\\:'.escapeURL(), '!%25%5E*()%5B%5D%7B%7D%5C:', 'String#escapeURL | non url special chars');
+  equal('http://www.amazon.com/Kindle-Special-Offers-Wireless-Reader/dp/B004HFS6Z0/ref=amb_link_356652042_2?pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-1&pf_rd_r=1RKN5V41WJ23AXKFSQ56&pf_rd_t=101&pf_rd_p=1306249942&pf_rd_i=507846'.escapeURL(), 'http://www.amazon.com/Kindle-Special-Offers-Wireless-Reader/dp/B004HFS6Z0/ref=amb_link_356652042_2?pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-1&pf_rd_r=1RKN5V41WJ23AXKFSQ56&pf_rd_t=101&pf_rd_p=1306249942&pf_rd_i=507846', 'String#escapeURL | amazon link');
+  equal('http://twitter.com/#!/nov/status/85613699410296833'.escapeURL(), 'http://twitter.com/#!/nov/status/85613699410296833', 'String#escapeURL | twitter link');
+  equal('http://cgi.ebay.com/T-Shirt-Tee-NEW-Naruto-Shippuuden-Kakashi-Adult-Men-XL-/350233503515?_trksid=p5197.m263&_trkparms=algo=SIC&itu=UCI%2BIA%2BUA%2BFICS%2 fBUFI%2BDDSIC&otn=10&pmod=260625794431%2B370476659389&po=LVI&ps=63&clkid=962675460977455716#ht_3216wt_1141'.escapeURL(), 'http://cgi.ebay.com/T-Shirt-Tee-NEW-Naruto-Shippuuden-Kakashi-Adult-Men-XL-/350233503515?_trksid=p5197.m263&_trkparms=algo=SIC&itu=UCI%252BIA%252BUA%252BFICS%252%20fBUFI%252BDDSIC&otn=10&pmod=260625794431%252B370476659389&po=LVI&ps=63&clkid=962675460977455716#ht_3216wt_1141', 'String#escapeURL | ebay link');
+
+
+  equal('what a day...'.escapeURL(true), 'what%20a%20day...', 'String#escapeURL | full | ...');
+  equal('/?:@&=+$#'.escapeURL(true), '%2F%3F%3A%40%26%3D%2B%24%23', 'String#escapeURL | full | url chars');
+  equal('!%^*()[]{}\\:'.escapeURL(true), '!%25%5E*()%5B%5D%7B%7D%5C%3A', 'String#escapeURL | full | non url special chars');
+  equal('http://www.amazon.com/Kindle-Special-Offers-Wireless-Reader/dp/B004HFS6Z0/ref=amb_link_356652042_2?pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-1&pf_rd_r=1RKN5V41WJ23AXKFSQ56&pf_rd_t=101&pf_rd_p=1306249942&pf_rd_i=507846'.escapeURL(true), 'http%3A%2F%2Fwww.amazon.com%2FKindle-Special-Offers-Wireless-Reader%2Fdp%2FB004HFS6Z0%2Fref%3Damb_link_356652042_2%3Fpf_rd_m%3DATVPDKIKX0DER%26pf_rd_s%3Dcenter-1%26pf_rd_r%3D1RKN5V41WJ23AXKFSQ56%26pf_rd_t%3D101%26pf_rd_p%3D1306249942%26pf_rd_i%3D507846', 'String#escapeURL | full | amazon link');
+  equal('http://cgi.ebay.com/T-Shirt-Tee-NEW-Naruto-Shippuuden-Kakashi-Adult-Men-XL-/350233503515?_trksid=p5197.m263&_trkparms=algo=SIC&itu=UCI%2BIA%2BUA%2BFICS%2 fBUFI%2BDDSIC&otn=10&pmod=260625794431%2B370476659389&po=LVI&ps=63&clkid=962675460977455716#ht_3216wt_1141'.escapeURL(true), 'http%3A%2F%2Fcgi.ebay.com%2FT-Shirt-Tee-NEW-Naruto-Shippuuden-Kakashi-Adult-Men-XL-%2F350233503515%3F_trksid%3Dp5197.m263%26_trkparms%3Dalgo%3DSIC%26itu%3DUCI%252BIA%252BUA%252BFICS%252%20fBUFI%252BDDSIC%26otn%3D10%26pmod%3D260625794431%252B370476659389%26po%3DLVI%26ps%3D63%26clkid%3D962675460977455716%23ht_3216wt_1141', 'String#escapeURL | full | ebay link');
+
+  equal('what%20a%20day...'.unescapeURL(), 'what a day...', 'String#unescapeURL | full | ...');
+  equal('%2F%3F%3A%40%26%3D%2B%24%23'.unescapeURL(), '/?:@&=+$#', 'String#unescapeURL | full | url chars');
+  equal('!%25%5E*()%5B%5D%7B%7D%5C%3A'.unescapeURL(), '!%^*()[]{}\\:', 'String#unescapeURL | full | non url special chars');
+  equal('http%3A%2F%2Fsomedomain.com%3Fparam%3D%22this%3A%20isn\'t%20an%20easy%20URL%20to%20escape%22'.unescapeURL(), 'http://somedomain.com?param="this: isn\'t an easy URL to escape"', 'String#unescapeURL | full | fake url')
+  equal('http%3A%2F%2Fwww.amazon.com%2FKindle-Special-Offers-Wireless-Reader%2Fdp%2FB004HFS6Z0%2Fref%3Damb_link_356652042_2%3Fpf_rd_m%3DATVPDKIKX0DER%26pf_rd_s%3Dcenter-1%26pf_rd_r%3D1RKN5V41WJ23AXKFSQ56%26pf_rd_t%3D101%26pf_rd_p%3D1306249942%26pf_rd_i%3D507846'.unescapeURL(), 'http://www.amazon.com/Kindle-Special-Offers-Wireless-Reader/dp/B004HFS6Z0/ref=amb_link_356652042_2?pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-1&pf_rd_r=1RKN5V41WJ23AXKFSQ56&pf_rd_t=101&pf_rd_p=1306249942&pf_rd_i=507846', 'String#unescapeURL | full | amazon link');
+  equal('http://cgi.ebay.com/T-Shirt-Tee-NEW-Naruto-Shippuuden-Kakashi-Adult-Men-XL-/350233503515?_trksid=p5197.m263&_trkparms=algo%3DSIC%26itu%3DUCI%252BIA%252BUA%252BFICS%252BUFI%252BDDSIC%26otn%3D10%26pmod%3D260625794431%252B370476659389%26po%3DLVI%26ps%3D63%26clkid%3D962675460977455716'.unescapeURL(), 'http://cgi.ebay.com/T-Shirt-Tee-NEW-Naruto-Shippuuden-Kakashi-Adult-Men-XL-/350233503515?_trksid=p5197.m263&_trkparms=algo=SIC&itu=UCI%2BIA%2BUA%2BFICS%2BUFI%2BDDSIC&otn=10&pmod=260625794431%2B370476659389&po=LVI&ps=63&clkid=962675460977455716', 'String#unescapeURL | full | ebay link');
+
+
+  equal('what%20a%20day...'.unescapeURL(true), 'what a day...', 'String#unescapeURL | ...');
+  equal('%2F%3F%3A%40%26%3D%2B%24%23'.unescapeURL(true), '%2F%3F%3A%40%26%3D%2B%24%23', 'String#unescapeURL | url chars');
+  equal('!%25%5E*()%5B%5D%7B%7D%5C:'.unescapeURL(true), '!%^*()[]{}\\:', 'String#unescapeURL | non url special chars');
+  equal('http%3A%2F%2Fsomedomain.com%3Fparam%3D%22this%3A%20isn\'t%20an%20easy%20URL%20to%20escape%22'.unescapeURL(true), 'http%3A%2F%2Fsomedomain.com%3Fparam%3D"this%3A isn\'t an easy URL to escape"', 'String#unescapeURL | fake url')
+  equal('http%3A%2F%2Fwww.amazon.com%2FKindle-Special-Offers-Wireless-Reader%2Fdp%2FB004HFS6Z0%2Fref%3Damb_link_356652042_2%3Fpf_rd_m%3DATVPDKIKX0DER%26pf_rd_s%3Dcenter-1%26pf_rd_r%3D1RKN5V41WJ23AXKFSQ56%26pf_rd_t%3D101%26pf_rd_p%3D1306249942%26pf_rd_i%3D507846'.unescapeURL(true), 'http%3A%2F%2Fwww.amazon.com%2FKindle-Special-Offers-Wireless-Reader%2Fdp%2FB004HFS6Z0%2Fref%3Damb_link_356652042_2%3Fpf_rd_m%3DATVPDKIKX0DER%26pf_rd_s%3Dcenter-1%26pf_rd_r%3D1RKN5V41WJ23AXKFSQ56%26pf_rd_t%3D101%26pf_rd_p%3D1306249942%26pf_rd_i%3D507846', 'String#unescapeURL | amazon link');
+  equal('http://twitter.com/#!/nov/status/85613699410296833'.unescapeURL(true), 'http://twitter.com/#!/nov/status/85613699410296833', 'String#unescapeURL | twitter link');
+  equal('http://cgi.ebay.com/T-Shirt-Tee-NEW-Naruto-Shippuuden-Kakashi-Adult-Men-XL-/350233503515?_trksid=p5197.m263&_trkparms=algo=SIC&itu=UCI%2BIA%2BUA%2BFICS%2fBUFI%2BDDSIC&otn=10&pmod=260625794431%2B370476659389&po=LVI&ps=63&clkid=962675460977455716#ht_3216wt_1141'.unescapeURL(true), 'http://cgi.ebay.com/T-Shirt-Tee-NEW-Naruto-Shippuuden-Kakashi-Adult-Men-XL-/350233503515?_trksid=p5197.m263&_trkparms=algo=SIC&itu=UCI%2BIA%2BUA%2BFICS%2fBUFI%2BDDSIC&otn=10&pmod=260625794431%2B370476659389&po=LVI&ps=63&clkid=962675460977455716#ht_3216wt_1141', 'String#unescapeURL | ebay link');
+
+
+
+  raisesError(function() { '% 23'.unescapeURL(); }, 'String#unescapeURL | partial | should raise an error for malformed urls');
+  raisesError(function() { '% 23'.unescapeURL(true); }, 'String#unescapeURL | full | should raise an error for malformed urls');
+
+
+
+
+  equal('<p>some text</p>'.escapeHTML(), '&lt;p&gt;some text&lt;&#x2f;p&gt;', 'String#escapeHTML | <p>some text</p>');
+  equal('war & peace & food'.escapeHTML(), 'war &amp; peace &amp; food', 'String#escapeHTML | war & peace');
+  equal('&lt;span&gt;already escaped, yo&lt;/span&gt;', '&lt;span&gt;already escaped, yo&lt;/span&gt;', 'String#escapeHTML | already escaped will stay escaped');
+
+  equal("hell's".escapeHTML(), 'hell&apos;s', "String#escapeHTML | works on '");
+  equal('I know that "feel" bro'.escapeHTML(), 'I know that &quot;feel&quot; bro', 'String#escapeHTML | works on "');
+  equal('feel the /'.escapeHTML(), 'feel the &#x2f;', 'String#escapeHTML | works on /');
+
+  equal('&lt;p&gt;some text&lt;/p&gt;'.unescapeHTML(), '<p>some text</p>', 'String#unescapeHTML | <p>some text</p>');
+  equal('war &amp; peace &amp; food'.unescapeHTML(), 'war & peace & food', 'String#unescapeHTML | war & peace');
+  equal('<span>already escaped, yo</span>', '<span>already escaped, yo</span>', 'String#escapeHTML | already escaped will stay escaped');
+
+  equal('hell&apos;s'.unescapeHTML(), "hell's", "String#unescapeHTML | works on '");
+  equal('I know that &quot;feel&quot; bro'.unescapeHTML(), 'I know that "feel" bro', 'String#unescapeHTML | works on "');
+  equal('feel the &#x2f;'.unescapeHTML(), 'feel the /', 'String#unescapeHTML | works on /');
+
+  equal('&gt;'.escapeHTML().unescapeHTML(), '&gt;', 'String#unescapeHTML | is the reverse of escapeHTML');
+  equal('&amp;lt;'.unescapeHTML(), '&lt;', 'String#unescapeHTML | unescapes a single level of HTML escaping');
+
+  equal('This webpage is not available'.encodeBase64(), 'VGhpcyB3ZWJwYWdlIGlzIG5vdCBhdmFpbGFibGU=', 'String#encodeBase64 | webpage');
+  equal('I grow, I prosper; Now, gods, stand up for bastards!'.encodeBase64(), 'SSBncm93LCBJIHByb3NwZXI7IE5vdywgZ29kcywgc3RhbmQgdXAgZm9yIGJhc3RhcmRzIQ==', 'String#encodeBase64 | gods');
+  equal('rรคksmรถrgรฅs'.encodeBase64(), 'csOka3Ntw7ZyZ8Olcw==', 'String#encodeBase64 | shrimp sandwich');
+  equal('rรƒยคksmรƒยถrgรƒยฅs'.encodeBase64(), 'csODwqRrc23Dg8K2cmfDg8Klcw==', 'String#encodeBase64 | shrimp sandwich encoded');
+
+
+  equal('ะะ‘ะ’'.encodeBase64(), '0JDQkdCS', 'String#encodeBase64 | Russian');
+  equal('ๆ—ฅๆœฌ่ชž'.encodeBase64(), '5pel5pys6Kqe', 'String#encodeBase64 | Japanese');
+  equal('ใซใปใ‚“ใ”'.encodeBase64(), '44Gr44G744KT44GU', 'String#encodeBase64 | Hiragana');
+  equal('ํ•œ๊ตญ์–ด'.encodeBase64(), '7ZWc6rWt7Ja0', 'String#encodeBase64 | Korean');
+
+  equal('ะะ‘ะ’'.encodeBase64().decodeBase64(), 'ะะ‘ะ’', 'String#encodeBase64 | reverse | Russian');
+  equal('ๆ—ฅๆœฌ่ชž'.encodeBase64().decodeBase64(), 'ๆ—ฅๆœฌ่ชž', 'String#encodeBase64 | reverse | Japanese');
+  equal('ใซใปใ‚“ใ”'.encodeBase64().decodeBase64(), 'ใซใปใ‚“ใ”', 'String#encodeBase64 | reverse | Hiragana');
+  equal('ํ•œ๊ตญ์–ด'.encodeBase64().decodeBase64(), 'ํ•œ๊ตญ์–ด', 'String#encodeBase64 | reverse | Korean');
+
+
+  // Ensure that btoa and atob don't leak in node
+  if(environment == 'node') {
+    equal(typeof btoa, 'undefined', 'btoa global does not exist in node');
+    equal(typeof atob, 'undefined', 'atob global does not exist in node');
+  }
+
+  equal('L2hvd2FyZHNmaXJld29ya3MvYXBpL29yZGVyLzc1TU0lMjBNSVg='.decodeBase64(), '/howardsfireworks/api/order/75MM%20MIX', 'String#decodeBase64 | %20')
+
+  equal('VGhpcyB3ZWJwYWdlIGlzIG5vdCBhdmFpbGFibGU='.decodeBase64(), 'This webpage is not available', 'String#decodeBase64 | webpage');
+  equal('SSBncm93LCBJIHByb3NwZXI7IE5vdywgZ29kcywgc3RhbmQgdXAgZm9yIGJhc3RhcmRzIQ=='.decodeBase64(), 'I grow, I prosper; Now, gods, stand up for bastards!', 'String#decodeBase64 | gods');
+
+  raisesError(function() { '@#$^#$^#@$^'.decodeBase64(); }, 'String#decodeBase64 | should throw an error on non-base64 chars');
+
+  var test;
+
+  equal('reuben sandwich'.capitalize(), 'Reuben sandwich', 'String#capitalize | should capitalize first letter of first word only.', { mootools: 'Reuben Sandwich' });
+  equal('Reuben sandwich'.capitalize(), 'Reuben sandwich', 'String#capitalize | should leave the string alone', { mootools: 'Reuben Sandwich' });
+  equal('REUBEN SANDWICH'.capitalize(), 'Reuben sandwich', 'String#capitalize | should uncapitalize all other letters', { mootools: 'REUBEN SANDWICH' });
+  equal('ั„ั‹ะฒะฐ ะนั†ัƒะบ'.capitalize(), 'ะคั‹ะฒะฐ ะนั†ัƒะบ', 'String#capitalize | should capitalize unicode letters', { mootools: 'ะคั‹ะฒะฐ ะนั†ัƒะบ' });
+
+  equal('reuben sandwich'.capitalize(true), 'Reuben Sandwich', 'String#capitalize | all | should capitalize all first letters', { prototype: 'Reuben sandwich' });
+  equal('Reuben sandwich'.capitalize(true), 'Reuben Sandwich', 'String#capitalize | all | should capitalize the second letter only', { prototype: 'Reuben sandwich' });
+  equal('REUBEN SANDWICH'.capitalize(true), 'Reuben Sandwich', 'String#capitalize | all | should uncapitalize all other letters', { prototype: 'Reuben sandwich', mootools: 'REUBEN SANDWICH' });
+  equal('ั„ั‹ะฒะฐ ะนั†ัƒะบ'.capitalize(true), 'ะคั‹ะฒะฐ ะ™ั†ัƒะบ', 'String#capitalize | all | should capitalize unicode letters', { prototype: 'ะคั‹ะฒะฐ ะนั†ัƒะบ' });
+  equal('what a shame of a title'.capitalize(true), 'What A Shame Of A Title', 'String#capitalize | all | all lower-case', { prototype: 'What a shame of a title' });
+  equal('What A Shame Of A Title'.capitalize(true), 'What A Shame Of A Title', 'String#capitalize | all | already capitalized', { prototype: 'What a shame of a title' });
+  equal(' what a shame of a title    '.capitalize(true), ' What A Shame Of A Title    ', 'String#capitalize | all | preserves whitespace', { prototype: ' what a shame of a title    ' });
+  equal(' what a shame of\n a title    '.capitalize(true), ' What A Shame Of\n A Title    ', 'String#capitalize | all | preserves new lines', { prototype: ' what a shame of\n a title    ' });
+
+  equal('reuben-sandwich'.capitalize(true), 'Reuben-Sandwich', 'String#capitalize | all | hyphen');
+  equal('reuben,sandwich'.capitalize(true), 'Reuben,Sandwich', 'String#capitalize | all | comma');
+  equal('reuben;sandwich'.capitalize(true), 'Reuben;Sandwich', 'String#capitalize | all | semicolon');
+  equal('reuben.sandwich'.capitalize(true), 'Reuben.Sandwich', 'String#capitalize | all | period');
+  equal('reuben_sandwich'.capitalize(true), 'Reuben_Sandwich', 'String#capitalize | all | underscore');
+  equal('reuben\nsandwich'.capitalize(true), 'Reuben\nSandwich', 'String#capitalize | all | new line');
+  equal("reuben's sandwich".capitalize(true), "Reuben's Sandwich", 'String#capitalize | all | apostrophe should not trigger capitalize');
+  equal('ั„ั‹ะฒะฐ-ะนั†ัƒะบ'.capitalize(true), 'ะคั‹ะฒะฐ-ะ™ั†ัƒะบ', 'String#capitalize | all | Russian with hyphens');
+  equal('ั„ั‹ะฒะฐ,ะนั†ัƒะบ'.capitalize(true), 'ะคั‹ะฒะฐ,ะ™ั†ัƒะบ', 'String#capitalize | all | Russian with comma');
+  equal('ั„ั‹ะฒะฐ;ะนั†ัƒะบ'.capitalize(true), 'ะคั‹ะฒะฐ;ะ™ั†ัƒะบ', 'String#capitalize | all | Russian with semicolon');
+  equal('ั„ั‹ะฒะฐ7ะนั†ัƒะบ'.capitalize(true), 'ะคั‹ะฒะฐ7ะ™ั†ัƒะบ', 'String#capitalize | all | Russian with 7');
+
+  equal('wasabi'.chars(), ['w','a','s','a','b','i'], 'String#chars | splits string into constituent chars');
+  equal(' wasabi \n'.chars(), [' ','w','a','s','a','b','i',' ','\n'], 'String#chars | should not trim whitespace');
+
+  equal('   wasabi   '.trim(), 'wasabi', 'String#trim | should trim both left and right whitespace');
+  equal('   wasabi   '.trimLeft(), 'wasabi   ', 'String#trim | should trim left whitespace only');
+  equal('   wasabi   '.trimRight(), '   wasabi', 'String#trim | should trim right whitespace only');
+
+
+  raisesError(function(){ 'wasabi'.pad(-1); }, 'String#pad | -1 raises error');
+  raisesError(function(){ 'wasabi'.pad(Infinity); }, 'String#pad | Infinity raises error');
+
+  equal('wasabi'.pad(), 'wasabi', 'String#pad | no arguments default to 0');
+  equal('wasabi'.pad(undefined), 'wasabi', 'String#pad | undefined defaults to 0');
+  equal('wasabi'.pad(null), 'wasabi', 'String#pad | null defaults to 0');
+  equal('wasabi'.pad(NaN), 'wasabi', 'String#pad | NaN defaults to 0');
+
+  equal('wasabi'.pad(0), 'wasabi', 'String#pad | 0');
+  equal('wasabi'.pad(1), 'wasabi', 'String#pad | 1');
+  equal('wasabi'.pad(2), 'wasabi', 'String#pad | 2');
+  equal('wasabi'.pad(3), 'wasabi', 'String#pad | 3');
+  equal('wasabi'.pad(4), 'wasabi', 'String#pad | 4');
+  equal('wasabi'.pad(5), 'wasabi', 'String#pad | 5');
+  equal('wasabi'.pad(6), 'wasabi', 'String#pad | 6');
+  equal('wasabi'.pad(7), 'wasabi ', 'String#pad | 7');
+  equal('wasabi'.pad(8), ' wasabi ', 'String#pad | 8');
+  equal('wasabi'.pad(9), ' wasabi  ', 'String#pad | 9');
+  equal('wasabi'.pad(10), '  wasabi  ', 'String#pad | 10');
+  equal('wasabi'.pad(12), '   wasabi   ', 'String#pad | 12');
+  equal('wasabi'.pad(20), '       wasabi       ', 'String#pad | 12');
+
+  equal('wasabi'.pad(8, '"'), '"wasabi"', 'String#pad | padding with quotes');
+  equal('wasabi'.pad(8, ''), 'wasabi', 'String#pad | empty string should have no padding');
+  equal('wasabi'.pad(8, 's'), 'swasabis', 'String#pad | padding with s');
+  equal('wasabi'.pad(8, 5), '5wasabi5', 'String#pad | padding with a number');
+  equal('wasabi'.pad(12, '-'), '---wasabi---', 'String#pad | should pad the string with 6 hyphens');
+
+
+  raisesError(function() { 'wasabi'.padLeft(-1) }, 'String#padLeft | -1 raises error');
+  raisesError(function() { 'wasabi'.padLeft(Infinity) }, 'String#padLeft | Infinity raises error');
+
+  equal('wasabi'.padLeft(0), 'wasabi', 'String#padLeft | 0');
+  equal('wasabi'.padLeft(1), 'wasabi', 'String#padLeft | 1');
+  equal('wasabi'.padLeft(2), 'wasabi', 'String#padLeft | 2');
+  equal('wasabi'.padLeft(3), 'wasabi', 'String#padLeft | 3');
+  equal('wasabi'.padLeft(4), 'wasabi', 'String#padLeft | 4');
+  equal('wasabi'.padLeft(5), 'wasabi', 'String#padLeft | 5');
+  equal('wasabi'.padLeft(6), 'wasabi', 'String#padLeft | 6');
+  equal('wasabi'.padLeft(7), ' wasabi', 'String#padLeft | 7');
+  equal('wasabi'.padLeft(8), '  wasabi', 'String#padLeft | 8');
+  equal('wasabi'.padLeft(9), '   wasabi', 'String#padLeft | 9');
+  equal('wasabi'.padLeft(10), '    wasabi', 'String#padLeft | 10');
+  equal('wasabi'.padLeft(12), '      wasabi', 'String#padLeft | 12');
+  equal('wasabi'.padLeft(20), '              wasabi', 'String#padLeft | 20');
+  equal('wasabi'.padLeft(12, '-'), '------wasabi', 'String#padLeft | 12 with hyphens');
+  equal('wasabi'.padLeft(12, '+'), '++++++wasabi', 'String#padLeft | 12 with plusses');
+
+
+  raisesError(function() { 'wasabi'.padRight(-1) }, 'String#padRight | -1 raises error');
+  raisesError(function() { 'wasabi'.padRight(Infinity) }, 'String#padRight | Infinity raises error');
+
+  equal('wasabi'.padRight(0), 'wasabi', 'String#padRight | 0');
+  equal('wasabi'.padRight(1), 'wasabi', 'String#padRight | 1');
+  equal('wasabi'.padRight(2), 'wasabi', 'String#padRight | 2');
+  equal('wasabi'.padRight(3), 'wasabi', 'String#padRight | 3');
+  equal('wasabi'.padRight(4), 'wasabi', 'String#padRight | 4');
+  equal('wasabi'.padRight(5), 'wasabi', 'String#padRight | 5');
+  equal('wasabi'.padRight(6), 'wasabi', 'String#padRight | 6');
+  equal('wasabi'.padRight(7), 'wasabi ', 'String#padRight | 7');
+  equal('wasabi'.padRight(8), 'wasabi  ', 'String#padRight | 8');
+  equal('wasabi'.padRight(9), 'wasabi   ', 'String#padRight | 9');
+  equal('wasabi'.padRight(10), 'wasabi    ', 'String#padRight | 10');
+  equal('wasabi'.padRight(12), 'wasabi      ', 'String#padRight | 12');
+  equal('wasabi'.padRight(20), 'wasabi              ', 'String#padRight | 20');
+  equal('wasabi'.padRight(12, '-'), 'wasabi------', 'String#padRight | 12 with hyphens');
+  equal('wasabi'.padRight(12, '+'), 'wasabi++++++', 'String#padRight | 12 with plusses');
+
+
+
+  equal('wasabi'.repeat(0), '', 'String#repeat | 0 should repeat the string 0 times');
+  equal('wasabi'.repeat(2), 'wasabiwasabi', 'String#repeat | 2 should repeat the string 2 times');
+  equal('wasabi'.repeat(2.5), 'wasabiwasabi', 'String#repeat | 2.5 should floor to 2 times');
+
+
+  equal(String.prototype.repeat.call(true, 3), 'truetruetrue', 'String#repeat | boolean coerced to string');
+  equal(String.prototype.repeat.call({}, 3), '[object Object][object Object][object Object]', 'String#repeat | object coerced to string');
+  equal(String.prototype.repeat.call(1, 3), '111', 'String#repeat | number coerced to string');
+  equal('a'.repeat('3'), 'aaa', 'String#repeat | count should be coerced to number');
+  equal('a'.repeat('a'), '', 'String#repeat | NaN coercions should be 0');
+
+  var undefinedContext = (function(){ return this; }).call(undefined);
+
+  // Can't test this in IE etc where calling with null
+  // context reverts back to the global object.
+  if(undefinedContext === undefined) {
+    raisesError(function(){ String.prototype.repeat.call(undefined) }, 'String#repeat | raises error on undefined');
+    raisesError(function(){ String.prototype.repeat.call(null) }, 'String#repeat | raises error on null');
+  }
+
+  raisesError(function(){ 'a'.repeat(-1) }, 'String#repeat | negative number raises error');
+  raisesError(function(){ 'a'.repeat(Infinity) }, 'String#repeat | Infinity raises error');
+  raisesError(function(){ 'a'.repeat(-Infinity) }, 'String#repeat | -Infinity raises error');
+
+
+  // "each" will return an array of everything that was matched, defaulting to individual characters
+  equal('g'.each(), ['g'], 'String#each | each should return an array of each char');
+
+  // Each without a first parameter assumes "each character"
+  var result = 'g'.each(function(str, i) {
+    equal(str, 'g', 'String#each | char should be passed as the first argument');
+  });
+
+  equal(result, ['g'], "String#each | ['g'] should be the resulting value");
+
+  var counter = 0;
+  result = 'ginger'.each(function(str, i) {
+    equal(str, 'ginger'.charAt(counter), 'String#each | ginger | char should be passed as the first argument');
+    equal(i, counter, 'String#each | ginger | index should be passed as the second argument');
+    counter++;
+  });
+  equal(counter, 6, 'String#each | ginger | should have ran 6 times');
+  equal(result, ['g','i','n','g','e','r'], 'String#each | ginger | resulting array should contain all the characters');
+
+  counter = 0;
+  result = 'ginger'.each('g', function(str, i) {
+    equal(str, 'g', 'String#each | string argument | match should be passed as the first argument to the block');
+    counter++;
+  });
+  equal(counter, 2, 'String#each | string argument | should have ran 2 times');
+  equal(result, ['g','g'], "String#each | string argument | resulting array should be ['g','g']");
+
+  counter = 0;
+  test = ['g','i','g','e'];
+  result = 'ginger'.each(/[a-i]/g, function(str, i) {
+    equal(str, test[i], 'String#each | regexp argument | match should be passed as the first argument to the block');
+    counter++;
+  });
+  equal(counter, 4, 'String#each | regexp argument | should have ran 4 times');
+  equal(result, ['g','i','g','e'], "String#each | regexp argument | resulting array should have been ['g','i','g','e']");
+
+
+  // .each should do the same thing as String#scan in ruby except that .each doesn't respect capturing groups
+  var testString = 'cruel world';
+
+  result = testString.each(/\w+/g);
+  equal(result, ['cruel', 'world'], 'String#each | complex regexp | /\\w+/g');
+
+  result = testString.each(/.../g);
+  equal(result, ['cru', 'el ', 'wor'], 'String#each | complex regexp | /.../');
+
+  result = testString.each(/(..)(..)/g);
+  equal(result, ['crue', 'l wo'], 'String#each | complex regexp | /(..)(..)/');
+
+
+  result = testString.each(/\w+/);
+  equal(result, ['cruel', 'world'], 'String#each non-global regexes should still be global');
+
+
+  // #shift
+
+
+  equal('ใ‚ฏ'.shift(1), 'ใ‚ฐ', 'String#shift | should shift 1 code up');
+  equal('ใ‚ฐ'.shift(-1), 'ใ‚ฏ', 'String#shift | should shift 1 code down');
+  equal('ใƒ˜'.shift(2), 'ใƒš', 'String#shift | should shift 2 codes');
+  equal('ใƒš'.shift(-2), 'ใƒ˜', 'String#shift | should shift -2 codes');
+  equal('ใ‚ฏ'.shift(0), 'ใ‚ฏ', 'String#shift | should shift 0 codes');
+  equal('ใ‚ฏ'.shift(), 'ใ‚ฏ', 'String#shift | no params simply returns the string');
+  equal('ใ‚ซใ‚ญใ‚ฏใ‚ฑใ‚ณ'.shift(1), 'ใ‚ฌใ‚ฎใ‚ฐใ‚ฒใ‚ด', 'String#shift | multiple characters up one');
+  equal('ใ‚ฌใ‚ฎใ‚ฐใ‚ฒใ‚ด'.shift(-1), 'ใ‚ซใ‚ญใ‚ฏใ‚ฑใ‚ณ', 'String#shift | multiple characters down one');
+
+
+
+  // test each char code
+
+  equal('jumpy'.codes(), [106,117,109,112,121], 'String#codes | jumpy');
+
+  counter = 0;
+  test = [103,105,110,103,101,114];
+  result = 'ginger'.codes(function(str, i) {
+    equal(str, test[i], 'String#codes | ginger codes | char code should have been passed into the block');
+    counter++;
+  });
+  equal(counter, 6, 'String#codes | ginger codes | should have ran 6 times');
+  equal(result, test, 'String#codes | ginger codes | result should be an array');
+
+  // test each char
+  counter = 0;
+  result = 'ginger'.chars(function(str, i) {
+    equal(str, 'ginger'.charAt(counter), 'String#chars | ginger | char code should be the first argument in the block');
+    equal(i, counter, 'String#chars | ginger | index should be the second argument in the block');
+    counter++;
+  });
+  equal(counter, 6, 'String#chars | ginger | should have run 6 times');
+  equal(result, ['g','i','n','g','e','r'], 'String#chars | result should be an array');
+
+  // test each char collects when properly returned
+  counter = 0;
+  result = 'ginger'.chars(function(str, i) {
+    counter++;
+    return str.toUpperCase();
+  });
+  equal(result, ['G','I','N','G','E','R'], 'String#chars | ginger | resulting array is properly collected');
+
+  counter = 0;
+  var sentence = 'these pretzels are \n\n making me         thirsty!\n\n';
+  test = ['these', 'pretzels', 'are', 'making', 'me', 'thirsty!'];
+  result = sentence.words(function(str, i) {
+    equal(str, test[i], 'String#words | match is the first argument');
+    counter++;
+  });
+  equal(counter, 6, 'String#words | should have run 6 times');
+  equal(result, test, 'String#words | result should be an array of matches');
+
+  counter = 0;
+  var paragraph = 'these\npretzels\nare\n\nmaking\nme\n         thirsty!\n\n\n\n';
+  test = ['these', 'pretzels', 'are', '', 'making', 'me', '         thirsty!'];
+  result = paragraph.lines(function(str, i) {
+    equal(str, test[i], 'String#lines | match is the first argument');
+    counter++;
+  });
+  equal(counter, 7, 'String#lines | should have run 7 times');
+  equal(result, test, 'String#lines | result should be an array of matches');
+
+  result = 'one\ntwo'.lines(function(str, i) {
+    return str.capitalize();
+  });
+  equal(['One','Two'], result, 'String#lines | lines can be modified');
+
+  counter = 0;
+  var essay = 'the history of the united states\n\n';
+  essay +=    'it all began back in 1776 when someone declared something from someone.\n';
+  essay +=    'it was at this point that we had to get our rears in gear\n\n';
+  essay +=    'The British got their revenge in the late 60s with the British Invasion,\n';
+  essay +=    'which claimed the lives of over 32,352 young women across the nation.\n\n\n\n\n';
+  essay +=    'The End\n\n\n\n\n\n\n';
+  test = ['the history of the united states', 'it all began back in 1776 when someone declared something from someone.\nit was at this point that we had to get our rears in gear', 'The British got their revenge in the late 60s with the British Invasion,\nwhich claimed the lives of over 32,352 young women across the nation.', 'The End'];
+  result = essay.paragraphs(function(str, i) {
+    equal(str, test[i], 'String#paragraphs | match is the first argument');
+    counter ++;
+  });
+  equal(counter, 4, 'String#paragraphs | should have run 4 times');
+  equal(result, test, 'String#paragraphs | result should be an array of matches');
+
+
+  equal(''.codes(), [], 'String#codes | empty string');
+  equal(''.chars(), [], 'String#chars | empty string');
+  equal(''.words(), [], 'String#words | empty string');
+  equal(''.lines(), [''], 'String#lines | empty string');
+  equal(''.paragraphs(), [''], 'String#paragraphs | empty string');
+  equal(''.each('f'), [], 'String#each | empty string | each f');
+  equal(''.each(/foo/), [], 'String#each | empty string | each /foo/');
+  equal(''.each(function() {}), [], 'String#each | empty string | passing a block');
+
+
+
+
+  // startsWith/endsWith is defined in Harmony, so only passing a regex or
+  // 3 arguments will actually test this code.
+
+  equal('hello'.startsWith('hell', 0, true), true, 'String#startsWith | hello starts with hell');
+  equal('HELLO'.startsWith('HELL', 0, true), true, 'String#startsWith | HELLO starts with HELL');
+  equal('HELLO'.startsWith('hell', 0, true), false, 'String#startsWith | HELLO starts with hell');
+  equal('HELLO'.startsWith('hell', 0, true), false, 'String#startsWith | case sensitive | HELLO starts with hell');
+  equal('hello'.startsWith(/hell/, 0, true), true, 'String#startsWith | accepts regex', { prototype: false });
+  equal('hello'.startsWith(/[a-h]/, 0, true), true, 'String#startsWith | accepts regex alternates', { prototype: false });
+  equal('HELLO'.startsWith('hell', 0, false), true, 'String#startsWith | case insensitive | HELLO starts with hell', { prototype: false });
+  equal('HELLO'.startsWith(), false, 'String#startsWith | undefined produces false');
+  equal('hello'.startsWith('hell', -20, true), true, 'String#startsWith | from pos -20');
+  equal('hello'.startsWith('hell', 1, true), false, 'String#startsWith | from pos 1');
+  equal('hello'.startsWith('hell', 2, true), false, 'String#startsWith | from pos 2');
+  equal('hello'.startsWith('hell', 3, true), false, 'String#startsWith | from pos 3');
+  equal('hello'.startsWith('hell', 4, true), false, 'String#startsWith | from pos 4');
+  equal('hello'.startsWith('hell', 5, true), false, 'String#startsWith | from pos 5');
+  equal('hello'.startsWith('hell', 20, true), false, 'String#startsWith | from pos 20');
+  equal('10'.startsWith(10), true, 'String#startsWith | Numbers will be converted');
+  equal('valley girls\nrock'.startsWith('valley girls', 0, true), true, 'String#startsWith | valley girls rock starts with valley girls');
+  equal('valley girls\nrock'.startsWith('valley girls r', 0, true), false, 'String#startsWith | valley girls rock starts with valley girls r');
+
+
+  equal('vader'.endsWith('der', 5, true), true, 'String#endsWith | vader ends with der');
+  equal('VADER'.endsWith('DER', 5, true), true, 'String#endsWith | VADER ends with DER');
+  equal('VADER'.endsWith('der', 5, true), false, 'String#endsWith | VADER ends with der');
+  equal('VADER'.endsWith('DER', 5, false), true, 'String#endsWith | case insensitive | VADER ends with DER');
+  equal('vader'.endsWith(/der/, 5, true), true, 'String#endsWith | accepts regex', { prototype: false });
+  equal('vader'.endsWith(/[q-z]/, 5, true), true, 'String#endsWith | accepts regex alternates', { prototype: false });
+  equal('VADER'.endsWith('der', 5, false), true, 'String#endsWith | case insensitive |  VADER ends with der', { prototype: false });
+  equal('VADER'.endsWith('DER', 5, true), true, 'String#endsWith | case sensitive | VADER ends with DER');
+  equal('VADER'.endsWith('der', 5, true), false, 'String#endsWith | case sensitive |  VADER ends with der');
+  equal('vader'.endsWith('der', -20, true), false, 'String#startsWith | from pos -20');
+  equal('vader'.endsWith('der', 0, true), false, 'String#startsWith | from pos 0');
+  equal('vader'.endsWith('der', 1, true), false, 'String#startsWith | from pos 1');
+  equal('vader'.endsWith('der', 2, true), false, 'String#startsWith | from pos 2');
+  equal('vader'.endsWith('der', 3, true), false, 'String#startsWith | from pos 3');
+  equal('vader'.endsWith('der', 4, true), false, 'String#startsWith | from pos 4');
+  equal('vader'.endsWith('der', 20, true), true, 'String#startsWith | from pos 20');
+  equal('HELLO'.endsWith(), false, 'String#endsWith | undefined produces false');
+  equal('10'.endsWith(10), true, 'String#endsWith | Numbers will be converted');
+  equal('i aint your\nfather'.endsWith('father', 18, true), true, 'String#endsWith | vader ends with der');
+  equal('i aint your\nfather'.endsWith('r father', 18, false), false, 'String#endsWith | vader ends with der');
+
+
+  equal(''.isBlank(), true, 'String#blank | blank string');
+  equal('0'.isBlank(), false, 'String#blank | 0');
+  equal('            '.isBlank(), true, 'String#blank | successive blanks');
+  equal('\n'.isBlank(), true, 'String#blank | new line');
+  equal('\t\t\t\t'.isBlank(), true, 'String#blank | tabs');
+  equal('ๆ—ฅๆœฌ่ชžใงใฏใ€€ใ€Œใƒžใ‚นใ€ใ€€ใจใ„ใ†ใฎ็ŸฅใฃใฆใŸ๏ผŸ'.isBlank(), false, 'String#blank | japanese');
+  equal('mayonnaise'.isBlank(), false, 'String#blank | mayonnaise');
+
+
+  equal('foo'.has('f'), true, 'String#has | foo has f');
+  equal('foo'.has('oo'), true, 'String#has | foo has oo');
+  equal('foo'.has(/f/), true, 'String#has | foo has /f/');
+  equal('foo'.has(/[a-g]/), true, 'String#has | foo has /[a-g]/');
+  equal('foo'.has(/[p-z]/), false, 'String#has | foo has /[p-z]/');
+  equal('flu?ffy'.has('?'), true, 'String#has | flu?ffy has ?');
+  equal('flu?ffy'.has('\?'), true, 'String#has | flu?ffy has one slash and ?');
+  equal('flu?ffy'.has('\\?'), false, 'String#has | flu?ffy has two slashes and ?');
+  equal('flu?ffy'.has('\\\?'), false, 'String#has | flu?ffy has three slashes and ?');
+  equal('flu?ffy'.has(/\?/), true, 'String#has | flu?ffy has one slash and ? in a regex');
+  equal('flu?ffy'.has(/\\?/), true, 'String#has | flu?ffy has two slashes and ? in a regex');
+  equal('flu?ffy'.has(/\\\?/), false, 'String#has | flu?ffy has three slashes and ? in a regex');
+  equal('flu\\?ffy'.has(/\\\?/), true, 'String#has | flu\\?ffy has three slashes and ? in a regex');
+
+  equal('schfifty'.add(' five'), 'schfifty five', 'String#add | schfifty five');
+  equal('dopamine'.add('e', 3), 'dopeamine', 'String#add | dopeamine');
+  equal('spelling eror'.add('r', -3), 'spelling error', 'String#add | add from the end');
+  equal('flack'.add('a', 0), 'aflack', 'String#add | add at 0');
+  equal('five'.add('schfifty', 20), 'fiveschfifty', 'String#add | adds out of positive range');
+  equal('five'.add('schfifty ', -20), 'schfifty five', 'String#add | adds out of negative range');
+  equal('five'.add('schfifty', 4), 'fiveschfifty', 'String#add | add at position 4');
+  equal('five'.add('schfifty', 5), 'fiveschfifty', 'String#add | add at position 5');
+  equal(''.add(['schfifty', ' five']), 'schfifty, five', 'String#add | arrays are stringified');
+
+  equal('schfifty five'.remove('five'), 'schfifty ', 'String#remove | five');
+  equal('schfifty five'.remove(/five/), 'schfifty ', 'String#remove | /five/');
+  equal('schfifty five'.remove(/f/), 'schifty five', 'String#remove | /f/');
+  equal('schfifty five'.remove(/f/g), 'schity ive', 'String#remove | /f/g');
+  equal('schfifty five'.remove(/[a-f]/g), 'shity iv', 'String#remove | /[a-f]/');
+  equal('?'.remove('?'), '', 'String#remove | strings have tokens escaped');
+  equal('?('.remove('?('), '', 'String#remove | strings have all tokens escaped');
+
+  equal('schfifty'.insert(' five'), 'schfifty five', 'String#insert | schfifty five');
+  equal('dopamine'.insert('e', 3), 'dopeamine', 'String#insert | dopeamine');
+  equal('spelling eror'.insert('r', -3), 'spelling error', 'String#insert | inserts from the end');
+  equal('flack'.insert('a', 0), 'aflack', 'String#insert | inserts at 0');
+  equal('five'.insert('schfifty', 20), 'fiveschfifty', 'String#insert | adds out of positive range');
+  equal('five'.insert('schfifty', -20), 'schfiftyfive', 'String#insert | adds out of negative range');
+  equal('five'.insert('schfifty', 4), 'fiveschfifty', 'String#insert | inserts at position 4');
+  equal('five'.insert('schfifty', 5), 'fiveschfifty', 'String#insert | inserts at position 5');
+
+  equal('abcd'.insert('X', 2), 'abXcd', 'String#insert | X | 2');
+  equal('abcd'.insert('X', 1), 'aXbcd', 'String#insert | X | 1');
+  equal('abcd'.insert('X', 0), 'Xabcd', 'String#insert | X | 0');
+  equal('abcd'.insert('X', -1), 'abcXd', 'String#insert | X | -1');
+  equal('abcd'.insert('X', -2), 'abXcd', 'String#insert | X | -2');
+
+
+  equal('4em'.toNumber(), 4, 'String#toNumber | 4em');
+  equal('10px'.toNumber(), 10, 'String#toNumber | 10px');
+  equal('10,000'.toNumber(), 10000, 'String#toNumber | 10,000');
+  equal('5,322,144,444'.toNumber(), 5322144444, 'String#toNumber | 5,322,144,444');
+  equal('10.532'.toNumber(), 10.532, 'String#toNumber | 10.532');
+  equal('10'.toNumber(), 10, 'String#toNumber | 10');
+  equal('95.25%'.toNumber(), 95.25, 'String#toNumber | 95.25%');
+  equal('10.848'.toNumber(), 10.848, 'String#toNumber | 10.848');
+
+  equal('1234blue'.toNumber(), 1234, 'String#toNumber | 1234blue');
+  equal(isNaN('0xA'.toNumber()), false, 'String#toNumber | "0xA" should not be NaN');
+  equal('22.5'.toNumber(), 22.5, 'String#toNumber | 22.5');
+  equal(isNaN('blue'.toNumber()), true, 'String#toNumber | "blue" should not be NaN');
+
+  equal('010'.toNumber(), 10, 'String#toNumber | "010" should be 10');
+  equal('0908'.toNumber(), 908, 'String#toNumber | "0908" should be 908');
+  equal('22.34.5'.toNumber(), 22.34, 'String#toNumber | "22.34.5" should be 22.34');
+
+  equal(isNaN('........'.toNumber()), true, 'String#toNumber | "......." should be NaN');
+
+  equal('1.45kg'.toNumber(), 1.45, 'String#toNumber | "1.45kg"');
+  equal('77.3'.toNumber(), 77.3, 'String#toNumber | 77.3');
+  equal('077.3'.toNumber(), 77.3, 'String#toNumber | "077.3" should be 77.3');
+  equal(isNaN('0x77.3'.toNumber()), false, 'String#toNumber | "0x77.3" is not NaN');
+  equal('.3'.toNumber(), 0.3, 'String#toNumber | ".3" should be 0.3');
+  equal('0.1e6'.toNumber(), 100000, 'String#toNumber | "0.1e6" should be 100000');
+
+
+  // This should handle hexadecimal, etc
+  equal('ff'.toNumber(16), 255, 'String#toNumber | hex | ff');
+  equal('00'.toNumber(16), 0, 'String#toNumber | hex | 00');
+  equal('33'.toNumber(16), 51, 'String#toNumber | hex | 33');
+  equal('66'.toNumber(16), 102, 'String#toNumber | hex | 66');
+  equal('99'.toNumber(16), 153, 'String#toNumber | hex | 99');
+  equal('bb'.toNumber(16), 187, 'String#toNumber | hex | bb');
+
+  equal(('๏ผ’๏ผ๏ผ').toNumber(), 200, 'String#toNumber | full-width | should work on full-width integers');
+  equal(('๏ผ•๏ผŽ๏ผ’๏ผ“๏ผ”๏ผ•').toNumber(), 5.2345, 'String#toNumber | full-width | should work on full-width decimals');
+
+
+
+  equal('spoon'.reverse(), 'noops', 'String#reverse | spoon');
+  equal('amanaplanacanalpanama'.reverse(), 'amanaplanacanalpanama', 'String#reverse | amanaplanacanalpanama');
+
+
+  equal('the rain in     spain    falls mainly   on     the        plain'.compact(), 'the rain in spain falls mainly on the plain', 'String#compact | basic');
+  equal('\n\n\nthe \n\n\nrain in     spain    falls mainly   on     the        plain\n\n'.compact(), 'the rain in spain falls mainly on the plain', 'String#compact | with newlines');
+  equal('\n\n\n\n           \t\t\t\t          \n\n      \t'.compact(), '', 'String#compact | with newlines and tabs');
+
+  var largeJapaneseSpaces = 'ใ€€ใ€€ใ€€ๆ—ฅๆœฌ่ชžใ€€ใ€€ใ€€ใ€€ใ€€ใฎใ€€ใ€€ใ€€ใ€€ใ€€ใ‚นใƒšใƒผใ‚นใ€€ใ€€ใ€€ใ€€ใ€€ใ‚‚ใ€€ใ€€';
+  var compactedWithoutJapaneseSpaces = 'ๆ—ฅๆœฌ่ชžใ€€ใฎใ€€ใ‚นใƒšใƒผใ‚นใ€€ใ‚‚';
+  var compactedWithTrailingJapaneseSpaces = 'ใ€€ๆ—ฅๆœฌ่ชžใ€€ใฎใ€€ใ‚นใƒšใƒผใ‚นใ€€ใ‚‚ใ€€';
+
+  equal('moo\tmoo'.compact(), 'moo moo', 'String#compact | moo moo tab');
+  equal('moo \tmoo'.compact(), 'moo moo', 'String#compact | moo moo space tab');
+  equal('moo \t moo'.compact(), 'moo moo', 'String#compact | moo moo space tab space');
+
+
+  equal('foop'.at(0), 'f', 'String#at | pos 0');
+  equal('foop'.at(1), 'o', 'String#at | pos 1');
+  equal('foop'.at(2), 'o', 'String#at | pos 2');
+  equal('foop'.at(3), 'p', 'String#at | pos 3');
+  equal('foop'.at(4), 'f', 'String#at | pos 4');
+  equal('foop'.at(5), 'o', 'String#at | pos 5');
+  equal('foop'.at(1224), 'f', 'String#at | out of bounds');
+  equal('foop'.at(-1), 'p', 'String#at | negative | pos -1');
+  equal('foop'.at(-2), 'o', 'String#at | negative | pos -2');
+  equal('foop'.at(-3), 'o', 'String#at | negative | pos -3');
+  equal('foop'.at(-4), 'f', 'String#at | negative | pos -4');
+  equal('foop'.at(-5), 'p', 'String#at | negative | pos -5');
+  equal('foop'.at(-1224), 'f', 'String#at | negative | out of bounds');
+
+  equal('foop'.at(0, false), 'f', 'String#at | pos 0');
+  equal('foop'.at(1, false), 'o', 'String#at | pos 1');
+  equal('foop'.at(2, false), 'o', 'String#at | pos 2');
+  equal('foop'.at(3, false), 'p', 'String#at | pos 3');
+  equal('foop'.at(4, false), '', 'String#at | pos 4');
+  equal('foop'.at(1224, false), '', 'String#at | out of bounds');
+  equal('foop'.at(-1, false), '', 'String#at | negative | pos -1');
+  equal('foop'.at(-2, false), '', 'String#at | negative | pos -2');
+  equal('foop'.at(-3, false), '', 'String#at | negative | pos -3');
+  equal('foop'.at(-4, false), '', 'String#at | negative | pos -4');
+  equal('foop'.at(-5, false), '', 'String#at | negative | pos -5');
+  equal('foop'.at(-1224, false), '', 'String#at | negative | out of bounds');
+
+  equal('wowzers'.at(0,2,4,6), ['w','w','e','s'], 'String#at | handles enumerated params');
+  equal('wowzers'.at(0,2,4,6,18), ['w','w','e','s','e'], 'String#at | handles enumerated params');
+  equal('wowzers'.at(0,2,4,6,18,false), ['w','w','e','s',''], 'String#at | handles enumerated params');
+
+
+  equal('quack'.first(), 'q', 'String#first | first character');
+  equal('quack'.first(2), 'qu', 'String#first | first 2 characters');
+  equal('quack'.first(3), 'qua', 'String#first | first 3 characters');
+  equal('quack'.first(4), 'quac', 'String#first | first 4 characters');
+  equal('quack'.first(20), 'quack', 'String#first | first 20 characters');
+  equal('quack'.first(0), '', 'String#first | first 0 characters');
+  equal('quack'.first(-1), '', 'String#first | first -1 characters');
+  equal('quack'.first(-5), '', 'String#first | first -5 characters');
+  equal('quack'.first(-10), '', 'String#first | first -10 characters');
+
+
+
+  equal('quack'.last(), 'k', 'String#last | last character');
+  equal('quack'.last(2), 'ck', 'String#last | last 2 characters');
+  equal('quack'.last(3), 'ack', 'String#last | last 3 characters');
+  equal('quack'.last(4), 'uack', 'String#last | last 4 characters');
+  equal('quack'.last(10), 'quack', 'String#last | last 10 characters');
+  equal('quack'.last(-1), '', 'String#last | last -1 characters');
+  equal('quack'.last(-5), '', 'String#last | last -5 characters');
+  equal('quack'.last(-10), '', 'String#last | last -10 characters');
+  equal('fa'.last(3), 'fa', 'String#last | last 3 characters');
+
+
+  equal('quack'.from(), 'quack', 'String#from | no params');
+  equal('quack'.from(0), 'quack', 'String#from | from 0');
+  equal('quack'.from(2), 'ack', 'String#from | from 2');
+  equal('quack'.from(4), 'k', 'String#from | from 4');
+  equal('quack'.from(-1), 'k', 'String#from | from -1');
+  equal('quack'.from(-3), 'ack', 'String#from | from -3');
+  equal('quack'.from(-4), 'uack', 'String#from | from -4');
+
+  equal('quack'.from('q'), 'quack', 'String#from | strings | q');
+  equal('quack'.from('u'), 'uack', 'String#from | strings | u');
+  equal('quack'.from('a'), 'ack', 'String#from | strings | a');
+  equal('quack'.from('k'), 'k', 'String#from | strings | k');
+  equal('quack'.from(''), 'quack', 'String#from | strings | empty string');
+  equal('quack'.from('ua'), 'uack', 'String#from | strings | 2 characters');
+  equal('quack'.from('uo'), '', 'String#from | strings | 2 non-existent characters');
+  equal('quack'.from('quack'), 'quack', 'String#from | strings | full string');
+
+
+  equal('quack'.to(), 'quack', 'String#to | no params');
+  equal('quack'.to(0), '', 'String#to | to 0');
+  equal('quack'.to(1), 'q', 'String#to | to 1');
+  equal('quack'.to(2), 'qu', 'String#to | to 2');
+  equal('quack'.to(4), 'quac', 'String#to | to 4');
+  equal('quack'.to(-1), 'quac', 'String#to | to -1');
+  equal('quack'.to(-3), 'qu', 'String#to | to -3');
+  equal('quack'.to(-4), 'q', 'String#to | to -4');
+
+  equal('quack'.to('q'), '', 'String#to | strings | q');
+  equal('quack'.to('u'), 'q', 'String#to | strings | u');
+  equal('quack'.to('a'), 'qu', 'String#to | strings | a');
+  equal('quack'.to('k'), 'quac', 'String#to | strings | k');
+  equal('quack'.to(''), '', 'String#to | strings | empty string');
+  equal('quack'.to('ua'), 'q', 'String#to | strings | 2 characters');
+  equal('quack'.to('uo'), '', 'String#to | strings | 2 non-existent characters');
+  equal('quack'.to('quack'), '', 'String#to | strings | full string');
+
+  equal('hop_on_pop'.dasherize(), 'hop-on-pop', 'String#dasherize | underscores');
+  equal('HOP_ON_POP'.dasherize(), 'hop-on-pop', 'String#dasherize | capitals and underscores', { prototype: 'HOP-ON-POP' });
+  equal('hopOnPop'.dasherize(), 'hop-on-pop', 'String#dasherize | camel-case', { prototype: 'hopOnPop' });
+  equal('watch me fail'.dasherize(), 'watch-me-fail', 'String#dasherize | whitespace', { prototype: 'watch me fail' });
+  equal('watch me fail_sad_face'.dasherize(), 'watch-me-fail-sad-face', 'String#dasherize | whitespace sad face', { prototype: 'watch me fail-sad-face' });
+  equal('waTch me su_cCeed'.dasherize(), 'wa-tch-me-su-c-ceed', 'String#dasherize | complex whitespace', { prototype: 'waTch me su-cCeed' });
+
+  equal('aManAPlanACanalPanama'.dasherize(), 'a-man-a-plan-a-canal-panama', 'String#dasherize | single characters', { prototype: 'aManAPlanACanalPanama' });
+
+
+
+  equal('hop-on-pop'.camelize(), 'HopOnPop', 'String#camelize | dashes', { prototype: 'hopOnPop' });
+  equal('HOP-ON-POP'.camelize(), 'HopOnPop', 'String#camelize | capital dashes', { prototype: 'HOPONPOP' });
+  equal('hop_on_pop'.camelize(), 'HopOnPop', 'String#camelize | underscores', { prototype: 'hop_on_pop' });
+  equal('hop-on-pop'.camelize(false), 'hopOnPop', 'String#camelize | first false | dashes');
+  equal('HOP-ON-POP'.camelize(false), 'hopOnPop', 'String#camelize | first false | capital dashes', { prototype: 'HOPONPOP' });
+  equal('hop_on_pop'.camelize(false), 'hopOnPop', 'String#camelize | first false | underscores', { prototype: 'hop_on_pop' });
+  equal('hop-on-pop'.camelize(true), 'HopOnPop', 'String#camelize | first true | dashes', { prototype: 'hopOnPop' });
+  equal('HOP-ON-POP'.camelize(true), 'HopOnPop', 'String#camelize | first true | capital dashes', { prototype: 'HOPONPOP' });
+  equal('hop_on_pop'.camelize(true), 'HopOnPop', 'String#camelize | first true | underscores', { prototype: 'hop_on_pop' });
+
+  equal('watch me fail'.camelize(), 'WatchMeFail', 'String#camelize | whitespace', { prototype: 'watch me fail' });
+  equal('watch   me   fail'.camelize(), 'WatchMeFail', 'String#camelize | long whitespace', { prototype: 'watch   me   fail' });
+  equal('watch me fail-sad-face'.camelize(), 'WatchMeFailSadFace', 'String#camelize | whitespace sad face', { prototype: 'watch me failSadFace' });
+  equal('waTch me su-cCeed'.camelize(), 'WaTchMeSuCCeed', 'String#camelize | complex whitespace', { prototype: 'waTch me suCCeed' });
+
+  equal('watch me fail'.camelize(false), 'watchMeFail', 'String#camelize | first false | whitespace', { prototype: 'watch me fail' });
+  equal('watch me fail-sad-face'.camelize(false), 'watchMeFailSadFace', 'String#camelize | first false | whitespace sad face', { prototype: 'watch me failSadFace' });
+  equal('waTch me su-cCeed'.camelize(false), 'waTchMeSuCCeed', 'String#camelize | first false | complex whitespace', { prototype: 'waTch me suCCeed' });
+
+
+
+
+  equal('hopOnPop'.underscore(), 'hop_on_pop', 'String#underscore | camel-case');
+  equal('HopOnPop'.underscore(), 'hop_on_pop', 'String#underscore | camel-case capital first');
+  equal('HOPONPOP'.underscore(), 'hoponpop', 'String#underscore | all caps', { prototype: 'hoponpop' });
+  equal('HOP-ON-POP'.underscore(), 'hop_on_pop', 'String#underscore | caps and dashes', { prototype: 'hop_on_pop' });
+  equal('hop-on-pop'.underscore(), 'hop_on_pop', 'String#underscore | lower-case and dashes');
+
+  equal('watch me fail'.underscore(), 'watch_me_fail', 'String#underscore | whitespace', { prototype: 'watch me fail' });
+  equal('watch   me   fail'.underscore(), 'watch_me_fail', 'String#underscore | long whitespace', { prototype: 'watch   me   fail' });
+  equal('watch me fail-sad-face'.underscore(), 'watch_me_fail_sad_face', 'String#underscore | whitespace sad face', { prototype: 'watch me fail_sad_face' });
+  equal('waTch me su-cCeed'.underscore(), 'wa_tch_me_su_c_ceed', 'String#underscore | complex whitespace', { prototype: 'wa_tch me su_c_ceed' });
+
+
+  equal('hopOnPop'.spacify(), 'hop on pop', 'String#spacify | camel-case');
+  equal('HopOnPop'.spacify(), 'hop on pop', 'String#spacify | camel-case capital first');
+  equal('HOPONPOP'.spacify(), 'hoponpop', 'String#spacify | all caps', { prototype: 'hoponpop' });
+  equal('HOP-ON-POP'.spacify(), 'hop on pop', 'String#spacify | caps and dashes', { prototype: 'hop on pop' });
+  equal('hop-on-pop'.spacify(), 'hop on pop', 'String#spacify | lower-case and dashes');
+
+  equal('watch_me_fail'.spacify(), 'watch me fail', 'String#spacify | whitespace');
+  equal('watch-meFail-sad-face'.spacify(), 'watch me fail sad face', 'String#spacify | whitespace sad face');
+  equal('waTch me su-cCeed'.spacify(), 'wa tch me su c ceed', 'String#spacify | complex whitespace');
+
+
+  var stripped;
+  var html =
+  '<div class="outer">' +
+  '<p>text with <a href="http://foobar.com/">links</a>, &quot;entities&quot; and <b>bold</b> tags</p>' +
+  '</div>';
+  var allStripped = 'text with links, &quot;entities&quot; and bold tags';
+
+  var malformed_html = '<div class="outer"><p>paragraph';
+
+
+  stripped =
+  '<div class="outer">' +
+  '<p>text with links, &quot;entities&quot; and <b>bold</b> tags</p>' +
+  '</div>';
+
+  equal(html.stripTags('a'), stripped, 'String#stripTags | stripped a tags', { prototype: allStripped });
+  equal(html.stripTags('a') == html, false, 'String#stripTags | stripped <a> tags was changed');
+
+
+  stripped =
+  '<div class="outer">' +
+  '<p>text with links, &quot;entities&quot; and bold tags</p>' +
+  '</div>';
+  equal(html.stripTags('a', 'b'), stripped, 'String#stripTags | stripped <a> and <b> tags', { prototype: allStripped });
+  equal(html.stripTags(['a', 'b']), stripped, 'String#stripTags | array | stripped <a> and <b> tags', { prototype: allStripped });
+
+
+  stripped =
+  '<div class="outer">' +
+  'text with links, &quot;entities&quot; and <b>bold</b> tags' +
+  '</div>';
+  equal(html.stripTags('p', 'a'), stripped, 'String#stripTags | stripped <p> and <a> tags', { prototype: allStripped });
+  equal(html.stripTags(['p', 'a']), stripped, 'String#stripTags | array | stripped <p> and <a> tags', { prototype: allStripped });
+
+
+  stripped = '<p>text with <a href="http://foobar.com/">links</a>, &quot;entities&quot; and <b>bold</b> tags</p>';
+  equal(html.stripTags('div'), stripped, 'String#stripTags | stripped <div> tags', { prototype: allStripped});
+
+
+  stripped = 'text with links, &quot;entities&quot; and bold tags';
+  equal(html.stripTags(), stripped, 'String#stripTags | all tags stripped');
+
+
+  stripped = '<p>paragraph';
+  equal(malformed_html.stripTags('div'), stripped, 'String#stripTags | malformed | div tag stripped', { prototype: 'paragraph' });
+
+  stripped = '<div class="outer">paragraph';
+  equal(malformed_html.stripTags('p'), stripped, 'String#stripTags | malformed | p tags stripped', { prototype: 'paragraph' });
+
+  stripped = 'paragraph';
+  equal(malformed_html.stripTags(), stripped, 'String#stripTags | malformed | all tags stripped');
+
+
+
+  equal('<b NOT BOLD</b>'.stripTags(), '<b NOT BOLD', "String#stripTags | does not strip tags that aren't properly closed", { prototype: '' });
+  equal('a < b'.stripTags(), 'a < b', 'String#stripTags | does not strip less than');
+  equal('a > b'.stripTags(), 'a > b', 'String#stripTags | does not strip greater than');
+  equal('</foo  >>'.stripTags(), '>', 'String#stripTags | strips closing tags with white space', { prototype: '</foo  >>' });
+
+
+
+  /* Stipping self-closing tags */
+  equal('<input type="text" class="blech" />'.stripTags(), '', 'String#stripTags | full input stripped');
+
+  equal('<b>bold<b> and <i>italic</i> and <a>link</a>'.stripTags('b','i'), 'bold and italic and <a>link</a>', 'String#stripTags | handles multi args', { prototype: 'bold and italic and link' });
+
+  html =
+  '<form action="poo.php" method="post">' +
+  '<p>' +
+  '<label>label for text:</label>' +
+  '<input type="text" value="brabra" />' +
+  '<input type="submit" value="submit" />' +
+  '</p>' +
+  '</form>';
+
+  equal(html.stripTags(), 'label for text:', 'String#stripTags | form | all tags removed');
+  equal(html.stripTags('input'), '<form action="poo.php" method="post"><p><label>label for text:</label></p></form>', 'String#stripTags | form | input tags stripped', { prototype: 'label for text:' });
+  equal(html.stripTags('input', 'p', 'form'), '<label>label for text:</label>', 'String#stripTags | form | input, p, and form tags stripped', { prototype: 'label for text:' });
+
+  /* Stripping namespaced tags */
+  equal('<xsl:template>foobar</xsl:template>'.stripTags(), 'foobar', 'String#stripTags | strips tags with xml namespaces', { prototype: '<xsl:template>foobar</xsl:template>' });
+  equal('<xsl:template>foobar</xsl:template>'.stripTags('xsl:template'), 'foobar', 'String#stripTags | strips xsl:template', { prototype: '<xsl:template>foobar</xsl:template>' });
+  equal('<xsl/template>foobar</xsl/template>'.stripTags('xsl/template'), 'foobar', 'String#stripTags | strips xsl/template', { prototype: '<xsl/template>foobar</xsl/template>' });
+
+
+  /* No errors on RegExp */
+  equal('<xsl(template>foobar</xsl(template>'.stripTags('xsl(template'), 'foobar', 'String#stripTags | no regexp errors on tokens', { prototype: '<xsl(template>foobar</xsl(template>' });
+
+
+
+
+  html =
+  '<div class="outer">' +
+  '<p>text with <a href="http://foobar.com/">links</a>, &quot;entities&quot; and <b>bold</b> tags</p>' +
+  '</div>';
+  var removed;
+
+  removed =
+  '<div class="outer">' +
+  '<p>text with , &quot;entities&quot; and <b>bold</b> tags</p>' +
+  '</div>';
+  equal(html.removeTags('a'), removed, 'String#removeTags | <a> tag removed');
+  equal(html.removeTags('a') == html, false, 'String#removeTags | html was changed');
+
+
+  removed =
+  '<div class="outer">' +
+  '<p>text with , &quot;entities&quot; and  tags</p>' +
+  '</div>';
+  equal(html.removeTags('a', 'b'), removed, 'String#removeTags | <a> and <b> tags removed');
+  equal(html.removeTags(['a', 'b']), removed, 'String#removeTags | array | <a> and <b> tags removed');
+
+
+  removed =
+  '<div class="outer"></div>';
+  equal(html.removeTags('p', 'a'), removed, 'String#removeTags | <p> and <a> tags removed');
+  equal(html.removeTags(['p', 'a']), removed, 'String#removeTags | array | <p> and <a> tags removed');
+
+
+  equal(html.removeTags('div'), '', 'String#removeTags | <div> tags removed');
+  equal(html.removeTags(), '', 'String#removeTags | removing all tags');
+
+  equal(malformed_html.removeTags('div'), malformed_html, 'String#removeTags | malformed | <div> tags removed');
+  equal(malformed_html.removeTags('p'), malformed_html, 'String#removeTags | malformed | <p> tags removed');
+  equal(malformed_html.removeTags(), malformed_html, 'String#removeTags | malformed | all tags removed');
+
+
+
+  equal('<b NOT BOLD</b>'.removeTags(), '<b NOT BOLD</b>', 'String#removeTags | unclosed opening tag untouched');
+  equal('a < b'.removeTags(), 'a < b', 'String#removeTags | less than unaffected');
+  equal('a > b'.removeTags(), 'a > b', 'String#removeTags | greater than unaffected');
+  equal('</foo  >>'.removeTags(), '</foo  >>', 'String#removeTags | malformed closing tag unaffected');
+
+
+
+  /* Stipping self-closing tags */
+  equal('<input type="text" class="blech" />'.removeTags(), '', 'String#removeTags');
+
+  html =
+  '<form action="poo.php" method="post">' +
+  '<p>' +
+  '<label>label for text:</label>' +
+  '<input type="text" value="brabra" />' +
+  '<input type="submit" value="submit" />' +
+  '</p>' +
+  '</form>';
+
+  equal(html.removeTags(), '', 'String#removeTags | form | removing all tags');
+  equal(html.removeTags('input'), '<form action="poo.php" method="post"><p><label>label for text:</label></p></form>', 'String#removeTags | form | removing input tags');
+  equal(html.removeTags('input', 'p', 'form'), '', 'String#removeTags | form | removing input, p, and form tags');
+
+  /* Stripping namespaced tags */
+  equal('<xsl:template>foobar</xsl:template>'.removeTags(), '', 'String#removeTags | form | xml namespaced tags removed');
+  equal('<xsl:template>foobar</xsl:template>'.removeTags('xsl:template'), '', 'String#removeTags | form | xsl:template removed');
+  equal('<xsl/template>foobar</xsl/template>'.removeTags('xsl/template'), '', 'String#removeTags | form | xsl/template removed');
+
+
+  /* No errors on RegExp */
+  raisesError(function(){ '<xsl(template>foobar</xsl(template>'.removeTags('xsl(template') }, 'String#removeTags | form | you now have the power to cause your own regex pain');
+
+  equal('<b>bold</b> and <i>italic</i> and <a>link</a>'.removeTags('b','i'), ' and  and <a>link</a>', 'String#removeTags | handles multi args');
+
+
+
+  equal(''.escapeRegExp(), '', 'String#escapeRegExp | blank');
+  equal('|'.escapeRegExp(), '\\|', 'String#escapeRegExp | pipe');
+  equal(''.capitalize(), '', 'String#capitalize | blank');
+  equal('wasabi'.capitalize(), 'Wasabi', 'String#capitalize | wasabi');
+  equal(''.trim(), '', 'String#trim | blank');
+  equal(' wasabi '.trim(), 'wasabi', 'String#trim | wasabi with whitespace');
+  equal(''.trimLeft(), '', 'String#trimLeft | blank');
+  equal(' wasabi '.trimLeft(), 'wasabi ', 'String#trimLeft | wasabi with whitespace');
+  equal(''.trimRight(), '', 'String#trimRight | blank');
+  equal(' wasabi '.trimRight(), ' wasabi', 'String#trimRight | wasabi with whitespace');
+  equal('wasabi'.repeat(0), '', 'String#repeat | repeating 0 times');
+  equal('wasabi'.repeat(1), 'wasabi', 'String#repeat | repeating 1 time');
+  equal('wasabi'.repeat(2), 'wasabiwasabi', 'String#repeat | repeating 2 time');
+  equal(''.insert('-', 0), '-', 'String#insert | - inserted at 0');
+  equal('b'.insert('-', 0), '-b', 'String#insert | b inserted at 0');
+  equal('b'.insert('-', 1), 'b-', 'String#insert | b inserted at 1');
+  equal(''.reverse(), '', 'String#reverse | blank');
+  equal('wasabi'.reverse(), 'ibasaw', 'String#reverse | wasabi');
+  equal(''.compact(), '', 'String#compact | blank');
+  equal('run   tell    dat'.compact(), 'run tell dat', 'String#compact | with extra whitespace');
+  equal(''.at(3), '', 'String#at | blank');
+  equal('wasabi'.at(0), 'w', 'String#at | wasabi at pos 0');
+  equal(''.first(), '', 'String#first | blank');
+  equal('wasabi'.first(), 'w', 'String#first | no params');
+  equal(''.last(), '', 'String#last | blank');
+  equal('wasabi'.last(), 'i', 'String#last | no params');
+  equal(''.from(0), '', 'String#from | blank');
+  equal('wasabi'.from(3), 'abi', 'String#from | from pos 3');
+  equal(''.to(0), '', 'String#to | blank');
+  equal('wasabi'.to(3), 'was', 'String#to | to pos 3');
+  equal(''.dasherize(), '', 'String#dasherize | blank');
+  equal('noFingWay'.dasherize(), 'no-fing-way', 'String#dasherize | noFingWay', { prototype: 'noFingWay' });
+  equal(''.underscore(), '', 'String#underscore | blank');
+  equal('noFingWay'.underscore(), 'no_fing_way', 'String#underscore | noFingWay');
+  equal(''.camelize(), '', 'String#camelize | blank');
+  equal('no-fing-way'.camelize(), 'NoFingWay', 'String#camelize | no-fing-way', { prototype: 'noFingWay' });
+  equal(''.stripTags(), '', 'String#stripTags | blank');
+  equal('chilled <b>monkey</b> brains'.stripTags(), 'chilled monkey brains', 'String#stripTags | chilled <b>monkey</b> brains');
+  equal(''.removeTags(), '', 'String#removeTags | blank');
+  equal('chilled <b>monkey</b> brains'.removeTags(), 'chilled  brains', 'String#removeTags | chilled <b>monkey</b> brains');
+
+
+  // Thanks to Steven Levitah (http://stevenlevithan.com/demo/split.cfm) for inspiration and information here.
+
+
+  // String#split is now removed from the main packages and is in /lib/extra...
+  // keeping the unit tests as they are valuable.
+  //equal('a,b,c,d,e'.split(',') , ['a','b','c','d','e'] , 'Array#split | splits on standard commas');
+  //equal('a|b|c|d|e'.split(',') , ['a|b|c|d|e'] , "Array#split | doesn't split on standard commas");
+  //equal('a|b|c|d|e'.split('|') , ['a','b','c','d','e'] , 'Array#split | splits on pipes');
+  //equal('a,b,c,d,e'.split(/,/) , ['a','b','c','d','e'] , 'Array#split | splits on standard regexp commas');
+  //equal('a|b|c|d|e'.split(/\|/) , ['a','b','c','d','e'] , 'Array#split | splits on standard regexp pipes');
+  //equal('a|b|c|d|e'.split(/[,|]/) , ['a','b','c','d','e'] , 'Array#split | splits on classes');
+  //equal('a|b|c|d|e'.split(/[a-z]/) , ['','|','|','|','|',''] , 'Array#split | splits on classes with ranges');
+  //equal('a|b|c|d|e'.split(/\|*/) , ['a','b','c','d','e'] , 'Array#split | splits on star');
+  //equal('a|b|c|d|e'.split(/\|?/) , ['a','b','c','d','e'] , 'Array#split | splits on optionals');
+
+  //equal('a,b,c,d,e'.split(',', 2) , ['a','b'] , 'Array#split | handles limits');
+
+  //equal('a|||b|c|d|e'.split('|') , ['a', '', '', 'b','c','d','e'] , 'Array#split | splits back-to-back separators on a string');
+  //equal('a|||b|c|d|e'.split(/\|/) , ['a', '', '', 'b','c','d','e'] , 'Array#split | splits back-to-back separators on a regexp');
+
+  //equal('paragraph one\n\nparagraph two\n\n\n'.split(/\n/) , ['paragraph one', '', 'paragraph two','','',''] , 'Array#split | splits on new lines');
+  //equal(''.split() , [''] , 'Array#split | has a single null string for null separators on null strings');
+  //equal(''.split(/./) , [''] , 'Array#split | has a single null string for separators on null strings');
+
+  //equal(''.split(/.?/) , [] , 'Array#split | has a single null string for optionals on null strings');
+  //equal(''.split(/.??/) , [] , 'Array#split | has a single null string for double optionals on null strings');
+
+  //equal('a'.split(/./) , ['',''] , 'Array#split | has two entries when splitting on the only character in the string');
+  //equal('a'.split(/-/) , ['a'] , 'Array#split | has one entry when only one character and no match');
+  //equal('a-b'.split(/-/) , ['a', 'b'] , 'Array#split | properly splits hyphens');
+  //equal('a-b'.split(/-?/) , ['a', 'b'] , 'Array#split | properly splits optional hyphens');
+
+
+  //equal('a:b:c'.split(/(:)/) , ['a',':','b',':','c'] , 'Array#split | respects capturing groups');
+
+
+  //equal('ab'.split(/a*/) , ['', 'b'] , 'Array#split | complex regex splitting | /a*/');
+  //equal('ab'.split(/a*?/) , ['a', 'b'] , 'Array#split | complex regex splitting | /a*?/');
+  //equal('ab'.split(/(?:ab)/) , ['', ''] , 'Array#split | complex regex splitting | /(?:ab)/');
+  //equal('ab'.split(/(?:ab)*/) , ['', ''] , 'Array#split | complex regex splitting | /(?:ab)*/');
+  //equal('ab'.split(/(?:ab)*?/) , ['a', 'b'] , 'Array#split | complex regex splitting | /(?:ab)*?/');
+  //equal('test'.split('') , ['t', 'e', 's', 't'] , 'Array#split | complex regex splitting | empty string');
+  //equal('test'.split() , ['test'] , 'Array#split | complex regex splitting | no argument');
+  //equal('111'.split(1) , ['', '', '', ''] , 'Array#split | complex regex splitting | 1');
+  //equal('test'.split(/(?:)/, 2) , ['t', 'e'] , 'Array#split | complex regex splitting | index is 2');
+  //equal('test'.split(/(?:)/, -1) , ['t', 'e', 's', 't'] , 'Array#split | complex regex splitting | index is -1');
+  //equal('test'.split(/(?:)/, undefined) , ['t', 'e', 's', 't'] , 'Array#split | complex regex splitting | index is undefined');
+  //equal('test'.split(/(?:)/, null) , [] , 'Array#split | complex regex splitting | index is undefined');
+  //equal('test'.split(/(?:)/, NaN) , [] , 'Array#split | complex regex splitting | index is NaN');
+  //equal('test'.split(/(?:)/, true) , ['t'] , 'Array#split | complex regex splitting | index is true');
+  //equal('test'.split(/(?:)/, '2') , ['t', 'e'] , 'Array#split | complex regex splitting | index is "2"');
+  //equal('test'.split(/(?:)/, 'two') , [] , 'Array#split | complex regex splitting | index is two');
+  //equal('a'.split(/-/) , ['a'] , 'Array#split | complex regex splitting | a | /-/');
+  //equal('a'.split(/-?/) , ['a'] , 'Array#split | complex regex splitting | a | /-?/');
+  //equal('a'.split(/-??/) , ['a'] , 'Array#split | complex regex splitting | a | /-??/');
+  //equal('a'.split(/a/) , ['', ''] , 'Array#split | complex regex splitting | a | /a/');
+  //equal('a'.split(/a?/) , ['', ''] , 'Array#split | complex regex splitting | a | /a?/');
+  //equal('a'.split(/a??/) , ['a'] , 'Array#split | complex regex splitting | a | /a??/');
+  //equal('ab'.split(/-/) , ['ab'] , 'Array#split | complex regex splitting | ab | /-/');
+  //equal('ab'.split(/-?/) , ['a', 'b'] , 'Array#split | complex regex splitting | ab | /-?/');
+  //equal('ab'.split(/-??/) , ['a', 'b'] , 'Array#split | complex regex splitting | ab | /-??/');
+  //equal('a-b'.split(/-/) , ['a', 'b'] , 'Array#split | complex regex splitting | a-b | /-/');
+  //equal('a-b'.split(/-?/) , ['a', 'b'] , 'Array#split | complex regex splitting | a-b | /-?/');
+  //equal('a-b'.split(/-??/) , ['a', '-', 'b'] , 'Array#split | complex regex splitting | a-b | /-??/');
+  //equal('a--b'.split(/-/) , ['a', '', 'b'] , 'Array#split | complex regex splitting | a--b | /-/');
+  //equal('a--b'.split(/-?/) , ['a', '', 'b'] , 'Array#split | complex regex splitting | a--b | /-?/');
+  //equal('a--b'.split(/-??/) , ['a', '-', '-', 'b'] , 'Array#split | complex regex splitting | a--b | /-??/');
+  //equal(''.split(/()()/) , [] , 'Array#split | complex regex splitting | empty string | /()()/');
+  //equal('.'.split(/()()/) , ['.'] , 'Array#split | complex regex splitting | . | /()()/');
+  //equal('.'.split(/(.?)(.?)/) , ['', '.', '', ''] , 'Array#split | complex regex splitting | . | /(.?)(.?)/');
+  //equal('.'.split(/(.??)(.??)/) , ['.'] , 'Array#split | complex regex splitting | . | /(.??)(.??)/');
+  //equal('.'.split(/(.)?(.)?/) , ['', '.', undefined, ''] , 'Array#split | complex regex splitting | . | /(.)?(.)?/');
+  //equal('tesst'.split(/(s)*/) , ['t', undefined, 'e', 's', 't'] , 'Array#split | complex regex splitting | tesst | /(s)*/');
+  //equal('tesst'.split(/(s)*?/) , ['t', undefined, 'e', undefined, 's', undefined, 's', undefined, 't'] , 'Array#split | complex regex splitting | tesst | /(s)*?/');
+  //equal('tesst'.split(/(s*)/) , ['t', '', 'e', 'ss', 't'] , 'Array#split | complex regex splitting | tesst | /(s*)/');
+  //equal('tesst'.split(/(s*?)/) , ['t', '', 'e', '', 's', '', 's', '', 't'] , 'Array#split | complex regex splitting | tesst | /(s*?)/');
+  //equal('tesst'.split(/(?:s)*/) , ['t', 'e', 't'] , 'Array#split | complex regex splitting | tesst | /(?:s)*/');
+  //equal('tesst'.split(/(?=s+)/) , ['te', 's', 'st'] , 'Array#split | complex regex splitting | tesst | /(?=s+)/');
+  //equal('test'.split('t') , ['', 'es', ''] , 'Array#split | complex regex splitting | test | t');
+  //equal('test'.split('es') , ['t', 't'] , 'Array#split | complex regex splitting | test | es');
+  //equal('test'.split(/t/) , ['', 'es', ''] , 'Array#split | complex regex splitting | test | /t/');
+  //equal('test'.split(/es/) , ['t', 't'] , 'Array#split | complex regex splitting | test | /es/');
+  //equal('test'.split(/(t)/) , ['', 't', 'es', 't', ''] , 'Array#split | complex regex splitting | test | /(t)/');
+  //equal('test'.split(/(es)/) , ['t', 'es', 't'] , 'Array#split | complex regex splitting | test | /(es)/');
+  //equal('test'.split(/(t)(e)(s)(t)/) , ['', 't', 'e', 's', 't', ''] , 'Array#split | complex regex splitting | test | /(t)(e)(s)(t)/');
+  //equal('.'.split(/(((.((.??)))))/) , ['', '.', '.', '.', '', '', ''] , 'Array#split | complex regex splitting | . | /(((.((.??)))))/');
+  //equal('.'.split(/(((((.??)))))/) , ['.'] , 'Array#split | complex regex splitting | . | /(((((.??)))))/');
+
+
+  /*
+   * Patching the String#match method broke Prototype in IE in a very specific way:
+   *
+   *  var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]
+   *    .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
+   *    .replace(/\s+/g, '').split(',');
+   *
+   * Very unlikely that this would cause problems but after much debate I've decided not to
+   * patch the method, as it's simply too far-reaching with too few benefits, and too few unit tests
+   * to justify it. Will reconsider if the demand arises.
+   *
+   var match = 'on'.match(/on(e)?/);
+   equal(match[1], undefined, 'String#match | capturing group should be undefined');
+
+   var match = 'on'.match(/\b/g);
+   equal(match[0], '', 'String#match | first match should be empty string');
+   equal(match[1], '', 'String#match | second match should be empty string');
+   */
+
+  var str = 'Gotta be an entire sentence.';
+
+  equal(str.truncate(29), 'Gotta be an entire sentence.', 'String#truncate | no arguments | 29');
+  equal(str.truncate(28), 'Gotta be an entire sentence.', 'String#truncate | no arguments | 28');
+  equal(str.truncate(21), 'Gotta be an entire se...', 'String#truncate | split words | 21');
+  equal(str.truncate(20), 'Gotta be an entire s...', 'String#truncate | split words | 20');
+  equal(str.truncate(14), 'Gotta be an en...', 'String#truncate | split words | 14');
+  equal(str.truncate(13), 'Gotta be an e...', 'String#truncate | split words | 13');
+  equal(str.truncate(11), 'Gotta be an...', 'String#truncate | split words | 11');
+  equal(str.truncate(10), 'Gotta be a...', 'String#truncate | split words | 10');
+  equal(str.truncate(4), 'Gott...', 'String#truncate | split words | 4');
+  equal(str.truncate(3), 'Got...', 'String#truncate | split words | 3', { prototype: '...' });
+  equal(str.truncate(2), 'Go...', 'String#truncate | split words | 2', { prototype: '...' });
+  equal(str.truncate(1), 'G...', 'String#truncate | split words | 1', { prototype: '...' });
+  equal(str.truncate(0), '...', 'String#truncate | split words | 0', { prototype: '...' });
+
+  equal(str.truncateOnWord(21), 'Gotta be an entire...', 'String#truncate | no split | 21');
+  equal(str.truncateOnWord(20), 'Gotta be an entire...', 'String#truncate | no split | 20');
+  equal(str.truncateOnWord(19), 'Gotta be an entire...', 'String#truncate | no split | 19');
+  equal(str.truncateOnWord(18), 'Gotta be an entire...', 'String#truncate | no split | 18');
+  equal(str.truncateOnWord(17), 'Gotta be an...', 'String#truncate | no split | 17');
+  equal(str.truncateOnWord(14), 'Gotta be an...', 'String#truncate | no split | 14');
+  equal(str.truncateOnWord(13), 'Gotta be an...', 'String#truncate | no split | 13', { prototype: 'Gotta be a...' });
+  equal(str.truncateOnWord(11), 'Gotta be an...', 'String#truncate | no split | 11');
+  equal(str.truncateOnWord(10), 'Gotta be...', 'String#truncate | no split | 10', { prototype: 'Gotta b...' });
+  equal(str.truncateOnWord(4), '...', 'String#truncate | no split | 4', { prototype: 'G...' });
+  equal(str.truncateOnWord(3), '...', 'String#truncate | no split | 3', { prototype: '...' });
+  equal(str.truncateOnWord(2), '...', 'String#truncate | no split | 2', { prototype: '...' });
+  equal(str.truncateOnWord(1), '...', 'String#truncate | no split | 1', { prototype: '...' });
+  equal(str.truncateOnWord(0), '...', 'String#truncate | no split | 0', { prototype: '...' });
+
+  equal('GOTTA BE AN ENTIRE SENTENCE.'.truncateOnWord(21), 'GOTTA BE AN ENTIRE...', 'String#truncate | caps too | 21');
+  equal('GOTTA BE AN ENTIRE SENTENCE.'.truncateOnWord(17), 'GOTTA BE AN...', 'String#truncate | caps too | 20', { prototype: 'GOTTA BE AN ENTIR...' });
+
+  equal('gotta. be. an. entire. sentence.'.truncateOnWord(17), 'gotta. be. an....', 'String#truncate | no special punctuation treatment | 17');
+  equal('too short!'.truncate(30), 'too short!', 'String#truncate | will not add ellipsis if the string is too short');
+  equal('almost there'.truncateOnWord(11), 'almost...', 'String#truncate | will not add more than the original string', { prototype: 'almost t...' });
+
+  equal('Gotta be an entire sentence.'.truncateOnWord(22, 'right', 'hooha'), 'Gotta be an entirehooha', 'String#truncate | different ellipsis', { prototype: 'Gotta be an entirhooha' });
+  equal('Gotta be an entire sentence.'.truncate(22, 'right', 'hooha'), 'Gotta be an entire senhooha', 'String#truncate | different ellipsis');
+
+  equal('booh pooh mooh'.truncate(7, 'right', 455), 'booh po455', 'String#truncate | converts numbers to strings', { prototype: '455' });
+
+  equal('ใ“ใ‚“ใชใ€€ใ‚นใƒˆใƒชใƒณใ‚ฐใฏใ€€ใ‚ใพใ‚Šใ€€ใชใ„ใ€€ใจใ€€ๆ€ใ†ใ€€ใ‘ใฉใ€‚ใ€‚ใ€‚'.truncateOnWord(6), 'ใ“ใ‚“ใช...', 'String#truncate | correctly finds spaces in Japanese');
+  equal('ํ•œ๊ตญ์–ด ๋„ ์ด๋ ‡๊ฒŒ ํ•  ์ˆ˜ ์žˆ์–ด์š”?'.truncateOnWord(9), 'ํ•œ๊ตญ์–ด ๋„ ์ด๋ ‡๊ฒŒ...', 'String#truncate | correctly finds spaces in Korean', { prototype: 'ํ•œ๊ตญ์–ด ๋„ ...' });
+
+
+  // String#truncate
+
+  equal(str.truncate(21, 'middle'), 'Gotta be an... sentence.', 'String#truncate | middle | no arguments | 21');
+  equal(str.truncate(11, 'middle'), 'Gotta ...ence.', 'String#truncate | middle | no arguments | 11');
+  equal(str.truncate(4, 'middle'), 'Go...e.', 'String#truncate | middle | no arguments | 4');
+  equal(str.truncate(3, 'middle'), 'Go....', 'String#truncate | middle | no arguments | 3');
+  equal(str.truncate(2, 'middle'), 'G....', 'String#truncate | middle | no arguments | 2');
+  equal(str.truncate(0, 'middle'), '...', 'String#truncate | middle | no arguments | 0');
+  equal(str.truncate(-100, 'middle'), '...', 'String#truncate | middle | no arguments | -100');
+
+  equal(str.truncateOnWord(21, 'middle'), 'Gotta be an...sentence.', 'String#truncate | middle | no split | 21');
+  equal(str.truncateOnWord(11, 'middle'), 'Gotta...', 'String#truncate | middle | no split | 11');
+  equal(str.truncateOnWord(4, 'middle'), '...', 'String#truncate | middle | no split | 4');
+  equal(str.truncateOnWord(3, 'middle'), '...', 'String#truncate | middle | no split | 3');
+  equal(str.truncateOnWord(2, 'middle'), '...', 'String#truncate | middle | no split | 2');
+  equal(str.truncateOnWord(0, 'middle'), '...', 'String#truncate | middle | no split | 0');
+  equal(str.truncateOnWord(-100, 'middle'), '...', 'String#truncate | middle | no split | -100');
+
+  equal(str.truncate(21, 'left'), '...e an entire sentence.', 'String#truncate | left | no arguments | 21');
+  equal(str.truncate(11, 'left'), '...e sentence.', 'String#truncate | left | no arguments | 11');
+  equal(str.truncate(9, 'left'), '...sentence.', 'String#truncate | left | no arguments | 9');
+  equal(str.truncate(4, 'left'), '...nce.', 'String#truncate | left | no arguments | 4');
+  equal(str.truncate(3, 'left'), '...ce.', 'String#truncate | left | no arguments | 3');
+  equal(str.truncate(2, 'left'), '...e.', 'String#truncate | left | no arguments | 2');
+  equal(str.truncate(0, 'left'), '...', 'String#truncate | left | no arguments | 0');
+  equal(str.truncate(-100, 'left'), '...', 'String#truncate | left | no arguments | -100');
+
+  equal(str.truncateOnWord(21, 'left'), '...an entire sentence.', 'String#truncate | left | no split | 21');
+  equal(str.truncateOnWord(11, 'left'), '...sentence.', 'String#truncate | left | no split | 11');
+  equal(str.truncateOnWord(9, 'left'), '...sentence.', 'String#truncate | left | no split | 9');
+  equal(str.truncateOnWord(4, 'left'), '...', 'String#truncate | left | no split | 4');
+  equal(str.truncateOnWord(3, 'left'), '...', 'String#truncate | left | no split | 3');
+  equal(str.truncateOnWord(2, 'left'), '...', 'String#truncate | left | no split | 2');
+  equal(str.truncateOnWord(0, 'left'), '...', 'String#truncate | left | no split | 0');
+  equal(str.truncateOnWord(-100, 'left'), '...', 'String#truncate | left | no split | -100');
+
+
+  equal('123456'.truncate(2, 'left'), '...56', 'String#truncate | split | splitter not included left | 2');
+  equal('123456'.truncate(2, 'middle'), '1...6', 'String#truncate | split | splitter not included center | 2');
+  equal('123456'.truncate(2), '12...', 'String#truncate | split | splitter not included right | 2');
+
+  equal('123456'.truncateOnWord(2, 'left'), '...', 'String#truncate | splitter not included left | 2');
+  equal('123456'.truncateOnWord(2, 'middle'), '...', 'String#truncate | splitter not included center | 2');
+  equal('123456'.truncateOnWord(2), '...', 'String#truncate | splitter not included right | 2');
+
+  equal(str.truncate(28, 'left', '>>> '), 'Gotta be an entire sentence.', 'String#truncate | custom [splitter] | 28');
+  equal(str.truncate(23, 'left', '>>> '), '>>>  be an entire sentence.', 'String#truncate | custom [splitter] | 23');
+  equal(str.truncate(5, 'left', '>>> '), '>>> ence.', 'String#truncate | custom [splitter] | 5');
+  equal(str.truncate(4, 'left', '>>> '), '>>> nce.', 'String#truncate | split | custom [splitter] | 4');
+  equal(str.truncateOnWord(3, 'left', '>>> '), '>>> ', 'String#truncate | custom [splitter] | 4 | >>>');
+
+  equal(str.truncate(3, 'middle', '-'), 'Go-.', 'String#truncate | custom [splitter] | 4 | -');
+  equal(str.truncateOnWord(10, 'right', ''), 'Gotta be', 'String#truncate | empty [splitter]  | 10');
+
+  equal('Alpha Beta Gamma Delta Epsilon'.truncateOnWord(20, 'middle', ''), 'Alpha BetaEpsilon', 'String#truncate | Issue 311');
+
+  // String#assign
+
+  var obj1 = { first: 'Harry' };
+  var obj2 = { last: 'Potter' };
+
+  equal('Welcome, {name}.'.assign({ name: 'program' }), 'Welcome, program.', 'String#assign | basic functionality');
+  equal('Welcome, {1}.'.assign('program'), 'Welcome, program.', 'String#assign | numeric params');
+  equal('Welcome, {1}.'.assign({ name: 'program' }), 'Welcome, {1}.', 'String#assign | numeric params will be untouched if object passed');
+  equal('Welcome, {name}. You are {age} years old and have {points} points left.'.assign({ name: 'program', age: 21, points: 345 }), 'Welcome, program. You are 21 years old and have 345 points left.', 'String#assign | 1 hash');
+  equal('Welcome, {1}. You are {2} years old and have {3} points left.'.assign('program', 21, 345), 'Welcome, program. You are 21 years old and have 345 points left.', 'String#assign | 3 arguments');
+  equal('Welcome, {name}. You are {age} years old and have {3} points left.'.assign({ name: 'program' }, { age: 21 }, 345), 'Welcome, program. You are 21 years old and have 345 points left.', 'String#assign | complex');
+
+  equal('Hello, {first} {last}'.assign(obj1, obj2), 'Hello, Harry Potter', 'String#assign | passing 2 objects');
+  equal(obj1.first, 'Harry', 'String#assign | obj1 retains its properties');
+  equal(obj1.last, undefined, 'String#assign | obj1 is untampered');
+  equal(obj2.last, 'Potter', 'String#assign | obj2 retains its properties');
+  equal(obj2.first, undefined, 'String#assign | obj2 is untampered');
+
+  equal('Hello, {1}'.assign(''), 'Hello, ', 'String#assign | empty string as argument');
+  equal('Hello, {empty}'.assign({ empty: '' }), 'Hello, ', 'String#assign | empty string as object');
+
+  equal('{{1} {2}}'.assign(5,6), '{5 6}', 'String#assign | nested braces');
+  equal('{one {1} {2} two}'.assign(5,6), '{one 5 6 two}', 'String#assign | nested braces with strings outside');
+
+  equal('Hello, {first} {last}'.assign([obj1, obj2]), 'Hello, Harry Potter', 'String#assign | passing 2 objects in an array');
+
+
+});
level2/node_modules/sugar/test/environments/sugar/string_range.js
@@ -0,0 +1,122 @@
+
+test('String Ranges', function () {
+
+  var range;
+  var mergedRange;
+  var clonedRange;
+  var result;
+  var count;
+
+  range = String.range('k', 'o');
+
+  equal(range.toString(), 'k..o', 'String | Range | toString');
+  equal(range.isValid(), true, 'String | Range | isValid');
+  equal(range.span(), 5, 'String | Range | span');
+  equal(range.contains(), false, 'String | Range | contains undefined');
+  equal(range.contains('j'), false, 'String | Range | contains j');
+  equal(range.contains('k'), true, 'String | Range | contains k');
+  equal(range.contains('l'), true, 'String | Range | contains l');
+  equal(range.contains('m'), true, 'String | Range | contains m');
+  equal(range.contains('n'), true, 'String | Range | contains n');
+  equal(range.contains('o'), true, 'String | Range | contains o');
+  equal(range.contains('p'), false, 'String | Range | contains p');
+
+
+  mergedRange = range.union(String.range('h', 'm'));
+  equal(mergedRange.start, 'h', 'String | h..m | k..o | start');
+  equal(mergedRange.end, 'o', 'String | h..m | k..o | end');
+
+  mergedRange = range.union(String.range('a', 'd'));
+  equal(mergedRange.start, 'a', 'String | a..d | k..o | start');
+  equal(mergedRange.end, 'o', 'String | a..d | a..d | end');
+
+  mergedRange = range.union(String.range('a', 'c'));
+  equal(mergedRange.start, 'a', 'String | a..c | k..o | start');
+  equal(mergedRange.end, 'o', 'String | a..c | k..o | end');
+
+  mergedRange = range.union(String.range('a', 'a'));
+  equal(mergedRange.start, 'a', 'String | a..a | k..o | start');
+  equal(mergedRange.end, 'o', 'String | a..a | k..o | end');
+
+  mergedRange = range.union(String.range('a', 'z'));
+  equal(mergedRange.start, 'a', 'String | a..z | k..o | start');
+  equal(mergedRange.end, 'z', 'String | a..z | k..o | end');
+
+  mergedRange = range.union(String.range('', ''));
+  equal(mergedRange.isValid(), false, 'String | .. | k..o | is not valid');
+  equal(mergedRange.start, '', 'String | .. | k..o | start');
+  equal(mergedRange.end, 'o', 'String | .. | k..o | end');
+
+
+  mergedRange = range.intersect(String.range('a', 'd'));
+  equal(mergedRange.isValid(), false, 'String | a..d | k..o | invalid');
+
+  mergedRange = range.intersect(String.range('c', 'l'));
+  equal(mergedRange.start, 'k', 'String | c..l | k..o | start');
+  equal(mergedRange.end, 'l', 'String | c..l | k..o | end');
+
+  mergedRange = range.intersect(String.range('l', 'n'));
+  equal(mergedRange.start, 'l', 'String | l..n | k..o | start');
+  equal(mergedRange.end, 'n', 'String | l..n | k..o | end');
+
+  mergedRange = range.intersect(String.range('n', 'q'));
+  equal(mergedRange.start, 'n', 'String | n..q | k..o | start');
+  equal(mergedRange.end, 'o', 'String | n..q | k..o | end');
+
+  mergedRange = range.intersect(String.range('s', 'z'));
+  equal(mergedRange.isValid(), false, 'String | s..z | k..o | invalid');
+
+  clonedRange = range.clone();
+
+  equal(clonedRange.start, 'k', 'String | Range | cloned range start');
+  equal(clonedRange.end, 'o', 'String | Range | cloned range end');
+  equal(clonedRange === range, false, 'String | Range | cloned range should not be strictly equal');
+
+
+  count = 0;
+
+  result = range.every(1, function() {
+    count++;
+  });
+
+  equal(result, ['k','l','m','n','o'], 'String | Range | result should be an array');
+  equal(count, 5, 'String | Range | every 1');
+
+  count = 0;
+
+  result = range.every(2, function() {
+    count++;
+  });
+
+  equal(result, ['k','m','o'], 'String | Range every 2 | result should be an array');
+  equal(count, 3, 'String | Range every 2 | count');
+
+  count = 0;
+
+  result = range.every(function() {
+    count++;
+  });
+
+  equal(result, ['k','l','m','n','o'], 'String | Range | result should be an array');
+  equal(count, 5, 'String | Range | every 1');
+
+  equal(range.clamp('z'), 'o', 'String | Range#clamp | z');
+  equal(range.clamp('j'), 'k', 'String | Range#clamp | j');
+  equal(range.clamp('k'), 'k', 'String | Range#clamp | k');
+  equal(range.clamp('l'), 'l', 'String | Range#clamp | l');
+  equal(range.clamp('n'), 'n', 'String | Range#clamp | n');
+  equal(range.clamp('o'), 'o', 'String | Range#clamp | o');
+  equal(range.clamp('p'), 'o', 'String | Range#clamp | p');
+  equal(range.clamp(-1), -1, 'String | Range#clamp | -1');
+  equal(range.clamp(0),   0, 'String | Range#clamp | 0');
+  equal(range.clamp(1),   1, 'String | Range#clamp | 1');
+
+  range = String.range('o', 'k');
+
+  equal(range.toString(), 'o..k', 'String | Range | inverse | toString');
+  equal(range.isValid(), true, 'String | Range | inverse | isValid');
+
+  equal(String.range(NaN, NaN).toString(), 'Invalid Range', 'String | Range | invalid | toString');
+
+
+});
level2/node_modules/sugar/test/javascripts/date_helper.js
@@ -0,0 +1,86 @@
+
+dateEqual = function(a, b, message) {
+  var format = '{yyyy}-{MM}-{dd} {HH}:{mm}:{ss}{tz}'
+  var buffer = 50; // Number of milliseconds of "play" to make sure these tests pass.
+  if(typeof b == 'number') {
+    var d = new Date();
+    d.setTime(d.getTime() + b);
+    b = d;
+  }
+  var offset = Math.abs(a.getTime() - b.getTime());
+  equal(offset < buffer, true, message + ' | expected: ' + b.format(format) + ' got: ' + a.format(format), null, 1);
+}
+
+dateRangeEqual = function(a, b, message) {
+  dateEqual(a.start, b.start, message);
+  dateEqual(a.end, b.end, message);
+}
+
+getRelativeDate = function(year, month, day, hours, minutes, seconds, milliseconds) {
+  var d = this.getFullYear ? new Date(this.getTime()) : new Date();
+  var setYear  = d.getFullYear() + (year || 0)
+  var setMonth = d.getMonth() + (month || 0)
+  var setDate  = d.getDate() + (day || 0);
+  // Relative dates that have no more specificity than months only walk
+  // the bounds of months, they can't traverse into a new month if the
+  // target month doesn't have the same number of days.
+  if(day === undefined && month !== undefined) {
+    setDate = Math.min(setDate, new Date(setYear, setMonth).daysInMonth());
+    d.setDate(setDate);
+  }
+  d.setFullYear(setYear);
+  d.setMonth(setMonth);
+  d.setDate(setDate);
+  d.setHours(d.getHours() + (hours || 0));
+  d.setMinutes(d.getMinutes() + (minutes || 0));
+  d.setSeconds(d.getSeconds() + (seconds || 0));
+  d.setMilliseconds(d.getMilliseconds() + (milliseconds || 0));
+  return d;
+}
+
+getUTCDate = function(year, month, day, hours, minutes, seconds, milliseconds) {
+  var d = new Date();
+  if(year) d.setFullYear(year);
+  d.setUTCDate(15); // Pre-emptively preventing a month overflow situation
+  d.setUTCMonth(month === undefined ? 0 : month - 1);
+  d.setUTCDate(day === undefined ? 1 : day);
+  d.setUTCHours(hours === undefined ? 0 : hours);
+  d.setUTCMinutes(minutes === undefined ? 0 : minutes);
+  d.setUTCSeconds(seconds === undefined ? 0 : seconds);
+  d.setUTCMilliseconds(milliseconds === undefined ? 0 : milliseconds);
+  return d;
+}
+
+getDateWithWeekdayAndOffset = function(weekday, offset, hours, minutes, seconds, milliseconds) {
+  var d = new Date();
+  if(offset) d.setDate(d.getDate() + offset);
+  d.setDate(d.getDate() + (weekday - d.getDay()));
+  d.setHours(hours || 0);
+  d.setMinutes(minutes || 0);
+  d.setSeconds(seconds || 0);
+  d.setMilliseconds(milliseconds || 0);
+  return d;
+}
+
+getDaysInMonth = function(year, month) {
+  return 32 - new Date(year, month, 32).getDate();
+}
+
+getWeekdayFromDate = function(d, utc) {
+  var day = utc ? d.getUTCDay() : d.getDay();
+  return ['sunday','monday','tuesday','wednesday','thursday','friday','saturday','sunday'][day];
+}
+
+getMonthFromDate = function(d, utc) {
+  var month = utc ? d.getUTCMonth() : d.getMonth();
+  return ['january','february','march','april','may','june','july','august','september','october','november','december'][month];
+}
+
+getHours = function(num) {
+  return Math.floor(num < 0 ? 24 + num : num);
+}
+
+
+toUTC = function(d) {
+  return d.addMinutes(-d.getTimezoneOffset());
+}
level2/node_modules/sugar/test/javascripts/object_helper.js
@@ -0,0 +1,54 @@
+
+objectPrototypeMethods = {};
+sugarEnabledMethods = [
+  'isArray','isBoolean','isDate','isFunction','isNumber','isString','isRegExp','isNaN','isObject',                       // Type methods
+  'keys','values','select','reject','each','merge','isEmpty','equals','clone','watch','tap','has','toQueryString',       // Hash methods
+  'any','all','none','count','find','findAll','isEmpty','sum','average','min','max','least','most','map','reduce','size' // Enumerable methods
+];
+
+rememberObjectProtoypeMethods = function() {
+  for(var m in Object.prototype) {
+    if(!Object.prototype.hasOwnProperty(m)) continue;
+    objectPrototypeMethods[m] = Object.prototype[m];
+  }
+}
+
+restoreObjectPrototypeMethods = function() {
+  // Cannot iterate over Object.prototype's methods if they've been defined in a modern browser
+  // that implements defineProperty, so we'll have to set back the known ones that have been overridden.
+  sugarEnabledMethods.forEach(function(name){
+    // This is a cute one. Checking for the name in the hash isn't enough because it itself is
+    // an object that has been extended, so each and every one of the methods being held here are being
+    // perfectly shadowed!
+    if(objectPrototypeMethods.hasOwnProperty(name) && objectPrototypeMethods[name]) {
+      Object.prototype[name] = objectPrototypeMethods[name];
+    } else {
+      delete Object.prototype[name];
+    }
+  });
+}
+
+testIterateOverObject = function(obj, fn) {
+  var key;
+  for(key in obj) {
+    if(!Object.hasOwnProperty(key)) continue;
+    fn.call(obj, key, obj[key]);
+  }
+}
+
+testClassAndInstance = function(name, obj, args, expected, message) {
+  if(!testIsArray(args)) {
+    args = [args];
+  }
+  equal(Object[name].apply(obj, [obj].concat(args)), expected, message);
+  if(Object.extended) {
+    extended = Object.extended(obj);
+    equal(extended[name].apply(extended, args), expected, message + ' | On extended object');
+  }
+}
+
+assertQueryStringGenerated = function(obj, args, expected, message) {
+  expected = expected.replace(/\[/g, '%5B').replace(/\]/g, '%5D');
+  testClassAndInstance('toQueryString', obj, args, expected, message);
+}
+
level2/node_modules/sugar/test/javascripts/overrides.js
@@ -0,0 +1,68 @@
+
+var overridingFunction = function() {
+  return 'Something totally unexpected!';
+}
+
+Object.map      = overridingFunction;
+Object.each     = overridingFunction;
+Object.any      = overridingFunction;
+Object.sum      = overridingFunction;
+Object.isObject = overridingFunction;
+Object.isNumber = overridingFunction;
+Object.extend   = overridingFunction;
+
+Array.create              = overridingFunction;
+Array.prototype.findAll   = overridingFunction;
+Array.prototype.all       = overridingFunction;
+Array.prototype.add       = overridingFunction;
+Array.prototype.groupBy   = overridingFunction;
+
+String.extend              = overridingFunction;
+String.range               = overridingFunction;
+String.prototype.each      = overridingFunction;
+String.prototype.to        = overridingFunction;
+String.prototype.pad       = overridingFunction;
+String.prototype.insert    = overridingFunction;
+String.prototype.pluralize = overridingFunction;
+String.prototype.hankaku   = overridingFunction;
+
+Number.random                  = overridingFunction;
+Number.range                   = overridingFunction;
+Number.prototype.log           = overridingFunction;
+Number.prototype.ceil          = overridingFunction;
+Number.prototype.round         = overridingFunction;
+Number.prototype.floor         = overridingFunction;
+Number.prototype.abs           = overridingFunction;
+Number.prototype.upto          = overridingFunction;
+Number.prototype.downto        = overridingFunction;
+Number.prototype.secondsBefore = overridingFunction;
+Number.prototype.duration      = overridingFunction;
+
+RegExp.escape             = overridingFunction;
+RegExp.prototype.getFlags = overridingFunction;
+RegExp.prototype.setFlags = overridingFunction;
+
+Function.prototype.lazy     = overridingFunction;
+Function.prototype.throttle = overridingFunction;
+Function.prototype.debounce = overridingFunction;
+
+
+Date['ISO8601_DATE'] = 'none!';
+Date.create               = overridingFunction;
+Date.range                = overridingFunction;
+Date.prototype.set        = overridingFunction;
+Date.prototype.iso        = overridingFunction;
+Date.prototype.endOfDay   = overridingFunction;
+Date.prototype.long       = overridingFunction;
+Date.prototype.isToday    = overridingFunction;
+Date.prototype.addMinutes = overridingFunction;
+
+
+
+
+// ES6
+
+// Array.prototype.find       = overridingFunction;
+// Array.prototype.findIndex  = overridingFunction;
+// String.prototype.repeat    = overridingFunction;
+// String.prototype.normalize = overridingFunction;
level2/node_modules/sugar/test/javascripts/setup.js
@@ -0,0 +1,365 @@
+if(typeof environment == 'undefined') environment = 'default'; // Override me!
+
+// The scope when none is set.
+nullScope = (function(){ return this; }).call();
+
+var results;
+var currentTest;
+var moduleName;
+var moduleSetupMethod;
+var moduleTeardownMethod;
+
+var syncTestsRunning;
+var asyncTestsRunning;
+
+// Capturing the timers here b/c Mootools (and maybe other frameworks) may clear a timeout that
+// it kicked off after this script is loaded, which would throw off a simple incrementing mechanism.
+var capturedTimers = [];
+var testStartTime;
+var runtime;
+
+
+// Arrays and objects must be treated separately here because in IE arrays with undefined
+// elements will not pass the .hasOwnProperty check. For example [undefined].hasOwnProperty('0')
+// will report false.
+var arrayEqual = function(one, two) {
+  var i, result = true;
+  if(!one || !two) {
+    return false;
+  }
+  testArrayEach(one, function(a, i) {
+    if(!testIsEqual(one[i], two[i])) {
+      result = false;
+    }
+  });
+  return result && one.length === two.length;
+}
+
+var sortOnStringValue = function(arr) {
+  return arr.sort(function(a, b) {
+    var aType = typeof a;
+    var bType = typeof b;
+    var aVal = String(a);
+    var bVal = String(b);
+    if(aType != bType) {
+      return aType < bType;
+    }
+    if(aVal === bVal) return 0;
+    return a < b ? -1 : 1;
+  });
+}
+
+var testArrayIndexOf = function(arr, obj) {
+  for(var i = 0; i < arr.length; i++) {
+    if(arr[i] === obj) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+testCloneObject = function(obj) {
+  var result = {}, key;
+  for(key in obj) {
+    if(!obj.hasOwnProperty(key)) continue;
+    result[key] = obj[key];
+  }
+  return result;
+}
+
+testArrayEach = function(arr, fn, sparse) {
+  var length = arr.length, i = 0;
+  while(i < length) {
+    if(!(i in arr)) {
+      return testIterateOverSparseArray(arr, fn, i);
+    } else if(fn.call(arr, arr[i], i, arr) === false) {
+      break;
+    }
+    i++;
+  }
+}
+
+testIterateOverSparseArray = function(arr, fn, fromIndex) {
+  var indexes = [], i;
+  for(i in arr) {
+    if(testIsArrayIndex(arr, i) && i >= fromIndex) {
+      indexes.push(parseInt(i));
+    }
+  }
+  testArrayEach(indexes.sort(), function(index) {
+    return fn.call(arr, arr[index], index, arr);
+  });
+  return arr;
+}
+
+testIsArrayIndex = function(arr, i) {
+  return i in arr && (i >>> 0) == i && i != 0xffffffff;
+}
+
+testIsArray = function(obj) {
+  return Object.prototype.toString.call(obj) === '[object Array]';
+}
+
+testPadNumber = function(val, place, sign) {
+  var num = Math.abs(val);
+  var len = Math.abs(num).toString().replace(/\.\d+/, '').length;
+  var str =  new Array(Math.max(0, place - len) + 1).join('0') + num;
+  if(val < 0 || sign) {
+    str = (val < 0 ? '-' : '+') + str;
+  }
+  return str;
+}
+
+testCapitalize = function(str) {
+  return str.slice(0,1).toUpperCase() + str.slice(1);
+}
+
+var objectEqual = function(one, two) {
+  var onep = 0, twop = 0, key;
+  if(one && two) {
+    for(key in one) {
+      if(!one.hasOwnProperty(key)) continue;
+      onep++;
+      if(!testIsEqual(one[key], two[key])) {
+        return false;
+      }
+    }
+    for(key in two) {
+      if(!two.hasOwnProperty(key)) continue;
+      twop++;
+    }
+  }
+  return onep === twop && String(one) === String(two);
+}
+
+var testIsEqual = function(one, two) {
+
+  var type, klass;
+
+  type = typeof one;
+
+  if(type === 'string' || type === 'boolean' || one == null) {
+    return one === two;
+  } else if(type === 'number') {
+    return typeof two === 'number' && ((isNaN(one) && isNaN(two)) || one === two);
+  }
+
+  klass = Object.prototype.toString.call(one);
+
+  if(klass === '[object Date]') {
+    return one.getTime() === two.getTime();
+  } else if(klass === '[object RegExp]') {
+    return String(one) === String(two);
+  } else if(klass === '[object Array]') {
+    return arrayEqual(one, two);
+  } else if((klass === '[object Object]' || klass === '[object Arguments]') && ('hasOwnProperty' in one) && type === 'object') {
+    return objectEqual(one, two);
+  } else if(klass === '[object Number]' && isNaN(one) && isNaN(two)) {
+    return true;
+  }
+
+  return one === two;
+}
+
+var testIsClass = function(obj, klass) {
+  return Object.prototype.toString.call(obj) === '[object ' + klass + ']';
+}
+
+var addFailure = function(actual, expected, message, stack, warning) {
+  var meta = getMeta(stack);
+  currentTest.failures.push({
+    actual: actual,
+    expected: expected,
+    message: message,
+    file: meta.file,
+    line: meta.line,
+    col: meta.col,
+    warning: !!warning
+  });
+}
+
+var getMeta = function(stack) {
+  var level = 4;
+  if(stack !== undefined) {
+    level += stack;
+  }
+  var e = new Error();
+  if(!e.stack) {
+    return {};
+  }
+  var s = e.stack.split(/@|^\s+at/m);
+  var match = s[level].match(/(.+\.js):(\d+)(?::(\d+))?/);
+  if(!match) match = [];
+  return { file: match[1], line: match[2], col: match[3] };
+}
+
+var checkCanFinish = function() {
+  if(!syncTestsRunning && asyncTestsRunning === 0) {
+    testsFinished();
+  }
+}
+
+var testsStarted = function() {
+  if(typeof testsStartedCallback != 'undefined') {
+    testsStartedCallback(results);
+  }
+  if(environment == 'node') {
+    console.info('\n----------------------- STARTING TESTS ----------------------------\n');
+  }
+  testStartTime = new Date();
+}
+
+var testsFinished = function() {
+  runtime = new Date() - testStartTime;
+  if(typeof testsFinishedCallback != 'undefined') {
+    testsFinishedCallback(results, runtime);
+  }
+  if(environment == 'node') {
+    this.totalFailures = 0
+    // displayResults will increment totalFailures by 1 for each failed test encountered
+    displayResults();
+    // will exit now setting the status to the number of failed tests
+    process.exit(this.totalFailures);
+  }
+  results = [];
+}
+
+var displayResults = function() {
+  var i, j, failure, totalAssertions = 0, totalFailures = 0;
+  for (i = 0; i < results.length; i += 1) {
+    totalAssertions += results[i].assertions;
+    this.totalFailures += results[i].failures.length;
+    for(j = 0; j < results[i].failures.length; j++) {
+      failure = results[i].failures[j];
+      console.info('\n'+ (j + 1) + ') Failure:');
+      console.info(failure.message);
+      console.info('Expected: ' + JSON.stringify(failure.expected) + ' but was: ' + JSON.stringify(failure.actual));
+      console.info('File: ' + failure.file + ', Line: ' + failure.line, ' Col: ' + failure.col + '\n');
+    }
+  };
+  var time = (runtime / 1000);
+  console.info(results.length + ' tests, ' + totalAssertions + ' assertions, ' + this.totalFailures + ' failures, ' + time + 's\n');
+}
+
+test = function(name, fn) {
+  if(moduleSetupMethod) {
+    moduleSetupMethod();
+  }
+  if(!results) {
+    results = [];
+    syncTestsRunning = true;
+    asyncTestsRunning = 0;
+    testsStarted();
+  }
+  currentTest = {
+    name: name,
+    assertions: 0,
+    failures: []
+  };
+  try {
+    fn.call();
+  } catch(e) {
+    console.info(e.stack);
+  }
+  results.push(currentTest);
+  if(moduleTeardownMethod) {
+    moduleTeardownMethod();
+  }
+}
+
+var removeCapturedTimer = function(remove) {
+  var result = [], timer;
+  for (var i = 0, len = capturedTimers.length; i < len; i++) {
+    timer = capturedTimers[i];
+    if(timer !== remove) {
+      result.push(timer);
+    }
+  };
+  capturedTimers = result;
+};
+
+testModule = function(name, options) {
+  moduleName = name;
+  moduleSetupMethod = options.setup;
+  moduleTeardownMethod = options.teardown;
+}
+
+equal = function(actual, expected, message, exceptions, stack) {
+  exceptions = exceptions || {};
+  if(environment in exceptions) {
+    expected = exceptions[environment];
+  }
+  currentTest.assertions++;
+  if(!testIsEqual(actual, expected)) {
+    addFailure(actual, expected, message, stack);
+  }
+}
+
+notEqual = function(actual, expected, message, exceptions) {
+  equal(actual !== expected, true, message + ' | strict equality', exceptions, 1);
+}
+
+equalWithWarning = function(expected, actual, message) {
+  if(expected != actual) {
+    addFailure(actual, expected, message, null, true);
+  }
+}
+
+equalWithMargin = function(actual, expected, margin, message) {
+  equal((actual > expected - margin) && (actual < expected + margin), true, message, null, 1);
+}
+
+// Array content is equal, but order may differ
+arrayEquivalent = function(a, b, message) {
+  equal(sortOnStringValue(a), sortOnStringValue(b), message);
+}
+
+raisesError = function(fn, message, exceptions) {
+  var raised = false;
+  try {
+    fn.call();
+  } catch(e) {
+    raised = true;
+  }
+  equal(raised, true, message, exceptions, 1);
+}
+
+skipEnvironments = function(environments, test) {
+  if(testArrayIndexOf(environments, environment) === -1) {
+    test.call();
+  }
+}
+
+syncTestsFinished = function() {
+  syncTestsRunning = false;
+  checkCanFinish();
+}
+
+// This method has 2 benefits:
+// 1. It gives asynchronous functions their own scope so vars can't be overwritten later by other asynchronous functions
+// 2. It runs the tests after the CPU is free decreasing the chance of timing based errors.
+async = function(fn) {
+  asyncTestsRunning++;
+  setTimeout(function() {
+    fn();
+  }, 100);
+}
+
+asyncFinished = function() {
+  asyncTestsRunning--;
+  checkCanFinish();
+}
+
+if(typeof console === 'undefined') {
+  var consoleFn = function() {
+    var messages = Array.prototype.slice.call(arguments);
+    messages = messages.map(function(arg) {
+      return String(arg);
+    })
+    $('<p/>').text(messages.join(',')).appendTo(document.body);
+  }
+  console = {
+    log: consoleFn,
+    info: consoleFn
+  }
+}
level2/node_modules/sugar/tmp/array/to_sentence.js
@@ -0,0 +1,1 @@
+Array.extend({toSentence:function(b){var a="";if(this.length===0)return a;if(typeof b!=="string")b="and";a=" "+b+" ";b=" "+b+" ";switch(this.length){case 1:a=this[0];break;case 2:a=this.join(a);break;default:a=this.first(this.length-1).join(", ")+b+this.last()}return a}});
level2/node_modules/sugar/tmp/string/namespace.js
@@ -0,0 +1,1 @@
+(function(b){String.extend({namespace:function(a){a=a||b;this.split(".").every(function(d,c){return!!(a=a[c])});return a}})})(this);
level2/node_modules/sugar/tmp/string/split.js
@@ -0,0 +1,2 @@
+String.extend({split:function(b,c){var d=[],g=0,e;e=function(f,i){var h=f.toString().match(/[^/]*$/)[0];if(i)h=(h+i).split("").sort().join("").replace(/([gimy])\1+/g,"$1");return h}(b,"g");b=RegExp(b.source,e);var j,a,k;RegExp.a||(j=RegExp("^"+b.source+"$(?!\\s)",e));if(c===undefined||c<0)c=Infinity;else{c=Math.floor(c);if(!c)return[]}for(;a=b.exec(this);){e=a.index+a[0].length;if(e>g){d.push(this.slice(g,a.index));!RegExp.a&&a.length>1&&a[0].replace(j,function(){for(var f=1;f<arguments.length-2;f++)if(arguments[f]===
+undefined)a[f]=undefined});a.length>1&&a.index<this.length&&array.prototype.push.apply(d,a.slice(1));k=a[0].length;g=e;if(d.length>=c)break}b.lastIndex===a.index&&b.lastIndex++}if(g===this.length){if(k||!b.test(""))d.push("")}else d.push(this.slice(g));return d.length>c?d.slice(0,c):d}},function(b){return Object.prototype.toString.call(b)==="[object RegExp]"});
level2/node_modules/sugar/.npmignore
@@ -0,0 +1,16 @@
+**/*.html
+**/*.min.js
+component.json
+docs/
+script/
+test/images/
+test/stylesheets/
+test/javascripts/jquery-1.7.1.js
+test/javascripts/environment.js
+test/javascripts/tooltip.js
+test/javascripts/suite.js
+test/environments/underscore/
+test/environments/prototype/
+test/environments/mootools/
+release/copyright.txt
+release/precompiled/
level2/node_modules/sugar/.travis.yml
@@ -0,0 +1,8 @@
+language: node_js
+node_js:
+  - "0.10"
+  - "0.8"
+  - "0.6"
+branches:
+  only:
+    - master
level2/node_modules/sugar/bower.json
@@ -0,0 +1,17 @@
+{
+  "name": "sugar",
+  "version": "1.4.1",
+  "main": "./release/sugar.min.js",
+  "ignore": [
+    "/docs",
+    "/lib",
+    "/script",
+    "/test",
+    "/performance",
+    ".gitignore",
+    ".npmignore",
+    ".travis.yml",
+    "package.json"
+  ],
+  "dependencies": {}
+}
level2/node_modules/sugar/CAUTION.md
@@ -0,0 +1,417 @@
+## Caution!
+
+Here you will find points of caution when updating Sugar to a new version.
+Think of it as a pruned Changelog with the most front-facing changes surfaced.
+If your code breaks on update check here first! Read all the ones that are greater than the version you are migrating from.
+
+### Note about versions < 1.3.9
+
+Version 1.4.0 improves future-compatibility by ensuring that browser updates do not cause breakages going forward. Upgrading is highly recommended, however as there are also many API changes, [this patch](https://raw.github.com/andrewplummer/Sugar/master/lib/patches/sugar-es6-patch.min.js) was created for older versions. Just drop it in after the main script.
+
+
+
+v1.4.1+
+=======
+
+- Level: Minor
+  - `Object.select` and `Object.reject` now will match values when passed an object. This means that if you pass `{foo:'bar'}`, it will no longer match if the value of `foo` in your object is not `bar`. Previously it would match simply if the key existed.
+
+
+v1.4.0+
+=======
+
+- Level: Major
+  - `pad`, `padLeft`, and `padRight` now pad to the exact character. This means that `padLeft(20)` will produce a string exactly 20 characters long, instead of adding 20 characters worth of padding to the left side of the string as before. You can use `String#repeat` for the same effect as the old functionality.
+
+- Level: Major
+  - `Object.fromQueryString` now does not cast values by default. This means that all values in the resulting object are strings, unless `castBoolean` is true, which will cast boolean values of "true" and "false" only. Digits are no longer cast to numbers at all. Additionally, the "deep" argument is now removed. Deep parameters will always be parsed if they can be.
+
+- Level: Major
+  - `Function#lazy` now has different arguments. `limit` is now the third argument with `immediate` taking its place as second. Additionally `immediate` -- which determines whether lazy functions are executed immediately then lock or lock then execute after a timeout -- is now false by default.
+
+- Level: Major
+  - Date range methods `eachDay`, `eachMonth`, etc. are deprecated in favor of the syntax `every("day")`, etc.
+
+- Level: Major
+  - Date range method `duration` is deprecated in favor of `span`. Additionally it will add 1 to the duration to include the starting number itself. In effect for date ranges this means that `duration` will be 1 ms longer.
+
+- Level: Major
+  - `Range#step` alias was removed. Use `Range#every` instead.
+
+- Level: Major
+  - Date formatting tokens `z` and `zz` are now `Z` and `ZZ`. Additionally `zzz` was removed.
+
+- Level: Moderate
+  - `Array#find` now works according to the ES6 spec. This means that it will no longer take a `fromIndex` or `loop` arguments. Instead, the second argument is the context in which the function matcher will be run. If you need the previous functionality, use `Array#findFrom` and `Array#findIndexFrom` instead.
+
+- Level: Moderate
+  - `Array.sortBy` now performs a natural sort by default. This means numbers (any consecutive numbers, so this will include currency formatting, etc.) will sort as numbers, (2 before 100). If you do not want this behavior, set the flag `Array.AlphanumericSortNatural` to `false`.
+
+- Level: Moderate
+  - `Object.clone` now will error if being called on a user-created class instance or host object (DOM Elements, Events, etc). A number of complex issues tie in here, but in the end it is unreliable to call `clone` on an object that is not a standard data types as 1) hidden properties cannot be cloned 2) the original arguments to the constructor cannot be known 3) even if they could be known the issue of whether or not the constructor should actually be called again is not clear.
+
+- Level: Moderate
+  - The `split` argument was removed from `String#truncate`. For truncating without splitting words, use `String#truncateOnWord` instead. Argument position is adjusted accordingly.
+
+- Level: Moderate
+  - Class instances are now internally matched by reference only. This means that `Object.equal(new Person, new Person)` will now be `false`. This was in fact the original intended behavior but a bug had not been closed here leading to it not actually being `false`. Although a case can be made for matching class instances by value, in the end it is too expensive and tricky to distinguish them from host objects, which should never be matched by value. Instead it is better to check for equality of class instances on a unique identifier or the like.
+
+- Level: Moderate
+  - `Object.isObject` will now no longer return true for class instances for the same reasons listed above. This also was intended behavior but was defective.
+
+- Level: Moderate
+  - `String#normalize` is now deprecated, but still available as a separate script in the `lib/plugins` directory.
+
+- Level: Moderate
+  - Date ranges are now their own package (the "range" package), are not dependent on the Date package, and work on numbers and strings as well.
+
+- Level: Minor
+  - Enumerable methods on object will now coerce primitive types. This means that `Object.findAll('foo')` will now treat `'foo'` as `new String('foo')`. This is reversed from the previous behavior which would error on primitive types and coerce objects to primitive types where possible.
+
+- Level: Minor
+  - `String#capitalize` passing the `all` flag now will not capitalize after an apostrophe.
+
+- Level: Very Minor
+  - Date ranges that have an end that is less than the start are now no longer considered invalid, and can be iterated across in exactly the same manner. This means that ranges can now be iterated in reverse and .start and .end are no longer equivalent to .min and .max.
+
+- Level: Very Minor
+  - Removed `Number#upto` and `Number#downto` will now work on inverse ranges. In other words (1).downto(5) if represented as an array will now produce [1,2,3,4,5] even though 1 is less than 5 and the operator was "downto". It will also step through the range accordingly.
+
+- Level: Very Minor
+  - Passing a regex to array matching methods like `findAll` will now match it directly against the element in the array, regardless of whether or not the matched element is a string or not. This makes the logic more straightforward but it also means that it will stringify the element before attempting to match. If, for example, you have instances of classes in the array and the regex is /t/, the /t/ will return true for that element as it will match the stringified "[object Object]" of the instance, which is likely not what you want, so caution is needed here.
+
+- Level: Very Minor
+  - Passing `null` to `.map` will now have the same effect as `undefined` (or no arguments), that is, no mapping will occur. This will apply to any method making use of the internal `transformArgument`, so `Array#min`, `Array#max`, and `Array#groupBy` are affected as well.
+
+- Level: Very Minor
+  - `String#pad/padLeft/padRight` will now raise an error on padding to a negative number. Conversely, they will no longer raise an error on undefined/null/NaN.
+
+
+v1.3.9+
+=======
+
+- Level: Major
+  - Removed `String#namespace`.
+
+
+v1.3.8+
+=======
+
+- Level: Major
+  - Renamed `Date#getWeek` and `Date#setWeek` to `Date#getISOWeek` and `Date#setISOWeek`.
+
+- Level: Very Minor
+  - Object.clone will now preserve a date's internal utc flag when set.
+
+
+v1.3.7+
+=======
+
+- Level: Major
+  - `String#startsWith` and `String#endsWith` now accept different arguments to better align them with the Harmony proposal of the same name. The second argument is now the "position" that limits where the string starts/ends, and the "case" flag indicating case sensitivity is now pushed to the 3rd argument.
+
+- Level: Major
+  - Enumerable object methods are now included when using `Object.extend()` making it that much more dangerous, especially as generic methods like `count` are now defined on the Object prototype. If you use this method, make sure you are in the habit of using `hasOwnProperty` when checking for presence inside a hash (probably not a bad idea anyway). Also note that Sugar itself has a number of areas that may exhibit unexpected behavior when this method is applied. Please report if you find any.
+
+- Level: Moderate
+  - Aliases on dates such as `daysAgo` will now count "past" an integer instead of rounding. This means that `Date.create('23 hours ago').daysAgo()` will now be `0`. There is however a small margin of error (roughly 0.1%) that will trigger a round up, which is higher for months, which have a more vague definition and a higher margin for error.
+
+
+v1.3.6+
+=======
+
+- Level: Very Minor
+  - Float values should be properly parsed in `Object.fromQueryString`, meaning IP addresses and the like will now parse as strings instead of truncated numbers.
+
+- Level: Very Minor
+  - NaN is no longer true for `isOdd`.
+
+- Level: Very Minor
+  - Date parsing now only allows a maximum of 59 for minutes and seconds.
+
+
+v1.3.5+
+=======
+
+- Level: Very Minor
+  - `Array.create` now properly creates arrays from objects.
+
+
+v1.3.2+
+=======
+
+- Level: Minor
+  - `Date.create` will no longer set the UTC flag on dates created from an ISO string with the "Z" flag. This can be considered a bug introduced in the last release. The "Z" flag indicates that a date is in UTC time, but should not serve as an indication that the date should further be manipulated as UTC, only as a cue when parsing. If you want the date to actually behave as UTC (internally call UTC methods), then you need to explicitly set with `Date#utc(true)` now.
+
+
+v1.3.1+
+=======
+
+
+- Level: Major
+  - Array methods that allow fuzzy matching on an object (`findAll`, `filter`, `some`, etc.) as well as `unique`, `intersect`, `union`, and `subtract`, will now match by reference unless certain conditions are met. Most notably this means that arrays of functions as well as arrays of host objects (DOM elements, etc.) will now only match elements that are strictly true by reference (`===`). If you are using arrays of host objects or functions (event handlers and the like), use caution upgrading. Other kinds of arrays such as primitives (strings, numbers, etc) as well as object literals and instances of user-defined objects should not be affected.
+
+- Level: Major
+  - `Date#toUTC` deprecated. Previously, this method would subtract the timezone offset of the date, providing a pseudo-utc date. This was a very primitive way of handling the challenge of manipulating utc dates and had drawbacks such as subsequent date manipulations resetting to a localized time. This is now deprecated in favor of `Date#utc`, which simply sets an internal flag that will tell Sugar to use utc-based date methods or not. `Date#utc` will NOT manipulate the time in any way. To create a utc-based date that is set to utc time, a flag has been added to `Date#create`, and other creation methods like `Date#future` and `Date#past` to set the utc flag before parsing out the date.
+
+- Level: Major
+  - `Date#setUTC` deprecated. Instead, simply set the utc flag using `Date#utc` or passing `true` as the third argument to `Date#create`. After this point, utc-based methods will be used internally, making this method unnecessary.
+
+- Level: Major
+  - `Date#setUTCWeek` deprecated. Set the utc flag and use `Date#setWeek` instead.
+
+- Level: Major
+  - `Date#getUTCWeek` deprecated. Set the utc flag and use `Date#getWeek` instead.
+
+- Level: Major
+  - `Date#setUTCWeekday` deprecated. Set the utc flag and use `Date#setWeekday` instead.
+
+- Level: Minor
+  - `Date#clone` will now preserve the utc flag.
+
+- Level: Minor
+  - Array methods matching an empty object `{}` will now return true instead of false against another empty object.
+
+- Level: Very Minor
+  - `Date#setWeekday` now returns a timestamp instead of `undefined`.
+
+
+v1.3+
+=======
+
+
+- Level: Major
+  - Date locales are now moved into a separate package. This means that now with the default download package, setting the date locale to anything other than English will throw an error. If you require locales other than English, please include them from [the customize page](http://sugarjs.com/customize).
+
+- Level: Major
+  - `Array#min`, `Array#max`, `Array#least`, and `Array#most` now return a single value instead of an array. If you need to get "all min" or "all max" values, then pass `true` as the second argument to these methods.
+
+- Level: Major
+  - `Array#has` is deprecated. Use `Array#some` or `Array#any` instead.
+
+- Level: Major
+  - `String#toDate` is deprecated. Use `Date.create` instead.
+
+- Level: Major
+  - `String#add`, `String#insert`, `Array#add`, and `Array#insert` now consider negative indexes to be the same as built-in `Array#slice`. This means that adding 'd' to 'abc' at index -1 now results in 'abdc' instead of 'abcd'.
+
+- Level: Major
+  - Date parsing is now scoped by locale. Previously setting a locale would add all its formats into a common pool that would match from that point on. Now the locale must be either be set beforehand `Date.setLocale('fr')` or explicitly passed `Date.create('...', 'fr')`. The exception to this are numeric formats which are core formats and will parse in any locale.
+
+- Level: Minor
+  - Extended objects that are otherwise identical to non-extended counterparts are now considered equal by `Object.equal`.
+
+- Level: Minor
+  - `Object.isEmpty` will now error when passed a non-object. This includes object versions of primitives like strings, numbers, and booleans.
+
+- Level: Minor
+  - Default date output format `date.format()` now includes the time. Previously this was just the date.
+
+- Level: Minor
+  - `Array#groupBy` no longer returns extended objects. Use `Object.extended` on the result if you need this.
+
+- Level: Minor
+  - Unrecognized locale codes will now simply produce an invalid date instead of throwing an error. Likewise, fully qualified locale codes ('it_IT') will fall back to 2 character codes ('it') before giving up.
+
+- Level: Very Minor
+  - Array methods using fuzzy matching (findAll, remove, etc.) now match instances of classes as well as plain objects.
+
+- Level: Very Minor
+  - `String#capitalize` with the first argument as `true` (capitalize all words) will now capitalize any letter whose previous letter could not be capitalized. Previously only words after spaces were counted.
+
+
+
+v1.2.5+
+=======
+
+- Level: Major
+  - `String#truncate` arguments changed. `ellipsis` (`"..."` by default) is now the last argument of four. Second argument is now `split` which is true by default, so the method will behave like standard truncate methods by default. `from` added as the third parameter and determines where to truncate. Can be `"right"` (default), `"left"`, or `"middle"`.
+
+- Level: Major
+  - `Function#debounce` no longer has an argument `wait`. Equivalent function is now `Function#throttle` (no arguments). `fn.debounce(100, false)` is now `fn.throttle(100)`.
+
+- Level: Minor
+  - `Object.isObject` now returns `true` for extended objects.
+
+
+v1.2.4+
+=======
+
+- Level: Minor
+  - Object.equal and its instance method form "equals" is now considered "egal". This means that, for example, new String('w') will NOT be equal to 'w', etc. Previously equal was nearly egal, but not quite, so this should only affect very small edge cases. This now means that Sugar will match Underscore's _.isEqual method 100% of the time with the only exception being custom "isEqual" methods that Underscore checks explicitly.
+
+- Level: Very Minor
+  - Object.merge will now merge properties of non-objects like functions.
+
+
+
+v1.2.3+
+=======
+
+- Level: Major
+  - String#compare, Number#compare, and Date#compare are deprecated
+
+- Level: Major
+  - Object.merge params are swapped. `resolve` is now the 4th parameter and `deep` is now 3rd.
+  - When using extended objects, this are now 2nd and 3rd parameters. `deep` is now false by default.
+
+- Level: Minor
+  - Array#sortBy now exhibits more sensible behavior when sorting on strings.
+
+
+
+v1.2.2+
+=======
+
+- Level: Very Minor
+  - Extended objects now keep their "Hash" constructor (which is internal) so they no longer have `Object` as their constructor. If you are doing instanceof checks here this may break (which you shouldn't be doing anyway)
+
+
+
+v1.2+
+=====
+
+- Level: Major
+  - Array methods now use "fuzzy object matching" when passing an object. As an example, `arr.find({ foo: 'bar' })` would previously only match an identical object, now it will match any object whose `foo` property is `bar`. Additionally, note that passing regexes and functions will be used to match (regexes match against strings, functions are callbacks that return `true`/`false`), not compared directly. This applies to the following array methods: `every`, `some`, `filter`, `find`, `findAll`, `findIndex`, `remove`, `none`, `count`, and `exclude`.
+
+- Level: Major
+  - Object.sugar renamed to Object.restore. However Object.sugar() equivalent is now Object.extend().
+
+- Level: Minor
+  - Object.merge now also merges undefined properties.
+
+
+
+v1.1.2+
+=======
+
+- Level: Minor
+  - Function#after will now call a method immediately if the passed value is `0`.
+
+- Level: Very minor
+  - Object.isEmpty will now properly report `false` for primitive types like `null` and empty strings.
+
+
+
+v1.1.1+
+=======
+
+- Level: Major
+  - Object.merge no longer merges an arbitrary number of arguments. Use extended objects and chaining instead.
+
+- Level: Minor
+  - Array#remove and Array#exclude now no longer accept an arbitrary number of arguments. Pass only 1 argument to these methods (may be a nested array).
+
+
+
+v1.1+
+=====
+
+- Level: Major
+  - Object.equals renamed to Object.equal.
+
+- Level: Major
+  - Number#format "thousands" and "decimal" parameters are now pushed to the 2nd and 3rd parameters, adding a "place" for the decimal as the 1st.
+
+- Level: Minor
+  - A few tokens were removed from Date#format. See sugarjs.com/dates for a list of currently accepted tokens.
+
+- Level: Minor
+  - Function#lazy now executes, then waits as opposed to waiting, then executing.
+
+- Level: Minor
+  - Array#sortBy is now no longer destructive, so you will need to set the variable explicitly.
+
+
+
+v1.0+
+=====
+
+
+- Level: Major
+  - String#normalize is removed, but now available in the Inflections module, available at sugarjs.com/customize.
+
+- Level: Major
+  - String#is/hasArmenian, is/hasBopomofo, is/hasEthiopic, and is/hasGeorgian deprecated.
+
+- Level: Minor
+  - Passing a second parameter to Date#set now no longer resets only the time, but also any unit less specific than the most specific one set. So if the object `{ hours: 5 }` is passed with the second parameter `true`, minutes, seconds, and milliseconds will be reset.
+
+- Level: Minor
+  - Passing "relative" as the format to Date#format is now deprecated, and instead Date#relative.
+
+- Level: Minor
+  - Date.allowVariant deprecated in favor of the locale system. Any locale that is not 'en' or 'en-US' will use variants when ambiguities exist.
+
+- Level: Very minor
+  - Date#format token suffixes " short", and " pad" deprecated.
+
+- Level: Very minor
+  - When passing a function to Date#format or Date#relative, the "dir" parameter is now deprecated. Instead the milliseconds argument has a sign directly on it.
+
+
+
+v0.9.5+
+=======
+
+
+- Level: Major
+  - Array#split deprecated.
+
+- Level: Major
+  - String#toObject is now Object.fromQueryString.
+
+- Level: Major
+  - Function.lazy is now Function#lazy and is called directly on function instances.
+
+- Level: Major
+  - Function#defer is now simply Function#delay with no arguments.
+
+- Level: Moderate
+  - Object.clone is now shallow by default.
+
+
+
+v0.9.3+
+=======
+
+
+- Level: Major
+  - Array#each is no longer an alias of Array#forEach and now has its own behavior including a 2nd parameter for the start index, 3rd parameter for looping from the beginning, returning the array, allowing returning false to break the loop, passing the array as scope to the callback, and support for sparse arrays.
+
+- Level: Major
+  - Array#eachFromIndex deprecated.
+
+- Level: Major
+  - Array#removeAtIndex renamed to Array#removeAt.
+
+- Level: Major
+  - Array#collect deprecated.
+
+- Level: Major
+  - Array#shuffle deprecated.
+
+- Level: Major
+  - String#titleize deprecated. It is now available again as of v1.2 in the Inflections module, available at sugarjs.com/customize.
+
+- Level: Major
+  - String#pad/padLeft/padRight changed argument order to padding first and number second.
+
+- Level: Moderate
+  - Array#indexOf/lastIndexOf now performs a simple `===` equality check instead of a deep recursive property check.
+
+- Level: Minor
+  - String#repeat will now return a blank string on numbers less than 1.
+
+- Level: Minor
+  - String#dasherize and String#underscore now strip whitespace.
+
+
+
+v0.9.1+
+=======
+
+- Level: Major
+  - Object.create changed to Object.extended.
+
+
level2/node_modules/sugar/CHANGELOG.md
@@ -0,0 +1,688 @@
+v1.4.1
+======
+
+### API Changes ###
+
+- Fix for Object.select/reject not performing value match. (Issue #362)
+- Fix for Object.merge not properly merging when target object isn't an object (Issue #365)
+- Fix for development script not running properly in meteor (Issue #361)
+
+
+v1.4.0
+======
+
+### API Changes ###
+
+- Adding generalized ranges for Numbers and Strings in addition to Dates.
+- Date ranges are now part of the Range package and are no longer dependent on the Date package.
+- Adding `clamp` for ranges and an alias for Number.
+- Adding `cap` for ranges and an alias for Number.
+- Added `String#truncateOnWords`. Part of the `String#truncate` functionality is now here.
+- `Array.create` will understand ranges and can build an array from one.
+- `DateRange#duration` is deprecated in favor of `Range#span`.
+- Fix for relative times with "4 weeks" that are actually past the single month threshold.
+- `Number#upto` and `Number#downto` will now work on inverse ranges.
+- `pad`, `padLeft`, and `padRight` now pad to the specified length, instead of simply adding to string.
+- Fuzzy matching methods like `findAll` now directly match regexes against elements, regardless of whether or not they are strings.
+- Instances of classes are now entirely matched by reference only, as originally intended. This means that any equality checking inside Sugar will consider them equal only if they are `===`.
+- `Object.clone` now only works on known object types and does not work on instances of user-created classes.
+- `String#assign` now can be passed an array as well as enumerated arguments.
+- Fixed global variable leak #328
+- Optimization for `Array#removeAt` #324
+- Fix for `isThisWeek` being false when not en locale.
+- Timezone formatting tokens changed to align with Moment.js better.
+- Major performance optimization for date formatting and more.
+- Added `Date#beginningOfISOWeek` and `Date#endOfISOWeek`
+- Fix for `Array#create` not working on argument objects of zero-length (Issue #299).
+- Fix for `String#capitalize` capitalizing after apostrophes (Issue #325).
+- Fix for extended objects `select` and `reject` returning plain objects.
+- Fix for `Object.merge` not merging certain deep objects.
+- Added Date.SugarNewDate to allow customization of internally created dates.
+- Removed `multiMatch` in favor of a cached matcher system.
+- Fix for environments where regexes are functions.
+- Fix for `Function#cancel` not properly clearing all timers (Issue #346).
+- Fix for lazy functions not being able to recursively call themselves.
+- Added option `immediate` to `Function#lazy`, which is now false by default.
+- Added `Function#every`.
+- Exposed `Array.AlphanumericSort` to allow its use in native `Array#sort`.
+- Added `Array.AlphanumericSortNatural` that is on by default and triggers a natural sort.
+- Fixed strings not being coerced into objects in < IE8.
+- `Array.find` now aligns with ES6 spec.
+- Fixed bug with array like objects iterated over with loop = true.
+- Fixed `String#truncate` not returning primitives.
+- `String#repeat` is now aligned more with spec. `String#pad` follows suit.
+- Added `Array#findFrom` and `Array#findIndexFrom`.
+- Removed `String#normalize`.
+- Removed `Range#step` alias.
+- Removed `deep` argument from `Object.fromQueryString` and replaced with optional boolean casting.
+
+### Performance Enhancements ###
+
+- Object.map: up to 682% faster
+- Date#format: up to 21,400% faster
+- Array#min/max/less/more up to 83% faster
+- Enumerable methods like findAll/findIndex/map/any/count/sum/etc.: up to 11,270% faster
+- isString/isNumber/isBoolean: up to 77% faster
+- isEqual returns up front when === (can be *much* faster). Many methods use this internally as well.
+- Math related functions (and internals that use them): up to 16% faster.
+- getRegExpFlags is up to 1000% faster.
+- Range#every up to 52% faster for dates, 1500% faster for numbers/strings.
+- Array#at and String#at up to 242% faster for single index lookups.
+- String#assign up to 30% faster.
+
+
+v1.3.9
+======
+
+### API Changes ###
+
+- Added `Object.toQueryString`.
+- Fix for timezone offset -0330, etc (Issue #262).
+- Fix for methods like `isToday` not working when using a non-English locale (Issue #264).
+- Removed `Sugar#namespace` to fix conflict with jQuery (Issue #265).
+
+
+v1.3.8
+======
+
+### API Changes ###
+
+- Renamed `Date#getWeek` and `Date#setWeek` to `Date#getISOWeek` and `Date#setISOWeek`.
+- Updating `Date#setWeek` (now `Date#setISOWeek`) to follow ISO-8601 standard.
+- Allowing lazy and throttled functions to return a memoized result, allowing them to double as a caching mechanism.
+- Performance improvement to return early using typeof for type checks.
+- Performance improvement for loops.
+- Fix for Array#sample sometimes returning undefined (Issue #252).
+- Fix for French locales (Issue #249).
+- Fix for conflict with Coffeescript (Issue #248).
+- Fix for Object.clone not preserving date _utc flag (Issue #256).
+
+
+v1.3.7
+======
+
+### API Changes ###
+
+- Added Object.select and Object.reject to help filter keys in objects.
+- String#startsWith and String#endsWith have changed to match the Harmony proposal better.
+- Fix for Date.create not preserving the UTC flag when the source is also a date (Issue #235).
+- Object.clone on arrays with the "deep" flag set to true should create a deep clone of the array (Issue #237).
+- Array#min/max should throw an error when comparing to undefined (Issue #232).
+- Fix for dates that fallback to native parsing when forcing UTC flag (Issue #244).
+- Date#since/fromNow aliases will now count "past" integers instead of rounding (Issue #236).
+- Adding enumerable methods to `Object.extend()`.
+
+
+v1.3.6
+======
+
+### API Changes ###
+
+- Faster String#repeat (Issue #214 - Thanks to @termi!).
+- Fixed issue with Array#sample skewing randomization (Issue #216).
+- Limiting minute/second parsing to 0-59 (Issue #219).
+- Fixed issue with `addMonths` (Issue #221).
+- Fixed issue with NaN being true for `isOdd` (Issue #220).
+- Fixed issue with HTML escaping (Issue #212).
+- Fixed issue with float values parsing in `Object.fromQueryString` (Issue #225).
+- Internal refactoring of `Object.each`.
+- Fixed issue with `7 July` date format (Issue #227).
+- Added "'yy" as a valid year format.
+- Allowing empty strings for thousands separator and decimal in `Number#format` (Issue #229).
+
+v1.3.5
+======
+
+### API Changes ###
+
+- Now allowing "n days later" etc. as a parsable format (#199).
+- Added support for "the nth" format (#205).
+- Fixed issue with `Array.create` on objects (#195).
+- Fixed am/pm issues with Date parsing (#201).
+- Fixed issues with `Date.future` (#210), zh-CN locale time parsing, (#204).
+- Added support for Finnish locale (#185), Dutch, and Danish.
+- Fixed `Number.random` to have better random distribution (#196).
+- Issue with Date cloning (#200).
+
+v1.3.4
+======
+
+### API Changes ###
+
+- Refactored 3rd utc argument into a separate object for clarity.
+
+
+v1.3.3
+======
+
+### Internal Changes ###
+
+- multiMatch does not treat functions as callbacks when matching against other functions.
+
+
+v1.3.2
+======
+
+### API Changes ###
+
+- `Date#create` on ISO dates no longer sets the utc flag.
+- Fixed implementation of 'Function#bind', which was overriding native method due to an error in the MDN docs.
+
+
+v1.3.1
+======
+
+
+### API Changes ###
+
+- Matching by value in arrays is now opt-in and must meet certain requirements, namely being of class [object Object], and having `hasOwnProperty`. This will notably now exclude functions and host objects such as DOM elements. This means that such excluded objects will be matched by reference only.
+- Fixed issue with Array.create not properly creating arrays #171
+- Empty objects now match themselves in arrays #176
+- Date#setWeekday now returns a timestamp for consistency with Date#setDay #181
+- Date#toUTC deprecated in favor of Date#utc and utc flag in Date#create.
+- Date#setUTC deprecated in favor of direct use of utc flag.
+- Date#setUTCWeek deprecated in favor of direct use of utc flag.
+- Date#getUTCWeek deprecated in favor of direct use of utc flag.
+- Date#setUTCWeekday deprecated in favor of direct use of utc flag.
+- Date#clone now clones the utc flag of the date.
+- Fixed issue with DateRange causing an infinite loop when DST traverses back 1 hour.
+- Better date disambiguation for ambiguous dates ("Sunday", etc)
+- Various date parsing fixes.
+- Timers set by delays are now exposed #170
+- Function#debounce debounced function is now canceled instead of original.
+- Internal refactoring of class check handling.
+
+
+
+v1.3
+======
+
+
+### API Changes ###
+
+- Sugar packages are now further split up and can easily be customized and repackaged. Aside from "core" there is the "es5" package that can be opted out of if <= IE8 support isn't an issue. DateRanges (below) are now their own package, as are inflections.
+- Date locales are now a separate package, only English is included in the base "date" package.
+- Enumerable methods are now available as class methods on Object, and instance methods on extended objects. This includes: map, any, all, none, count, sum, average, find, findAll, min, max, least, most, and reduce.
+- Added Object.size (also available to extended objects)
+- Array#min, Array#max, Array#least, and Array#most now return a single element by default with the option to return multiple elements.
+- Object.equals now considers identical objects vs. extended objects to be the same
+- Refactored Object.isEmpty to be an enumerable method in the Array package. This means that it will error on non-objects now.
+- Added "language" package.
+- String#normalize moved from Inflections to Language package
+- String#has[Script] moved from String to Language package
+- String#hankaku and String#zenkaku moved from String to Language package
+- String#hiragana and String#katakana moved from String to Language package
+- String#namespace moved from Inflections to String package
+- String#parameterize now checks for normalize and also uses encodeURI for final output
+- String#split patching for regexes is now removed from the String package and is on its own in /lib/extra. It can be dropped in anywhere after Sugar is loaded.
+
+- Array#has is deprecated
+- Array#groupBy no longer returns extended objects
+- Array#groupBy no longer corrupts array length (Issue 142)
+- Enumerable methods now allow fuzzy matching of instances of classes (Issue 157)
+
+- All Math methods are now mapped onto Number and are accessible as instance methods
+
+- String#capitalize all will capitalize any letter after a letter that could not be capitalized.
+- String#insert, and Array#insert now treat negative indexes like String#slice
+- Fixed issue with decodeBase64 shim (Issue 145)
+
+- String#toDate is now deprecated.
+- Date parsing formats are now scoped by locale. This means that if the current locale is set to English, only English formats will be parsed when Date#create does not specify a locale, even if a different locale was initialized previously. Numeric and common formats are available in all locales.
+- Added output formats Date#long and Date#full which now included the time. Date#long (mm dd, yyyy hh:mm) is now the default for Date#format, and the previous default (no time) is now Date#short. Date#full includes milliseconds and weekday.
+- Date format "just now" now supported
+- Date#reset now supports resetting a unit based on a string.
+- Date#advance and other advance methods can now reset the time.
+- Date#advance now accepts string input like "4 days" (Issue 161)
+- Date.past and Date.future added which allow date parsing that prefers the past or future when the specified date is ambiguous ("Sunday", etc.)
+- Date parsing now allows time to be in front of the date string as well
+- Fixed various issues with timezones, DST, and date parsing (Issue 146), (Issue 138)
+- Added "in 3 days", etc. as a parsable format
+- Added "the 2nd Tuesday of November", etc. as a parsable format
+- Added more parsable formats with weekdays (such as "last monday", etc) in various locales
+- Added time parsing in non-English date formats
+- Fully qualified ISO language codes will now match more generic codes. This means passing "it_IT" will correctly find "it" if the more specific locale is not available.
+- Unknown languages codes will now simply return an invalid date instead of throwing an error.
+- Added support for full kanji numerals in date parsing
+- Added support for time suffixes in Asian time strings (ๆ™‚ etc)
+- Added support for various relative formats in CKJ dates  (ๅ…ˆ้€ฑๆฐดๆ›œๆ—ฅ etc)
+- Fixed inconsistently not allowing spaces before am/pm (Issue 144)
+
+- Added DateRange, accessed through Date.range as a separate package
+
+
+v1.2.5
+======
+
+
+### API Changes ###
+
+- String#truncate refactored to split words by default (standard behavior) allow splitting in various positions, and changing argument order.
+- Object.isObject should be true for extended objects as well.
+- Function#throttle added to take the place of Function#debounce with `false` as the `wait` parameter.
+- Date parsing support for hour/minute/second fractions (now take the place of milliseconds).
+- Date parsing support now sees commas in decimals.
+- Date parsing support for .NET dates.
+
+
+v1.2.4
+======
+
+
+### API Changes ###
+
+- Major performance improvement for Array#unique, Array#union, Array#intersect (now On vs. Onยฒ)
+- Array#min, Array#max, Array#most, Array#least also benefit from this.
+- Object.equal(s) is now egal (this should only matter for edge cases) like Underscore.
+- Object.merge will now work on non-objects as well.
+- Custom formats in Date.addFormat will now override built-in formats.
+- Fix for Array#union incorrectly flattening arrays.
+- Fix for isObject not working across iframes.
+- Fix for String#chars incorrectly trimming.
+- Fix for String#each not matching all characters.
+
+### Internal Changes ###
+
+- multiArgs now flatten is opt-in
+
+v1.2.3
+======
+
+
+### API Changes ###
+
+- String#compare, Number#compare, and Date#compare are deprecated.
+- Array#sortBy now makes much more sensible sorting when sorting on strings.
+- Added Array.AlphanumericSortOrder
+- Added Array.AlphanumericSortIgnore
+- Added Array.AlphanumericSortIgnoreCase
+- Added Array.AlphanumericSortEquivalents
+- Object.merge defaults are now more sensible. shallow/deep is 3rd with shallow default and resolve is 4th
+- Added Number#duration to dates package.
+- Bugfix for leaking globals.
+- Bugfix for String#compact (Issue 115)
+
+### Internal Changes ###
+
+- Cleanup for toISOString internal code.
+
+
+
+
+v1.2.2
+======
+
+
+### API Changes ###
+
+- Performance optimization for Object.merge and by extension Object.clone
+- Object.extended now maintains its "Hash" constructor and checks against it when cloning.
+- Object.merge now will also clone dates and regexes as well.
+- Reset dates that will be set with UTC methods (fixes issue #98).
+
+### Internal Changes ###
+
+
+- Removed references to isDefined, isNull, and isObjectPrimitive
+
+
+
+
+v1.2.1
+======
+
+
+### API Changes ###
+
+- Added Object.has to fix issue #97. Stand-in for Object#hasOwnProperty.
+- Fixed issue with String#has not escaping regex tokens.
+- Date.setLocale can now overwrite a default locale object.
+- Date locales can now add their own formats.
+- Fix for Ender, which was broken when modularized in 1.2.
+- Workaround for Ender requiring externs.
+
+### Internal Changes ###
+
+- Date optional tokens now start from {0}
+- References to Object.extend and Object.restore now held and allowed to be restored later.
+
+
+v1.2
+====
+
+
+### API Changes ###
+
+- Allowed external libraries to extend natives through a common interface "extend".
+- Renamed "sugar" to "restore" to restore Sugar methods on a given class.
+- Extending Object.prototype functionality is now on "extend" instead.
+- Split the date library into its own module that hooks into this new interface.
+- Added a new module: String inflections
+- Object.keys now passes values as part of the callback like array methods.
+- Object.merge now merges undefined properties as well.
+- Array#every now uses fuzzy object matching
+- Array#some now uses fuzzy object matching
+- Array#filter now uses fuzzy object matching
+- Array#find now uses fuzzy object matching
+- Array#findAll now uses fuzzy object matching
+- Array#findIndex now uses fuzzy object matching
+- Array#remove now uses fuzzy object matching
+- Array#none now uses fuzzy object matching
+- Array#count now uses fuzzy object matching
+- Array#exclude now uses fuzzy object matching
+- Array#clone is now no longer based off Array#concat, which will fail on sparse arrays in IE7.
+- Added Number#abbr
+- Added Number#metric
+- Added Number#bytes
+- Added Number#isInteger
+- Fixed issue with Number#ordinalize where 113 would be "113rd".
+- String#each will now pass the match into the callback
+- String#toDate will now check for Date.create before hooking into it.
+- String#underscore will now check for acronyms if Inflectors module is present.
+- String#camelize will now check for acronyms if Inflectors module is present.
+- RegExp.escape will now perform a [toString] operation on non-strings (ie. numbers, etc).
+- Function#fill now uses internal Array#splice to fill in arguments.
+- Added support for JSON date format Date(xxxxxxxxxx).
+- Fixed issues with Date#getWeek.
+- Fixed issues with traversing months before January.
+- String#titleize added to inflections module.
+
+
+### Internal Changes ###
+
+- Reworked "multiMatch" to recursively traverse object structures.
+- mergeObject now merges undefined properties as well
+- Created method arrayIntersect to handle both Array#intersect and Array#subtract
+- Array#intersect and Array#subtract will not allow fuzzy object matching
+- Array#indexOf and Array#lastIndexOf polyfills now work off arrayIndexOf
+- Added internal support for other dates that use timestamps.
+- Reworked adding of Date#toISOString and Date#toJSON support.
+
+
+
+
+v1.1.3
+======
+
+### API Changes ###
+
+- Fixed issue with Object.isEmpty where strings with length > 0 will return true.
+
+### Internal Changes ###
+
+- Updated Array#sortBy to use .compare method when available.
+
+
+v1.1.2
+======
+
+### API Changes ###
+
+- Added Array#findIndex.
+- Added Array#sample.
+- Added String#compare.
+- Added Number#compare.
+- Added Date#compare.
+- Fixed issue with floats not properly being recognized in the query string.
+- Fixed issue with Object.isEmpty on non-object types and null.
+- Fixed issue with arrayEach not allowing negative start indexes.
+- Fixed issue with Array#reduce not recognizing 0 as a starting value.
+- Fixed issue with Array#add not allowing negative indexes.
+- Fixed issue with Number.random not recognizing upper limit of 0.
+- Fixed issue with String#dasherize not working on single camel cased letters.
+- Fixed issue with String#assign not working on an empty string or other falsy value.
+- Fixed issues with French and German date months not being correct.
+- Fixed Function#after not calling the method immediately when num is 0.
+
+
+### Internal Changes ###
+
+- Refactored Array#reduce and Array#reduceRight to use the same internal method.
+- Refactored String#camelize to be smaller.
+- Refactored checkMonthTraversal to be more robust in a variety of situations.
+
+v1.1.1
+======
+
+### API Changes ###
+
+- Object.merge now accepts a third parameter that determines what to do in the case of property conflicts. This parameter can be true, false, or a function. This change means that it now no longer accepts an arbitrary number of arguments.
+- Added Object.isNaN
+- Added Object.tap
+- Consolidated the arguments that are passed to mapping functions on methods such as Array#min/max/groupBy/sortBy. All such functions will now be passed the array element, array index, and array object, in that order, to conform to ES5 Array#map behavior.
+- Array#flatten can now accept a level of nesting to flatten to. Default is all levels.
+- Array#remove no longer works like a reverse concat (ie. no longer flattens arguments passed to it as if they were passed as separate arguments, so removing arrays within arrays should now work properly. This applies to Array#exclude as well.
+- Added Array#zip
+
+### Internal Changes ###
+
+- Refactored way in which type/hash methods are mapped
+- Fixed Date bug "2 weeks from Monday"
+
+v1.1
+====
+
+### API Changes ###
+
+- Array#unique can now unique on a function, giving a shortcut to uniquify deep objects
+- Object.equals renamed to Object.equal in its class method only
+- Object.equal now much more robust, can handle cyclic references, etc
+- Number#format now accepts a parameter <place> for the decimal. "thousands", and "decimal" are pushed to 2nd and 3rd params
+- Date#format now accepts different format tokens. A few counterintuitive ones removed, and others were added to match moment.js including fff, ddd, mmm, etc
+- Function#lazy now executes immediately and locks instead of setting a delay
+- Added RegExp#getFlags
+- Added Function#fill, which allows arguments to be intelligently curried
+- Fixed broken support for SpiderMonkey under CouchDB
+- Fixed sortBy is unintentionally destructive
+- Full Asian date number formats now accepted
+- Array#map/min/max/most/least/groupBy/sortBy no longer errors on undefined, null, etc
+- Fixed a bug with locking on Number#format when passing digits
+
+### Internal Changes ###
+
+- Optimized for Google closure compilers max compression level
+- Minified script dropped about 5kb
+- Intelligently determining if cloned objects are extended
+- transformArgument now just accepts <map> not the arguments object
+- refactored asian digits to be globally replaced
+- Date#toJSON and Date#toISOString now properly fall back to native methods
+- Significantly wrote asynchronous function tests to be more reliable
+
+
+
+v1.0
+====
+
+### API Changes ###
+
+- Object.sugar() now will add all extended object (hash) methods to Object.prototype, letting you opt-in this functionality
+- Object.watch() will observe changes in an object property and fire a callback if it has changed
+- Array.create() quickly creates arrays, most notably from an arguments object
+- Array#groupBy now allows a callback to iterate over each group
+- String#normalize method deprecated, but still available in lib directory
+- String#is/hasArmenian, is/hasBopomofo, is/hasEthiopic, and is/hasGeorgian deprecated
+- String#is/hasLatin added
+- String#toDate now accepts a locale parameter
+- String#spacify added
+- String#assign added
+- Date module completely reworked to allow locales
+- Date#format " short" token suffix deprecated
+- Date#format " pad" token suffix deprecated
+- Date#format "dir" parameter passed to the callback deprecated in favor of using the sign directly on the time itself
+- Date#format locale now passed to the callback instead of the above
+- Date#format passing no arguments now outputs a default simple date format for the current locale
+- Date#relative same treatment as Date#format for callbacks as above
+- Date.allowVariant for ambiguous dates (8/10/03) refactored to use locales instead
+- Date.RFC1123 and Date.RFC1036 fix to not display GMT
+- Date.setLocale will set an available locale or allow extending the Date class with new locales
+- Date.getLocale gets a localization object (current localization by default)
+- Date.addFormat allows additional date formats to be added
+- Date#set passing true for the second param will now reset any units less specific, not just the time
+- Date#isBefore/isAfter/isBetween now uses a straight comparison rather than trying to extend the bounds of the date based on specificity
+- Date#format now accepts a second locale parameter that outputs the date in a specific locale. If no locale is set the current locale is used.
+- Date#format passing "relative" as the format is now deprecated. Use Date#relative instead
+- Function#lazy now accepts a "limit" parameter that will prevent a lazy function from queueing calls beyond a certain limit
+- Function#debounce now accepts a "wait" parameter (default is true) that will allow function execution AFTER the timeout to be turned off so the function is run immediately
+
+
+
+
+### Internal Changes ###
+
+- major docs updates
+- arrayEach will now default to not loop over sparse arrays unless explicitly told to
+- major internal refactoring of the Date module to be more compact, robust, and light
+- date module will be distilled and contained on its own in the repo
+
+
+v0.9.5
+======
+
+### API Changes ###
+
+- .sugar method added to all classes to reinstate Sugar methods conditionally.
+- Object.clone is now shallow by default, with an option for deep cloning
+- Object.merge will now ignore non-objects
+- Object.fromQueryString now takes the place of String#toObject.
+- Nested object/array param parsing now possible with Object.fromQueryString.
+- Array#remove now accepts unlimited parameters
+- Array#exclude now accepts unlimited parameters
+- Array#union now accepts unlimited parameters
+- Array#subtract now accepts unlimited parameters
+- Array#intersect now accepts unlimited parameters
+- Array#split deprecated
+- Array#compact no longer removes functions (bug)
+- Array#compact now accepts flag to compact all falsy values
+- Number#upto and Number#downto now accept a third parameter to traverse in multiples of > 1
+- Number#pad now accepts a third parameter that is the base of the number
+- Number#hex now accepts a parameter to pad the resulting string with a default of 1
+- String#escapeHTML added
+- String#truncate added. Will truncate a string without breaking words.
+- String#toObject now refactored to Object.fromQueryString
+- Function.lazy refactored to Function#lazy
+- Function#lazy functions can now be cancelled via Function#cancel
+- Function#defer deprecated -> use Function#delay instead
+- Function#debounce added
+- Function#after added
+- Function#once added
+
+
+### Internal Changes ###
+
+- extendWithNativeCondition removed. Functionality now contained in extend
+- shuffled and removed some dependencies to make it easier to extract the date module
+- more robust equality comparison:
+- multiArgs added to collect arguments
+- array indexes now checked with hasProperty instead of hasOwnProperty
+- object builders are now going through extend so they can store their references
+- Object.clone refactored to use multiArgs
+- Object.isEmpty now returns false if passed argument itself is falsy
+- String#stripTags refactored to use multiArgs
+- String#removeTags refactored to use multiArgs
+-- "null" now taken into consideration for objects
+-- object key length compared
+-- strict equality matches in multiMatch
+
+
+v0.9.4
+======
+
+- Emergency fix for Array#compact incorrectly detecting NaN.
+
+v0.9.3
+======
+
+### API Changes ###
+
+- Array.isArray polyfill added and aliased by Object.isArray (es5)
+- Array#every/some/map/filter now throws a TypeError if no arguments passed (es5)
+- Array#every/some/map/filter now defers to native if available and no arguments passed (es5)
+- Array#none/any/all/has aliases similarly throw TypeErrors if no arguments passed (es5)
+- Array#indexOf/lastIndexOf now performs a simple strict equality check. Added to v0.9.2 but separately here (es5)
+- Array#indexOf/lastIndexOf refactored to defer to String#indexOf/lastIndexOf if a string is passed as the scope (es5)
+- Array#forEach/reduce/reduceRight now all throw a TypeError if callback is not callable (es5)
+- Array#reduce/reduceRight now throw a TypeError if the array is empty and no initial value passed (es5)
+- Array#each is now no longer an alias of forEach and has different behavior:
+ - second parameter is the index to start from
+ - third parameter is a boolean that runs the loop from the beginning if true
+ - returns the array
+ - fn returning false will break out of the loop
+ - will throw a TypeError if fn is not callable (same as forEach)
+ - array is now passed as the scope
+ - now detects sparse arrays and switches to a different algorithm to handle them
+- Array#find refactored to use an internal method insted of Array#findAll to avoid collisions
+- Array#find now breaks as soon as it finds an element
+- Array#eachFromIndex removed
+- Array#removeAtIndex renamed to Array#removeAt
+- Array#unique refactored to use an internal method instead of Array#find to avoid collisions
+- Array#subtract/intersect refactored to use an internal method instead of Array#find to avoid collisions
+- Array#subtract/intersect refactored to use Array.isArray instead of Object.isArray
+- Array#union refactored to use an internal method instead of Array#unique to avoid collisions
+- Array#min/max refactored to use an internal method instead of Array#unique to avoid collisions
+- Array#least/most will now throw a TypeError if the first argument exists, but is not a string or function
+- Array#least/most refactored to use an internal method instead of Array#unique to avoid collisions
+- Array#groupBy will now throw a TypeError if the first argument exists, but is not a string or function
+- Array#sortBy will now throw a TypeError if the first argument exists, but is not a string or function
+- Array#compact/flatten now internally uses Array.isArray instead of Object.isArray
+- Array#collect alias removed
+- Array#shuffle alias removed
+- String#hankaku/zenkaku/hiragana/katakana refactored to shift char codes instead of using a hash table
+- String#hankaku/zenkaku refactored to be much more accurate & strictly defined
+- String#shift added
+- String#trim refactored to handle all characters covered in es5/unicode (es5)
+- String#trim refactored to check for support and polyfill as needed (es5)
+- String#titleize removed
+- String#capitalize refactored to allow capitalization of all letters
+- String#pad/padLeft/padRight refactored to accept the number as the second param and padding as the first
+- String#repeat refactored to return a blank string on num < 1
+- String#add refactored to act in parallel with Array#add
+- String#remove added as a reciprocal of String#add and a parallel of Array#remove
+- String#dasherize/underscore refactored to strip whitespace
+- Object.keys refactored to defer to native if < 2 arguments instead of == 1
+- Object.keys will now throw a TypeError if non-object passed (es5)
+- Number.random fixed which had implied globals min & max
+- Date.now polyfill added (es5)
+- Date#toISOString refactored polyfill to check for native browser support (es5)
+- Date#toJSON added as a polyfill alias to Date#toISOString with similar native checks (es5)
+- Date#format/relative refactored to point to an internal method to avoid collisions
+- fixed date methods in ambiguous situations such as "5 months ago" when the target month does not have enough days
+- Function#bind refactored to check for native support and behave much more closely to spec (es5)
+- added documentation for unicode block methods
+- added devanagari and ethiopic scripts
+
+
+### Internal Changes ###
+
+- refactored unicode script methods to use .test instead of .match
+- extendWithNativeCondition refactored to allow a "supported" flag
+- getMinOrMax refactored to use iterateOverObject
+- getFromIndexes renamed to getAtIndexes
+- toIntegerWithDefault added
+- arrayFind added
+- arrayEach added
+- arrayUnique added
+- isArrayIndex added (es5)
+- toUint32 added (es5)
+- checkCallback added (es5)
+- checkFirstArgumentExists added (es5)
+- buildObject refactored to be less invasive
+
+
+
+v0.9.2
+======
+
+- Emergency fix to alleviate issues with indexOf/lastIndexOf breaking on functions/deep objects
+
+
+
+v0.9.1
+======
+
+- Change Object.create to Object.extended to avoid collision with ES5
+- Use of defineProperty in modern browsers to prevent enumeration in for..in loops.
+- Add test for for..in loop breakage and allowed older browsers to have a "warning" message.
+- Object.isArray will now alias native Array.isArray if it is present.
+- Fix collisions with Prototype on Object.clone.
+- Test cleanup.
+
level2/node_modules/sugar/LICENSE
@@ -0,0 +1,6 @@
+
+Copyright ยฉ 2011 Andrew Plummer
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the โ€œSoftwareโ€), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sub-license, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+The above copyright notice, and every other copyright notice found in this software, and all the attributions in every file, and this permission notice shall be included in all copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED โ€œAS ISโ€, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
level2/node_modules/sugar/package.json
@@ -0,0 +1,36 @@
+{
+  "name": "sugar",
+  "version": "1.4.1",
+  "description": "A Javascript library for working with native objects.",
+  "keywords": [
+    "functional",
+    "utility",
+    "ender"
+  ],
+  "homepage": "http://sugarjs.com/",
+  "author": {
+    "name": "Andrew Plummer"
+  },
+  "main": "./release/sugar-full.development.js",
+  "directories": {
+    "lib": "./lib"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/andrewplummer/Sugar.git"
+  },
+  "engines": {
+    "node": ">= 0.4.0"
+  },
+  "scripts": {
+    "test": "node test/environments/node/test.js"
+  },
+  "license": "MIT",
+  "readme": "# Sugar\n\n[![Build Status](https://secure.travis-ci.org/andrewplummer/Sugar.png)](http://travis-ci.org/andrewplummer/Sugar)\n\nA Javascript library for working with native objects.\nhttp://sugarjs.com/\n\n\n## Upgrading\n\nIf you are upgrading from an older version, please have a look at [`CAUTION.md`](https://github.com/andrewplummer/Sugar/blob/master/CAUTION.md) which is a vetted changelog\nthat details the severity of what has changed, and (sometimes) strategies for migrating.\nGoing through this before you upgrade can make the process a lot less painful!\nAlso please refer there for notes about a patch that applies to versions prior to v1.3.9.\n\n\n## Edge Build\n\nPublic stable releases will be made available on the site and also exist in `release/`.\nAny push made to `master` branch *should* have its unit tests passing, although maybe not\nin all browsing environments (IE, etc) to ensure that it is stable, at least to a certain degree.\n\n\n## Custom Builds\n\nSugar now allows custom builds that let you opt in or out packages. This can be done [here](http://sugarjs.com/customize).\nCustom builds can also be created with `script/build_custom.rb`. With ruby installed, simply call:\n\n```\nruby script/build_custom.rb core array string\n```\n\nlisting the packages you want to include. The advantage of using this\nscript is that it will perform all the minification on the fly, providing more fine-grained control by allowing you to\nmanipulate the source code in `lib/` before building. If you want to remove specific methods from a package, you can do it this way.\nBe careful about removing dependencies, however, especially methods in `core.js`, the extending methods of which are required.\n\n\n## Unit Tests Node\n\nUse the `npm test` command toย run unitย tests.\n\n\n## Date Localizations\n\nSugar has the following localizations available:\n\n- English (en)\n- French (fr)\n- German (de)\n- Spanish (es)\n- Italian (it)\n- Russian (ru)\n- Finnish (fi)\n- Swedish (sv)\n- Danish (da)\n- Dutch (nl)\n- Polish (pl)\n- Portuguese (pt)\n- Korean (ko)\n- Japanese (ja)\n- Simplified Chinese (zh-CN)\n- Traditional Chinese (zh-TW)\n\n\nThese files can be added separately or built into the main package on the [customize page](http://sugarjs.com/customize).\nIn addition to these major locales, custom locales can be added using:\n\n```\nDate.addLocale(LOCALE_CODE, LOCALIZATION_OBJECT)\n```\n\nDocumentation for this available [here](http://sugarjs.com/dates). Also refer to `lib/locales` for examples of what kind of data and formats are required in localization objects. All localizations, including those not found in the main package will be kept here.\n\n\n\n## Timezones\n\nDealing with timezones in Javascript can be tricky. Although timezones are outside the scope of Sugar, it does provide a hook that can allow timezone shifted dates to be used internally in place of normal ones. See [the date reference](http://sugarjs.com/dates#timezones) for more.\n\n\n## Contributing Locales\n\nIf you do add a custom format for your locale, please consider forking and adding it to the repo! This especially includes the addition of new locales, but also new formats or tweaks to existing locales. Not everything can be added to the main package, but I would like to have as many languages/formats as possible available. When adding a locale contribution, the most important thing is to add unit tests that assert the correct format. These unit tests are found at `test/environments/sugar/date_LOCALE.js`. Simply add or adjust the formats for the locale (the more tests, the better!) and issue me a pull request -- I will update the code to add these locales/formats. Have a look at other unit tests files for an example of the unit testing format.\n\n\n## Contributing Lib Comparisons\n\nLib comparisons to various other libraries can be seen at http://sugarjs.com/libs. This is one of the areas where contributions are most welcome, as I don't have extensive knowledge of many different libraries, and there is much to cover. To contribute simply find or create the appropriate lib name in `docs/libs`, and follow the format provided. This will be an ongoing process, and I will push changes here out to the site every so often.\n\n\n## Other Contributions\n\nFor other contributions, please add well formed unit tests in the Sugar environment at `test/environments/sugar/MODULE.js`. Unit tests can be run directly in the browser from `test/default.html`, and should all be passing in all major browsers (Webkit,Mozilla,Opera, and IE6+). Node.js unit tests should also be passing and can be run in the console with `npm test`.\n\nAlso note that the source code is in the `lib` directory, and `release` is automatically built, so there is no need to changes files there.\n\n",
+  "readmeFilename": "README.md",
+  "bugs": {
+    "url": "https://github.com/andrewplummer/Sugar/issues"
+  },
+  "_id": "sugar@1.4.1",
+  "_from": "sugar@"
+}
level2/node_modules/sugar/README.md
@@ -0,0 +1,97 @@
+# Sugar
+
+[![Build Status](https://secure.travis-ci.org/andrewplummer/Sugar.png)](http://travis-ci.org/andrewplummer/Sugar)
+
+A Javascript library for working with native objects.
+http://sugarjs.com/
+
+
+## Upgrading
+
+If you are upgrading from an older version, please have a look at [`CAUTION.md`](https://github.com/andrewplummer/Sugar/blob/master/CAUTION.md) which is a vetted changelog
+that details the severity of what has changed, and (sometimes) strategies for migrating.
+Going through this before you upgrade can make the process a lot less painful!
+Also please refer there for notes about a patch that applies to versions prior to v1.3.9.
+
+
+## Edge Build
+
+Public stable releases will be made available on the site and also exist in `release/`.
+Any push made to `master` branch *should* have its unit tests passing, although maybe not
+in all browsing environments (IE, etc) to ensure that it is stable, at least to a certain degree.
+
+
+## Custom Builds
+
+Sugar now allows custom builds that let you opt in or out packages. This can be done [here](http://sugarjs.com/customize).
+Custom builds can also be created with `script/build_custom.rb`. With ruby installed, simply call:
+
+```
+ruby script/build_custom.rb core array string
+```
+
+listing the packages you want to include. The advantage of using this
+script is that it will perform all the minification on the fly, providing more fine-grained control by allowing you to
+manipulate the source code in `lib/` before building. If you want to remove specific methods from a package, you can do it this way.
+Be careful about removing dependencies, however, especially methods in `core.js`, the extending methods of which are required.
+
+
+## Unit Tests Node
+
+Use the `npm test` command toย run unitย tests.
+
+
+## Date Localizations
+
+Sugar has the following localizations available:
+
+- English (en)
+- French (fr)
+- German (de)
+- Spanish (es)
+- Italian (it)
+- Russian (ru)
+- Finnish (fi)
+- Swedish (sv)
+- Danish (da)
+- Dutch (nl)
+- Polish (pl)
+- Portuguese (pt)
+- Korean (ko)
+- Japanese (ja)
+- Simplified Chinese (zh-CN)
+- Traditional Chinese (zh-TW)
+
+
+These files can be added separately or built into the main package on the [customize page](http://sugarjs.com/customize).
+In addition to these major locales, custom locales can be added using:
+
+```
+Date.addLocale(LOCALE_CODE, LOCALIZATION_OBJECT)
+```
+
+Documentation for this available [here](http://sugarjs.com/dates). Also refer to `lib/locales` for examples of what kind of data and formats are required in localization objects. All localizations, including those not found in the main package will be kept here.
+
+
+
+## Timezones
+
+Dealing with timezones in Javascript can be tricky. Although timezones are outside the scope of Sugar, it does provide a hook that can allow timezone shifted dates to be used internally in place of normal ones. See [the date reference](http://sugarjs.com/dates#timezones) for more.
+
+
+## Contributing Locales
+
+If you do add a custom format for your locale, please consider forking and adding it to the repo! This especially includes the addition of new locales, but also new formats or tweaks to existing locales. Not everything can be added to the main package, but I would like to have as many languages/formats as possible available. When adding a locale contribution, the most important thing is to add unit tests that assert the correct format. These unit tests are found at `test/environments/sugar/date_LOCALE.js`. Simply add or adjust the formats for the locale (the more tests, the better!) and issue me a pull request -- I will update the code to add these locales/formats. Have a look at other unit tests files for an example of the unit testing format.
+
+
+## Contributing Lib Comparisons
+
+Lib comparisons to various other libraries can be seen at http://sugarjs.com/libs. This is one of the areas where contributions are most welcome, as I don't have extensive knowledge of many different libraries, and there is much to cover. To contribute simply find or create the appropriate lib name in `docs/libs`, and follow the format provided. This will be an ongoing process, and I will push changes here out to the site every so often.
+
+
+## Other Contributions
+
+For other contributions, please add well formed unit tests in the Sugar environment at `test/environments/sugar/MODULE.js`. Unit tests can be run directly in the browser from `test/default.html`, and should all be passing in all major browsers (Webkit,Mozilla,Opera, and IE6+). Node.js unit tests should also be passing and can be run in the console with `npm test`.
+
+Also note that the source code is in the `lib` directory, and `release` is automatically built, so there is no need to changes files there.
+
level2/node_modules/underscore/LICENSE
@@ -0,0 +1,23 @@
+Copyright (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative
+Reporters & Editors
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
level2/node_modules/underscore/package.json
@@ -0,0 +1,46 @@
+{
+  "name": "underscore",
+  "description": "JavaScript's functional programming helper library.",
+  "homepage": "http://underscorejs.org",
+  "keywords": [
+    "util",
+    "functional",
+    "server",
+    "client",
+    "browser"
+  ],
+  "author": {
+    "name": "Jeremy Ashkenas",
+    "email": "jeremy@documentcloud.org"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/jashkenas/underscore.git"
+  },
+  "main": "underscore.js",
+  "version": "1.5.2",
+  "devDependencies": {
+    "phantomjs": "1.9.0-1"
+  },
+  "scripts": {
+    "test": "phantomjs test/vendor/runner.js test/index.html?noglobals=true"
+  },
+  "licenses": [
+    {
+      "type": "MIT",
+      "url": "https://raw.github.com/jashkenas/underscore/master/LICENSE"
+    }
+  ],
+  "files": [
+    "underscore.js",
+    "underscore-min.js",
+    "LICENSE"
+  ],
+  "readme": "                       __\n                      /\\ \\                                                         __\n     __  __    ___    \\_\\ \\     __   _ __   ____    ___    ___   _ __    __       /\\_\\    ____\n    /\\ \\/\\ \\ /' _ `\\  /'_  \\  /'__`\\/\\  __\\/ ,__\\  / ___\\ / __`\\/\\  __\\/'__`\\     \\/\\ \\  /',__\\\n    \\ \\ \\_\\ \\/\\ \\/\\ \\/\\ \\ \\ \\/\\  __/\\ \\ \\//\\__, `\\/\\ \\__//\\ \\ \\ \\ \\ \\//\\  __/  __  \\ \\ \\/\\__, `\\\n     \\ \\____/\\ \\_\\ \\_\\ \\___,_\\ \\____\\\\ \\_\\\\/\\____/\\ \\____\\ \\____/\\ \\_\\\\ \\____\\/\\_\\ _\\ \\ \\/\\____/\n      \\/___/  \\/_/\\/_/\\/__,_ /\\/____/ \\/_/ \\/___/  \\/____/\\/___/  \\/_/ \\/____/\\/_//\\ \\_\\ \\/___/\n                                                                                  \\ \\____/\n                                                                                   \\/___/\n\nUnderscore.js is a utility-belt library for JavaScript that provides\nsupport for the usual functional suspects (each, map, reduce, filter...)\nwithout extending any core JavaScript objects.\n\nFor Docs, License, Tests, and pre-packed downloads, see:\nhttp://underscorejs.org\n\nUnderscore is an open-sourced component of DocumentCloud:\nhttps://github.com/documentcloud\n\nMany thanks to our contributors:\nhttps://github.com/jashkenas/underscore/contributors\n",
+  "readmeFilename": "README.md",
+  "bugs": {
+    "url": "https://github.com/jashkenas/underscore/issues"
+  },
+  "_id": "underscore@1.5.2",
+  "_from": "underscore@*"
+}
level2/node_modules/underscore/README.md
@@ -0,0 +1,22 @@
+                       __
+                      /\ \                                                         __
+     __  __    ___    \_\ \     __   _ __   ____    ___    ___   _ __    __       /\_\    ____
+    /\ \/\ \ /' _ `\  /'_  \  /'__`\/\  __\/ ,__\  / ___\ / __`\/\  __\/'__`\     \/\ \  /',__\
+    \ \ \_\ \/\ \/\ \/\ \ \ \/\  __/\ \ \//\__, `\/\ \__//\ \ \ \ \ \//\  __/  __  \ \ \/\__, `\
+     \ \____/\ \_\ \_\ \___,_\ \____\\ \_\\/\____/\ \____\ \____/\ \_\\ \____\/\_\ _\ \ \/\____/
+      \/___/  \/_/\/_/\/__,_ /\/____/ \/_/ \/___/  \/____/\/___/  \/_/ \/____/\/_//\ \_\ \/___/
+                                                                                  \ \____/
+                                                                                   \/___/
+
+Underscore.js is a utility-belt library for JavaScript that provides
+support for the usual functional suspects (each, map, reduce, filter...)
+without extending any core JavaScript objects.
+
+For Docs, License, Tests, and pre-packed downloads, see:
+http://underscorejs.org
+
+Underscore is an open-sourced component of DocumentCloud:
+https://github.com/documentcloud
+
+Many thanks to our contributors:
+https://github.com/jashkenas/underscore/contributors
level2/node_modules/underscore/underscore-min.js
@@ -0,0 +1,6 @@
+//     Underscore.js 1.5.2
+//     http://underscorejs.org
+//     (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+//     Underscore may be freely distributed under the MIT license.
+(function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,w=Object.keys,_=i.bind,j=function(n){return n instanceof j?n:this instanceof j?(this._wrapped=n,void 0):new j(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION="1.5.2";var A=j.each=j.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a=j.keys(n),u=0,i=a.length;i>u;u++)if(t.call(e,n[a[u]],a[u],n)===r)return};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var E="Reduce of empty array with no initial value";j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(E);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(E);return r},j.find=j.detect=function(n,t,r){var e;return O(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var O=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:O(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,function(n){return n[t]})},j.where=function(n,t,r){return j.isEmpty(t)?r?void 0:[]:j[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},j.findWhere=function(n,t){return j.where(n,t,!0)},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);if(!t&&j.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>e.computed&&(e={value:n,computed:a})}),e.value},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);if(!t&&j.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a<e.computed&&(e={value:n,computed:a})}),e.value},j.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=j.random(r++),e[r-1]=e[t],e[t]=n}),e},j.sample=function(n,t,r){return arguments.length<2||r?n[j.random(n.length-1)]:j.shuffle(n).slice(0,Math.max(0,t))};var k=function(n){return j.isFunction(n)?n:function(t){return t[n]}};j.sortBy=function(n,t,r){var e=k(t);return j.pluck(j.map(n,function(n,t,u){return{value:n,index:t,criteria:e.call(r,n,t,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={},i=null==r?j.identity:k(r);return A(t,function(r,a){var o=i.call(e,r,a,t);n(u,o,r)}),u}};j.groupBy=F(function(n,t,r){(j.has(n,t)?n[t]:n[t]=[]).push(r)}),j.indexBy=F(function(n,t,r){n[t]=r}),j.countBy=F(function(n,t){j.has(n,t)?n[t]++:n[t]=1}),j.sortedIndex=function(n,t,r,e){r=null==r?j.identity:k(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])<u?i=o+1:a=o}return i},j.toArray=function(n){return n?j.isArray(n)?o.call(n):n.length===+n.length?j.map(n,j.identity):j.values(n):[]},j.size=function(n){return null==n?0:n.length===+n.length?n.length:j.keys(n).length},j.first=j.head=j.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:o.call(n,0,t)},j.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},j.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},j.rest=j.tail=j.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},j.compact=function(n){return j.filter(n,j.identity)};var M=function(n,t,r){return t&&j.every(n,j.isArray)?c.apply(r,n):(A(n,function(n){j.isArray(n)||j.isArguments(n)?t?a.apply(r,n):M(n,t,r):r.push(n)}),r)};j.flatten=function(n,t){return M(n,t,[])},j.without=function(n){return j.difference(n,o.call(arguments,1))},j.uniq=j.unique=function(n,t,r,e){j.isFunction(t)&&(e=r,r=t,t=!1);var u=r?j.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:j.contains(a,r))||(a.push(r),i.push(n[e]))}),i},j.union=function(){return j.uniq(j.flatten(arguments,!0))},j.intersection=function(n){var t=o.call(arguments,1);return j.filter(j.uniq(n),function(n){return j.every(t,function(t){return j.indexOf(t,n)>=0})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,"length").concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,""+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var R=function(){};j.bind=function(n,t){var r,e;if(_&&n.bind===_)return _.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));R.prototype=n.prototype;var u=new R;R.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},j.bindAll=function(n){var t=o.call(arguments,1);if(0===t.length)throw new Error("bindAll must be passed function names");return A(t,function(t){n[t]=j.bind(n[t],n)}),n},j.memoize=function(n,t){var r={};return t||(t=j.identity),function(){var e=t.apply(this,arguments);return j.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},j.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},j.defer=function(n){return j.delay.apply(j,[n,1].concat(o.call(arguments,1)))},j.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var c=function(){o=r.leading===!1?0:new Date,a=null,i=n.apply(e,u)};return function(){var l=new Date;o||r.leading!==!1||(o=l);var f=t-(l-o);return e=this,u=arguments,0>=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u)):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u,i,a,o;return function(){i=this,u=arguments,a=new Date;var c=function(){var l=new Date-a;t>l?e=setTimeout(c,t-l):(e=null,r||(o=n.apply(i,u)))},l=r&&!e;return e||(e=setTimeout(c,t)),l&&(o=n.apply(i,u)),o}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},j.keys=w||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},j.pairs=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},j.invert=function(n){for(var t={},r=j.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==String(t);case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o))return!1;r.push(n),e.push(t);var c=0,f=!0;if("[object Array]"==u){if(c=n.length,f=c==t.length)for(;c--&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c--)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return"[object Array]"==l.call(n)},j.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){j["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,"callee"))}),"function"!=typeof/./&&(j.isFunction=function(n){return"function"==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var I={escape:{"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;"}};I.unescape=j.invert(I.escape);var T={escape:new RegExp("["+j.keys(I.escape).join("")+"]","g"),unescape:new RegExp("("+j.keys(I.unescape).join("|")+")","g")};j.each(["escape","unescape"],function(n){j[n]=function(t){return null==t?"":(""+t).replace(T[n],function(t){return I[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+"";return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n","	":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return"\\"+B[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=new Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this);
+//# sourceMappingURL=underscore-min.map
\ No newline at end of file
level2/node_modules/underscore/underscore.js
@@ -0,0 +1,1276 @@
+//     Underscore.js 1.5.2
+//     http://underscorejs.org
+//     (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+//     Underscore may be freely distributed under the MIT license.
+
+(function() {
+
+  // Baseline setup
+  // --------------
+
+  // Establish the root object, `window` in the browser, or `exports` on the server.
+  var root = this;
+
+  // Save the previous value of the `_` variable.
+  var previousUnderscore = root._;
+
+  // Establish the object that gets returned to break out of a loop iteration.
+  var breaker = {};
+
+  // Save bytes in the minified (but not gzipped) version:
+  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
+
+  // Create quick reference variables for speed access to core prototypes.
+  var
+    push             = ArrayProto.push,
+    slice            = ArrayProto.slice,
+    concat           = ArrayProto.concat,
+    toString         = ObjProto.toString,
+    hasOwnProperty   = ObjProto.hasOwnProperty;
+
+  // All **ECMAScript 5** native function implementations that we hope to use
+  // are declared here.
+  var
+    nativeForEach      = ArrayProto.forEach,
+    nativeMap          = ArrayProto.map,
+    nativeReduce       = ArrayProto.reduce,
+    nativeReduceRight  = ArrayProto.reduceRight,
+    nativeFilter       = ArrayProto.filter,
+    nativeEvery        = ArrayProto.every,
+    nativeSome         = ArrayProto.some,
+    nativeIndexOf      = ArrayProto.indexOf,
+    nativeLastIndexOf  = ArrayProto.lastIndexOf,
+    nativeIsArray      = Array.isArray,
+    nativeKeys         = Object.keys,
+    nativeBind         = FuncProto.bind;
+
+  // Create a safe reference to the Underscore object for use below.
+  var _ = function(obj) {
+    if (obj instanceof _) return obj;
+    if (!(this instanceof _)) return new _(obj);
+    this._wrapped = obj;
+  };
+
+  // Export the Underscore object for **Node.js**, with
+  // backwards-compatibility for the old `require()` API. If we're in
+  // the browser, add `_` as a global object via a string identifier,
+  // for Closure Compiler "advanced" mode.
+  if (typeof exports !== 'undefined') {
+    if (typeof module !== 'undefined' && module.exports) {
+      exports = module.exports = _;
+    }
+    exports._ = _;
+  } else {
+    root._ = _;
+  }
+
+  // Current version.
+  _.VERSION = '1.5.2';
+
+  // Collection Functions
+  // --------------------
+
+  // The cornerstone, an `each` implementation, aka `forEach`.
+  // Handles objects with the built-in `forEach`, arrays, and raw objects.
+  // Delegates to **ECMAScript 5**'s native `forEach` if available.
+  var each = _.each = _.forEach = function(obj, iterator, context) {
+    if (obj == null) return;
+    if (nativeForEach && obj.forEach === nativeForEach) {
+      obj.forEach(iterator, context);
+    } else if (obj.length === +obj.length) {
+      for (var i = 0, length = obj.length; i < length; i++) {
+        if (iterator.call(context, obj[i], i, obj) === breaker) return;
+      }
+    } else {
+      var keys = _.keys(obj);
+      for (var i = 0, length = keys.length; i < length; i++) {
+        if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
+      }
+    }
+  };
+
+  // Return the results of applying the iterator to each element.
+  // Delegates to **ECMAScript 5**'s native `map` if available.
+  _.map = _.collect = function(obj, iterator, context) {
+    var results = [];
+    if (obj == null) return results;
+    if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
+    each(obj, function(value, index, list) {
+      results.push(iterator.call(context, value, index, list));
+    });
+    return results;
+  };
+
+  var reduceError = 'Reduce of empty array with no initial value';
+
+  // **Reduce** builds up a single result from a list of values, aka `inject`,
+  // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
+  _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
+    var initial = arguments.length > 2;
+    if (obj == null) obj = [];
+    if (nativeReduce && obj.reduce === nativeReduce) {
+      if (context) iterator = _.bind(iterator, context);
+      return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
+    }
+    each(obj, function(value, index, list) {
+      if (!initial) {
+        memo = value;
+        initial = true;
+      } else {
+        memo = iterator.call(context, memo, value, index, list);
+      }
+    });
+    if (!initial) throw new TypeError(reduceError);
+    return memo;
+  };
+
+  // The right-associative version of reduce, also known as `foldr`.
+  // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
+  _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
+    var initial = arguments.length > 2;
+    if (obj == null) obj = [];
+    if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
+      if (context) iterator = _.bind(iterator, context);
+      return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
+    }
+    var length = obj.length;
+    if (length !== +length) {
+      var keys = _.keys(obj);
+      length = keys.length;
+    }
+    each(obj, function(value, index, list) {
+      index = keys ? keys[--length] : --length;
+      if (!initial) {
+        memo = obj[index];
+        initial = true;
+      } else {
+        memo = iterator.call(context, memo, obj[index], index, list);
+      }
+    });
+    if (!initial) throw new TypeError(reduceError);
+    return memo;
+  };
+
+  // Return the first value which passes a truth test. Aliased as `detect`.
+  _.find = _.detect = function(obj, iterator, context) {
+    var result;
+    any(obj, function(value, index, list) {
+      if (iterator.call(context, value, index, list)) {
+        result = value;
+        return true;
+      }
+    });
+    return result;
+  };
+
+  // Return all the elements that pass a truth test.
+  // Delegates to **ECMAScript 5**'s native `filter` if available.
+  // Aliased as `select`.
+  _.filter = _.select = function(obj, iterator, context) {
+    var results = [];
+    if (obj == null) return results;
+    if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
+    each(obj, function(value, index, list) {
+      if (iterator.call(context, value, index, list)) results.push(value);
+    });
+    return results;
+  };
+
+  // Return all the elements for which a truth test fails.
+  _.reject = function(obj, iterator, context) {
+    return _.filter(obj, function(value, index, list) {
+      return !iterator.call(context, value, index, list);
+    }, context);
+  };
+
+  // Determine whether all of the elements match a truth test.
+  // Delegates to **ECMAScript 5**'s native `every` if available.
+  // Aliased as `all`.
+  _.every = _.all = function(obj, iterator, context) {
+    iterator || (iterator = _.identity);
+    var result = true;
+    if (obj == null) return result;
+    if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
+    each(obj, function(value, index, list) {
+      if (!(result = result && iterator.call(context, value, index, list))) return breaker;
+    });
+    return !!result;
+  };
+
+  // Determine if at least one element in the object matches a truth test.
+  // Delegates to **ECMAScript 5**'s native `some` if available.
+  // Aliased as `any`.
+  var any = _.some = _.any = function(obj, iterator, context) {
+    iterator || (iterator = _.identity);
+    var result = false;
+    if (obj == null) return result;
+    if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
+    each(obj, function(value, index, list) {
+      if (result || (result = iterator.call(context, value, index, list))) return breaker;
+    });
+    return !!result;
+  };
+
+  // Determine if the array or object contains a given value (using `===`).
+  // Aliased as `include`.
+  _.contains = _.include = function(obj, target) {
+    if (obj == null) return false;
+    if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
+    return any(obj, function(value) {
+      return value === target;
+    });
+  };
+
+  // Invoke a method (with arguments) on every item in a collection.
+  _.invoke = function(obj, method) {
+    var args = slice.call(arguments, 2);
+    var isFunc = _.isFunction(method);
+    return _.map(obj, function(value) {
+      return (isFunc ? method : value[method]).apply(value, args);
+    });
+  };
+
+  // Convenience version of a common use case of `map`: fetching a property.
+  _.pluck = function(obj, key) {
+    return _.map(obj, function(value){ return value[key]; });
+  };
+
+  // Convenience version of a common use case of `filter`: selecting only objects
+  // containing specific `key:value` pairs.
+  _.where = function(obj, attrs, first) {
+    if (_.isEmpty(attrs)) return first ? void 0 : [];
+    return _[first ? 'find' : 'filter'](obj, function(value) {
+      for (var key in attrs) {
+        if (attrs[key] !== value[key]) return false;
+      }
+      return true;
+    });
+  };
+
+  // Convenience version of a common use case of `find`: getting the first object
+  // containing specific `key:value` pairs.
+  _.findWhere = function(obj, attrs) {
+    return _.where(obj, attrs, true);
+  };
+
+  // Return the maximum element or (element-based computation).
+  // Can't optimize arrays of integers longer than 65,535 elements.
+  // See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797)
+  _.max = function(obj, iterator, context) {
+    if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
+      return Math.max.apply(Math, obj);
+    }
+    if (!iterator && _.isEmpty(obj)) return -Infinity;
+    var result = {computed : -Infinity, value: -Infinity};
+    each(obj, function(value, index, list) {
+      var computed = iterator ? iterator.call(context, value, index, list) : value;
+      computed > result.computed && (result = {value : value, computed : computed});
+    });
+    return result.value;
+  };
+
+  // Return the minimum element (or element-based computation).
+  _.min = function(obj, iterator, context) {
+    if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
+      return Math.min.apply(Math, obj);
+    }
+    if (!iterator && _.isEmpty(obj)) return Infinity;
+    var result = {computed : Infinity, value: Infinity};
+    each(obj, function(value, index, list) {
+      var computed = iterator ? iterator.call(context, value, index, list) : value;
+      computed < result.computed && (result = {value : value, computed : computed});
+    });
+    return result.value;
+  };
+
+  // Shuffle an array, using the modern version of the 
+  // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisherโ€“Yates_shuffle).
+  _.shuffle = function(obj) {
+    var rand;
+    var index = 0;
+    var shuffled = [];
+    each(obj, function(value) {
+      rand = _.random(index++);
+      shuffled[index - 1] = shuffled[rand];
+      shuffled[rand] = value;
+    });
+    return shuffled;
+  };
+
+  // Sample **n** random values from an array.
+  // If **n** is not specified, returns a single random element from the array.
+  // The internal `guard` argument allows it to work with `map`.
+  _.sample = function(obj, n, guard) {
+    if (arguments.length < 2 || guard) {
+      return obj[_.random(obj.length - 1)];
+    }
+    return _.shuffle(obj).slice(0, Math.max(0, n));
+  };
+
+  // An internal function to generate lookup iterators.
+  var lookupIterator = function(value) {
+    return _.isFunction(value) ? value : function(obj){ return obj[value]; };
+  };
+
+  // Sort the object's values by a criterion produced by an iterator.
+  _.sortBy = function(obj, value, context) {
+    var iterator = lookupIterator(value);
+    return _.pluck(_.map(obj, function(value, index, list) {
+      return {
+        value: value,
+        index: index,
+        criteria: iterator.call(context, value, index, list)
+      };
+    }).sort(function(left, right) {
+      var a = left.criteria;
+      var b = right.criteria;
+      if (a !== b) {
+        if (a > b || a === void 0) return 1;
+        if (a < b || b === void 0) return -1;
+      }
+      return left.index - right.index;
+    }), 'value');
+  };
+
+  // An internal function used for aggregate "group by" operations.
+  var group = function(behavior) {
+    return function(obj, value, context) {
+      var result = {};
+      var iterator = value == null ? _.identity : lookupIterator(value);
+      each(obj, function(value, index) {
+        var key = iterator.call(context, value, index, obj);
+        behavior(result, key, value);
+      });
+      return result;
+    };
+  };
+
+  // Groups the object's values by a criterion. Pass either a string attribute
+  // to group by, or a function that returns the criterion.
+  _.groupBy = group(function(result, key, value) {
+    (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
+  });
+
+  // Indexes the object's values by a criterion, similar to `groupBy`, but for
+  // when you know that your index values will be unique.
+  _.indexBy = group(function(result, key, value) {
+    result[key] = value;
+  });
+
+  // Counts instances of an object that group by a certain criterion. Pass
+  // either a string attribute to count by, or a function that returns the
+  // criterion.
+  _.countBy = group(function(result, key) {
+    _.has(result, key) ? result[key]++ : result[key] = 1;
+  });
+
+  // Use a comparator function to figure out the smallest index at which
+  // an object should be inserted so as to maintain order. Uses binary search.
+  _.sortedIndex = function(array, obj, iterator, context) {
+    iterator = iterator == null ? _.identity : lookupIterator(iterator);
+    var value = iterator.call(context, obj);
+    var low = 0, high = array.length;
+    while (low < high) {
+      var mid = (low + high) >>> 1;
+      iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
+    }
+    return low;
+  };
+
+  // Safely create a real, live array from anything iterable.
+  _.toArray = function(obj) {
+    if (!obj) return [];
+    if (_.isArray(obj)) return slice.call(obj);
+    if (obj.length === +obj.length) return _.map(obj, _.identity);
+    return _.values(obj);
+  };
+
+  // Return the number of elements in an object.
+  _.size = function(obj) {
+    if (obj == null) return 0;
+    return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
+  };
+
+  // Array Functions
+  // ---------------
+
+  // Get the first element of an array. Passing **n** will return the first N
+  // values in the array. Aliased as `head` and `take`. The **guard** check
+  // allows it to work with `_.map`.
+  _.first = _.head = _.take = function(array, n, guard) {
+    if (array == null) return void 0;
+    return (n == null) || guard ? array[0] : slice.call(array, 0, n);
+  };
+
+  // Returns everything but the last entry of the array. Especially useful on
+  // the arguments object. Passing **n** will return all the values in
+  // the array, excluding the last N. The **guard** check allows it to work with
+  // `_.map`.
+  _.initial = function(array, n, guard) {
+    return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
+  };
+
+  // Get the last element of an array. Passing **n** will return the last N
+  // values in the array. The **guard** check allows it to work with `_.map`.
+  _.last = function(array, n, guard) {
+    if (array == null) return void 0;
+    if ((n == null) || guard) {
+      return array[array.length - 1];
+    } else {
+      return slice.call(array, Math.max(array.length - n, 0));
+    }
+  };
+
+  // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
+  // Especially useful on the arguments object. Passing an **n** will return
+  // the rest N values in the array. The **guard**
+  // check allows it to work with `_.map`.
+  _.rest = _.tail = _.drop = function(array, n, guard) {
+    return slice.call(array, (n == null) || guard ? 1 : n);
+  };
+
+  // Trim out all falsy values from an array.
+  _.compact = function(array) {
+    return _.filter(array, _.identity);
+  };
+
+  // Internal implementation of a recursive `flatten` function.
+  var flatten = function(input, shallow, output) {
+    if (shallow && _.every(input, _.isArray)) {
+      return concat.apply(output, input);
+    }
+    each(input, function(value) {
+      if (_.isArray(value) || _.isArguments(value)) {
+        shallow ? push.apply(output, value) : flatten(value, shallow, output);
+      } else {
+        output.push(value);
+      }
+    });
+    return output;
+  };
+
+  // Flatten out an array, either recursively (by default), or just one level.
+  _.flatten = function(array, shallow) {
+    return flatten(array, shallow, []);
+  };
+
+  // Return a version of the array that does not contain the specified value(s).
+  _.without = function(array) {
+    return _.difference(array, slice.call(arguments, 1));
+  };
+
+  // Produce a duplicate-free version of the array. If the array has already
+  // been sorted, you have the option of using a faster algorithm.
+  // Aliased as `unique`.
+  _.uniq = _.unique = function(array, isSorted, iterator, context) {
+    if (_.isFunction(isSorted)) {
+      context = iterator;
+      iterator = isSorted;
+      isSorted = false;
+    }
+    var initial = iterator ? _.map(array, iterator, context) : array;
+    var results = [];
+    var seen = [];
+    each(initial, function(value, index) {
+      if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
+        seen.push(value);
+        results.push(array[index]);
+      }
+    });
+    return results;
+  };
+
+  // Produce an array that contains the union: each distinct element from all of
+  // the passed-in arrays.
+  _.union = function() {
+    return _.uniq(_.flatten(arguments, true));
+  };
+
+  // Produce an array that contains every item shared between all the
+  // passed-in arrays.
+  _.intersection = function(array) {
+    var rest = slice.call(arguments, 1);
+    return _.filter(_.uniq(array), function(item) {
+      return _.every(rest, function(other) {
+        return _.indexOf(other, item) >= 0;
+      });
+    });
+  };
+
+  // Take the difference between one array and a number of other arrays.
+  // Only the elements present in just the first array will remain.
+  _.difference = function(array) {
+    var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
+    return _.filter(array, function(value){ return !_.contains(rest, value); });
+  };
+
+  // Zip together multiple lists into a single array -- elements that share
+  // an index go together.
+  _.zip = function() {
+    var length = _.max(_.pluck(arguments, "length").concat(0));
+    var results = new Array(length);
+    for (var i = 0; i < length; i++) {
+      results[i] = _.pluck(arguments, '' + i);
+    }
+    return results;
+  };
+
+  // Converts lists into objects. Pass either a single array of `[key, value]`
+  // pairs, or two parallel arrays of the same length -- one of keys, and one of
+  // the corresponding values.
+  _.object = function(list, values) {
+    if (list == null) return {};
+    var result = {};
+    for (var i = 0, length = list.length; i < length; i++) {
+      if (values) {
+        result[list[i]] = values[i];
+      } else {
+        result[list[i][0]] = list[i][1];
+      }
+    }
+    return result;
+  };
+
+  // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
+  // we need this function. Return the position of the first occurrence of an
+  // item in an array, or -1 if the item is not included in the array.
+  // Delegates to **ECMAScript 5**'s native `indexOf` if available.
+  // If the array is large and already in sort order, pass `true`
+  // for **isSorted** to use binary search.
+  _.indexOf = function(array, item, isSorted) {
+    if (array == null) return -1;
+    var i = 0, length = array.length;
+    if (isSorted) {
+      if (typeof isSorted == 'number') {
+        i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted);
+      } else {
+        i = _.sortedIndex(array, item);
+        return array[i] === item ? i : -1;
+      }
+    }
+    if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
+    for (; i < length; i++) if (array[i] === item) return i;
+    return -1;
+  };
+
+  // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
+  _.lastIndexOf = function(array, item, from) {
+    if (array == null) return -1;
+    var hasIndex = from != null;
+    if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
+      return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
+    }
+    var i = (hasIndex ? from : array.length);
+    while (i--) if (array[i] === item) return i;
+    return -1;
+  };
+
+  // Generate an integer Array containing an arithmetic progression. A port of
+  // the native Python `range()` function. See
+  // [the Python documentation](http://docs.python.org/library/functions.html#range).
+  _.range = function(start, stop, step) {
+    if (arguments.length <= 1) {
+      stop = start || 0;
+      start = 0;
+    }
+    step = arguments[2] || 1;
+
+    var length = Math.max(Math.ceil((stop - start) / step), 0);
+    var idx = 0;
+    var range = new Array(length);
+
+    while(idx < length) {
+      range[idx++] = start;
+      start += step;
+    }
+
+    return range;
+  };
+
+  // Function (ahem) Functions
+  // ------------------
+
+  // Reusable constructor function for prototype setting.
+  var ctor = function(){};
+
+  // Create a function bound to a given object (assigning `this`, and arguments,
+  // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
+  // available.
+  _.bind = function(func, context) {
+    var args, bound;
+    if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
+    if (!_.isFunction(func)) throw new TypeError;
+    args = slice.call(arguments, 2);
+    return bound = function() {
+      if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
+      ctor.prototype = func.prototype;
+      var self = new ctor;
+      ctor.prototype = null;
+      var result = func.apply(self, args.concat(slice.call(arguments)));
+      if (Object(result) === result) return result;
+      return self;
+    };
+  };
+
+  // Partially apply a function by creating a version that has had some of its
+  // arguments pre-filled, without changing its dynamic `this` context.
+  _.partial = function(func) {
+    var args = slice.call(arguments, 1);
+    return function() {
+      return func.apply(this, args.concat(slice.call(arguments)));
+    };
+  };
+
+  // Bind all of an object's methods to that object. Useful for ensuring that
+  // all callbacks defined on an object belong to it.
+  _.bindAll = function(obj) {
+    var funcs = slice.call(arguments, 1);
+    if (funcs.length === 0) throw new Error("bindAll must be passed function names");
+    each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
+    return obj;
+  };
+
+  // Memoize an expensive function by storing its results.
+  _.memoize = function(func, hasher) {
+    var memo = {};
+    hasher || (hasher = _.identity);
+    return function() {
+      var key = hasher.apply(this, arguments);
+      return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
+    };
+  };
+
+  // Delays a function for the given number of milliseconds, and then calls
+  // it with the arguments supplied.
+  _.delay = function(func, wait) {
+    var args = slice.call(arguments, 2);
+    return setTimeout(function(){ return func.apply(null, args); }, wait);
+  };
+
+  // Defers a function, scheduling it to run after the current call stack has
+  // cleared.
+  _.defer = function(func) {
+    return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
+  };
+
+  // Returns a function, that, when invoked, will only be triggered at most once
+  // during a given window of time. Normally, the throttled function will run
+  // as much as it can, without ever going more than once per `wait` duration;
+  // but if you'd like to disable the execution on the leading edge, pass
+  // `{leading: false}`. To disable execution on the trailing edge, ditto.
+  _.throttle = function(func, wait, options) {
+    var context, args, result;
+    var timeout = null;
+    var previous = 0;
+    options || (options = {});
+    var later = function() {
+      previous = options.leading === false ? 0 : new Date;
+      timeout = null;
+      result = func.apply(context, args);
+    };
+    return function() {
+      var now = new Date;
+      if (!previous && options.leading === false) previous = now;
+      var remaining = wait - (now - previous);
+      context = this;
+      args = arguments;
+      if (remaining <= 0) {
+        clearTimeout(timeout);
+        timeout = null;
+        previous = now;
+        result = func.apply(context, args);
+      } else if (!timeout && options.trailing !== false) {
+        timeout = setTimeout(later, remaining);
+      }
+      return result;
+    };
+  };
+
+  // Returns a function, that, as long as it continues to be invoked, will not
+  // be triggered. The function will be called after it stops being called for
+  // N milliseconds. If `immediate` is passed, trigger the function on the
+  // leading edge, instead of the trailing.
+  _.debounce = function(func, wait, immediate) {
+    var timeout, args, context, timestamp, result;
+    return function() {
+      context = this;
+      args = arguments;
+      timestamp = new Date();
+      var later = function() {
+        var last = (new Date()) - timestamp;
+        if (last < wait) {
+          timeout = setTimeout(later, wait - last);
+        } else {
+          timeout = null;
+          if (!immediate) result = func.apply(context, args);
+        }
+      };
+      var callNow = immediate && !timeout;
+      if (!timeout) {
+        timeout = setTimeout(later, wait);
+      }
+      if (callNow) result = func.apply(context, args);
+      return result;
+    };
+  };
+
+  // Returns a function that will be executed at most one time, no matter how
+  // often you call it. Useful for lazy initialization.
+  _.once = function(func) {
+    var ran = false, memo;
+    return function() {
+      if (ran) return memo;
+      ran = true;
+      memo = func.apply(this, arguments);
+      func = null;
+      return memo;
+    };
+  };
+
+  // Returns the first function passed as an argument to the second,
+  // allowing you to adjust arguments, run code before and after, and
+  // conditionally execute the original function.
+  _.wrap = function(func, wrapper) {
+    return function() {
+      var args = [func];
+      push.apply(args, arguments);
+      return wrapper.apply(this, args);
+    };
+  };
+
+  // Returns a function that is the composition of a list of functions, each
+  // consuming the return value of the function that follows.
+  _.compose = function() {
+    var funcs = arguments;
+    return function() {
+      var args = arguments;
+      for (var i = funcs.length - 1; i >= 0; i--) {
+        args = [funcs[i].apply(this, args)];
+      }
+      return args[0];
+    };
+  };
+
+  // Returns a function that will only be executed after being called N times.
+  _.after = function(times, func) {
+    return function() {
+      if (--times < 1) {
+        return func.apply(this, arguments);
+      }
+    };
+  };
+
+  // Object Functions
+  // ----------------
+
+  // Retrieve the names of an object's properties.
+  // Delegates to **ECMAScript 5**'s native `Object.keys`
+  _.keys = nativeKeys || function(obj) {
+    if (obj !== Object(obj)) throw new TypeError('Invalid object');
+    var keys = [];
+    for (var key in obj) if (_.has(obj, key)) keys.push(key);
+    return keys;
+  };
+
+  // Retrieve the values of an object's properties.
+  _.values = function(obj) {
+    var keys = _.keys(obj);
+    var length = keys.length;
+    var values = new Array(length);
+    for (var i = 0; i < length; i++) {
+      values[i] = obj[keys[i]];
+    }
+    return values;
+  };
+
+  // Convert an object into a list of `[key, value]` pairs.
+  _.pairs = function(obj) {
+    var keys = _.keys(obj);
+    var length = keys.length;
+    var pairs = new Array(length);
+    for (var i = 0; i < length; i++) {
+      pairs[i] = [keys[i], obj[keys[i]]];
+    }
+    return pairs;
+  };
+
+  // Invert the keys and values of an object. The values must be serializable.
+  _.invert = function(obj) {
+    var result = {};
+    var keys = _.keys(obj);
+    for (var i = 0, length = keys.length; i < length; i++) {
+      result[obj[keys[i]]] = keys[i];
+    }
+    return result;
+  };
+
+  // Return a sorted list of the function names available on the object.
+  // Aliased as `methods`
+  _.functions = _.methods = function(obj) {
+    var names = [];
+    for (var key in obj) {
+      if (_.isFunction(obj[key])) names.push(key);
+    }
+    return names.sort();
+  };
+
+  // Extend a given object with all the properties in passed-in object(s).
+  _.extend = function(obj) {
+    each(slice.call(arguments, 1), function(source) {
+      if (source) {
+        for (var prop in source) {
+          obj[prop] = source[prop];
+        }
+      }
+    });
+    return obj;
+  };
+
+  // Return a copy of the object only containing the whitelisted properties.
+  _.pick = function(obj) {
+    var copy = {};
+    var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
+    each(keys, function(key) {
+      if (key in obj) copy[key] = obj[key];
+    });
+    return copy;
+  };
+
+   // Return a copy of the object without the blacklisted properties.
+  _.omit = function(obj) {
+    var copy = {};
+    var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
+    for (var key in obj) {
+      if (!_.contains(keys, key)) copy[key] = obj[key];
+    }
+    return copy;
+  };
+
+  // Fill in a given object with default properties.
+  _.defaults = function(obj) {
+    each(slice.call(arguments, 1), function(source) {
+      if (source) {
+        for (var prop in source) {
+          if (obj[prop] === void 0) obj[prop] = source[prop];
+        }
+      }
+    });
+    return obj;
+  };
+
+  // Create a (shallow-cloned) duplicate of an object.
+  _.clone = function(obj) {
+    if (!_.isObject(obj)) return obj;
+    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
+  };
+
+  // Invokes interceptor with the obj, and then returns obj.
+  // The primary purpose of this method is to "tap into" a method chain, in
+  // order to perform operations on intermediate results within the chain.
+  _.tap = function(obj, interceptor) {
+    interceptor(obj);
+    return obj;
+  };
+
+  // Internal recursive comparison function for `isEqual`.
+  var eq = function(a, b, aStack, bStack) {
+    // Identical objects are equal. `0 === -0`, but they aren't identical.
+    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
+    if (a === b) return a !== 0 || 1 / a == 1 / b;
+    // A strict comparison is necessary because `null == undefined`.
+    if (a == null || b == null) return a === b;
+    // Unwrap any wrapped objects.
+    if (a instanceof _) a = a._wrapped;
+    if (b instanceof _) b = b._wrapped;
+    // Compare `[[Class]]` names.
+    var className = toString.call(a);
+    if (className != toString.call(b)) return false;
+    switch (className) {
+      // Strings, numbers, dates, and booleans are compared by value.
+      case '[object String]':
+        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+        // equivalent to `new String("5")`.
+        return a == String(b);
+      case '[object Number]':
+        // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
+        // other numeric values.
+        return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
+      case '[object Date]':
+      case '[object Boolean]':
+        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+        // millisecond representations. Note that invalid dates with millisecond representations
+        // of `NaN` are not equivalent.
+        return +a == +b;
+      // RegExps are compared by their source patterns and flags.
+      case '[object RegExp]':
+        return a.source == b.source &&
+               a.global == b.global &&
+               a.multiline == b.multiline &&
+               a.ignoreCase == b.ignoreCase;
+    }
+    if (typeof a != 'object' || typeof b != 'object') return false;
+    // Assume equality for cyclic structures. The algorithm for detecting cyclic
+    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+    var length = aStack.length;
+    while (length--) {
+      // Linear search. Performance is inversely proportional to the number of
+      // unique nested structures.
+      if (aStack[length] == a) return bStack[length] == b;
+    }
+    // Objects with different constructors are not equivalent, but `Object`s
+    // from different frames are.
+    var aCtor = a.constructor, bCtor = b.constructor;
+    if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
+                             _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
+      return false;
+    }
+    // Add the first object to the stack of traversed objects.
+    aStack.push(a);
+    bStack.push(b);
+    var size = 0, result = true;
+    // Recursively compare objects and arrays.
+    if (className == '[object Array]') {
+      // Compare array lengths to determine if a deep comparison is necessary.
+      size = a.length;
+      result = size == b.length;
+      if (result) {
+        // Deep compare the contents, ignoring non-numeric properties.
+        while (size--) {
+          if (!(result = eq(a[size], b[size], aStack, bStack))) break;
+        }
+      }
+    } else {
+      // Deep compare objects.
+      for (var key in a) {
+        if (_.has(a, key)) {
+          // Count the expected number of properties.
+          size++;
+          // Deep compare each member.
+          if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
+        }
+      }
+      // Ensure that both objects contain the same number of properties.
+      if (result) {
+        for (key in b) {
+          if (_.has(b, key) && !(size--)) break;
+        }
+        result = !size;
+      }
+    }
+    // Remove the first object from the stack of traversed objects.
+    aStack.pop();
+    bStack.pop();
+    return result;
+  };
+
+  // Perform a deep comparison to check if two objects are equal.
+  _.isEqual = function(a, b) {
+    return eq(a, b, [], []);
+  };
+
+  // Is a given array, string, or object empty?
+  // An "empty" object has no enumerable own-properties.
+  _.isEmpty = function(obj) {
+    if (obj == null) return true;
+    if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
+    for (var key in obj) if (_.has(obj, key)) return false;
+    return true;
+  };
+
+  // Is a given value a DOM element?
+  _.isElement = function(obj) {
+    return !!(obj && obj.nodeType === 1);
+  };
+
+  // Is a given value an array?
+  // Delegates to ECMA5's native Array.isArray
+  _.isArray = nativeIsArray || function(obj) {
+    return toString.call(obj) == '[object Array]';
+  };
+
+  // Is a given variable an object?
+  _.isObject = function(obj) {
+    return obj === Object(obj);
+  };
+
+  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
+  each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
+    _['is' + name] = function(obj) {
+      return toString.call(obj) == '[object ' + name + ']';
+    };
+  });
+
+  // Define a fallback version of the method in browsers (ahem, IE), where
+  // there isn't any inspectable "Arguments" type.
+  if (!_.isArguments(arguments)) {
+    _.isArguments = function(obj) {
+      return !!(obj && _.has(obj, 'callee'));
+    };
+  }
+
+  // Optimize `isFunction` if appropriate.
+  if (typeof (/./) !== 'function') {
+    _.isFunction = function(obj) {
+      return typeof obj === 'function';
+    };
+  }
+
+  // Is a given object a finite number?
+  _.isFinite = function(obj) {
+    return isFinite(obj) && !isNaN(parseFloat(obj));
+  };
+
+  // Is the given value `NaN`? (NaN is the only number which does not equal itself).
+  _.isNaN = function(obj) {
+    return _.isNumber(obj) && obj != +obj;
+  };
+
+  // Is a given value a boolean?
+  _.isBoolean = function(obj) {
+    return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
+  };
+
+  // Is a given value equal to null?
+  _.isNull = function(obj) {
+    return obj === null;
+  };
+
+  // Is a given variable undefined?
+  _.isUndefined = function(obj) {
+    return obj === void 0;
+  };
+
+  // Shortcut function for checking if an object has a given property directly
+  // on itself (in other words, not on a prototype).
+  _.has = function(obj, key) {
+    return hasOwnProperty.call(obj, key);
+  };
+
+  // Utility Functions
+  // -----------------
+
+  // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
+  // previous owner. Returns a reference to the Underscore object.
+  _.noConflict = function() {
+    root._ = previousUnderscore;
+    return this;
+  };
+
+  // Keep the identity function around for default iterators.
+  _.identity = function(value) {
+    return value;
+  };
+
+  // Run a function **n** times.
+  _.times = function(n, iterator, context) {
+    var accum = Array(Math.max(0, n));
+    for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
+    return accum;
+  };
+
+  // Return a random integer between min and max (inclusive).
+  _.random = function(min, max) {
+    if (max == null) {
+      max = min;
+      min = 0;
+    }
+    return min + Math.floor(Math.random() * (max - min + 1));
+  };
+
+  // List of HTML entities for escaping.
+  var entityMap = {
+    escape: {
+      '&': '&amp;',
+      '<': '&lt;',
+      '>': '&gt;',
+      '"': '&quot;',
+      "'": '&#x27;'
+    }
+  };
+  entityMap.unescape = _.invert(entityMap.escape);
+
+  // Regexes containing the keys and values listed immediately above.
+  var entityRegexes = {
+    escape:   new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
+    unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
+  };
+
+  // Functions for escaping and unescaping strings to/from HTML interpolation.
+  _.each(['escape', 'unescape'], function(method) {
+    _[method] = function(string) {
+      if (string == null) return '';
+      return ('' + string).replace(entityRegexes[method], function(match) {
+        return entityMap[method][match];
+      });
+    };
+  });
+
+  // If the value of the named `property` is a function then invoke it with the
+  // `object` as context; otherwise, return it.
+  _.result = function(object, property) {
+    if (object == null) return void 0;
+    var value = object[property];
+    return _.isFunction(value) ? value.call(object) : value;
+  };
+
+  // Add your own custom functions to the Underscore object.
+  _.mixin = function(obj) {
+    each(_.functions(obj), function(name) {
+      var func = _[name] = obj[name];
+      _.prototype[name] = function() {
+        var args = [this._wrapped];
+        push.apply(args, arguments);
+        return result.call(this, func.apply(_, args));
+      };
+    });
+  };
+
+  // Generate a unique integer id (unique within the entire client session).
+  // Useful for temporary DOM ids.
+  var idCounter = 0;
+  _.uniqueId = function(prefix) {
+    var id = ++idCounter + '';
+    return prefix ? prefix + id : id;
+  };
+
+  // By default, Underscore uses ERB-style template delimiters, change the
+  // following template settings to use alternative delimiters.
+  _.templateSettings = {
+    evaluate    : /<%([\s\S]+?)%>/g,
+    interpolate : /<%=([\s\S]+?)%>/g,
+    escape      : /<%-([\s\S]+?)%>/g
+  };
+
+  // When customizing `templateSettings`, if you don't want to define an
+  // interpolation, evaluation or escaping regex, we need one that is
+  // guaranteed not to match.
+  var noMatch = /(.)^/;
+
+  // Certain characters need to be escaped so that they can be put into a
+  // string literal.
+  var escapes = {
+    "'":      "'",
+    '\\':     '\\',
+    '\r':     'r',
+    '\n':     'n',
+    '\t':     't',
+    '\u2028': 'u2028',
+    '\u2029': 'u2029'
+  };
+
+  var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
+
+  // JavaScript micro-templating, similar to John Resig's implementation.
+  // Underscore templating handles arbitrary delimiters, preserves whitespace,
+  // and correctly escapes quotes within interpolated code.
+  _.template = function(text, data, settings) {
+    var render;
+    settings = _.defaults({}, settings, _.templateSettings);
+
+    // Combine delimiters into one regular expression via alternation.
+    var matcher = new RegExp([
+      (settings.escape || noMatch).source,
+      (settings.interpolate || noMatch).source,
+      (settings.evaluate || noMatch).source
+    ].join('|') + '|$', 'g');
+
+    // Compile the template source, escaping string literals appropriately.
+    var index = 0;
+    var source = "__p+='";
+    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
+      source += text.slice(index, offset)
+        .replace(escaper, function(match) { return '\\' + escapes[match]; });
+
+      if (escape) {
+        source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
+      }
+      if (interpolate) {
+        source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
+      }
+      if (evaluate) {
+        source += "';\n" + evaluate + "\n__p+='";
+      }
+      index = offset + match.length;
+      return match;
+    });
+    source += "';\n";
+
+    // If a variable is not specified, place data values in local scope.
+    if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
+
+    source = "var __t,__p='',__j=Array.prototype.join," +
+      "print=function(){__p+=__j.call(arguments,'');};\n" +
+      source + "return __p;\n";
+
+    try {
+      render = new Function(settings.variable || 'obj', '_', source);
+    } catch (e) {
+      e.source = source;
+      throw e;
+    }
+
+    if (data) return render(data, _);
+    var template = function(data) {
+      return render.call(this, data, _);
+    };
+
+    // Provide the compiled function source as a convenience for precompilation.
+    template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
+
+    return template;
+  };
+
+  // Add a "chain" function, which will delegate to the wrapper.
+  _.chain = function(obj) {
+    return _(obj).chain();
+  };
+
+  // OOP
+  // ---------------
+  // If Underscore is called as a function, it returns a wrapped object that
+  // can be used OO-style. This wrapper holds altered versions of all the
+  // underscore functions. Wrapped objects may be chained.
+
+  // Helper function to continue chaining intermediate results.
+  var result = function(obj) {
+    return this._chain ? _(obj).chain() : obj;
+  };
+
+  // Add all of the Underscore functions to the wrapper object.
+  _.mixin(_);
+
+  // Add all mutator Array functions to the wrapper.
+  each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
+    var method = ArrayProto[name];
+    _.prototype[name] = function() {
+      var obj = this._wrapped;
+      method.apply(obj, arguments);
+      if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
+      return result.call(this, obj);
+    };
+  });
+
+  // Add all accessor Array functions to the wrapper.
+  each(['concat', 'join', 'slice'], function(name) {
+    var method = ArrayProto[name];
+    _.prototype[name] = function() {
+      return result.call(this, method.apply(this._wrapped, arguments));
+    };
+  });
+
+  _.extend(_.prototype, {
+
+    // Start chaining a wrapped Underscore object.
+    chain: function() {
+      this._chain = true;
+      return this;
+    },
+
+    // Extracts the result from a wrapped and chained object.
+    value: function() {
+      return this._wrapped;
+    }
+
+  });
+
+}).call(this);
level2/test/data/downloaded_test_cases/version1/level2-aUSeyGpde8.json
@@ -0,0 +1,1 @@
+{"cpu_usage": 15022617819, "level": 2, "results": {"backend_deficit": 737.6666666666667, "good_responses": 217, "correct": true}, "id": "level2-aUSeyGpde8", "score": 124.79166666666666, "input": "94fea7af56bdbcb75201fe0377c2904496a6a13f567530d3835509a34ec6aabf", "exitstatus": 0, "correct": true, "termsig": null}
\ No newline at end of file
level2/test/data/downloaded_test_cases/version1/level2-DsuFanfu0x.json
@@ -0,0 +1,1 @@
+{"cpu_usage": 17032862798, "level": 2, "results": {"good_responses": 199, "backend_deficit": 746.6666666666667, "correct": true}, "correct": true, "score": 105.66666666666666, "input": "eeeb3ff2fb7b2cf3aa1a9d5d8cdd3f1d0ca499bedad12b8a97cd05ca5b070321", "exitstatus": 0, "id": "level2-DsuFanfu0x", "termsig": null}
\ No newline at end of file
level2/test/data/downloaded_test_cases/version1/level2-Fi8KAtxHBe.json
@@ -0,0 +1,1 @@
+{"cpu_usage": 11727460399, "level": 2, "results": {"good_responses": 248, "backend_deficit": 744.6666666666667, "correct": true}, "correct": true, "score": 154.91666666666666, "input": "750854c97d65d3c2754a287cc801d5275a640ebd86bc5282baaa5d51fdb98326", "exitstatus": 0, "id": "level2-Fi8KAtxHBe", "termsig": null}
\ No newline at end of file
level2/test/data/downloaded_test_cases/version1/level2-SqSgpc5BeY.json
@@ -0,0 +1,1 @@
+{"cpu_usage": 12939628025, "level": 2, "results": {"good_responses": 222, "backend_deficit": 756.6666666666667, "correct": true}, "correct": true, "score": 127.41666666666666, "input": "39eb1f3f8e9846e98967ff3c2e40f6fa5bdb8c5d32519f71c409daafe73d2e2c", "exitstatus": 0, "id": "level2-SqSgpc5BeY", "termsig": null}
\ No newline at end of file
level2/test/data/downloaded_test_cases/version1/level2-x9NNVGpudm.json
@@ -0,0 +1,1 @@
+{"cpu_usage": 16705275735, "level": 2, "results": {"good_responses": 205, "backend_deficit": 733.6666666666667, "correct": true}, "correct": true, "score": 113.29166666666666, "input": "940e5d1fbd2de4ac59a7e867ff303b96e5de024fabe7aa9c3a41432ae17eba13", "exitstatus": 0, "id": "level2-x9NNVGpudm", "termsig": null}
\ No newline at end of file
level2/test/data/results-bnclpdtsojreibxn.json
@@ -0,0 +1,1 @@
+{"good_responses":48,"backend_deficit":163.66666666666674,"correct":true}
\ No newline at end of file
level2/test/data/results-fvuchsvbauallewe.json
@@ -0,0 +1,1 @@
+{"good_responses":226,"backend_deficit":285.66666666666674,"correct":true}
\ No newline at end of file
level2/test/data/results-gyxqmdozxiskloln.json
@@ -0,0 +1,1 @@
+{"good_responses":179,"backend_deficit":712.6666666666667,"correct":true}
\ No newline at end of file
level2/test/data/results-iulgehlhutbcozwk.json
@@ -0,0 +1,1 @@
+{"good_responses":175,"backend_deficit":392.66666666666674,"correct":true}
\ No newline at end of file
level2/test/data/results-jqpskhgxghzsjdsj.json
@@ -0,0 +1,1 @@
+{"good_responses":171,"backend_deficit":382.66666666666674,"correct":true}
\ No newline at end of file
level2/test/data/results-kuockyglcesksiau.json
@@ -0,0 +1,1 @@
+{"good_responses":145,"backend_deficit":274.66666666666674,"correct":true}
\ No newline at end of file
level2/test/data/results-lvvkjgtwoaxlhqmo.json
@@ -0,0 +1,1 @@
+{"good_responses":187,"backend_deficit":696.6666666666667,"correct":true}
\ No newline at end of file
level2/test/data/results-mzaymlycvxxpokst.json
@@ -0,0 +1,1 @@
+{"good_responses":176,"backend_deficit":720.6666666666667,"correct":true}
\ No newline at end of file
level2/test/data/results-poocfxaoclqmutdn.json
@@ -0,0 +1,1 @@
+{"good_responses":152,"backend_deficit":356.66666666666674,"correct":true}
\ No newline at end of file
level2/test/data/results-puhksxazgtazsjgd.json
@@ -0,0 +1,1 @@
+{"good_responses":174,"backend_deficit":702.6666666666667,"correct":true}
\ No newline at end of file
level2/test/data/results-qmnyuyondlweiyck.json
@@ -0,0 +1,1 @@
+{"good_responses":40,"backend_deficit":167.66666666666674,"correct":true}
\ No newline at end of file
level2/test/data/results-tbkbbcisntazfdoa.json
@@ -0,0 +1,1 @@
+{"good_responses":42,"backend_deficit":192.66666666666674,"correct":true}
\ No newline at end of file
level2/test/data/results-tyrlgfrbkkchfbpr.json
@@ -0,0 +1,1 @@
+{"good_responses":149,"backend_deficit":228.66666666666674,"correct":true}
\ No newline at end of file
level2/test/data/results-whaglltgczywkaoe.json
@@ -0,0 +1,1 @@
+{"good_responses":157,"backend_deficit":263.66666666666674,"correct":true}
\ No newline at end of file
level2/test/data/results-xcfppnbkwnhuedpm.json
@@ -0,0 +1,1 @@
+{"good_responses":119,"backend_deficit":241.66666666666674,"correct":true}
\ No newline at end of file
level2/test/data/results-xigthxydjvwayxeb.json
@@ -0,0 +1,1 @@
+{"good_responses":162,"backend_deficit":309.66666666666674,"correct":true}
\ No newline at end of file
level2/test/data/results-yimleehnqxyljnmh.json
@@ -0,0 +1,1 @@
+{"good_responses":175,"backend_deficit":267.66666666666674,"correct":true}
\ No newline at end of file
level2/test/lib/__init__.py
level2/test/lib/__init__.pyc
Binary file
level2/test/lib/ca-certificates.crt
@@ -0,0 +1,3918 @@
+# Generated using the default CA bundle on Ubuntu Linux 11.10 on November 25, 2011
+
+-----BEGIN CERTIFICATE-----
+MIIEuDCCA6CgAwIBAgIBBDANBgkqhkiG9w0BAQUFADCBtDELMAkGA1UEBhMCQlIx
+EzARBgNVBAoTCklDUC1CcmFzaWwxPTA7BgNVBAsTNEluc3RpdHV0byBOYWNpb25h
+bCBkZSBUZWNub2xvZ2lhIGRhIEluZm9ybWFjYW8gLSBJVEkxETAPBgNVBAcTCEJy
+YXNpbGlhMQswCQYDVQQIEwJERjExMC8GA1UEAxMoQXV0b3JpZGFkZSBDZXJ0aWZp
+Y2Fkb3JhIFJhaXogQnJhc2lsZWlyYTAeFw0wMTExMzAxMjU4MDBaFw0xMTExMzAy
+MzU5MDBaMIG0MQswCQYDVQQGEwJCUjETMBEGA1UEChMKSUNQLUJyYXNpbDE9MDsG
+A1UECxM0SW5zdGl0dXRvIE5hY2lvbmFsIGRlIFRlY25vbG9naWEgZGEgSW5mb3Jt
+YWNhbyAtIElUSTERMA8GA1UEBxMIQnJhc2lsaWExCzAJBgNVBAgTAkRGMTEwLwYD
+VQQDEyhBdXRvcmlkYWRlIENlcnRpZmljYWRvcmEgUmFpeiBCcmFzaWxlaXJhMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwPMudwX/hvm+Uh2b/lQAcHVA
+isamaLkWdkwP9/S/tOKIgRrL6Oy+ZIGlOUdd6uYtk9Ma/3pUpgcfNAj0vYm5gsyj
+Qo9emsc+x6m4VWwk9iqMZSCK5EQkAq/Ut4n7KuLE1+gdftwdIgxfUsPt4CyNrY50
+QV57KM2UT8x5rrmzEjr7TICGpSUAl2gVqe6xaii+bmYR1QrmWaBSAG59LrkrjrYt
+bRhFboUDe1DK+6T8s5L6k8c8okpbHpa9veMztDVC9sPJ60MWXh6anVKo1UcLcbUR
+yEeNvZneVRKAAU6ouwdjDvwlsaKydFKwed0ToQ47bmUKgcm+wV3eTRk36UOnTwID
+AQABo4HSMIHPME4GA1UdIARHMEUwQwYFYEwBAQAwOjA4BggrBgEFBQcCARYsaHR0
+cDovL2FjcmFpei5pY3BicmFzaWwuZ292LmJyL0RQQ2FjcmFpei5wZGYwPQYDVR0f
+BDYwNDAyoDCgLoYsaHR0cDovL2FjcmFpei5pY3BicmFzaWwuZ292LmJyL0xDUmFj
+cmFpei5jcmwwHQYDVR0OBBYEFIr68VeEERM1kEL6V0lUaQ2kxPA3MA8GA1UdEwEB
+/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAZA5c1
+U/hgIh6OcgLAfiJgFWpvmDZWqlV30/bHFpj8iBobJSm5uDpt7TirYh1Uxe3fQaGl
+YjJe+9zd+izPRbBqXPVQA34EXcwk4qpWuf1hHriWfdrx8AcqSqr6CuQFwSr75Fos
+SzlwDADa70mT7wZjAmQhnZx2xJ6wfWlT9VQfS//JYeIc7Fue2JNLd00UOSMMaiK/
+t79enKNHEA2fupH3vEigf5Eh4bVAN5VohrTm6MY53x7XQZZr1ME7a55lFEnSeT0u
+mlOAjR2mAbvSM5X5oSZNrmetdzyTj2flCM8CC7MLab0kkdngRIlUBGHF1/S5nmPb
+K+9A46sd33oqK8n8
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIHPTCCBSWgAwIBAgIBADANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290
+IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB
+IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA
+Y2FjZXJ0Lm9yZzAeFw0wMzAzMzAxMjI5NDlaFw0zMzAzMjkxMjI5NDlaMHkxEDAO
+BgNVBAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEi
+MCAGA1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJ
+ARYSc3VwcG9ydEBjYWNlcnQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
+CgKCAgEAziLA4kZ97DYoB1CW8qAzQIxL8TtmPzHlawI229Z89vGIj053NgVBlfkJ
+8BLPRoZzYLdufujAWGSuzbCtRRcMY/pnCujW0r8+55jE8Ez64AO7NV1sId6eINm6
+zWYyN3L69wj1x81YyY7nDl7qPv4coRQKFWyGhFtkZip6qUtTefWIonvuLwphK42y
+fk1WpRPs6tqSnqxEQR5YYGUFZvjARL3LlPdCfgv3ZWiYUQXw8wWRBB0bF4LsyFe7
+w2t6iPGwcswlWyCR7BYCEo8y6RcYSNDHBS4CMEK4JZwFaz+qOqfrU0j36NK2B5jc
+G8Y0f3/JHIJ6BVgrCFvzOKKrF11myZjXnhCLotLddJr3cQxyYN/Nb5gznZY0dj4k
+epKwDpUeb+agRThHqtdB7Uq3EvbXG4OKDy7YCbZZ16oE/9KTfWgu3YtLq1i6L43q
+laegw1SJpfvbi1EinbLDvhG+LJGGi5Z4rSDTii8aP8bQUWWHIbEZAWV/RRyH9XzQ
+QUxPKZgh/TMfdQwEUfoZd9vUFBzugcMd9Zi3aQaRIt0AUMyBMawSB3s42mhb5ivU
+fslfrejrckzzAeVLIL+aplfKkQABi6F1ITe1Yw1nPkZPcCBnzsXWWdsC4PDSy826
+YreQQejdIOQpvGQpQsgi3Hia/0PsmBsJUUtaWsJx8cTLc6nloQsCAwEAAaOCAc4w
+ggHKMB0GA1UdDgQWBBQWtTIb1Mfz4OaO873SsDrusjkY0TCBowYDVR0jBIGbMIGY
+gBQWtTIb1Mfz4OaO873SsDrusjkY0aF9pHsweTEQMA4GA1UEChMHUm9vdCBDQTEe
+MBwGA1UECxMVaHR0cDovL3d3dy5jYWNlcnQub3JnMSIwIAYDVQQDExlDQSBDZXJ0
+IFNpZ25pbmcgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QGNhY2Vy
+dC5vcmeCAQAwDwYDVR0TAQH/BAUwAwEB/zAyBgNVHR8EKzApMCegJaAjhiFodHRw
+czovL3d3dy5jYWNlcnQub3JnL3Jldm9rZS5jcmwwMAYJYIZIAYb4QgEEBCMWIWh0
+dHBzOi8vd3d3LmNhY2VydC5vcmcvcmV2b2tlLmNybDA0BglghkgBhvhCAQgEJxYl
+aHR0cDovL3d3dy5jYWNlcnQub3JnL2luZGV4LnBocD9pZD0xMDBWBglghkgBhvhC
+AQ0ESRZHVG8gZ2V0IHlvdXIgb3duIGNlcnRpZmljYXRlIGZvciBGUkVFIGhlYWQg
+b3ZlciB0byBodHRwOi8vd3d3LmNhY2VydC5vcmcwDQYJKoZIhvcNAQEEBQADggIB
+ACjH7pyCArpcgBLKNQodgW+JapnM8mgPf6fhjViVPr3yBsOQWqy1YPaZQwGjiHCc
+nWKdpIevZ1gNMDY75q1I08t0AoZxPuIrA2jxNGJARjtT6ij0rPtmlVOKTV39O9lg
+18p5aTuxZZKmxoGCXJzN600BiqXfEVWqFcofN8CCmHBh22p8lqOOLlQ+TyGpkO/c
+gr/c6EWtTZBzCDyUZbAEmXZ/4rzCahWqlwQ3JNgelE5tDlG+1sSPypZt90Pf6DBl
+Jzt7u0NDY8RD97LsaMzhGY4i+5jhe1o+ATc7iwiwovOVThrLm82asduycPAtStvY
+sONvRUgzEv/+PDIqVPfE94rwiCPCR/5kenHA0R6mY7AHfqQv0wGP3J8rtsYIqQ+T
+SCX8Ev2fQtzzxD72V7DX3WnRBnc0CkvSyqD/HMaMyRa+xMwyN2hzXwj7UfdJUzYF
+CpUCTPJ5GhD22Dp1nPMd8aINcGeGG7MW9S/lpOt5hvk9C8JzC6WZrG/8Z7jlLwum
+GCSNe9FINSkYQKyTYOGWhlC0elnYjyELn8+CkcY7v2vcB5G5l1YjqrZslMZIBjzk
+zk6q5PYvCdxTby78dOs6Y5nCpqyJvKeyRKANihDjbPIky/qbn3BHLt4Ui9SyIAmW
+omTxJBzcoTWcFbLUvFUufQb1nA5V9FrWk9p2rSVzTMVD
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIGCDCCA/CgAwIBAgIBATANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290
+IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB
+IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA
+Y2FjZXJ0Lm9yZzAeFw0wNTEwMTQwNzM2NTVaFw0zMzAzMjgwNzM2NTVaMFQxFDAS
+BgNVBAoTC0NBY2VydCBJbmMuMR4wHAYDVQQLExVodHRwOi8vd3d3LkNBY2VydC5v
+cmcxHDAaBgNVBAMTE0NBY2VydCBDbGFzcyAzIFJvb3QwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQCrSTURSHzSJn5TlM9Dqd0o10Iqi/OHeBlYfA+e2ol9
+4fvrcpANdKGWZKufoCSZc9riVXbHF3v1BKxGuMO+f2SNEGwk82GcwPKQ+lHm9WkB
+Y8MPVuJKQs/iRIwlKKjFeQl9RrmK8+nzNCkIReQcn8uUBByBqBSzmGXEQ+xOgo0J
+0b2qW42S0OzekMV/CsLj6+YxWl50PpczWejDAz1gM7/30W9HxM3uYoNSbi4ImqTZ
+FRiRpoWSR7CuSOtttyHshRpocjWr//AQXcD0lKdq1TuSfkyQBX6TwSyLpI5idBVx
+bgtxA+qvFTia1NIFcm+M+SvrWnIl+TlG43IbPgTDZCciECqKT1inA62+tC4T7V2q
+SNfVfdQqe1z6RgRQ5MwOQluM7dvyz/yWk+DbETZUYjQ4jwxgmzuXVjit89Jbi6Bb
+6k6WuHzX1aCGcEDTkSm3ojyt9Yy7zxqSiuQ0e8DYbF/pCsLDpyCaWt8sXVJcukfV
+m+8kKHA4IC/VfynAskEDaJLM4JzMl0tF7zoQCqtwOpiVcK01seqFK6QcgCExqa5g
+eoAmSAC4AcCTY1UikTxW56/bOiXzjzFU6iaLgVn5odFTEcV7nQP2dBHgbbEsPyyG
+kZlxmqZ3izRg0RS0LKydr4wQ05/EavhvE/xzWfdmQnQeiuP43NJvmJzLR5iVQAX7
+6QIDAQABo4G/MIG8MA8GA1UdEwEB/wQFMAMBAf8wXQYIKwYBBQUHAQEEUTBPMCMG
+CCsGAQUFBzABhhdodHRwOi8vb2NzcC5DQWNlcnQub3JnLzAoBggrBgEFBQcwAoYc
+aHR0cDovL3d3dy5DQWNlcnQub3JnL2NhLmNydDBKBgNVHSAEQzBBMD8GCCsGAQQB
+gZBKMDMwMQYIKwYBBQUHAgEWJWh0dHA6Ly93d3cuQ0FjZXJ0Lm9yZy9pbmRleC5w
+aHA/aWQ9MTAwDQYJKoZIhvcNAQEEBQADggIBAH8IiKHaGlBJ2on7oQhy84r3HsQ6
+tHlbIDCxRd7CXdNlafHCXVRUPIVfuXtCkcKZ/RtRm6tGpaEQU55tiKxzbiwzpvD0
+nuB1wT6IRanhZkP+VlrRekF490DaSjrxC1uluxYG5sLnk7mFTZdPsR44Q4Dvmw2M
+77inYACHV30eRBzLI++bPJmdr7UpHEV5FpZNJ23xHGzDwlVks7wU4vOkHx4y/CcV
+Bc/dLq4+gmF78CEQGPZE6lM5+dzQmiDgxrvgu1pPxJnIB721vaLbLmINQjRBvP+L
+ivVRIqqIMADisNS8vmW61QNXeZvo3MhN+FDtkaVSKKKs+zZYPumUK5FQhxvWXtaM
+zPcPEAxSTtAWYeXlCmy/F8dyRlecmPVsYGN6b165Ti/Iubm7aoW8mA3t+T6XhDSU
+rgCvoeXnkm5OvfPi2RSLXNLrAWygF6UtEOucekq9ve7O/e0iQKtwOIj1CodqwqsF
+YMlIBdpTwd5Ed2qz8zw87YC8pjhKKSRf/lk7myV6VmMAZLldpGJ9VzZPrYPvH5JT
+oI53V93lYRE9IwCQTDz6o2CTBKOvNfYOao9PSmCnhQVsRqGP9Md246FZV/dxssRu
+FFxtbUFm3xuTsdQAw+7Lzzw9IYCpX2Nl/N3gX6T0K/CFcUHUZyX7GrGXrtaZghNB
+0m6lG5kngOcLqagA
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIESzCCAzOgAwIBAgIJAJigUTEEXRQpMA0GCSqGSIb3DQEBBQUAMHYxCzAJBgNV
+BAYTAkRFMQ8wDQYDVQQIEwZIZXNzZW4xDjAMBgNVBAcTBUZ1bGRhMRAwDgYDVQQK
+EwdEZWJjb25mMRMwEQYDVQQDEwpEZWJjb25mIENBMR8wHQYJKoZIhvcNAQkBFhBq
+b2VyZ0BkZWJpYW4ub3JnMB4XDTA1MTEwNTE3NTUxNFoXDTE1MTEwMzE3NTUxNFow
+djELMAkGA1UEBhMCREUxDzANBgNVBAgTBkhlc3NlbjEOMAwGA1UEBxMFRnVsZGEx
+EDAOBgNVBAoTB0RlYmNvbmYxEzARBgNVBAMTCkRlYmNvbmYgQ0ExHzAdBgkqhkiG
+9w0BCQEWEGpvZXJnQGRlYmlhbi5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCvbOo0SrIwI5IMlsshH8WF3dHB9r9JlSKhMPaybawa1EyvZspMQ3wa
+F5qxNf3Sj+NElEmjseEqvCZiIIzqwerHu0Qw62cDYCdCd2+Wb5m0bPYB5CGHiyU1
+eNP0je42O0YeXG2BvUujN8AviocVo39X2YwNQ0ryy4OaqYgm2pRlbtT2ESbF+SfV
+Y2iqQj/f8ymF+lHo/pz8tbAqxWcqaSiHFAVQJrdqtFhtoodoNiE3q76zJoUkZTXB
+k60Yc3MJSnatZCpnsSBr/D7zpntl0THrUjjtdRWCjQVhqfhM1yZJV+ApbLdheFh0
+ZWlSxdnp25p0q0XYw/7G92ELyFDfBUUNAgMBAAGjgdswgdgwHQYDVR0OBBYEFMuV
+dFNb4mCWUFbcP5LOtxFLrEVTMIGoBgNVHSMEgaAwgZ2AFMuVdFNb4mCWUFbcP5LO
+txFLrEVToXqkeDB2MQswCQYDVQQGEwJERTEPMA0GA1UECBMGSGVzc2VuMQ4wDAYD
+VQQHEwVGdWxkYTEQMA4GA1UEChMHRGViY29uZjETMBEGA1UEAxMKRGViY29uZiBD
+QTEfMB0GCSqGSIb3DQEJARYQam9lcmdAZGViaWFuLm9yZ4IJAJigUTEEXRQpMAwG
+A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAGZXxHg4mnkvilRIM1EQfGdY
+S5b/WcyF2MYSTeTvK4aIB6VHwpZoZCnDGj2m2D3CkHT0upAD9o0zM1tdsfncLzV+
+mDT/jNmBtYo4QXx5vEPwvEIcgrWjwk7SyaEUhZjtolTkHB7ACl0oD0r71St4iEPR
+qTUCEXk2E47bg1Fz58wNt/yo2+4iqiRjg1XCH4evkQuhpW+dTZnDyFNqwSYZapOE
+TBA+9zBb6xD1KM2DdY7r4GiyYItN0BKLfuWbh9LXGbl1C+f4P11g+m2MPiavIeCe
+1iazG5pcS3KoTLACsYlEX24TINtg4kcuS81XdllcnsV3Kdts0nIqPj6uhTTZD0k=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDvjCCA3ygAwIBAgIFJQaThoEwCwYHKoZIzjgEAwUAMIGFMQswCQYDVQQGEwJG
+UjEPMA0GA1UECBMGRnJhbmNlMQ4wDAYDVQQHEwVQYXJpczEQMA4GA1UEChMHUE0v
+U0dETjEOMAwGA1UECxMFRENTU0kxDjAMBgNVBAMTBUlHQy9BMSMwIQYJKoZIhvcN
+AQkBFhRpZ2NhQHNnZG4ucG0uZ291di5mcjAeFw0wMjEyMTMxNDM5MTVaFw0yMDEw
+MTcxNDM5MTRaMIGFMQswCQYDVQQGEwJGUjEPMA0GA1UECBMGRnJhbmNlMQ4wDAYD
+VQQHEwVQYXJpczEQMA4GA1UEChMHUE0vU0dETjEOMAwGA1UECxMFRENTU0kxDjAM
+BgNVBAMTBUlHQy9BMSMwIQYJKoZIhvcNAQkBFhRpZ2NhQHNnZG4ucG0uZ291di5m
+cjCCAbYwggErBgcqhkjOOAQBMIIBHgKBgQCFkMImdk9zDzJfTO4XPdAAmLbAdWws
+ZiEMZh19RyTo3CyhFqO77OIXrwY6vc1pcc3MgWJ0dgQpAgrDMtmFFxpUu4gmjVsx
+8GpxQC+4VOgLY8Cvmcd/UDzYg07EIRto8BwCpPJ/JfUxwzV2V3N713aAX+cEoKZ/
+s+kgxC6nZCA7oQIVALME/JYjkdW2uKIGngsEPbXAjdhDAoGADh/uqWJx94UBm31c
+9d8ZTBfRGRnmSSRVFDgPWgA69JD4BR5da8tKz+1HjfMhDXljbMH86ixpD5Ka1Z0V
+pRYUPbyAoB37tsmXMJY7kjyD19d5VdaZboUjVvhH6UJy5lpNNNGSvFl4fqkxyvw+
+pq1QV0N5RcvK120hlXdfHUX+YKYDgYQAAoGAQGr7IuKJcYIvJRMjxwl43KxXY2xC
+aoCiM/bv117MfI94aNf1UusGhp7CbYAY9CXuL60P0oPMAajbaTE5Z34AuITeHq3Y
+CNMHwxalip8BHqSSGmGiQsXeK7T+r1rPXsccZ1c5ikGDZ4xn5gUaCyy2rCmb+fOJ
+6VAfCbAbAjmNKwejdzB1MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgFGMBUG
+A1UdIAQOMAwwCgYIKoF6AXkBAQEwHQYDVR0OBBYEFPkeNRcUf8idzpKblYbLNxs0
+MQhSMB8GA1UdIwQYMBaAFPkeNRcUf8idzpKblYbLNxs0MQhSMAsGByqGSM44BAMF
+AAMvADAsAhRVh+CJA5eVyEYU5AO9Tm7GxX0rmQIUBCqsU5u1WxoZ5lEXicDX5/Ob
+sRQ=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYT
+AkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQ
+TS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG
+9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMB4XDTAyMTIxMzE0MjkyM1oXDTIw
+MTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAM
+BgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEO
+MAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2
+LmZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaI
+s9z4iPf930Pfeo2aSVz2TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2
+xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCWSo7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4
+u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYyHF2fYPepraX/z9E0+X1b
+F8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNdfrGoRpAx
+Vs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGd
+PDPQtQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNV
+HSAEDjAMMAoGCCqBegF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAx
+NjAfBgNVHSMEGDAWgBSjBS8YYFDCiQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUF
+AAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RKq89toB9RlPhJy3Q2FLwV3duJ
+L92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3QMZsyK10XZZOY
+YLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg
+Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2a
+NjSaTFR+FwNIlQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R
+0982gaEbeC9xs/FZTEYYKKuF0mBWWg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
+IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
+MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
+bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
+H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
+uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
+mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
+a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
+E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
+WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
+VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
+Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
+cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
+IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
+AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
+YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
+Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
+c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
+mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
+b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMw
+MTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
+QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYD
+VQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ul
+CDtbKRY654eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6n
+tGO0/7Gcrjyvd7ZWxbWroulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyl
+dI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1Zmne3yzxbrww2ywkEtvrNTVokMsAsJch
+PXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJuiGMx1I4S+6+JNM3GOGvDC
++Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8wHQYDVR0O
+BBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E
+BTADAQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBl
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFk
+ZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENB
+IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxtZBsfzQ3duQH6lmM0MkhHma6X
+7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0PhiVYrqW9yTkkz
+43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY
+eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJl
+pz/+0WatC7xrmYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOA
+WiFeIc9TVPC6b4nbqKqVz4vjccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
+b3JrMSAwHgYDVQQDExdBZGRUcnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAx
+MDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtB
+ZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIDAeBgNV
+BAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV
+6tsfSlbunyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nX
+GCwwfQ56HmIexkvA/X1id9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnP
+dzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSGAa2Il+tmzV7R/9x98oTaunet3IAIx6eH
+1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAwHM+A+WD+eeSI8t0A65RF
+62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0GA1UdDgQW
+BBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUw
+AwEB/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDEL
+MAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRU
+cnVzdCBUVFAgTmV0d29yazEgMB4GA1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJv
+b3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4JNojVhaTdt02KLmuG7jD8WS6
+IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL+YPoRNWyQSW/
+iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao
+GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh
+4SINhwBk/ox9Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQm
+XiLsks3/QppEIW1cxeMiHV9HEufOX1362KqxMy3ZdvJOOjMMK7MtkAY=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
+b3JrMSMwIQYDVQQDExpBZGRUcnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1
+MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcxCzAJBgNVBAYTAlNFMRQwEgYDVQQK
+EwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIzAh
+BgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwq
+xBb/4Oxx64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G
+87B4pfYOQnrjfxvM0PC3KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i
+2O+tCBGaKZnhqkRFmhJePp1tUvznoD1oL/BLcHwTOK28FSXx1s6rosAx1i+f4P8U
+WfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GRwVY18BTcZTYJbqukB8c1
+0cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HUMIHRMB0G
+A1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0T
+AQH/BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6Fr
+pGkwZzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQL
+ExRBZGRUcnVzdCBUVFAgTmV0d29yazEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlm
+aWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBABmrder4i2VhlRO6aQTv
+hsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxGGuoYQ992zPlm
+hpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X
+dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3
+P6CxB9bpT9zeRXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9Y
+iQBCYz95OdBEsIJuQRno3eDBiFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5no
+xqE=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc
+MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP
+bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2
+MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft
+ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lk
+hsmj76CGv2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym
+1BW32J/X3HGrfpq/m44zDyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsW
+OqMFf6Dch9Wc/HKpoH145LcxVR5lu9RhsCFg7RAycsWSJR74kEoYeEfffjA3PlAb
+2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP8c9GsEsPPt2IYriMqQko
+O3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAU
+AK3Zo/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB
+BQUAA4IBAQB8itEfGDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkF
+Zu90821fnZmv9ov761KyBZiibyrFVL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAb
+LjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft3OJvx8Fi8eNy1gTIdGcL+oir
+oQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43gKd8hdIaC2y+C
+MMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds
+sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc
+MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP
+bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2
+MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft
+ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP
+ADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC
+206B89enfHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFci
+KtZHgVdEglZTvYYUAQv8f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2
+JxhP7JsowtS013wMPgwr38oE18aO6lhOqKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9
+BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JNRvCAOVIyD+OEsnpD8l7e
+Xz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0gBe4lL8B
+PeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67
+Xnfn6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEq
+Z8A9W6Wa6897GqidFEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZ
+o2C7HK2JNDJiuEMhBnIMoVxtRsX6Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3
++L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnjB453cMor9H124HhnAgMBAAGj
+YzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3OpaaEg5+31IqEj
+FNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE
+AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmn
+xPBUlgtk87FYT15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2
+LHo1YGwRgJfMqZJS5ivmae2p+DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzccc
+obGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXgJXUjhx5c3LqdsKyzadsXg8n33gy8
+CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//ZoyzH1kUQ7rVyZ2OuMe
+IjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgOZtMA
+DjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2F
+AjgQ5ANh1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUX
+Om/9riW99XJZZLF0KjhfGEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPb
+AZO1XB4Y3WRayhgoPmMEEf0cjQAPuDffZ4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQl
+Zvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuPcX/9XhmgD0uRuMRUvAaw
+RY8mkaKO/qk=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID5jCCAs6gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMCVVMx
+HTAbBgNVBAoTFEFPTCBUaW1lIFdhcm5lciBJbmMuMRwwGgYDVQQLExNBbWVyaWNh
+IE9ubGluZSBJbmMuMTcwNQYDVQQDEy5BT0wgVGltZSBXYXJuZXIgUm9vdCBDZXJ0
+aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyOTA2MDAwMFoXDTM3MTEyMDE1
+MDMwMFowgYMxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRBT0wgVGltZSBXYXJuZXIg
+SW5jLjEcMBoGA1UECxMTQW1lcmljYSBPbmxpbmUgSW5jLjE3MDUGA1UEAxMuQU9M
+IFRpbWUgV2FybmVyIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnej8Mlo2k06AX3dLm/WpcZuS+U
+0pPlLYnKhHw/EEMbjIt8hFj4JHxIzyr9wBXZGH6EGhfT257XyuTZ16pYUYfw8ItI
+TuLCxFlpMGK2MKKMCxGZYTVtfu/FsRkGIBKOQuHfD5YQUqjPnF+VFNivO3ULMSAf
+RC+iYkGzuxgh28pxPIzstrkNn+9R7017EvILDOGsQI93f7DKeHEMXRZxcKLXwjqF
+zQ6axOAAsNUl6twr5JQtOJyJQVdkKGUZHLZEtMgxa44Be3ZZJX8VHIQIfHNlIAqh
+BC4aMqiaILGcLCFZ5/vP7nAtCMpjPiybkxlqpMKX/7eGV4iFbJ4VFitNLLMCAwEA
+AaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUoTYwFsuGkABFgFOxj8jY
+PXy+XxIwHwYDVR0jBBgwFoAUoTYwFsuGkABFgFOxj8jYPXy+XxIwDgYDVR0PAQH/
+BAQDAgGGMA0GCSqGSIb3DQEBBQUAA4IBAQCKIBilvrMvtKaEAEAwKfq0FHNMeUWn
+9nDg6H5kHgqVfGphwu9OH77/yZkfB2FK4V1Mza3u0FIy2VkyvNp5ctZ7CegCgTXT
+Ct8RHcl5oIBN/lrXVtbtDyqvpxh1MwzqwWEFT2qaifKNuZ8u77BfWgDrvq2g+EQF
+Z7zLBO+eZMXpyD8Fv8YvBxzDNnGGyjhmSs3WuEvGbKeXO/oTLW4jYYehY0KswsuX
+n2Fozy1MBJ3XJU8KDk2QixhWqJNIV9xvrr2eZ1d3iVCzvhGbRWeDhhmH05i9CBoW
+H1iCC+GWaQVLjuyDUTEH1dSf/1l7qG6Fz9NLqUmwX7A5KGgOc90lmt4S
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF5jCCA86gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMCVVMx
+HTAbBgNVBAoTFEFPTCBUaW1lIFdhcm5lciBJbmMuMRwwGgYDVQQLExNBbWVyaWNh
+IE9ubGluZSBJbmMuMTcwNQYDVQQDEy5BT0wgVGltZSBXYXJuZXIgUm9vdCBDZXJ0
+aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyOTA2MDAwMFoXDTM3MDkyODIz
+NDMwMFowgYMxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRBT0wgVGltZSBXYXJuZXIg
+SW5jLjEcMBoGA1UECxMTQW1lcmljYSBPbmxpbmUgSW5jLjE3MDUGA1UEAxMuQU9M
+IFRpbWUgV2FybmVyIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIw
+DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALQ3WggWmRToVbEbJGv8x4vmh6mJ
+7ouZzU9AhqS2TcnZsdw8TQ2FTBVsRotSeJ/4I/1n9SQ6aF3Q92RhQVSji6UI0ilb
+m2BPJoPRYxJWSXakFsKlnUWsi4SVqBax7J/qJBrvuVdcmiQhLE0OcR+mrF1FdAOY
+xFSMFkpBd4aVdQxHAWZg/BXxD+r1FHjHDtdugRxev17nOirYlxcwfACtCJ0zr7iZ
+YYCLqJV+FNwSbKTQ2O9ASQI2+W6p1h2WVgSysy0WVoaP2SBXgM1nEG2wTPDaRrbq
+JS5Gr42whTg0ixQmgiusrpkLjhTXUr2eacOGAgvqdnUxCc4zGSGFQ+aJLZ8lN2fx
+I2rSAG2X+Z/nKcrdH9cG6rjJuQkhn8g/BsXS6RJGAE57COtCPStIbp1n3UsC5ETz
+kxmlJ85per5n0/xQpCyrw2u544BMzwVhSyvcG7mm0tCq9Stz+86QNZ8MUhy/XCFh
+EVsVS6kkUfykXPcXnbDS+gfpj1bkGoxoigTTfFrjnqKhynFbotSg5ymFXQNoKk/S
+Btc9+cMDLz9l+WceR0DTYw/j1Y75hauXTLPXJuuWCpTehTacyH+BCQJJKg71ZDIM
+gtG6aoIbs0t0EfOMd9afv9w3pKdVBC/UMejTRrkDfNoSTllkt1ExMVCgyhwn2RAu
+rda9EGYrw7AiShJbAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
+FE9pbQN+nZ8HGEO8txBO1b+pxCAoMB8GA1UdIwQYMBaAFE9pbQN+nZ8HGEO8txBO
+1b+pxCAoMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAO/Ouyugu
+h4X7ZVnnrREUpVe8WJ8kEle7+z802u6teio0cnAxa8cZmIDJgt43d15Ui47y6mdP
+yXSEkVYJ1eV6moG2gcKtNuTxVBFT8zRFASbI5Rq8NEQh3q0l/HYWdyGQgJhXnU7q
+7C+qPBR7V8F+GBRn7iTGvboVsNIYvbdVgaxTwOjdaRITQrcCtQVBynlQboIOcXKT
+RuidDV29rs4prWPVVRaAMCf/drr3uNZK49m1+VLQTkCpx+XCMseqdiThawVQ68W/
+ClTluUI8JPu3B5wwn3la5uBAUhX0/Kr0VvlEl4ftDmVyXr4m+02kLQgH3thcoNyB
+M5kYJRF3p+v9WAksmWsbivNSPxpNSGDxoPYzAlOL7SUJuA0t7Zdz7NeWH45gDtoQ
+my8YJPamTQr5O8t1wswvziRpyQoijlmn94IM19drNZxDAGrElWe6nEXLuA4399xO
+AU++CrYD062KRffaJ00psUjf5BHklka9bAI+1lHIlRcBFanyqqryvy9lG2/QuRqT
+9Y41xICHPpQvZuTpqP9BnHAqTyo5GJUefvthATxRCC4oGKQWDzH9OmwjkyB24f0H
+hdFbP9IcczLd+rn4jM8Ch3qaluTtT4mNU0OrDhPAARW0eTjb/G49nlG2uBOLZ8/5
+fNkiHfZdxRwBL5joeiQYvITX+txyW/fBOmg=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
+RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
+VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
+DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
+ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
+VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
+mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
+IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
+mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
+XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
+dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
+jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
+BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
+DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
+9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
+jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
+Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
+ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
+R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEn
+MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL
+ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMg
+b2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAxNjEzNDNaFw0zNzA5MzAxNjEzNDRa
+MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBB
+ODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIw
+IAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0B
+AQEFAAOCAQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtb
+unXF/KGIJPov7coISjlUxFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0d
+BmpAPrMMhe5cG3nCYsS4No41XQEMIwRHNaqbYE6gZj3LJgqcQKH0XZi/caulAGgq
+7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jWDA+wWFjbw2Y3npuRVDM3
+0pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFVd9oKDMyX
+roDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIG
+A1UdEwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5j
+aGFtYmVyc2lnbi5vcmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p
+26EpW1eLTXYGduHRooowDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIA
+BzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hhbWJlcnNpZ24ub3JnMCcGA1Ud
+EgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYDVR0gBFEwTzBN
+BgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz
+aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEB
+AAxBl8IahsAifJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZd
+p0AJPaxJRUXcLo0waLIJuvvDL8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi
+1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wNUPf6s+xCX6ndbcj0dc97wXImsQEc
+XCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/nADydb47kMgkdTXg0
+eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1erfu
+tGWaIZDgqtCYvDi1czyL+Nw=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEn
+MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL
+ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENo
+YW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYxNDE4WhcNMzcwOTMwMTYxNDE4WjB9
+MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgy
+NzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4G
+A1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUA
+A4IBDQAwggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0
+Mi+ITaFgCPS3CU6gSS9J1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/s
+QJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8Oby4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpV
+eAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl6DJWk0aJqCWKZQbua795
+B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c8lCrEqWh
+z0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0T
+AQH/BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1i
+ZXJzaWduLm9yZy9jaGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4w
+TcbOX60Qq+UDpfqpFDAOBgNVHQ8BAf8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAH
+MCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBjaGFtYmVyc2lnbi5vcmcwKgYD
+VR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9yZzBbBgNVHSAE
+VDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh
+bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0B
+AQUFAAOCAQEAPDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUM
+bKGKfKX0j//U2K0X1S0E0T9YgOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXi
+ryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJPJ7oKXqJ1/6v/2j1pReQvayZzKWG
+VwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4IBHNfTIzSJRUTN3c
+ecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREest2d/
+AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw
+PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz
+cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9
+MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz
+IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ
+ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR
+VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL
+kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd
+EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas
+H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0
+HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud
+DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4
+QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu
+Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/
+AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8
+yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR
+FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA
+ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB
+kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7
+l7+ijrRU
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBM
+MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD
+QTAeFw0wMjA2MTExMDQ2MzlaFw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBM
+MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD
+QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6xwS7TT3zNJc4YPk/E
+jG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdLkKWo
+ePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GI
+ULdtlkIJ89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapu
+Ob7kky/ZR6By6/qmW6/KUz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUg
+AKpoC6EahQGcxEZjgoi2IrHu/qpGWX7PNSzVttpd90gzFFS269lvzs2I1qsb2pY7
+HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA
+uI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+GXYkHAQa
+TOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTg
+xSvgGrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1q
+CjqTE5s7FCMTY5w/0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5x
+O/fIR/RpbxXyEV6DHpx8Uq79AtoSqFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs
+6GAqm4VKQPNriiTsBhYscw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb
+MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
+GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj
+YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL
+MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
+BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM
+GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua
+BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe
+3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4
+YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR
+rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm
+ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU
+oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
+MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v
+QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t
+b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF
+AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q
+GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
+Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2
+G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi
+l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3
+smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB
+gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
+BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw
+MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl
+YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P
+RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3
+UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI
+2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8
+Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp
++2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+
+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O
+nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW
+/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g
+PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u
+QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY
+SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv
+IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/
+RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4
+zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd
+BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB
+ZQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL
+MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
+BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT
+IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw
+MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy
+ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N
+T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv
+biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR
+FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J
+cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW
+BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
+BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm
+fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv
+GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEb
+MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
+GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRp
+ZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVow
+fjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAiBgNV
+BAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPM
+cm3ye5drswfxdySRXyWP9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3S
+HpR7LZQdqnXXs5jLrLxkU0C8j6ysNstcrbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996
+CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rCoznl2yY4rYsK7hljxxwk
+3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3Vp6ea5EQz
+6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNV
+HQ4EFgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud
+EwEB/wQFMAMBAf8wgYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2Rv
+Y2EuY29tL1NlY3VyZUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRw
+Oi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmww
+DQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm4J4oqF7Tt/Q0
+5qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj
+Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtI
+gKvcnDe4IRRLDXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJ
+aD61JlfutuC23bkpgHl9j6PwpCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDl
+izeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1HRR3B7Hzs/Sk=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEb
+MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
+GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0
+aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTla
+MH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUwIwYD
+VQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWW
+fnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt
+TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7IL
+fhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW
+1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7
+kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0G
+A1UdDgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYD
+VR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21v
+ZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRo
+dHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu
+Y3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/
+HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32
+pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxIS
+jBc/lDb+XbDABHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+
+xqFx7D+gIIxmOom0jtTYsU0lR+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/Atyjcn
+dBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O9y5Xt5hwXsjEeLBi
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
+b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
+cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c
+JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP
+mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+
+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4
+VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/
+AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB
+AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
+BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun
+pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC
+dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf
+fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm
+NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx
+H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
+QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
+CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
+nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
+43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
+T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
+gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
+BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
+TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
+DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
+hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
+06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
+PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
+YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
+CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
+PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
+xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
+Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
+hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
+EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
+FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
+nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
+eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
+hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
+Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
+vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
++OkuE6N36B9K
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDKTCCApKgAwIBAgIENnAVljANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJV
+UzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMREwDwYDVQQL
+EwhEU1RDQSBFMTAeFw05ODEyMTAxODEwMjNaFw0xODEyMTAxODQwMjNaMEYxCzAJ
+BgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4x
+ETAPBgNVBAsTCERTVENBIEUxMIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQCg
+bIGpzzQeJN3+hijM3oMv+V7UQtLodGBmE5gGHKlREmlvMVW5SXIACH7TpWJENySZ
+j9mDSI+ZbZUTu0M7LklOiDfBu1h//uG9+LthzfNHwJmm8fOR6Hh8AMthyUQncWlV
+Sn5JTe2io74CTADKAqjuAQIxZA9SLRN0dja1erQtcQIBA6OCASQwggEgMBEGCWCG
+SAGG+EIBAQQEAwIABzBoBgNVHR8EYTBfMF2gW6BZpFcwVTELMAkGA1UEBhMCVVMx
+JDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjERMA8GA1UECxMI
+RFNUQ0EgRTExDTALBgNVBAMTBENSTDEwKwYDVR0QBCQwIoAPMTk5ODEyMTAxODEw
+MjNagQ8yMDE4MTIxMDE4MTAyM1owCwYDVR0PBAQDAgEGMB8GA1UdIwQYMBaAFGp5
+fpFpRhgTCgJ3pVlbYJglDqL4MB0GA1UdDgQWBBRqeX6RaUYYEwoCd6VZW2CYJQ6i
++DAMBgNVHRMEBTADAQH/MBkGCSqGSIb2fQdBAAQMMAobBFY0LjADAgSQMA0GCSqG
+SIb3DQEBBQUAA4GBACIS2Hod3IEGtgllsofIH160L+nEHvI8wbsEkBFKg05+k7lN
+QseSJqBcNJo4cvj9axY+IO6CizEqkzaFI4iKPANo08kJD038bKTaKHKTDomAsH3+
+gG9lbRgzl4vCa4nuYD3Im+9/KzJic5PLPON74nZ4RbyhkwS7hp86W0N6w4pl
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDKTCCApKgAwIBAgIENm7TzjANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJV
+UzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMREwDwYDVQQL
+EwhEU1RDQSBFMjAeFw05ODEyMDkxOTE3MjZaFw0xODEyMDkxOTQ3MjZaMEYxCzAJ
+BgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4x
+ETAPBgNVBAsTCERTVENBIEUyMIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQC/
+k48Xku8zExjrEH9OFr//Bo8qhbxe+SSmJIi2A7fBw18DW9Fvrn5C6mYjuGODVvso
+LeE4i7TuqAHhzhy2iCoiRoX7n6dwqUcUP87eZfCocfdPJmyMvMa1795JJ/9IKn3o
+TQPMx7JSxhcxEzu1TdvIxPbDDyQq2gyd55FbgM2UnQIBA6OCASQwggEgMBEGCWCG
+SAGG+EIBAQQEAwIABzBoBgNVHR8EYTBfMF2gW6BZpFcwVTELMAkGA1UEBhMCVVMx
+JDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjERMA8GA1UECxMI
+RFNUQ0EgRTIxDTALBgNVBAMTBENSTDEwKwYDVR0QBCQwIoAPMTk5ODEyMDkxOTE3
+MjZagQ8yMDE4MTIwOTE5MTcyNlowCwYDVR0PBAQDAgEGMB8GA1UdIwQYMBaAFB6C
+TShlgDzJQW6sNS5ay97u+DlbMB0GA1UdDgQWBBQegk0oZYA8yUFurDUuWsve7vg5
+WzAMBgNVHRMEBTADAQH/MBkGCSqGSIb2fQdBAAQMMAobBFY0LjADAgSQMA0GCSqG
+SIb3DQEBBQUAA4GBAEeNg61i8tuwnkUiBbmi1gMOOHLnnvx75pO2mqWilMg0HZHR
+xdf0CiUPPXiBng+xZ8SQTGPdXqfiup/1902lMXucKS1M/mQ+7LZT/uqb7YLbdHVL
+B3luHtgZg3Pe9T7Qtd7nS2h9Qy4qIOF+oHhEngj1mPnHfxsb1gYgAlihw6ID
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBb
+MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3Qx
+ETAPBgNVBAsTCERTVCBBQ0VTMRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0w
+MzExMjAyMTE5NThaFw0xNzExMjAyMTE5NThaMFsxCzAJBgNVBAYTAlVTMSAwHgYD
+VQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UECxMIRFNUIEFDRVMx
+FzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPu
+ktKe1jzIDZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7
+gLFViYsx+tC3dr5BPTCapCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZH
+fAjIgrrep4c9oW24MFbCswKBXy314powGCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4a
+ahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPyMjwmR/onJALJfh1biEIT
+ajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1UdEwEB/wQF
+MAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rk
+c3QuY29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjto
+dHRwOi8vd3d3LnRydXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMt
+aW5kZXguaHRtbDAdBgNVHQ4EFgQUCXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZI
+hvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V25FYrnJmQ6AgwbN99Pe7lv7Uk
+QIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6tFr8hlxCBPeP/
+h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq
+nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpR
+rscL9yuwNwXsvFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf2
+9w4LTJxoeHtxMcfrHuBnQfO3oKfN5XozNmr6mis=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
+MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
+DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
+PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
+Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
+rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
+OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
+xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
+7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
+aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
+HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
+SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
+ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
+AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
+R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
+JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
+Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEXDCCA0SgAwIBAgIEOGO5ZjANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML
+RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp
+bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5
+IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0xOTEy
+MjQxODIwNTFaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3
+LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp
+YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG
+A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq
+K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe
+sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX
+MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT
+XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/
+HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH
+4QIDAQABo3QwcjARBglghkgBhvhCAQEEBAMCAAcwHwYDVR0jBBgwFoAUVeSB0RGA
+vtiJuQijMfmhJAkWuXAwHQYDVR0OBBYEFFXkgdERgL7YibkIozH5oSQJFrlwMB0G
+CSqGSIb2fQdBAAQQMA4bCFY1LjA6NC4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEA
+WUesIYSKF8mciVMeuoCFGsY8Tj6xnLZ8xpJdGGQC49MGCBFhfGPjK50xA3B20qMo
+oPS7mmNz7W3lKtvtFKkrxjYR0CvrB4ul2p5cGZ1WEvVUKcgF7bISKo30Axv/55IQ
+h7A6tcOdBTcSo8f0FbnVpDkWm1M6I5HxqIKiaohowXkCIryqptau37AUX7iH0N18
+f3v/rxzP5tsHrV7bhZ3QKw0z2wTR5klAEyt2+z7pnIkPFc4YsIV4IU9rTw76NmfN
+B/L/CNDi3tm/Kq+4h4YhPATKt5Rof8886ZjXOP/swNlQ8C5LWK5Gb9Auw2DaclVy
+vUxFnmG6v4SBkgPR0ml8xQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
+VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
+ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
+KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
+ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1
+MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE
+ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j
+b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
+bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg
+U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA
+A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/
+I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3
+wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC
+AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb
+oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5
+BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p
+dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk
+MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp
+b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
+dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0
+MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi
+E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa
+MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI
+hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN
+95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd
+2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC
+VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0
+Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW
+KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw
+NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw
+NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy
+ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV
+BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo
+Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4
+4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9
+KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI
+rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi
+94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB
+sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi
+gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo
+kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE
+vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
+A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t
+O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua
+AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP
+9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/
+eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m
+0vdXcDazv/wor3ElhVsT/h5/WrQ8
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
+UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
+dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
+MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
+dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
+AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
+BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
+cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
+AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
+MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
+aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
+ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
+IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
+MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
+A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
+7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
+1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc
+MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT
+ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw
+MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j
+LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ
+KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo
+RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu
+WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw
+Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD
+AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK
+eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM
+zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+
+WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN
+/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAomgAwIBAgIEN3DPtTANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2Vj
+dXJlIGVCdXNpbmVzcyBDQS0yMB4XDTk5MDYyMzEyMTQ0NVoXDTE5MDYyMzEyMTQ0
+NVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkVxdWlmYXggU2VjdXJlMSYwJAYD
+VQQLEx1FcXVpZmF4IFNlY3VyZSBlQnVzaW5lc3MgQ0EtMjCBnzANBgkqhkiG9w0B
+AQEFAAOBjQAwgYkCgYEA5Dk5kx5SBhsoNviyoynF7Y6yEb3+6+e0dMKP/wXn2Z0G
+vxLIPw7y1tEkshHe0XMJitSxLJgJDR5QRrKDpkWNYmi7hRsgcDKqQM2mll/EcTc/
+BPO3QSQ5BxoeLmFYoBIL5aXfxavqN3HMHMg3OrmXUqesxWoklE6ce8/AatbfIb0C
+AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEX
+MBUGA1UEChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2VjdXJl
+IGVCdXNpbmVzcyBDQS0yMQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTkw
+NjIzMTIxNDQ1WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUUJ4L6q9euSBIplBq
+y/3YIHqngnYwHQYDVR0OBBYEFFCeC+qvXrkgSKZQasv92CB6p4J2MAwGA1UdEwQF
+MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
+A4GBAAyGgq3oThr1jokn4jVYPSm0B482UJW/bsGe68SQsoWou7dC4A8HOd/7npCy
+0cE+U58DRLB+S/Rv5Hwf5+Kx5Lia78O9zt4LMjTZ3ijtM2vE1Nc9ElirfQkty3D1
+E4qUoSek1nDFbZS1yX2doNLGCEnZZpum0/QL3MUmV+GRMOrN
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc
+MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT
+ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw
+MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj
+dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l
+c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC
+UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc
+58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/
+o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH
+MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr
+aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA
+A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA
+Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv
+8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEVzCCAz+gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBnTELMAkGA1UEBhMCRVMx
+IjAgBgNVBAcTGUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMTOUF1
+dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2
+MjYzNDA2ODEmMCQGCSqGSIb3DQEJARYXY2FAZmlybWFwcm9mZXNpb25hbC5jb20w
+HhcNMDExMDI0MjIwMDAwWhcNMTMxMDI0MjIwMDAwWjCBnTELMAkGA1UEBhMCRVMx
+IjAgBgNVBAcTGUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMTOUF1
+dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2
+MjYzNDA2ODEmMCQGCSqGSIb3DQEJARYXY2FAZmlybWFwcm9mZXNpb25hbC5jb20w
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDnIwNvbyOlXnjOlSztlB5u
+Cp4Bx+ow0Syd3Tfom5h5VtP8c9/Qit5Vj1H5WuretXDE7aTt/6MNbg9kUDGvASdY
+rv5sp0ovFy3Tc9UTHI9ZpTQsHVQERc1ouKDAA6XPhUJHlShbz++AbOCQl4oBPB3z
+hxAwJkh91/zpnZFx/0GaqUC1N5wpIE8fUuOgfRNtVLcK3ulqTgesrBlf3H5idPay
+BQC6haD9HThuy1q7hryUZzM1gywfI834yJFxzJeL764P3CkDG8A563DtwW4O2GcL
+iam8NeTvtjS0pbbELaW+0MOUJEjb35bTALVmGotmBQ/dPz/LP6pemkr4tErvlTcb
+AgMBAAGjgZ8wgZwwKgYDVR0RBCMwIYYfaHR0cDovL3d3dy5maXJtYXByb2Zlc2lv
+bmFsLmNvbTASBgNVHRMBAf8ECDAGAQH/AgEBMCsGA1UdEAQkMCKADzIwMDExMDI0
+MjIwMDAwWoEPMjAxMzEwMjQyMjAwMDBaMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E
+FgQUMwugZtHq2s7eYpMEKFK1FH84aLcwDQYJKoZIhvcNAQEFBQADggEBAEdz/o0n
+VPD11HecJ3lXV7cVVuzH2Fi3AQL0M+2TUIiefEaxvT8Ub/GzR0iLjJcG1+p+o1wq
+u00vR+L4OQbJnC4xGgN49Lw4xiKLMzHwFgQEffl25EvXwOaD7FnMP97/T2u3Z36m
+hoEyIwOdyPdfwUpgpZKpsaSgYMN4h7Mi8yrrW6ntBas3D7Hi05V2Y1Z0jFhyGzfl
+ZKG+TQyTmAyX9odtsz/ny4Cm7YjHX1BiAuiZdBbQ5rQ58SfLyEDW44YQqSMSkuBp
+QWOnryULwMWSyx6Yo1q6xTMPoJcB3X/ge9YGVM+h4k0460tQtcsm9MracEpqoeJ5
+quGnM/b9Sh/22WA=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEW
+MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFs
+IENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQG
+EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3Qg
+R2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1A
+PRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/NTL8
+Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hL
+TytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL
+5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7
+S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe
+2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
+FHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUap
+EBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6td
+EPx7srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv
+/NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywN
+A0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0
+abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa4qjJqhIF
+I8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz
+4iIprn2DQKi6bA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
+MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
+YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG
+EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg
+R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9
+9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq
+fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv
+iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU
+1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+
+bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW
+MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA
+ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l
+uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn
+Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS
+tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF
+PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un
+hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV
+5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY
+MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo
+R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx
+MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK
+Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9
+AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA
+ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0
+7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W
+kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI
+mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G
+A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ
+KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1
+6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl
+4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K
+oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj
+UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU
+AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW
+MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy
+c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD
+VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1
+c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
+AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81
+WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG
+FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq
+XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL
+se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb
+KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd
+IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73
+y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt
+hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc
+QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4
+Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV
+HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ
+KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z
+dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ
+L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr
+Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo
+ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY
+T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz
+GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m
+1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV
+OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH
+6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX
+QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW
+MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy
+c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE
+BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0
+IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV
+VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8
+cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT
+QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh
+F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v
+c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w
+mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd
+VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX
+teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ
+f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe
+Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+
+nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB
+/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY
+MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG
+9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc
+aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX
+IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn
+ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z
+uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN
+Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja
+QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW
+koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9
+ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt
+DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm
+bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw
+MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
+YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT
+aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ
+jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp
+xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp
+1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG
+snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ
+U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8
+9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
+BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B
+AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz
+yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE
+38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP
+AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad
+DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
+HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G
+A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
+Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1
+MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG
+A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL
+v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8
+eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq
+tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd
+C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa
+zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB
+mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH
+V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n
+bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG
+3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs
+J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO
+291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS
+ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd
+AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
+TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh
+MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE
+YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3
+MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo
+ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg
+MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN
+ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA
+PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w
+wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi
+EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY
+avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+
+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE
+sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h
+/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5
+IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj
+YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
+ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy
+OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P
+TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
+HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER
+dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf
+ReYNnyicsbkqWletNw+vHX/bvZ8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD
+VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv
+bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv
+b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH
+iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS
+r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4
+04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r
+GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9
+3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P
+lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUx
+ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0
+b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQD
+EylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikgVGFudXNpdHZhbnlraWFkbzAeFw05
+OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYDVQQGEwJIVTERMA8G
+A1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNh
+Z2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5l
+dExvY2sgVXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqG
+SIb3DQEBAQUAA4GNADCBiQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xK
+gZjupNTKihe5In+DCnVMm8Bp2GQ5o+2So/1bXHQawEfKOml2mrriRBf8TKPV/riX
+iK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr1nGTLbO/CVRY7QbrqHvc
+Q7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8E
+BAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1G
+SUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFu
+b3MgU3pvbGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBh
+bGFwamFuIGtlc3p1bHQuIEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExv
+Y2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGln
+aXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0
+IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh
+c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGph
+biBhIGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJo
+ZXRvIGF6IGVsbGVub3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBP
+UlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmlj
+YXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBo
+dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNA
+bmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06
+sPgzTEdM43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXa
+n3BukxowOR0w2y7jfLKRstE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKS
+NitjrFgBazMpUIaD8QFI
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUx
+ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0
+b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQD
+EytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBDKSBUYW51c2l0dmFueWtpYWRvMB4X
+DTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJBgNVBAYTAkhVMREw
+DwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9u
+c2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMr
+TmV0TG9jayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzAN
+BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNA
+OoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3ZW3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC
+2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63euyucYT2BDMIJTLrdKwW
+RMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQwDgYDVR0P
+AQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEW
+ggJNRklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0
+YWxhbm9zIFN6b2xnYWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFz
+b2sgYWxhcGphbiBrZXN6dWx0LiBBIGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBO
+ZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1iaXp0b3NpdGFzYSB2ZWRpLiBB
+IGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0ZWxlIGF6IGVs
+b2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs
+ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25s
+YXBqYW4gYSBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kg
+a2VyaGV0byBheiBlbGxlbm9yemVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4g
+SU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5kIHRoZSB1c2Ugb2YgdGhpcyBjZXJ0
+aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQUyBhdmFpbGFibGUg
+YXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwgYXQg
+Y3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmY
+ta3UzbM2xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2g
+pO0u9f38vf5NNwgMvOOWgyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4
+Fp1hBWeAyNDYpQcCNJgEjTME1A==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhV
+MRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMe
+TmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0
+dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFzcyBB
+KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oXDTE5MDIxOTIzMTQ0
+N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhC
+dWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQu
+MRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBL
+b3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSMD7tM9DceqQWC2ObhbHDqeLVu0ThEDaiD
+zl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZz+qMkjvN9wfcZnSX9EUi
+3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC/tmwqcm8
+WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LY
+Oph7tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2Esi
+NCubMvJIH5+hCoR64sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCC
+ApswDgYDVR0PAQH/BAQDAgAGMBIGA1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4
+QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZRUxFTSEgRXplbiB0
+YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRhdGFz
+aSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu
+IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtm
+ZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMg
+ZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVs
+amFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJhc2EgbWVndGFsYWxoYXRv
+IGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBzOi8vd3d3
+Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6
+ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1
+YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3Qg
+dG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRs
+b2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNAbmV0bG9jay5uZXQuMA0G
+CSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5ayZrU3/b39/zcT0mwBQO
+xmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjPytoUMaFP
+0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQ
+QeJBCWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxk
+f1qbFFgBJ34TUMdrKuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK
+8CtmdWOMovsEPoMOmzbwGOQmIMOM8CgHrTwXZoi1/baI
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIG0TCCBbmgAwIBAgIBezANBgkqhkiG9w0BAQUFADCByTELMAkGA1UEBhMCSFUx
+ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0
+b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMUIwQAYDVQQD
+EzlOZXRMb2NrIE1pbm9zaXRldHQgS296amVneXpvaSAoQ2xhc3MgUUEpIFRhbnVz
+aXR2YW55a2lhZG8xHjAcBgkqhkiG9w0BCQEWD2luZm9AbmV0bG9jay5odTAeFw0w
+MzAzMzAwMTQ3MTFaFw0yMjEyMTUwMTQ3MTFaMIHJMQswCQYDVQQGEwJIVTERMA8G
+A1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNh
+Z2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxQjBABgNVBAMTOU5l
+dExvY2sgTWlub3NpdGV0dCBLb3pqZWd5em9pIChDbGFzcyBRQSkgVGFudXNpdHZh
+bnlraWFkbzEeMBwGCSqGSIb3DQEJARYPaW5mb0BuZXRsb2NrLmh1MIIBIjANBgkq
+hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx1Ilstg91IRVCacbvWy5FPSKAtt2/Goq
+eKvld/Bu4IwjZ9ulZJm53QE+b+8tmjwi8F3JV6BVQX/yQ15YglMxZc4e8ia6AFQe
+r7C8HORSjKAyr7c3sVNnaHRnUPYtLmTeriZ539+Zhqurf4XsoPuAzPS4DB6TRWO5
+3Lhbm+1bOdRfYrCnjnxmOCyqsQhjF2d9zL2z8cM/z1A57dEZgxXbhxInlrfa6uWd
+vLrqOU+L73Sa58XQ0uqGURzk/mQIKAR5BevKxXEOC++r6uwSEaEYBTJp0QwsGj0l
+mT+1fMptsK6ZmfoIYOcZwvK9UdPM0wKswREMgM6r3JSda6M5UzrWhQIDAMV9o4IC
+wDCCArwwEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8EBAMCAQYwggJ1Bglg
+hkgBhvhCAQ0EggJmFoICYkZJR1lFTEVNISBFemVuIHRhbnVzaXR2YW55IGEgTmV0
+TG9jayBLZnQuIE1pbm9zaXRldHQgU3pvbGdhbHRhdGFzaSBTemFiYWx5emF0YWJh
+biBsZWlydCBlbGphcmFzb2sgYWxhcGphbiBrZXN6dWx0LiBBIG1pbm9zaXRldHQg
+ZWxla3Ryb25pa3VzIGFsYWlyYXMgam9naGF0YXMgZXJ2ZW55ZXN1bGVzZW5laywg
+dmFsYW1pbnQgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYSBNaW5vc2l0ZXR0IFN6
+b2xnYWx0YXRhc2kgU3phYmFseXphdGJhbiwgYXogQWx0YWxhbm9zIFN6ZXJ6b2Rl
+c2kgRmVsdGV0ZWxla2JlbiBlbG9pcnQgZWxsZW5vcnplc2kgZWxqYXJhcyBtZWd0
+ZXRlbGUuIEEgZG9rdW1lbnR1bW9rIG1lZ3RhbGFsaGF0b2sgYSBodHRwczovL3d3
+dy5uZXRsb2NrLmh1L2RvY3MvIGNpbWVuIHZhZ3kga2VyaGV0b2sgYXogaW5mb0Bu
+ZXRsb2NrLm5ldCBlLW1haWwgY2ltZW4uIFdBUk5JTkchIFRoZSBpc3N1YW5jZSBh
+bmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGFyZSBzdWJqZWN0IHRvIHRo
+ZSBOZXRMb2NrIFF1YWxpZmllZCBDUFMgYXZhaWxhYmxlIGF0IGh0dHBzOi8vd3d3
+Lm5ldGxvY2suaHUvZG9jcy8gb3IgYnkgZS1tYWlsIGF0IGluZm9AbmV0bG9jay5u
+ZXQwHQYDVR0OBBYEFAlqYhaSsFq7VQ7LdTI6MuWyIckoMA0GCSqGSIb3DQEBBQUA
+A4IBAQCRalCc23iBmz+LQuM7/KbD7kPgz/PigDVJRXYC4uMvBcXxKufAQTPGtpvQ
+MznNwNuhrWw3AkxYQTvyl5LGSKjN5Yo5iWH5Upfpvfb5lHTocQ68d4bDBsxafEp+
+NFAwLvt/MpqNPfMgW/hqyobzMUwsWYACff44yTB1HLdV47yfuqhthCgFdbOLDcCR
+VCHnpgu0mfVRQdzNo0ci2ccBgcTcR08m6h/t280NmPSjnLRzMkqWmf68f8glWPhY
+83ZmiVSkpj7EUFy6iRiCdUgh0k8T6GB+B3bbELVR5qq5aKrN9p2QdRLqOBrKROi3
+macqaJVmlaut74nLYKkGEsaUR+ko
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi
+MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu
+MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp
+dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV
+UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO
+ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz
+c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP
+OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl
+mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF
+BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4
+qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw
+gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB
+BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu
+bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp
+dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8
+6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/
+h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH
+/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv
+wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN
+pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
+GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
+b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV
+BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W
+YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa
+GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg
+Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J
+WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB
+rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp
++ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1
+ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i
+Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz
+PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og
+/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH
+oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI
+yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud
+EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2
+A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL
+MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT
+ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f
+BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn
+g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl
+fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K
+WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha
+B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc
+hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR
+TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD
+mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z
+ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y
+4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza
+8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
+GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
+b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV
+BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W
+YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM
+V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB
+4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr
+H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd
+8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv
+vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT
+mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe
+btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc
+T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt
+WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ
+c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A
+4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD
+VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG
+CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0
+aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0
+aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu
+dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw
+czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G
+A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC
+TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg
+Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0
+7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem
+d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd
++LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B
+4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN
+t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x
+DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57
+k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s
+zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j
+Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT
+mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK
+4SVhM7JZG+Ju1zdXtg2pEto=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC
+TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0
+aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0
+aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz
+MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw
+IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR
+dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp
+li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D
+rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ
+WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug
+F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU
+xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC
+Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv
+dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw
+ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl
+IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh
+c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy
+ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh
+Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI
+KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T
+KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq
+y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p
+dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD
+VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL
+MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk
+fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8
+7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R
+cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y
+mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW
+xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK
+SnQ2+Q==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
+IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
+BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
+aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
+9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy
+NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
+azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
+Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
+cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD
+cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs
+2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY
+JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE
+Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ
+n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A
+PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6
+MRkwFwYDVQQKExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJp
+dHkgMjA0OCBWMzAeFw0wMTAyMjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAX
+BgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAbBgNVBAsTFFJTQSBTZWN1cml0eSAy
+MDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt49VcdKA3Xtp
+eafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7Jylg
+/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGl
+wSMiuLgbWhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnh
+AMFRD0xS+ARaqn1y07iHKrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2
+PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP+Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpu
+AWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+BjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4EFgQUB8NR
+MKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYc
+HnmYv/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/
+Zb5gEydxiKRz44Rj0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+
+f00/FGj1EVDVwfSQpQgdMWD/YIwjVAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVO
+rSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395nzIlQnQFgCi/vcEkllgVsRch
+6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kApKnXwiJPZ9d3
+7CAFYd4=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK
+MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
+GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx
+MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg
+Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ
+iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa
+/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ
+jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI
+HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7
+sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w
+gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw
+KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG
+AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L
+URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO
+H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm
+I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY
+iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc
+f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI
+MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
+FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz
+MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv
+cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz
+Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO
+0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao
+wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj
+7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS
+8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT
+BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
+/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg
+JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC
+NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3
+6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/
+3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm
+D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS
+CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
+3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY
+MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t
+dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5
+WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD
+VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8
+9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ
+DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9
+Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N
+QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ
+xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G
+A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T
+AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG
+kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr
+Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5
+Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU
+JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot
+RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAgigAwIBAgIBJDANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP
+MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MxIENBMB4XDTAx
+MDQwNjEwNDkxM1oXDTIxMDQwNjEwNDkxM1owOTELMAkGA1UEBhMCRkkxDzANBgNV
+BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMSBDQTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBALWJHytPZwp5/8Ue+H887dF+2rDNbS82rDTG
+29lkFwhjMDMiikzujrsPDUJVyZ0upe/3p4zDq7mXy47vPxVnqIJyY1MPQYx9EJUk
+oVqlBvqSV536pQHydekfvFYmUk54GWVYVQNYwBSujHxVX3BbdyMGNpfzJLWaRpXk
+3w0LBUXl0fIdgrvGE+D+qnr9aTCU89JFhfzyMlsy3uhsXR/LpCJ0sICOXZT3BgBL
+qdReLjVQCfOAl/QMF6452F/NM8EcyonCIvdFEu1eEpOdY6uCLrnrQkFEy0oaAIIN
+nvmLVz5MxxftLItyM19yejhW1ebZrgUaHXVFsculJRwSVzb9IjcCAwEAAaMzMDEw
+DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQIR+IMi/ZTiFIwCwYDVR0PBAQDAgEG
+MA0GCSqGSIb3DQEBBQUAA4IBAQCLGrLJXWG04bkruVPRsoWdd44W7hE928Jj2VuX
+ZfsSZ9gqXLar5V7DtxYvyOirHYr9qxp81V9jz9yw3Xe5qObSIjiHBxTZ/75Wtf0H
+DjxVyhbMp6Z3N/vbXB9OWQaHowND9Rart4S9Tu+fMTfwRvFAttEMpWT4Y14h21VO
+TzF2nBBhjrZTOqMRvq9tfB69ri3iDGnHhVNoomG6xT60eVR4ngrHAr5i0RGCS2Uv
+kVrCqIexVmiUefkl98HVrhq4uz2PqYo4Ffdz0Fpg0YCw8NzVUM1O7pJIae2yIx4w
+zMiUyLb1O4Z/P6Yun/Y+LLWSlj7fLJOK/4GMDw9ZIRlXvVWa
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP
+MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx
+MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV
+BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o
+Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt
+5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s
+3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej
+vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu
+8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw
+DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG
+MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil
+zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/
+3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD
+FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6
+Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2
+ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJO
+TDEeMBwGA1UEChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFh
+dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEy
+MTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4wHAYDVQQKExVTdGFhdCBkZXIgTmVk
+ZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxhbmRlbiBSb290IENB
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFtvszn
+ExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw71
+9tV2U02PjLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MO
+hXeiD+EwR+4A5zN9RGcaC1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+U
+tFE5A3+y3qcym7RHjm+0Sq7lr7HcsBthvJly3uSJt3omXdozSVtSnA71iq3DuD3o
+BmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn622r+I/q85Ej0ZytqERAh
+SQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRVHSAAMDww
+OgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMv
+cm9vdC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA
+7Jbg0zTBLL9s+DANBgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k
+/rvuFbQvBgwp8qiSpGEN/KtcCFtREytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzm
+eafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbwMVcoEoJz6TMvplW0C5GUR5z6
+u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3ynGQI0DvDKcWy
+7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR
+iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
+MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
+U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
+NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
+ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
+ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
+DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
+8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
++lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
+X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
+K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
+1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
+A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
+zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
+YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
+bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
+DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
+L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
+eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
+xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
+VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
+WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW
+MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
+Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9
+MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi
+U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh
+cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk
+pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf
+OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C
+Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT
+Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi
+HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM
+Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w
++2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+
+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3
+Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B
+26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID
+AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE
+FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j
+ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js
+LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM
+BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0
+Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy
+dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh
+cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh
+YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg
+dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp
+bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ
+YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT
+TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ
+9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8
+jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW
+FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz
+ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1
+ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L
+EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu
+L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq
+yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC
+O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V
+um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh
+NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBk
+MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0
+YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg
+Q0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4MTgyMjA2MjBaMGQxCzAJBgNVBAYT
+AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp
+Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIICIjAN
+BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9
+m2BtRsiMMW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdih
+FvkcxC7mlSpnzNApbjyFNDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/
+TilftKaNXXsLmREDA/7n29uj/x2lzZAeAR81sH8A25Bvxn570e56eqeqDFdvpG3F
+EzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkCb6dJtDZd0KTeByy2dbco
+kdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn7uHbHaBu
+HYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNF
+vJbNcA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo
+19AOeCMgkckkKmUpWyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjC
+L3UcPX7ape8eYIVpQtPM+GP+HkM5haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJW
+bjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNYMUJDLXT5xp6mig/p/r+D5kNX
+JLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw
+FDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j
+BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzc
+K6FptWfUjNP9MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzf
+ky9NfEBWMXrrpA9gzXrzvsMnjgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7Ik
+Vh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQMbFamIp1TpBcahQq4FJHgmDmHtqB
+sfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4HVtA4oJVwIHaM190e
+3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtlvrsR
+ls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ip
+mXeascClOS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HH
+b6D0jqTsNFFbjCYDcKF31QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksf
+rK/7DZBaZmBwXarNeNQk7shBoJMBkpxqnvy5JMWzFYJ+vq6VK+uxwNrjAWALXmms
+hFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCyx/yP2FS1k2Kdzs9Z+z0Y
+zirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMWNY6E0F/6
+MBr1mmz0DlP5OlvRHA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
+BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln
+biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF
+MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT
+d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
+CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8
+76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+
+bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c
+6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE
+emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd
+MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt
+MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y
+MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y
+FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi
+aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM
+gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB
+qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7
+lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn
+8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov
+L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6
+45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO
+UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5
+O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC
+bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv
+GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a
+77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC
+hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3
+92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp
+Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w
+ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt
+Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFwTCCA6mgAwIBAgIITrIAZwwDXU8wDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE
+BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEjMCEGA1UEAxMaU3dpc3NTaWdu
+IFBsYXRpbnVtIENBIC0gRzIwHhcNMDYxMDI1MDgzNjAwWhcNMzYxMDI1MDgzNjAw
+WjBJMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMSMwIQYDVQQD
+ExpTd2lzc1NpZ24gUGxhdGludW0gQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAMrfogLi2vj8Bxax3mCq3pZcZB/HL37PZ/pEQtZ2Y5Wu669y
+IIpFR4ZieIbWIDkm9K6j/SPnpZy1IiEZtzeTIsBQnIJ71NUERFzLtMKfkr4k2Htn
+IuJpX+UFeNSH2XFwMyVTtIc7KZAoNppVRDBopIOXfw0enHb/FZ1glwCNioUD7IC+
+6ixuEFGSzH7VozPY1kneWCqv9hbrS3uQMpe5up1Y8fhXSQQeol0GcN1x2/ndi5ob
+jM89o03Oy3z2u5yg+gnOI2Ky6Q0f4nIoj5+saCB9bzuohTEJfwvH6GXp43gOCWcw
+izSC+13gzJ2BbWLuCB4ELE6b7P6pT1/9aXjvCR+htL/68++QHkwFix7qepF6w9fl
++zC8bBsQWJj3Gl/QKTIDE0ZNYWqFTFJ0LwYfexHihJfGmfNtf9dng34TaNhxKFrY
+zt3oEBSa/m0jh26OWnA81Y0JAKeqvLAxN23IhBQeW71FYyBrS3SMvds6DsHPWhaP
+pZjydomyExI7C3d3rLvlPClKknLKYRorXkzig3R3+jVIeoVNjZpTxN94ypeRSCtF
+KwH3HBqi7Ri6Cr2D+m+8jVeTO9TUps4e8aCxzqv9KyiaTxvXw3LbpMS/XUz13XuW
+ae5ogObnmLo2t/5u7Su9IPhlGdpVCX4l3P5hYnL5fhgC72O00Puv5TtjjGePAgMB
+AAGjgawwgakwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
+BBYEFFCvzAeHFUdvOMW0ZdHelarp35zMMB8GA1UdIwQYMBaAFFCvzAeHFUdvOMW0
+ZdHelarp35zMMEYGA1UdIAQ/MD0wOwYJYIV0AVkBAQEBMC4wLAYIKwYBBQUHAgEW
+IGh0dHA6Ly9yZXBvc2l0b3J5LnN3aXNzc2lnbi5jb20vMA0GCSqGSIb3DQEBBQUA
+A4ICAQAIhab1Fgz8RBrBY+D5VUYI/HAcQiiWjrfFwUF1TglxeeVtlspLpYhg0DB0
+uMoI3LQwnkAHFmtllXcBrqS3NQuB2nEVqXQXOHtYyvkv+8Bldo1bAbl93oI9ZLi+
+FHSjClTTLJUYFzX1UWs/j6KWYTl4a0vlpqD4U99REJNi54Av4tHgvI42Rncz7Lj7
+jposiU0xEQ8mngS7twSNC/K5/FqdOxa3L8iYq/6KUFkuozv8KV2LwUvJ4ooTHbG/
+u0IdUt1O2BReEMYxB+9xJ/cbOQncguqLs5WGXv312l0xpuAxtpTmREl0xRbl9x8D
+YSjFyMsSoEJL+WuICI20MhjzdZ/EfwBPBZWcoxcCw7NTm6ogOSkrZvqdr16zktK1
+puEa+S1BaYEUtLS17Yk9zvupnTVCRLEcFHOBzyoBNZox1S2PbYTfgE1X4z/FhHXa
+icYwu+uPyyIIoK6q8QNsOktNCaUOcsZWayFCTiMlFGiudgp8DAdwZPmaL/YFOSbG
+DI8Zf0NebvRbFS/bYV3mZy8/CJT5YLSYMdp08YSTcU1f+2BY0fvEwW2JorsgH51x
+kcsymxM9Pn2SUjWskpSi0xjCfMfqr3YFFt1nJ8J+HAciIfNAChs0B0QTwoRqjt8Z
+Wr9/6x3iGjjRXK9HkmuAtTClyY3YqzGBH9/CZjfTk6mFhnll0g==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE
+BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu
+IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow
+RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY
+U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
+MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv
+Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br
+YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF
+nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH
+6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt
+eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/
+c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ
+MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH
+HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf
+jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6
+5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB
+rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c
+wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0
+cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB
+AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp
+WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9
+xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ
+2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ
+IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8
+aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X
+em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR
+dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/
+OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+
+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy
+tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/
+MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj
+YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow
+PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp
+Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
+AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR
+IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q
+gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy
+yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts
+F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2
+jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx
+ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC
+VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK
+YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH
+EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN
+Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud
+DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE
+MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK
+UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ
+TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf
+qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK
+ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE
+JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7
+hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1
+EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm
+nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX
+udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz
+ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe
+LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl
+pYYsfPQS
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDXDCCAsWgAwIBAgICA+owDQYJKoZIhvcNAQEEBQAwgbwxCzAJBgNVBAYTAkRF
+MRAwDgYDVQQIEwdIYW1idXJnMRAwDgYDVQQHEwdIYW1idXJnMTowOAYDVQQKEzFU
+QyBUcnVzdENlbnRlciBmb3IgU2VjdXJpdHkgaW4gRGF0YSBOZXR3b3JrcyBHbWJI
+MSIwIAYDVQQLExlUQyBUcnVzdENlbnRlciBDbGFzcyAyIENBMSkwJwYJKoZIhvcN
+AQkBFhpjZXJ0aWZpY2F0ZUB0cnVzdGNlbnRlci5kZTAeFw05ODAzMDkxMTU5NTla
+Fw0xMTAxMDExMTU5NTlaMIG8MQswCQYDVQQGEwJERTEQMA4GA1UECBMHSGFtYnVy
+ZzEQMA4GA1UEBxMHSGFtYnVyZzE6MDgGA1UEChMxVEMgVHJ1c3RDZW50ZXIgZm9y
+IFNlY3VyaXR5IGluIERhdGEgTmV0d29ya3MgR21iSDEiMCAGA1UECxMZVEMgVHJ1
+c3RDZW50ZXIgQ2xhc3MgMiBDQTEpMCcGCSqGSIb3DQEJARYaY2VydGlmaWNhdGVA
+dHJ1c3RjZW50ZXIuZGUwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANo46O0y
+AClxgwENv4wB3NrGrTmkqYov1YtcaF9QxmL1Zr3KkSLsqh1R1z2zUbKDTl3LSbDw
+TFXlay3HhQswHJJOgtTKAu33b77c4OMUuAVT8pr0VotanoWT0bSCVq5Nu6hLVxa8
+/vhYnvgpjbB7zXjJT6yLZwzxnPv8V5tXXE8NAgMBAAGjazBpMA8GA1UdEwEB/wQF
+MAMBAf8wDgYDVR0PAQH/BAQDAgGGMDMGCWCGSAGG+EIBCAQmFiRodHRwOi8vd3d3
+LnRydXN0Y2VudGVyLmRlL2d1aWRlbGluZXMwEQYJYIZIAYb4QgEBBAQDAgAHMA0G
+CSqGSIb3DQEBBAUAA4GBAIRS+yjf/x91AbwBvgRWl2p0QiQxg/lGsQaKic+WLDO/
+jLVfenKhhQbOhvgFjuj5Jcrag4wGrOs2bYWRNAQ29ELw+HkuCkhcq8xRT3h2oNms
+Gb0q0WkEKJHKNhAngFdb0lz1wlurZIFjdFH0l7/NEij3TWZ/p/AcASZ4smZHcFFk
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDXDCCAsWgAwIBAgICA+swDQYJKoZIhvcNAQEEBQAwgbwxCzAJBgNVBAYTAkRF
+MRAwDgYDVQQIEwdIYW1idXJnMRAwDgYDVQQHEwdIYW1idXJnMTowOAYDVQQKEzFU
+QyBUcnVzdENlbnRlciBmb3IgU2VjdXJpdHkgaW4gRGF0YSBOZXR3b3JrcyBHbWJI
+MSIwIAYDVQQLExlUQyBUcnVzdENlbnRlciBDbGFzcyAzIENBMSkwJwYJKoZIhvcN
+AQkBFhpjZXJ0aWZpY2F0ZUB0cnVzdGNlbnRlci5kZTAeFw05ODAzMDkxMTU5NTla
+Fw0xMTAxMDExMTU5NTlaMIG8MQswCQYDVQQGEwJERTEQMA4GA1UECBMHSGFtYnVy
+ZzEQMA4GA1UEBxMHSGFtYnVyZzE6MDgGA1UEChMxVEMgVHJ1c3RDZW50ZXIgZm9y
+IFNlY3VyaXR5IGluIERhdGEgTmV0d29ya3MgR21iSDEiMCAGA1UECxMZVEMgVHJ1
+c3RDZW50ZXIgQ2xhc3MgMyBDQTEpMCcGCSqGSIb3DQEJARYaY2VydGlmaWNhdGVA
+dHJ1c3RjZW50ZXIuZGUwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALa0wTUF
+Lg2N7KBAahwOJ6ZQkmtQGwfeLud2zODa/ISoXoxjaitN2U4CdhHBC/KNecoAtvGw
+Dtf7pBc9r6tpepYnv68zoZoqWarEtTcI8hKlMbZD9TKWcSgoq40oht+77uMMfTDW
+w1Krj10nnGvAo+cFa1dJRLNu6mTP0o56UHd3AgMBAAGjazBpMA8GA1UdEwEB/wQF
+MAMBAf8wDgYDVR0PAQH/BAQDAgGGMDMGCWCGSAGG+EIBCAQmFiRodHRwOi8vd3d3
+LnRydXN0Y2VudGVyLmRlL2d1aWRlbGluZXMwEQYJYIZIAYb4QgEBBAQDAgAHMA0G
+CSqGSIb3DQEBBAUAA4GBABY9xs3Bu4VxhUafPiCPUSiZ7C1FIWMjWwS7TJC4iJIE
+Tb19AaM/9uzO8d7+feXhPrvGq14L3T2WxMup1Pkm5gZOngylerpuw3yCGdHHsbHD
+2w2Om0B8NwvxXej9H5CIpQ5ON2QhqE6NtJ/x3kit1VYYUimLRzQSCdS7kjXvD9s0
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJE
+SzEVMBMGA1UEChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQg
+Um9vdCBDQTAeFw0wMTA0MDUxNjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNV
+BAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJuZXQxHTAbBgNVBAsTFFREQyBJbnRl
+cm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxLhA
+vJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20jxsNu
+Zp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a
+0vnRrEvLznWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc1
+4izbSysseLlJ28TQx5yc5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGN
+eGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcD
+R0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZIAYb4QgEBBAQDAgAHMGUG
+A1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMMVERDIElu
+dGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxME
+Q1JMMTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3
+WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAw
+HQYDVR0OBBYEFGxkAcf9hW2syNqeUAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJ
+KoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4IBAQBO
+Q8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540mgwV5dOy0uaOX
+wTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+
+2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm89
+9qNLPg7kbWzbO0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0
+jUNAE4z9mQNUecYu6oah9jrUCbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38
+aQNiuJkFBT1reBK9sG9l
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFGTCCBAGgAwIBAgIEPki9xDANBgkqhkiG9w0BAQUFADAxMQswCQYDVQQGEwJE
+SzEMMAoGA1UEChMDVERDMRQwEgYDVQQDEwtUREMgT0NFUyBDQTAeFw0wMzAyMTEw
+ODM5MzBaFw0zNzAyMTEwOTA5MzBaMDExCzAJBgNVBAYTAkRLMQwwCgYDVQQKEwNU
+REMxFDASBgNVBAMTC1REQyBPQ0VTIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEArGL2YSCyz8DGhdfjeebM7fI5kqSXLmSjhFuHnEz9pPPEXyG9VhDr
+2y5h7JNp46PMvZnDBfwGuMo2HP6QjklMxFaaL1a8z3sM8W9Hpg1DTeLpHTk0zY0s
+2RKY+ePhwUp8hjjEqcRhiNJerxomTdXkoCJHhNlktxmW/OwZ5LKXJk5KTMuPJItU
+GBxIYXvViGjaXbXqzRowwYCDdlCqT9HU3Tjw7xb04QxQBr/q+3pJoSgrHPb8FTKj
+dGqPqcNiKXEx5TukYBdedObaE+3pHx8b0bJoc8YQNHVGEBDjkAB2QMuLt0MJIf+r
+TpPGWOmlgtt3xDqZsXKVSQTwtyv6e1mO3QIDAQABo4ICNzCCAjMwDwYDVR0TAQH/
+BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwgewGA1UdIASB5DCB4TCB3gYIKoFQgSkB
+AQEwgdEwLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuY2VydGlmaWthdC5kay9yZXBv
+c2l0b3J5MIGdBggrBgEFBQcCAjCBkDAKFgNUREMwAwIBARqBgUNlcnRpZmlrYXRl
+ciBmcmEgZGVubmUgQ0EgdWRzdGVkZXMgdW5kZXIgT0lEIDEuMi4yMDguMTY5LjEu
+MS4xLiBDZXJ0aWZpY2F0ZXMgZnJvbSB0aGlzIENBIGFyZSBpc3N1ZWQgdW5kZXIg
+T0lEIDEuMi4yMDguMTY5LjEuMS4xLjARBglghkgBhvhCAQEEBAMCAAcwgYEGA1Ud
+HwR6MHgwSKBGoESkQjBAMQswCQYDVQQGEwJESzEMMAoGA1UEChMDVERDMRQwEgYD
+VQQDEwtUREMgT0NFUyBDQTENMAsGA1UEAxMEQ1JMMTAsoCqgKIYmaHR0cDovL2Ny
+bC5vY2VzLmNlcnRpZmlrYXQuZGsvb2Nlcy5jcmwwKwYDVR0QBCQwIoAPMjAwMzAy
+MTEwODM5MzBagQ8yMDM3MDIxMTA5MDkzMFowHwYDVR0jBBgwFoAUYLWF7FZkfhIZ
+J2cdUBVLc647+RIwHQYDVR0OBBYEFGC1hexWZH4SGSdnHVAVS3OuO/kSMB0GCSqG
+SIb2fQdBAAQQMA4bCFY2LjA6NC4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEACrom
+JkbTc6gJ82sLMJn9iuFXehHTuJTXCRBuo7E4A9G28kNBKWKnctj7fAXmMXAnVBhO
+inxO5dHKjHiIzxvTkIvmI/gLDjNDfZziChmPyQE+dF10yYscA+UYyAFMP8uXBV2Y
+caaYb7Z8vTd/vuGTJW1v8AqtFxjhA7wHKcitJuj4YfD9IQl+mo6paH1IYnK9AOoB
+mbgGglGBTvH1tJFUuSN6AJqfXY3gPGS5GhKSKseCRHI53OI8xthV9RVOyAUO28bQ
+YqbsFbS1AoLbrIyigfCbmTH1ICCoiGEKB5+U/NDXG8wuF/MEJ3Zn61SD/aSQfgY9
+BKNDLdr8C2LqL19iUw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDLTCCApagAwIBAgIBADANBgkqhkiG9w0BAQQFADCB0TELMAkGA1UEBhMCWkEx
+FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMRowGAYD
+VQQKExFUaGF3dGUgQ29uc3VsdGluZzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBT
+ZXJ2aWNlcyBEaXZpc2lvbjEkMCIGA1UEAxMbVGhhd3RlIFBlcnNvbmFsIEZyZWVt
+YWlsIENBMSswKQYJKoZIhvcNAQkBFhxwZXJzb25hbC1mcmVlbWFpbEB0aGF3dGUu
+Y29tMB4XDTk2MDEwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgdExCzAJBgNVBAYT
+AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEa
+MBgGA1UEChMRVGhhd3RlIENvbnN1bHRpbmcxKDAmBgNVBAsTH0NlcnRpZmljYXRp
+b24gU2VydmljZXMgRGl2aXNpb24xJDAiBgNVBAMTG1RoYXd0ZSBQZXJzb25hbCBG
+cmVlbWFpbCBDQTErMCkGCSqGSIb3DQEJARYccGVyc29uYWwtZnJlZW1haWxAdGhh
+d3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1GnX1LCUZFtx6UfY
+DFG26nKRsIRefS0Nj3sS34UldSh0OkIsYyeflXtL734Zhx2G6qPduc6WZBrCFG5E
+rHzmj+hND3EfQDimAKOHePb5lIZererAXnbr2RSjXW56fAylS1V/Bhkpf56aJtVq
+uzgkCGqYx7Hao5iR/Xnb5VrEHLkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zAN
+BgkqhkiG9w0BAQQFAAOBgQDH7JJ+Tvj1lqVnYiqk8E0RYNBvjWBYYawmu1I1XAjP
+MPuoSpaKH2JCI4wXD/S6ZJwXrEcp352YXtJsYHFcoqzceePnbgBHH7UNKOgCneSa
+/RP0ptl8sfjcXyMmCZGAc9AUG95DqYMl8uacLxXK/qarigd1iwzdUYRr5PjRznei
+gQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx
+FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
+VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
+biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy
+dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t
+MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB
+MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG
+A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp
+b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl
+cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv
+bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE
+VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ
+ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR
+uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG
+9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI
+hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM
+pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB
+qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
+Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
+MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV
+BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw
+NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j
+LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG
+A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
+IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs
+W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta
+3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk
+6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6
+Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J
+NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA
+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP
+r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU
+DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz
+YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX
+xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2
+/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/
+LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7
+jVaMaA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx
+FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
+VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
+biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm
+MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx
+MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT
+DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3
+dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl
+cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3
+DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD
+gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91
+yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX
+L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj
+EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG
+7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e
+QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ
+qdq5snUb9kLy78fyGPmJvKP/iiMucEc=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICoTCCAgqgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBizELMAkGA1UEBhMCWkEx
+FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTEUMBIGA1UEBxMLRHVyYmFudmlsbGUxDzAN
+BgNVBAoTBlRoYXd0ZTEdMBsGA1UECxMUVGhhd3RlIENlcnRpZmljYXRpb24xHzAd
+BgNVBAMTFlRoYXd0ZSBUaW1lc3RhbXBpbmcgQ0EwHhcNOTcwMTAxMDAwMDAwWhcN
+MjAxMjMxMjM1OTU5WjCBizELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4g
+Q2FwZTEUMBIGA1UEBxMLRHVyYmFudmlsbGUxDzANBgNVBAoTBlRoYXd0ZTEdMBsG
+A1UECxMUVGhhd3RlIENlcnRpZmljYXRpb24xHzAdBgNVBAMTFlRoYXd0ZSBUaW1l
+c3RhbXBpbmcgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANYrWHhhRYZT
+6jR7UZztsOYuGA7+4F+oJ9O0yeB8WU4WDnNUYMF/9p8u6TqFJBU820cEY8OexJQa
+Wt9MevPZQx08EHp5JduQ/vBR5zDWQQD9nyjfeb6Uu522FOMjhdepQeBMpHmwKxqL
+8vg7ij5FrHGSALSQQZj7X+36ty6K+Ig3AgMBAAGjEzARMA8GA1UdEwEB/wQFMAMB
+Af8wDQYJKoZIhvcNAQEEBQADgYEAZ9viwuaHPUCDhjc1fR/OmsMMZiCouqoEiYbC
+9RAIDb/LogWK0E02PvTX72nGXuSwlG9KuefeW4i2e9vjJ+V2w/A1wcu1J5szedyQ
+pgCed/r8zSeUQhac0xxo7L9c3eWpexAKMnRUEzGLhQOEkbdYATAUOK8oyvyxUBkZ
+CayJSdM=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOc
+UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
+c8SxMQswCQYDVQQGDAJUUjEPMA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykg
+MjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8
+dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMxMDI3MTdaFw0xNTAz
+MjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsgU2Vy
+dGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYD
+VQQHDAZBTktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kg
+xLBsZXRpxZ9pbSB2ZSBCaWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEu
+xZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7
+XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GXyGl8hMW0kWxsE2qkVa2k
+heiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8iSi9BB35J
+YbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5C
+urKZ8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1
+JuTm5Rh8i27fbMx4W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51
+b0dewQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV
+9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46sWrv7/hg0Uw2ZkUd82YCdAR7
+kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxEq8Sn5RTOPEFh
+fEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy
+B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdA
+aLX/7KfS0zgYnNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKS
+RGQDJereW26fyfJOrN3H
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOc
+UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
+c8SxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xS
+S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg
+SGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcNMDUxMTA3MTAwNzU3
+WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVrdHJv
+bmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJU
+UjEPMA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSw
+bGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWe
+LiAoYykgS2FzxLFtIDIwMDUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqeLCDe2JAOCtFp0if7qnef
+J1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKIx+XlZEdh
+R3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJ
+Qv2gQrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGX
+JHpsmxcPbe9TmJEr5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1p
+zpwACPI2/z7woQ8arBT9pmAPAgMBAAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58S
+Fq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8GA1UdEwEB/wQFMAMBAf8wDQYJ
+KoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/nttRbj2hWyfIvwq
+ECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4
+Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFz
+gw2lGh1uEpJ+hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotH
+uFEJjOp9zYhys2AzsfAKRO8P9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LS
+y3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5UrbnBEI=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCB
+kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw
+IFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBaMIGTMQswCQYDVQQG
+EwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYD
+VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cu
+dXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6
+E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ysraP6LnD43m77VkIVni5c7yPeIbkFdicZ
+D0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlowHDyUwDAXlCCpVZvNvlK
+4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA9P4yPykq
+lXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulW
+bfXv33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQAB
+o4GrMIGoMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRT
+MtGzz3/64PGgXYVOktKeRR20TzA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3Js
+LnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dDLmNybDAqBgNVHSUEIzAhBggr
+BgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3DQEBBQUAA4IB
+AQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft
+Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyj
+j98C5OBxOvG0I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVH
+KWss5nbZqSl9Mt3JNjy9rjXxEZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv
+2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwPDPafepE39peC4N1xaf92P2BNPM/3
+mfnGV/TJVTl4uix5yaaIK/QI
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEojCCA4qgAwIBAgIQRL4Mi1AAJLQR0zYlJWfJiTANBgkqhkiG9w0BAQUFADCB
+rjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xNjA0BgNVBAMTLVVUTi1VU0VSRmlyc3Qt
+Q2xpZW50IEF1dGhlbnRpY2F0aW9uIGFuZCBFbWFpbDAeFw05OTA3MDkxNzI4NTBa
+Fw0xOTA3MDkxNzM2NThaMIGuMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAV
+BgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5l
+dHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTE2MDQGA1UE
+AxMtVVROLVVTRVJGaXJzdC1DbGllbnQgQXV0aGVudGljYXRpb24gYW5kIEVtYWls
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsjmFpPJ9q0E7YkY3rs3B
+YHW8OWX5ShpHornMSMxqmNVNNRm5pELlzkniii8efNIxB8dOtINknS4p1aJkxIW9
+hVE1eaROaJB7HHqkkqgX8pgV8pPMyaQylbsMTzC9mKALi+VuG6JG+ni8om+rWV6l
+L8/K2m2qL+usobNqqrcuZzWLeeEeaYji5kbNoKXqvgvOdjp6Dpvq/NonWz1zHyLm
+SGHGTPNpsaguG7bUMSAsvIKKjqQOpdeJQ/wWWq8dcdcRWdq6hw2v+vPhwvCkxWeM
+1tZUOt4KpLoDd7NlyP0e03RiqhjKaJMeoYV+9Udly/hNVyh00jT/MLbu9mIwFIws
+6wIDAQABo4G5MIG2MAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
+DgQWBBSJgmd9xJ0mcABLtFBIfN49rgRufTBYBgNVHR8EUTBPME2gS6BJhkdodHRw
+Oi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLVVTRVJGaXJzdC1DbGllbnRBdXRoZW50
+aWNhdGlvbmFuZEVtYWlsLmNybDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH
+AwQwDQYJKoZIhvcNAQEFBQADggEBALFtYV2mGn98q0rkMPxTbyUkxsrt4jFcKw7u
+7mFVbwQ+zznexRtJlOTrIEy05p5QLnLZjfWqo7NK2lYcYJeA3IKirUq9iiv/Cwm0
+xtcgBEXkzYABurorbs6q15L+5K/r9CYdFip/bDCVNy8zEqx/3cfREYxRmLLQo5HQ
+rfafnoOTHh1CuEava2bwm3/q4wMC5QJRwarVNZ1yQAOJujEdxRBoUp7fooXFXAim
+eOZTT7Hot9MUnpOmw2TjrH5xzbyf6QMbzPvprDHBr3wVdAKZw7JHpsIyYdfHb0gk
+USeh1YdV8nuPmD0Wnu51tvjQjvLzxq4oW6fw8zYX/MMF08oDSlQ=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB
+lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
+SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG
+A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe
+MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v
+d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh
+cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn
+0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ
+M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a
+MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd
+oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI
+DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy
+oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD
+VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0
+dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy
+bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF
+BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM
+//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli
+CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE
+CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t
+3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS
+KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
+IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
+BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
+aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
+9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy
+NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
+azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
+Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
+cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y
+LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+
+TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y
+TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0
+LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW
+I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw
+nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
+IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
+BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
+aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
+9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy
+NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
+azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
+Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
+cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY
+dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9
+WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS
+v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v
+UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu
+IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC
+W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCED9pHoGc8JpK83P/uUii5N0wDQYJKoZIhvcNAQEFBQAwXzELMAkG
+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
+cyAxIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
+MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
+BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAxIFB1YmxpYyBQcmlt
+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQDlGb9to1ZhLZlIcfZn3rmN67eehoAKkQ76OCWvRoiC5XOooJskXQ0f
+zGVuDLDQVoQYh5oGmxChc9+0WDlrbsH2FdWoqD+qEgaNMax/sDTXjzRniAnNFBHi
+TkVWaR94AoDa3EeRKbs2yWNcxeDXLYd7obcysHswuiovMaruo2fa2wIDAQABMA0G
+CSqGSIb3DQEBBQUAA4GBAFgVKTk8d6PaXCUDfGD67gmZPCcQcMgMCeazh88K4hiW
+NWLMv5sneYlfycQJ9M61Hd8qveXbhpxoJeUwfLaJFf5n0a3hUKw8fGJLj7qE1xIV
+Gx/KXQ/BUpQqEZnae88MNhPVNdwQGVnqlMEAv3WP2fr9dgTbYruQagPZRjXZ+Hxb
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDAjCCAmsCEEzH6qqYPnHTkxD4PTqJkZIwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
+BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
+c3MgMSBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
+MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
+emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
+DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
+FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMSBQdWJsaWMg
+UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
+YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
+MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
+AQUAA4GNADCBiQKBgQCq0Lq+Fi24g9TK0g+8djHKlNgdk4xWArzZbxpvUjZudVYK
+VdPfQ4chEWWKfo+9Id5rMj8bhDSVBZ1BNeuS65bdqlk/AVNtmU/t5eIqWpDBucSm
+Fc/IReumXY6cPvBkJHalzasab7bYe1FhbqZ/h8jit+U03EGI6glAvnOSPWvndQID
+AQABMA0GCSqGSIb3DQEBBQUAA4GBAKlPww3HZ74sy9mozS11534Vnjty637rXC0J
+h9ZrbWB85a7FkCMMXErQr7Fd88e2CtvgFZMN3QO8x3aKtd1Pw5sTdbgBwObJW2ul
+uIncrKTdcu1OofdPvAbT6shkdHvClUGcZXNY8ZCaPGqxmMnEh7zPRW1F4m4iP/68
+DzFc6PLZ
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwICEQCLW3VWhFSFCwDPrzhIzrGkMA0GCSqGSIb3DQEBBQUAMIHKMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
+cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
+LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
+aWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
+VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
+aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
+bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
+IENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
+LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN2E1Lm0+afY8wR4
+nN493GwTFtl63SRRZsDHJlkNrAYIwpTRMx/wgzUfbhvI3qpuFU5UJ+/EbRrsC+MO
+8ESlV8dAWB6jRx9x7GD2bZTIGDnt/kIYVt/kTEkQeE4BdjVjEjbdZrwBBDajVWjV
+ojYJrKshJlQGrT/KFOCsyq0GHZXi+J3x4GD/wn91K0zM2v6HmSHquv4+VNfSWXjb
+PG7PoBMAGrgnoeS+Z5bKoMWznN3JdZ7rMJpfo83ZrngZPyPpXNspva1VyBtUjGP2
+6KbqxzcSXKMpHgLZ2x87tNcPVkeBFQRKr4Mn0cVYiMHd9qqnoxjaaKptEVHhv2Vr
+n5Z20T0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAq2aN17O6x5q25lXQBfGfMY1a
+qtmqRiYPce2lrVNWYgFHKkTp/j90CxObufRNG7LRX7K20ohcs5/Ny9Sn2WCVhDr4
+wTcdYcrnsMXlkdpUpqwxga6X3s0IrLjAl4B/bnKk52kTlWUfxJM8/XmPBNQ+T+r3
+ns7NZ3xPZQL/kYVUc8f/NveGLezQXk//EZ9yBta4GvFMDSZl4kSAHsef493oCtrs
+pSCAaWihT37ha88HQfqDjrw43bAuEbFrskLMmrz5SCJ5ShkPshw+IHTZasO+8ih4
+E1Z5T21Q6huwtVexN2ZYI/PcD98Kh8TvhgXVOBRgmaNL3gaWcSzy27YfpO8/7g==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCEC0b/EoXjaOR6+f/9YtFvgswDQYJKoZIhvcNAQECBQAwXzELMAkG
+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
+cyAyIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
+MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
+BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAyIFB1YmxpYyBQcmlt
+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQC2WoujDWojg4BrzzmH9CETMwZMJaLtVRKXxaeAufqDwSCg+i8VDXyh
+YGt+eSz6Bg86rvYbb7HS/y8oUl+DfUvEerf4Zh+AVPy3wo5ZShRXRtGak75BkQO7
+FYCTXOvnzAhsPz6zSvz/S2wj1VCCJkQZjiPDceoZJEcEnnW/yKYAHwIDAQABMA0G
+CSqGSIb3DQEBAgUAA4GBAIobK/o5wXTXXtgZZKJYSi034DNHD6zt96rbHuSLBlxg
+J8pFUs4W7z8GZOeUaHxgMxURaa+dYo2jA1Rrpr7l7gUYYAS/QoD90KioHgE796Nc
+r6Pc5iaAIzy4RHT3Cq5Ji2F4zCS/iIqnDupzGUH9TQPwiNHleI2lKk/2lw0Xd8rY
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDAzCCAmwCEQC5L2DMiJ+hekYJuFtwbIqvMA0GCSqGSIb3DQEBBQUAMIHBMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0Ns
+YXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH
+MjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9y
+aXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazAe
+Fw05ODA1MTgwMDAwMDBaFw0yODA4MDEyMzU5NTlaMIHBMQswCQYDVQQGEwJVUzEX
+MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGlj
+IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMx
+KGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s
+eTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazCBnzANBgkqhkiG9w0B
+AQEFAAOBjQAwgYkCgYEAp4gBIXQs5xoD8JjhlzwPIQjxnNuX6Zr8wgQGE75fUsjM
+HiwSViy4AWkszJkfrbCWrnkE8hM5wXuYuggs6MKEEyyqaekJ9MepAqRCwiNPStjw
+DqL7MWzJ5m+ZJwf15vRMeJ5t60aG+rmGyVTyssSv1EYcWskVMP8NbPUtDm3Of3cC
+AwEAATANBgkqhkiG9w0BAQUFAAOBgQByLvl/0fFx+8Se9sVeUYpAmLho+Jscg9ji
+nb3/7aHmZuovCfTK1+qlK5X2JGCGTUQug6XELaDTrnhpb3LabK4I8GOSN+a7xDAX
+rXfMSTWqz9iP0b63GJZHc2pUIjRkLbYWm1lbtFFZOrMLFPQS32eg9K0yZF6xRnIn
+jBJ7xUS0rg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEGTCCAwECEGFwy0mMX5hFKeewptlQW3owDQYJKoZIhvcNAQEFBQAwgcoxCzAJ
+BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVy
+aVNpZ24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24s
+IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNp
+Z24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
+eSAtIEczMB4XDTk5MTAwMTAwMDAwMFoXDTM2MDcxNjIzNTk1OVowgcoxCzAJBgNV
+BAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNp
+Z24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIElu
+Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNpZ24g
+Q2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt
+IEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwoNwtUs22e5LeWU
+J92lvuCwTY+zYVY81nzD9M0+hsuiiOLh2KRpxbXiv8GmR1BeRjmL1Za6tW8UvxDO
+JxOeBUebMXoT2B/Z0wI3i60sR/COgQanDTAM6/c8DyAd3HJG7qUCyFvDyVZpTMUY
+wZF7C9UTAJu878NIPkZgIIUq1ZC2zYugzDLdt/1AVbJQHFauzI13TccgTacxdu9o
+koqQHgiBVrKtaaNS0MscxCM9H5n+TOgWY47GCI72MfbS+uV23bUckqNJzc0BzWjN
+qWm6o+sdDZykIKbBoMXRRkwXbdKsZj+WjOCE1Db/IlnF+RFgqF8EffIa9iVCYQ/E
+Srg+iQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQA0JhU8wI1NQ0kdvekhktdmnLfe
+xbjQ5F1fdiLAJvmEOjr5jLX77GDx6M4EsMjdpwOPMPOY36TmpDHf0xwLRtxyID+u
+7gU8pDM/CzmscHhzS5kr3zDCVLCoO1Wh/hYozUK9dG6A2ydEp85EXdQbkJgNHkKU
+sQAsBNB0owIFImNjzYO1+8FtYmtpdf1dcEG59b98377BMnMiIYtYgXsVkXq642RI
+sH/7NiXaldDxJBQX3RiAa0YjOVT1jmIJBB2UkKab5iXiQkWquJCtvgiPqQtCGJTP
+cjnhsUPgKM+351psE2tJs//jGHyJizNdrDPXp/naOlXJWBD5qu9ats9LS98q
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG
+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
+cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
+MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
+BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
+BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
+I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
+CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i
+2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ
+2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
+BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
+c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
+MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
+emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
+DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
+FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg
+UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
+YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
+MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
+AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4
+pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0
+13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID
+AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk
+U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i
+F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY
+oJ2daZH9
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
+cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
+LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
+aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
+VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
+aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
+bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
+IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
+LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b
+N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t
+KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu
+kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm
+CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ
+Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu
+imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te
+2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe
+DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
+/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p
+F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt
+TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
+ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
+ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln
+biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
+U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
+aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1
+nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex
+t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz
+SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG
+BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+
+rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/
+NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
+BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH
+BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
+aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv
+MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE
+p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y
+5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK
+WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ
+4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N
+hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDAjCCAmsCEDKIjprS9esTR/h/xCA3JfgwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
+BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
+c3MgNCBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
+MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
+emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
+DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
+FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgNCBQdWJsaWMg
+UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
+YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
+MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
+AQUAA4GNADCBiQKBgQC68OTP+cSuhVS5B1f5j8V/aBH4xBewRNzjMHPVKmIquNDM
+HO0oW369atyzkSTKQWI8/AIBvxwWMZQFl3Zuoq29YRdsTjCG8FE3KlDHqGKB3FtK
+qsGgtG7rL+VXxbErQHDbWk2hjh+9Ax/YA9SPTJlxvOKCzFjomDqG04Y48wApHwID
+AQABMA0GCSqGSIb3DQEBBQUAA4GBAIWMEsGnuVAVess+rLhDityq3RS6iYF+ATwj
+cSGIL4LcY/oCRaxFWdcqWERbt5+BO5JoPeI3JPV7bI92NZYJqFmduc4jq3TWg/0y
+cyfYaT5DdPauxYma51N86Xv2S/PBZYPejYqcPIiNOVn8qj8ijaHBZlCBckztImRP
+T8qAkbYp
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
+cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
+LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
+aWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
+VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
+aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
+bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
+IENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
+LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3LpRFpxlmr8Y+1
+GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaStBO3IFsJ
++mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0Gbd
+U6LM8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLm
+NxdLMEYH5IBtptiWLugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XY
+ufTsgsbSPZUd5cBPhMnZo0QoBmrXRazwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/
+ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAj/ola09b5KROJ1WrIhVZPMq1
+CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXttmhwwjIDLk5Mq
+g6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm
+fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c
+2NU8Qh0XwRJdRTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/
+bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBr
+MQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRl
+cm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv
+bW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2WhcNMjIwNjI0MDAxNjEyWjBrMQsw
+CQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5h
+dGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1l
+cmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h
+2mCxlCfLF9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4E
+lpF7sDPwsRROEW+1QK8bRaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdV
+ZqW1LS7YgFmypw23RuwhY/81q6UCzyr0TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq
+299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI/k4+oKsGGelT84ATB+0t
+vz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzsGHxBvfaL
+dXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
+AgEGMB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUF
+AAOCAQEAX/FBfXxcCLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcR
+zCSs00Rsca4BIGsDoo8Ytyk6feUWYFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3
+LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pzzkWKsKZJ/0x9nXGIxHYdkFsd
+7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBuYQa7FkKMcPcw
+++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt
+398znM/jra6O1I7mT1GvFpLgXPYHDw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID5TCCAs2gAwIBAgIEOeSXnjANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UEBhMC
+VVMxFDASBgNVBAoTC1dlbGxzIEZhcmdvMSwwKgYDVQQLEyNXZWxscyBGYXJnbyBD
+ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEvMC0GA1UEAxMmV2VsbHMgRmFyZ28gUm9v
+dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDAxMDExMTY0MTI4WhcNMjEwMTE0
+MTY0MTI4WjCBgjELMAkGA1UEBhMCVVMxFDASBgNVBAoTC1dlbGxzIEZhcmdvMSww
+KgYDVQQLEyNXZWxscyBGYXJnbyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEvMC0G
+A1UEAxMmV2VsbHMgRmFyZ28gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVqDM7Jvk0/82bfuUER84A4n13
+5zHCLielTWi5MbqNQ1mXx3Oqfz1cQJ4F5aHiidlMuD+b+Qy0yGIZLEWukR5zcUHE
+SxP9cMIlrCL1dQu3U+SlK93OvRw6esP3E48mVJwWa2uv+9iWsWCaSOAlIiR5NM4O
+JgALTqv9i86C1y8IcGjBqAr5dE8Hq6T54oN+J3N0Prj5OEL8pahbSCOz6+MlsoCu
+ltQKnMJ4msZoGK43YjdeUXWoWGPAUe5AeH6orxqg4bB4nVCMe+ez/I4jsNtlAHCE
+AQgAFG5Uhpq6zPk3EPbg3oQtnaSFN9OH4xXQwReQfhkhahKpdv0SAulPIV4XAgMB
+AAGjYTBfMA8GA1UdEwEB/wQFMAMBAf8wTAYDVR0gBEUwQzBBBgtghkgBhvt7hwcB
+CzAyMDAGCCsGAQUFBwIBFiRodHRwOi8vd3d3LndlbGxzZmFyZ28uY29tL2NlcnRw
+b2xpY3kwDQYJKoZIhvcNAQEFBQADggEBANIn3ZwKdyu7IvICtUpKkfnRLb7kuxpo
+7w6kAOnu5+/u9vnldKTC2FJYxHT7zmu1Oyl5GFrvm+0fazbuSCUlFLZWohDo7qd/
+0D+j0MNdJu4HzMPBJCGHHt8qElNvQRbn7a6U+oxy+hNH8Dx+rn0ROhPs7fpvcmR7
+nX1/Jv16+yWt6j4pf0zjAFcysLPp7VMX2YuyFA4w6OXVE8Zkr8QA1dhYJPz1j+zx
+x32l2w8n0cbyQIjmH/ZhqPRCyLk306m+LFZ4wnKbWV01QIroTmMatukgalHizqSQ
+33ZwmVxwQ023tqcZZE6St8WRPH9IFmV7Fv3L/PvZ1dZPIWU7Sn9Ho/s=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMx
+IDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxs
+cyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9v
+dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDcxMjEzMTcwNzU0WhcNMjIxMjE0
+MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdl
+bGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQD
+DC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+r
+WxxTkqxtnt3CxC5FlAM1iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjU
+Dk/41itMpBb570OYj7OeUt9tkTmPOL13i0Nj67eT/DBMHAGTthP796EfvyXhdDcs
+HqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8bJVhHlfXBIEyg1J55oNj
+z7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiBK0HmOFaf
+SZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/Slwxl
+AgMBAAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqG
+KGh0dHA6Ly9jcmwucGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0P
+AQH/BAQDAgHGMB0GA1UdDgQWBBQmlRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0j
+BIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGBi6SBiDCBhTELMAkGA1UEBhMC
+VVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNX
+ZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg
+Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEB
+ALkVsUSRzCPIK0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd
+/ZDJPHV3V3p9+N701NX3leZ0bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pB
+A4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSljqHyita04pO2t/caaH/+Xc/77szWn
+k4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+esE2fDbbFwRnzVlhE9
+iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJtylv
+2G0xffX8oRAHh84vWdw+WNs=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB
+gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk
+MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY
+UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx
+NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3
+dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy
+dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6
+38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP
+KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q
+DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4
+qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa
+JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi
+PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P
+BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs
+jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0
+eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD
+ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR
+vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt
+qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa
+IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy
+i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ
+O+7ETPTsJ3xCwnR8gooJybQDJbw=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIETzCCAzegAwIBAgIEO63vKTANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJQTDEfMB0GA1UE
+ChMWVFAgSW50ZXJuZXQgU3AuIHogby5vLjEkMCIGA1UECxMbQ2VudHJ1bSBDZXJ0eWZpa2Fjamkg
+U2lnbmV0MRswGQYDVQQDExJDQyBTaWduZXQgLSBSb290Q0EwHhcNMDEwOTIzMTQxODE3WhcNMTEw
+OTIzMTMxODE3WjB1MQswCQYDVQQGEwJQTDEfMB0GA1UEChMWVFAgSW50ZXJuZXQgU3AuIHogby5v
+LjEkMCIGA1UECxMbQ2VudHJ1bSBDZXJ0eWZpa2FjamkgU2lnbmV0MR8wHQYDVQQDExZDQyBTaWdu
+ZXQgLSBDQSBLbGFzYSAxMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4SRW9Q58g5DY1Hw7h
+gCRKBEdPdGn0MFHsfw7rlu/oQm7IChI/uWd9q5wwo77YojtTDjRnpgZsjqBeynX8T90vFILqsY2K
+5CF1OESalwvVr3sZiQX79lisuFKat92u6hBFikFIVxfHHB67Af+g7u0dEHdDW7lwy81MwFYxBTRy
+9wIDAQABo4IBbTCCAWkwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwggEEBgNVHSAE
+gfwwgfkwgfYGDSsGAQQBvj8CAQoBAQAwgeQwgZoGCCsGAQUFBwICMIGNGoGKQ2VydHlmaWthdCB3
+eXN0YXdpb255IHpnb2RuaWUgeiBkb2t1bWVudGVtOiAiUG9saXR5a2EgQ2VydHlmaWthY2ppIGRs
+YSBSb290Q0EiLiBDZXJ0eWZpa2F0IHd5c3Rhd2lvbnkgcHJ6ZXogUm9vdENBIHcgaGllcmFyY2hp
+aSBDQyBTaWduZXQuMEUGCCsGAQUFBwIBFjlodHRwOi8vd3d3LnNpZ25ldC5wbC9yZXBvenl0b3Jp
+dW0vZG9rdW1lbnR5L3BjX3Jvb3RjYS50eHQwHwYDVR0jBBgwFoAUwJvFIw0C4aZOSGsfAOnjmhQb
+sa8wHQYDVR0OBBYEFMODHtVZd1T7TftXR/nEI1zR54njMA0GCSqGSIb3DQEBBQUAA4IBAQBRIHQB
+FIGh8Jpxt87AgSLwIEEk4+oGy769u3NtoaR0R3WNMdmt7fXTi0tyTQ9V4AIszxVjhnUPaKnF1KYy
+f8Tl+YTzk9ZfFkZ3kCdSaILZAOIrmqWNLPmjUQ5/JiMGho0e1YmWUcMci84+pIisTsytFzVP32/W
++sz2H4FQAvOIMmxB7EJX9AdbnXn9EXZ+4nCqi0ft5z96ZqOJJiCB3vSaoYg+wdkcvb6souMJzuc2
+uptXtR1Xf3ihlHaGW+hmnpcwFA6AoNrom6Vgzk6U1ienx0Cw28BhRSKqzKkyXkuK8gRflZUx84uf
+tXncwKJrMiE3lvgOOBITRzcahirLer4c
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIE9zCCA9+gAwIBAgIEPL/xoTANBgkqhkiG9w0BAQUFADB2MQswCQYDVQQGEwJQTDEfMB0GA1UE
+ChMWVFAgSW50ZXJuZXQgU3AuIHogby5vLjEkMCIGA1UECxMbQ2VudHJ1bSBDZXJ0eWZpa2Fjamkg
+U2lnbmV0MSAwHgYDVQQDExdDQyBTaWduZXQgLSBQQ0EgS2xhc2EgMjAeFw0wMjA0MTkxMDI5NTNa
+Fw0xNzA0MTgxMjUzMDdaMHUxCzAJBgNVBAYTAlBMMR8wHQYDVQQKExZUUCBJbnRlcm5ldCBTcC4g
+eiBvLm8uMSQwIgYDVQQLExtDZW50cnVtIENlcnR5ZmlrYWNqaSBTaWduZXQxHzAdBgNVBAMTFkND
+IFNpZ25ldCAtIENBIEtsYXNhIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqgLJu
+QqY4yavbSgHg8CyfKTx4BokNSDOVz4eD9vptUr11Kqd06ED1hlH7Sg0goBFAfntNU/QTKwSBaNui
+me7C4sSEdgsKrPoAhGb4Mq8y7Ty7RqZz7mkzNMqzL2L2U4yQ2QjvpH8MH0IBqOWEcpSkpwnrCDIm
+RoTfd+YlZWKi2JceQixUUYIQ45Ox8+x8hHbvvZdgqtcvo8PW27qoHkp/7hMuJ44kDAGrmxffBXl/
+OBRZp0uO1CSLcMcVJzyr2phKhy406MYdWrtNPEluGs0GFDzd0nrIctiWAO4cmct4S72S9Q6e//0G
+O9f3/Ca5Kb2I1xYLj/xE+HgjHX9aD2MhAgMBAAGjggGMMIIBiDAPBgNVHRMBAf8EBTADAQH/MA4G
+A1UdDwEB/wQEAwIBBjCB4wYDVR0gBIHbMIHYMIHVBg0rBgEEAb4/AhQKAQEAMIHDMHUGCCsGAQUF
+BwICMGkaZ0NlcnR5ZmlrYXQgd3lzdGF3aW9ueSB6Z29kbmllIHogZG9rdW1lbnRlbTogIlBvbGl0
+eWthIENlcnR5ZmlrYWNqaSBQQ0EyIC0gQ2VydHlmaWthdHkgVXJ6ZWRvdyBLbGFzeSAyIi4wSgYI
+KwYBBQUHAgEWPmh0dHA6Ly93d3cuc2lnbmV0LnBsL3JlcG96eXRvcml1bS9kb2t1bWVudHkva2xh
+c2EyL3BjX3BjYTIudHh0MD8GA1UdHwQ4MDYwNKAyoDCGLmh0dHA6Ly93d3cuc2lnbmV0LnBsL3Jl
+cG96eXRvcml1bS9jcmwvcGNhMi5jcmwwHwYDVR0jBBgwFoAUwGxGyl2CfpYHRonE82AVXO08kMIw
+HQYDVR0OBBYEFLtFBlILy4HNKVSzvHxBTM0HDowlMA0GCSqGSIb3DQEBBQUAA4IBAQBWTsCbqXrX
+hBBev5v5cIuc6gJM8ww7oR0uMQRZoFSqvQUPWBYM2/TLI/f8UM9hSShUVj3zEsSj/vFHagUVmzuV
+Xo5u0WK8iaqATSyEVBhADHrPG6wYcLKJlagge/ILA0m+SieyP2sjYD9MUB9KZIEyBKv0429UuDTw
+6P7pslxMWJBSNyQxaLIs0SRKsqZZWkc7ZYAj2apSkBMX2Is1oHA+PwkF6jQMwCao/+CndXPUzfCF
+6caa9WwW31W26MlXCvSmJgfiTPwGvm4PkPmOnmWZ3CczzhHl4q7ztHFzshJH3sZWDnrWwBFjzz5e
+Pr3WHV1wA7EY6oT4zBx+2gT9XBTB
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEUzCCAzugAwIBAgIEPq+qjzANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQGEwJQTDE3MDUGA1UE
+ChMuQ1ppQyBDZW50cmFzdCBTQSB3IGltaWVuaXUgTWluaXN0cmEgR29zcG9kYXJraTEZMBcGA1UE
+AxMQQ1ppQyBDZW50cmFzdCBTQTAeFw0wMzA0MzAxMDUwNTVaFw0wODA0MjgxMDUwNTVaMGgxCzAJ
+BgNVBAYTAlBMMR8wHQYDVQQKExZUUCBJbnRlcm5ldCBTcC4geiBvLm8uMR8wHQYDVQQDExZDQyBT
+aWduZXQgLSBDQSBLbGFzYSAzMRcwFQYDVQQFEw5OdW1lciB3cGlzdTogNDCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBALVdeOM62cPH2NERFxbS5FIp/HSv3fgesdVsTUFxZbGtE+/E0RMl
+KZQJHH9emx7vRYubsi4EOLCjYsCOTFvgGRIpZzx7R7T5c0Di5XFkRU4gjBl7aHJoKb5SLzGlWdoX
+GsekVtl6keEACrizV2EafqjI8cnBWY7OxQ1ooLQp5AeFjXg+5PT0lO6TUZAubqjFbhVbxSWjqvdj
+93RGfyYE76MnNn4c2xWySD07n7uno06TC0IJe6+3WSX1h+76VsIFouWBXOoM7cxxiLjoqdBVu24+
+P8e81SukE7qEvOwDPmk9ZJFtt1nBNg8a1kaixcljrA/43XwOPz6qnJ+cIj/xywECAwEAAaOCAQow
+ggEGMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMDMGA1UdIAEB/wQpMCcwJQYEVR0g
+ADAdMBsGCCsGAQUFBwIBFg93d3cuY2VudHJhc3QucGwwgY4GA1UdIwSBhjCBg4AU2a7r85Cp1iJN
+W0Ca1LR6VG3996ShZaRjMGExCzAJBgNVBAYTAlBMMTcwNQYDVQQKEy5DWmlDIENlbnRyYXN0IFNB
+IHcgaW1pZW5pdSBNaW5pc3RyYSBHb3Nwb2RhcmtpMRkwFwYDVQQDExBDWmlDIENlbnRyYXN0IFNB
+ggQ9/0sQMB0GA1UdDgQWBBR7Y8wZkHq0zrY7nn1tFSdQ0PlJuTANBgkqhkiG9w0BAQUFAAOCAQEA
+ldt/svO5c1MU08FKgrOXCGEbEPbQxhpM0xcd6Iv3dCo6qugEgjEs9Qm5CwUNKMnFsvR27cJWUvZb
+MVcvwlwCwclOdwF6u/QRS8bC2HYErhYo9bp9yuxxzuow2A94c5fPqfVrjXy+vDouchAm6+A5Wjzv
+J8wxVFDCs+9iGACmyUWr/JGXCYiQIbQkwlkRKHHlan9ymKf1NvIej/3EpeT8fKr6ywxGuhAfqofW
+pg3WJY/RCB4lTzD8vZGNwfMFGkWhJkypad3i9w3lGmDVpsHaWtCgGfd0H7tUtWPkP+t7EjIRCD9J
+HYnTR+wbbewc5vOI+UobR15ynGfFIaSIiMTVtQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEejCCA2KgAwIBAgIEP4vk6TANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJQ
+TDEfMB0GA1UEChMWVFAgSW50ZXJuZXQgU3AuIHogby5vLjEkMCIGA1UECxMbQ2Vu
+dHJ1bSBDZXJ0eWZpa2FjamkgU2lnbmV0MR8wHQYDVQQDExZDQyBTaWduZXQgLSBD
+QSBLbGFzYSAyMB4XDTAzMTAxNDExNTgyMloXDTE3MDQxODEyNTMwN1owdzELMAkG
+A1UEBhMCUEwxHzAdBgNVBAoTFlRQIEludGVybmV0IFNwLiB6IG8uby4xJDAiBgNV
+BAsTG0NlbnRydW0gQ2VydHlmaWthY2ppIFNpZ25ldDEhMB8GA1UEAxMYQ0MgU2ln
+bmV0IC0gT0NTUCBLbGFzYSAyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCo
+VCsaBStblXQYVNthe3dvaCrfvKpPXngh4almm988iIlEv9CVTaAdCfaJNihvA+Vs
+Qw8++ix1VqteMQE474/MV/YaXigP0Zr0QB+g+/7PWVlv+5U9Gzp9+Xx4DJay8AoI
+iB7Iy5Qf9iZiHm5BiPRIuUXT4ZRbZRYPh0/76vgRsQIDAQABo4IBkjCCAY4wDgYD
+VR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMJMEEGA1UdHwQ6MDgwNqA0
+oDKGMGh0dHA6Ly93d3cuc2lnbmV0LnBsL3JlcG96eXRvcml1bS9jcmwva2xhc2Ey
+LmNybDCB2AYDVR0gBIHQMIHNMIHKBg4rBgEEAb4/AoFICgwBADCBtzBsBggrBgEF
+BQcCAjBgGl5DZXJ0eWZpa2F0IHd5ZGFueSB6Z29kbmllIHogZG9rdW1lbnRlbSAi
+UG9saXR5a2EgQ2VydHlmaWthY2ppIC0gQ2VydHlmaWthdHkgcmVzcG9uZGVyb3cg
+T0NTUCIuMEcGCCsGAQUFBwIBFjtodHRwOi8vd3d3LnNpZ25ldC5wbC9yZXBvenl0
+b3JpdW0vZG9rdW1lbnR5L3BjX29jc3BfMV8wLnBkZjAfBgNVHSMEGDAWgBS7RQZS
+C8uBzSlUs7x8QUzNBw6MJTAdBgNVHQ4EFgQUKEVrOY7cEHvsVgvoyZdytlbtgwEw
+CQYDVR0TBAIwADANBgkqhkiG9w0BAQUFAAOCAQEAQrRg5MV6dxr0HU2IsLInxhvt
+iUVmSFkIUsBCjzLoewOXA16d2oDyHhI/eE+VgAsp+2ANjZu4xRteHIHoYMsN218M
+eD2MLRsYS0U9xxAFK9gDj/KscPbrrdoqLvtPSMhUb4adJS9HLhvUe6BicvBf3A71
+iCNe431axGNDWKnpuj2KUpj4CFHYsWCXky847YtTXDjri9NIwJJauazsrSjK+oXp
+ngRS506mdQ7vWrtApkh8zhhWp7duCkjcCo1O8JxqYr2qEW1fXmgOISe010v2mmuv
+hHxPyVwoAU4KkOw0nbXZn53yak0is5+XmAjh0wWue44AssHrjC9nUh3mkLt6eQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEezCCA2OgAwIBAgIEP4vnLzANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJQ
+TDEfMB0GA1UEChMWVFAgSW50ZXJuZXQgU3AuIHogby5vLjEfMB0GA1UEAxMWQ0Mg
+U2lnbmV0IC0gQ0EgS2xhc2EgMzEXMBUGA1UEBRMOTnVtZXIgd3Bpc3U6IDQwHhcN
+MDMxMDE0MTIwODAwWhcNMDgwNDI4MTA1MDU1WjB3MQswCQYDVQQGEwJQTDEfMB0G
+A1UEChMWVFAgSW50ZXJuZXQgU3AuIHogby5vLjEkMCIGA1UECxMbQ2VudHJ1bSBD
+ZXJ0eWZpa2FjamkgU2lnbmV0MSEwHwYDVQQDExhDQyBTaWduZXQgLSBPQ1NQIEts
+YXNhIDMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM/9GwvARNuCVN+PqZmO
+4FqH8vTqhenUyqRkmAVT4YhLu0a9AXeLAYVDu+NTkYzsAUMAfu55rIKHNLlm6WbF
+KvLiKKz4p4pbUr+ToPcwl/TDotidloUdBAxDg0SL+PmQqACZDe3seJho2IYf2vDL
+/G4TLMbKmNB0mlWFuN0f4fJNAgMBAAGjggGgMIIBnDAOBgNVHQ8BAf8EBAMCB4Aw
+EwYDVR0lBAwwCgYIKwYBBQUHAwkwTwYDVR0fBEgwRjBEoEKgQIY+aHR0cDovL3d3
+dy5zaWduZXQucGwva3dhbGlmaWtvd2FuZS9yZXBvenl0b3JpdW0vY3JsL2tsYXNh
+My5jcmwwgdgGA1UdIASB0DCBzTCBygYOKwYBBAG+PwKCLAoCAQAwgbcwbAYIKwYB
+BQUHAgIwYBpeQ2VydHlmaWthdCB3eWRhbnkgemdvZG5pZSB6IGRva3VtZW50ZW0g
+IlBvbGl0eWthIENlcnR5ZmlrYWNqaSAtIENlcnR5ZmlrYXR5IHJlc3BvbmRlcm93
+IE9DU1AiLjBHBggrBgEFBQcCARY7aHR0cDovL3d3dy5zaWduZXQucGwvcmVwb3p5
+dG9yaXVtL2Rva3VtZW50eS9wY19vY3NwXzFfMC5wZGYwHwYDVR0jBBgwFoAUe2PM
+GZB6tM62O559bRUnUND5SbkwHQYDVR0OBBYEFG4jnCMvBALRQXtmDn9TyXQ/EKP+
+MAkGA1UdEwQCMAAwDQYJKoZIhvcNAQEFBQADggEBACXrKG5Def5lpRwmZom3UEDq
+bl7y4U3qomG4B+ok2FVZGgPZti+ZgvrenPj7PtbYCUBPsCSTNrznKinoT3gD9lQQ
+xkEHwdc6VD1GlFp+qI64u0+wS9Epatrdf7aBnizrOIB4LJd4E2TWQ6trspetjMIU
+upyWls1BmYUxB91R7QkTiAUSNZ87s3auhZuG4f0V0JLVCcg2rn7AN1rfMkgxCbHk
+GxiQbYWFljl6aatxR3odnnzVUe1I8uoY2JXpmmUcOG4dNGuQYziyKG3mtXCQWvug
+5qi9Mf3KUh1oSTKx6HfLjjNl1+wMB5Mdb8LF0XyZLdJM9yIZh7SBRsYm9QiXevY=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFGjCCBAKgAwIBAgIEPL7eEDANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJQTDEfMB0GA1UE
+ChMWVFAgSW50ZXJuZXQgU3AuIHogby5vLjEkMCIGA1UECxMbQ2VudHJ1bSBDZXJ0eWZpa2Fjamkg
+U2lnbmV0MRswGQYDVQQDExJDQyBTaWduZXQgLSBSb290Q0EwHhcNMDIwNDE4MTQ1NDA4WhcNMjYw
+OTIxMTU0MjE5WjB2MQswCQYDVQQGEwJQTDEfMB0GA1UEChMWVFAgSW50ZXJuZXQgU3AuIHogby5v
+LjEkMCIGA1UECxMbQ2VudHJ1bSBDZXJ0eWZpa2FjamkgU2lnbmV0MSAwHgYDVQQDExdDQyBTaWdu
+ZXQgLSBQQ0EgS2xhc2EgMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM7BrBlbN5ma
+M5eg0BOTqoZ+9NBDvU8Lm5rTdrMswFTCathzpVVLK/JD4K3+4oCZ9SRAspEXE4gvwb08ASY6w5s+
+HpRkeJw8YzMFR5kDZD5adgnCAy4vDfIXYZgppXPaTQ8wnfUZ7BZ7Zfa7QBemUIcJIzJBB0UqgtxW
+Ceol9IekpBRVmuuSA6QG0Jkm+pGDJ05yj2eQG8jTcBENM7sVA8rGRMyFA4skSZ+D0OG6FS2xC1i9
+JyN0ag1yII/LPx8HK5J4W9MaPRNjAEeaa2qI9EpchwrOxnyVbQfSedCG1VRJfAsE/9tT9CMUPZ3x
+W20QjQcSZJqVcmGW9gVsXKQOVLsCAwEAAaOCAbMwggGvMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
+AQH/BAQDAgEGMIIBBAYDVR0gBIH8MIH5MIH2Bg0rBgEEAb4/AgEKAQEBMIHkMIGaBggrBgEFBQcC
+AjCBjRqBikNlcnR5ZmlrYXQgd3lzdGF3aW9ueSB6Z29kbmllIHogZG9rdW1lbnRlbTogIlBvbGl0
+eWthIENlcnR5ZmlrYWNqaSBkbGEgUm9vdENBIi4gQ2VydHlmaWthdCB3eXN0YXdpb255IHByemV6
+IFJvb3RDQSB3IGhpZXJhcmNoaWkgQ0MgU2lnbmV0LjBFBggrBgEFBQcCARY5aHR0cDovL3d3dy5z
+aWduZXQucGwvcmVwb3p5dG9yaXVtL2Rva3VtZW50eS9wY19yb290Y2EudHh0MEQGA1UdHwQ9MDsw
+OaA3oDWGM2h0dHA6Ly93d3cuc2lnbmV0LnBsL3JlcG96eXRvcml1bS9yb290Y2Evcm9vdGNhLmNy
+bDAfBgNVHSMEGDAWgBTAm8UjDQLhpk5Iax8A6eOaFBuxrzAdBgNVHQ4EFgQUwGxGyl2CfpYHRonE
+82AVXO08kMIwDQYJKoZIhvcNAQEFBQADggEBABp1TAUsa+BeVWg4cjowc8yTJ5XN3GvN96GObMkx
+UGY7U9kVrLI71xBgoNVyzXTiMNDBvjh7vdPWjpl5SDiRpnnKiOFXA43HvNWzUaOkTu1mxjJsZsan
+ot1Xt6j0ZDC+03FjLHdYMyM9kSWp6afb4980EPYZCcSzgM5TOGfJmNii5Tq468VFKrX+52Aou1G2
+2Ohu+EEOlOrG7ylKv1hHUJJCjwN0ZVEIn1nDbrU9FeGCz8J9ihVUvnENEBbBkU37PWqWuHitKQDV
+tcwTwJJdR8cmKq3NmkwAm9fPacidQLpaw0WkuGrS+fEDhu1Nhy9xELP6NA9GRTCNxm/dXlcwnmY=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFGjCCBAKgAwIBAgIEPV0tNDANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJQTDEfMB0GA1UE
+ChMWVFAgSW50ZXJuZXQgU3AuIHogby5vLjEkMCIGA1UECxMbQ2VudHJ1bSBDZXJ0eWZpa2Fjamkg
+U2lnbmV0MRswGQYDVQQDExJDQyBTaWduZXQgLSBSb290Q0EwHhcNMDIwODE2MTY0OTU2WhcNMjYw
+OTIxMTU0MjE5WjB2MQswCQYDVQQGEwJQTDEfMB0GA1UEChMWVFAgSW50ZXJuZXQgU3AuIHogby5v
+LjEkMCIGA1UECxMbQ2VudHJ1bSBDZXJ0eWZpa2FjamkgU2lnbmV0MSAwHgYDVQQDExdDQyBTaWdu
+ZXQgLSBQQ0EgS2xhc2EgMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALN3LanJtdue
+Ne6geWUTFENa+lEuzqELcoqhYB+a/tJcPEkc6TX/bYPzalRRjqs+quMP6KZTU0DixOrV+K7iWaqA
+iQ913HX5IBLmKDCrTVW/ZvSDpiBKbxlHfSNuJxAuVT6HdbzK7yAW38ssX+yS2tZYHZ5FhZcfqzPE
+OpO94mAKcBUhk6T/ki0evXX/ZvvktwmF3hKattzwtM4JMLurAEl8SInyEYULw5JdlfcBez2Tg6Db
+w34hA1A+ckTwhxzecrB8TUe2BnQKOs9vr2cCACpFFcOmPkM0Drtjctr1QHm1tYSqRFRf9VcV5tfC
+3P8QqoK4ONjtLPHc9x5NE1uK/FMCAwEAAaOCAbMwggGvMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
+AQH/BAQDAgEGMIIBBAYDVR0gBIH8MIH5MIH2Bg0rBgEEAb4/AgEKAQECMIHkMIGaBggrBgEFBQcC
+AjCBjRqBikNlcnR5ZmlrYXQgd3lzdGF3aW9ueSB6Z29kbmllIHogZG9rdW1lbnRlbTogIlBvbGl0
+eWthIENlcnR5ZmlrYWNqaSBkbGEgUm9vdENBIi4gQ2VydHlmaWthdCB3eXN0YXdpb255IHByemV6
+IFJvb3RDQSB3IGhpZXJhcmNoaWkgQ0MgU2lnbmV0LjBFBggrBgEFBQcCARY5aHR0cDovL3d3dy5z
+aWduZXQucGwvcmVwb3p5dG9yaXVtL2Rva3VtZW50eS9wY19yb290Y2EudHh0MEQGA1UdHwQ9MDsw
+OaA3oDWGM2h0dHA6Ly93d3cuc2lnbmV0LnBsL3JlcG96eXRvcml1bS9yb290Y2Evcm9vdGNhLmNy
+bDAfBgNVHSMEGDAWgBTAm8UjDQLhpk5Iax8A6eOaFBuxrzAdBgNVHQ4EFgQUXvthcPHlH5BgGhlM
+ErJNXWlhlgAwDQYJKoZIhvcNAQEFBQADggEBACIce95Mvn710KCAISA0CuHD4aznTU6pLoCDShW4
+7OR+GTpJUm1coTcUqlBHV9mra4VFrBcBuOkHZoBLq/jmE0QJWnpSEULDcH9J3mF0nqO9SM+mWyJG
+dsJF/XU/7smummgjMNQXwzQTtWORF+6v5KUbWX85anO2wR+M6YTBWC55zWpWi4RG3vkHFs5Ze2oF
+JTlpuxw9ZgxTnWlwI9QR2MvEhYIUMKMOWxw1nt0kKj+5TCNQQGh/VJJ1dsiroGh/io1DOcePEhKz
+1Ag52y6Wf0nJJB9yk0sFakqZH18F7eQecQImgZyyeRtsG95leNugB3BXWCW+KxwiBrtQTXv4dTE=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEzzCCA7egAwIBAgIEO6ocGTANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJQTDEfMB0GA1UE
+ChMWVFAgSW50ZXJuZXQgU3AuIHogby5vLjEkMCIGA1UECxMbQ2VudHJ1bSBDZXJ0eWZpa2Fjamkg
+U2lnbmV0MRswGQYDVQQDExJDQyBTaWduZXQgLSBSb290Q0EwHhcNMDEwOTIwMTY0MjE5WhcNMjYw
+OTIxMTU0MjE5WjBxMQswCQYDVQQGEwJQTDEfMB0GA1UEChMWVFAgSW50ZXJuZXQgU3AuIHogby5v
+LjEkMCIGA1UECxMbQ2VudHJ1bSBDZXJ0eWZpa2FjamkgU2lnbmV0MRswGQYDVQQDExJDQyBTaWdu
+ZXQgLSBSb290Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrr2vydnNpELfGW3Ks
+ARiDhJvwDtUe4AbWev+OfMc3+vA29nX8ZmIwno3gmItjo5DbUCCRiCMq5c9epcGu+kg4a3BJChVX
+REl8gVh0ST15rr3RKrSc4VgsvQzl0ZUraeQLl8JoRT5PLsUj3qwF78jUCQVckiiLVcnGfZtFCm+D
+CJXliQBDMB9XFAUEiO/DtEBs0B7wJGx7lgJeJpQUcGiaOPjcJDYOk7rNAYmmD2gWeSlepufO8luU
+YG/YDxTC4mqhRqfa4MnVO5dqy+ICj2UvUpHbZDB0KfGRibgBYeQP1kuqgIzJN4UqknVAJb0aMBSP
+l+9k2fAUdchx1njlbdcbAgMBAAGjggFtMIIBaTAPBgNVHRMBAf8EBTADAQH/MIIBBAYDVR0gBIH8
+MIH5MIH2Bg0rBgEEAb4/AgEKAQEAMIHkMIGaBggrBgEFBQcCAjCBjRqBikNlcnR5ZmlrYXQgd3lz
+dGF3aW9ueSB6Z29kbmllIHogZG9rdW1lbnRlbTogIlBvbGl0eWthIENlcnR5ZmlrYWNqaSBkbGEg
+Um9vdENBIi4gQ2VydHlmaWthdCB3eXN0YXdpb255IHByemV6IFJvb3RDQSB3IGhpZXJhcmNoaWkg
+Q0MgU2lnbmV0LjBFBggrBgEFBQcCARY5aHR0cDovL3d3dy5zaWduZXQucGwvcmVwb3p5dG9yaXVt
+L2Rva3VtZW50eS9wY19yb290Y2EudHh0MB0GA1UdDgQWBBTAm8UjDQLhpk5Iax8A6eOaFBuxrzAf
+BgNVHSMEGDAWgBTAm8UjDQLhpk5Iax8A6eOaFBuxrzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcN
+AQEFBQADggEBAGnY5QmYqnnO9OqFOWZxxb25UHRnaRF6IV9aaGit5BZufZj2Tq3v8L3SgE34GOoI
+cdRMMG5JEpEU4mN/Ef3oY6Eo+7HfqaPHI4KFmbDSPiK5s+wmf+bQSm0Yq5/h4ZOdcAESlLQeLSt1
+CQk2JoKQJ6pyAf6xJBgWEIlm4RXE4J3324PUiOp83kW6MDvaa1xY976WyInr4rwoLgxVl11LZeKW
+ha0RJJxJgw/NyWpKG7LWCm1fglF8JH51vZNndGYq1iKtfnrIOvLZq6bzaCiZm1EurD8HE6P7pmAB
+KK6o3C2OXlNfNIgwkDN/cDqk5TYsTkrpfriJPdxXBH8hQOkW89g=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID/TCCA2agAwIBAgIEP4/gkTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJQTDEfMB0GA1UE
+ChMWVFAgSW50ZXJuZXQgU3AuIHogby5vLjEkMCIGA1UECxMbQ2VudHJ1bSBDZXJ0eWZpa2Fjamkg
+U2lnbmV0MR8wHQYDVQQDExZDQyBTaWduZXQgLSBDQSBLbGFzYSAxMB4XDTAzMTAxNzEyMjkwMloX
+DTExMDkyMzExMTgxN1owdjELMAkGA1UEBhMCUEwxHzAdBgNVBAoTFlRQIEludGVybmV0IFNwLiB6
+IG8uby4xJDAiBgNVBAsTG0NlbnRydW0gQ2VydHlmaWthY2ppIFNpZ25ldDEgMB4GA1UEAxMXQ0Mg
+U2lnbmV0IC0gVFNBIEtsYXNhIDEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOJYrISEtSsd
+uHajROh5/n7NGrkpYTT9NEaPe9+ucuQ37KxIbfJwXJjgUc1dw4wCkcQ12FJarD1X6mSQ4cfN/60v
+LfKI5ZD4nhJTMKlAj1pX9ScQ/MuyvKStCbn5WTkjPhjRAM0tdwXSnzuTEunfw0Oup559y3Iqxg1c
+ExflB6cfAgMBAAGjggGXMIIBkzBBBgNVHR8EOjA4MDagNKAyhjBodHRwOi8vd3d3LnNpZ25ldC5w
+bC9yZXBvenl0b3JpdW0vY3JsL2tsYXNhMS5jcmwwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQM
+MAoGCCsGAQUFBwMIMIHaBgNVHSAEgdIwgc8wgcwGDSsGAQQBvj8CZAoRAgEwgbowbwYIKwYBBQUH
+AgIwYxphQ2VydHlmaWthdCB3eXN0YXdpb255IHpnb2RuaWUgeiBkb2t1bWVudGVtICJQb2xpdHlr
+YSBDZXJ0eWZpa2FjamkgQ0MgU2lnbmV0IC0gWm5ha293YW5pZSBjemFzZW0iLjBHBggrBgEFBQcC
+ARY7aHR0cDovL3d3dy5zaWduZXQucGwvcmVwb3p5dG9yaXVtL2Rva3VtZW50eS9wY190c2ExXzJf
+MS5wZGYwHwYDVR0jBBgwFoAUw4Me1Vl3VPtN+1dH+cQjXNHnieMwHQYDVR0OBBYEFJdDwEqtcavO
+Yd9u9tej53vWXwNBMAkGA1UdEwQCMAAwDQYJKoZIhvcNAQEFBQADgYEAnpiQkqLCJQYXUrqMHUEz
++z3rOqS0XzSFnVVLhkVssvXc8S3FkJIiQTUrkScjI4CToCzujj3EyfNxH6yiLlMbskF8I31JxIeB
+vueqV+s+o76CZm3ycu9hb0I4lswuxoT+q5ZzPR8Irrb51rZXlolR+7KtwMg4sFDJZ8RNgOf7tbA=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEFTCCA36gAwIBAgIBADANBgkqhkiG9w0BAQQFADCBvjELMAkGA1UEBhMCVVMx
+EDAOBgNVBAgTB0luZGlhbmExFTATBgNVBAcTDEluZGlhbmFwb2xpczEoMCYGA1UE
+ChMfU29mdHdhcmUgaW4gdGhlIFB1YmxpYyBJbnRlcmVzdDETMBEGA1UECxMKaG9z
+dG1hc3RlcjEgMB4GA1UEAxMXQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxJTAjBgkq
+hkiG9w0BCQEWFmhvc3RtYXN0ZXJAc3BpLWluYy5vcmcwHhcNMDMwMTE1MTYyOTE3
+WhcNMDcwMTE0MTYyOTE3WjCBvjELMAkGA1UEBhMCVVMxEDAOBgNVBAgTB0luZGlh
+bmExFTATBgNVBAcTDEluZGlhbmFwb2xpczEoMCYGA1UEChMfU29mdHdhcmUgaW4g
+dGhlIFB1YmxpYyBJbnRlcmVzdDETMBEGA1UECxMKaG9zdG1hc3RlcjEgMB4GA1UE
+AxMXQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxJTAjBgkqhkiG9w0BCQEWFmhvc3Rt
+YXN0ZXJAc3BpLWluYy5vcmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPB6
+rdoiLR3RodtM22LMcfwfqb5OrJNl7fwmvskgF7yP6sdD2bOfDIXhg9852jhY8/kL
+VOFe1ELAL2OyN4RAxk0rliZQVgeTgqvgkOVIBbNwgnjN6mqtuWzFiPL+NXQExq40
+I3whM+4lEiwSHaV+MYxWanMdhc+kImT50LKfkxcdAgMBAAGjggEfMIIBGzAdBgNV
+HQ4EFgQUB63oQR1/vda/G4F6P4xLiN4E0vowgesGA1UdIwSB4zCB4IAUB63oQR1/
+vda/G4F6P4xLiN4E0vqhgcSkgcEwgb4xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdJ
+bmRpYW5hMRUwEwYDVQQHEwxJbmRpYW5hcG9saXMxKDAmBgNVBAoTH1NvZnR3YXJl
+IGluIHRoZSBQdWJsaWMgSW50ZXJlc3QxEzARBgNVBAsTCmhvc3RtYXN0ZXIxIDAe
+BgNVBAMTF0NlcnRpZmljYXRpb24gQXV0aG9yaXR5MSUwIwYJKoZIhvcNAQkBFhZo
+b3N0bWFzdGVyQHNwaS1pbmMub3JnggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcN
+AQEEBQADgYEAm/Abn8c2y1nO3fgpAIslxvi9iNBZDhQtJ0VQZY6wgSfANyDOR4DW
+iexO/AlorB49KnkFS7TjCAoLOZhcg5FaNiKnlstMI5krQmau1Qnb/vGSNsE/UGms
+1ts+QYPUs0KmGEAFUri2XzLy+aQo9Kw74VBvqnxvaaMeY5yMcKNOieY=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIIDjCCBfagAwIBAgIJAOiOtsn4KhQoMA0GCSqGSIb3DQEBBQUAMIG8MQswCQYD
+VQQGEwJVUzEQMA4GA1UECBMHSW5kaWFuYTEVMBMGA1UEBxMMSW5kaWFuYXBvbGlz
+MSgwJgYDVQQKEx9Tb2Z0d2FyZSBpbiB0aGUgUHVibGljIEludGVyZXN0MRMwEQYD
+VQQLEwpob3N0bWFzdGVyMR4wHAYDVQQDExVDZXJ0aWZpY2F0ZSBBdXRob3JpdHkx
+JTAjBgkqhkiG9w0BCQEWFmhvc3RtYXN0ZXJAc3BpLWluYy5vcmcwHhcNMDgwNTEz
+MDgwNzU2WhcNMTgwNTExMDgwNzU2WjCBvDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
+B0luZGlhbmExFTATBgNVBAcTDEluZGlhbmFwb2xpczEoMCYGA1UEChMfU29mdHdh
+cmUgaW4gdGhlIFB1YmxpYyBJbnRlcmVzdDETMBEGA1UECxMKaG9zdG1hc3RlcjEe
+MBwGA1UEAxMVQ2VydGlmaWNhdGUgQXV0aG9yaXR5MSUwIwYJKoZIhvcNAQkBFhZo
+b3N0bWFzdGVyQHNwaS1pbmMub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
+CgKCAgEA3DbmR0LCxFF1KYdAw9iOIQbSGE7r7yC9kDyFEBOMKVuUY/b0LfEGQpG5
+GcRCaQi/izZF6igFM0lIoCdDkzWKQdh4s/Dvs24t3dHLfer0dSbTPpA67tfnLAS1
+fOH1fMVO73e9XKKTM5LOfYFIz2u1IiwIg/3T1c87Lf21SZBb9q1NE8re06adU1Fx
+Y0b4ShZcmO4tbZoWoXaQ4mBDmdaJ1mwuepiyCwMs43pPx93jzONKao15Uvr0wa8u
+jyoIyxspgpJyQ7zOiKmqp4pRQ1WFmjcDeJPI8L20QcgHQprLNZd6ioFl3h1UCAHx
+ZFy3FxpRvB7DWYd2GBaY7r/2Z4GLBjXFS21ZGcfSxki+bhQog0oQnBv1b7ypjvVp
+/rLBVcznFMn5WxRTUQfqzj3kTygfPGEJ1zPSbqdu1McTCW9rXRTunYkbpWry9vjQ
+co7qch8vNGopCsUK7BxAhRL3pqXTT63AhYxMfHMgzFMY8bJYTAH1v+pk1Vw5xc5s
+zFNaVrpBDyXfa1C2x4qgvQLCxTtVpbJkIoRRKFauMe5e+wsWTUYFkYBE7axt8Feo
++uthSKDLG7Mfjs3FIXcDhB78rKNDCGOM7fkn77SwXWfWT+3Qiz5dW8mRvZYChD3F
+TbxCP3T9PF2sXEg2XocxLxhsxGjuoYvJWdAY4wCAs1QnLpnwFVMCAwEAAaOCAg8w
+ggILMB0GA1UdDgQWBBQ0cdE41xU2g0dr1zdkQjuOjVKdqzCB8QYDVR0jBIHpMIHm
+gBQ0cdE41xU2g0dr1zdkQjuOjVKdq6GBwqSBvzCBvDELMAkGA1UEBhMCVVMxEDAO
+BgNVBAgTB0luZGlhbmExFTATBgNVBAcTDEluZGlhbmFwb2xpczEoMCYGA1UEChMf
+U29mdHdhcmUgaW4gdGhlIFB1YmxpYyBJbnRlcmVzdDETMBEGA1UECxMKaG9zdG1h
+c3RlcjEeMBwGA1UEAxMVQ2VydGlmaWNhdGUgQXV0aG9yaXR5MSUwIwYJKoZIhvcN
+AQkBFhZob3N0bWFzdGVyQHNwaS1pbmMub3JnggkA6I62yfgqFCgwDwYDVR0TAQH/
+BAUwAwEB/zARBglghkgBhvhCAQEEBAMCAAcwCQYDVR0SBAIwADAuBglghkgBhvhC
+AQ0EIRYfU29mdHdhcmUgaW4gdGhlIFB1YmxpYyBJbnRlcmVzdDAwBglghkgBhvhC
+AQQEIxYhaHR0cHM6Ly9jYS5zcGktaW5jLm9yZy9jYS1jcmwucGVtMDIGCWCGSAGG
++EIBAwQlFiNodHRwczovL2NhLnNwaS1pbmMub3JnL2NlcnQtY3JsLnBlbTAhBgNV
+HREEGjAYgRZob3N0bWFzdGVyQHNwaS1pbmMub3JnMA4GA1UdDwEB/wQEAwIBBjAN
+BgkqhkiG9w0BAQUFAAOCAgEAtM294LnqsgMrfjLp3nI/yUuCXp3ir1UJogxU6M8Y
+PCggHam7AwIvUjki+RfPrWeQswN/2BXja367m1YBrzXU2rnHZxeb1NUON7MgQS4M
+AcRb+WU+wmHo0vBqlXDDxm/VNaSsWXLhid+hoJ0kvSl56WEq2dMeyUakCHhBknIP
+qxR17QnwovBc78MKYiC3wihmrkwvLo9FYyaW8O4x5otVm6o6+YI5HYg84gd1GuEP
+sTC8cTLSOv76oYnzQyzWcsR5pxVIBcDYLXIC48s9Fmq6ybgREOJJhcyWR2AFJS7v
+dVkz9UcZFu/abF8HyKZQth3LZjQl/GaD68W2MEH4RkRiqMEMVObqTFoo5q7Gt/5/
+O5aoLu7HaD7dAD0prypjq1/uSSotxdz70cbT0ZdWUoa2lOvUYFG3/B6bzAKb1B+P
++UqPti4oOxfMxaYF49LTtcYDyeFIQpvLP+QX4P4NAZUJurgNceQJcHdC2E3hQqlg
+g9cXiUPS1N2nGLar1CQlh7XU4vwuImm9rWgs/3K1mKoGnOcqarihk3bOsPN/nOHg
+T7jYhkalMwIsJWE3KpLIrIF0aGOHM3a9BX9e1dUCbb2v/ypaqknsmHlHU5H2DjRa
+yaXG67Ljxay2oHA1u8hRadDytaIybrw/oDc5fHE2pgXfDBLkFqfF1stjo5VwP+YE
+o2A=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UE
+AwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00x
+CzAJBgNVBAYTAkVTMB4XDTA4MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEW
+MBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZF
+RElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
+AgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHkWLn7
+09gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7
+XBZXehuDYAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5P
+Grjm6gSSrj0RuVFCPYewMYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAK
+t0SdE3QrwqXrIhWYENiLxQSfHY9g5QYbm8+5eaA9oiM/Qj9r+hwDezCNzmzAv+Yb
+X79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbkHQl/Sog4P75n/TSW9R28
+MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTTxKJxqvQU
+fecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI
+2Sf23EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyH
+K9caUPgn6C9D4zq92Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEae
+ZAwUswdbxcJzbPEHXEUkFDWug/FqTYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAP
+BgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz4SsrSbbXc6GqlPUB53NlTKxQ
+MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU9QHnc2VMrFAw
+RAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv
+bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWIm
+fQwng4/F9tqgaHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3
+gvoFNTPhNahXwOf9jU8/kzJPeGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKe
+I6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1PwkzQSulgUV1qzOMPPKC8W64iLgpq0i
+5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1ThCojz2GuHURwCRi
+ipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oIKiMn
+MCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZ
+o5NjEFIqnxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6
+zqylfDJKZ0DcMDQj3dcEI2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacN
+GHk0vFQYXlPKNFHtRQrmjseCNj6nOGOpMCwXEGCSn1WHElkQwg9naRHMTh5+Spqt
+r0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3otkYNbn5XOmeUwssfnHdK
+Z05phkOTOPu220+DkdRgfks+KzgHVZhepA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsx
+CzAJBgNVBAYTAkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRp
+ZmljYWNpw7NuIERpZ2l0YWwgLSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwa
+QUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4wHhcNMDYxMTI3MjA0NjI5WhcNMzAw
+NDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+U29jaWVkYWQgQ2Ft
+ZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJhIFMu
+QS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeG
+qentLhM0R7LQcNzJPNCNyu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzL
+fDe3fezTf3MZsGqy2IiKLUV0qPezuMDU2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQ
+Y5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU34ojC2I+GdV75LaeHM/J4
+Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP2yYe68yQ
+54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+b
+MMCm8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48j
+ilSH5L887uvDdUhfHjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++Ej
+YfDIJss2yKHzMI+ko6Kh3VOz3vCaMh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/zt
+A/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK5lw1omdMEWux+IBkAC1vImHF
+rEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1bczwmPS9KvqfJ
+pxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE
+AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCB
+lTCBkgYEVR0gADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFy
+YS5jb20vZHBjLzBaBggrBgEFBQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW50
+7WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2UgcHVlZGVuIGVuY29udHJhciBlbiBs
+YSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEfAygPU3zmpFmps4p6
+xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuXEpBc
+unvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/
+Jre7Ir5v/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dp
+ezy4ydV/NgIlqmjCMRW3MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42
+gzmRkBDI8ck1fj+404HGIGQatlDCIaR43NAvO2STdPCWkPHv+wlaNECW8DYSwaN0
+jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wkeZBWN7PGKX6jD/EpOe9+
+XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f/RWmnkJD
+W2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/
+RL5hRqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35r
+MDOhYil/SrnhLecUIw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxk
+BYn8eNZcLCZDqQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEc
+MBoGA1UEChMTSmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRp
+b25DQTAeFw0wNzEyMTIxNTAwMDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYT
+AkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zlcm5tZW50MRYwFAYDVQQLEw1BcHBs
+aWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp23gdE6H
+j6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4fl+K
+f5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55
+IrmTwcrNwVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cw
+FO5cjFW6WY2H/CPek9AEjP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDiht
+QWEjdnjDuGWk81quzMKq2edY3rZ+nYVunyoKb58DKTCXKB28t89UKU5RMfkntigm
+/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRUWssmP3HMlEYNllPqa0jQ
+k/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNVBAYTAkpQ
+MRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOC
+seODvOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
+ggEBADlqRHZ3ODrso2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJ
+hyzjVOGjprIIC8CFqMjSnHH2HZ9g/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+
+eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYDio+nEhEMy/0/ecGc/WLuo89U
+DNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmWdupwX3kSa+Sj
+B1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL
+rosot4LKGAfmt1t06SAZf7IbiVQ=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE
+BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h
+cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy
+MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg
+Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9
+thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM
+cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG
+L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i
+NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h
+X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b
+m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy
+Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja
+EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T
+KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF
+6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh
+OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD
+VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD
+VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp
+cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv
+ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl
+AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF
+661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9
+am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1
+ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481
+PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS
+3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k
+SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF
+3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM
+ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g
+StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz
+Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB
+jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEd
+MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3Mg
+Q2xhc3MgMiBDQSAxMB4XDTA2MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzEL
+MAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MR0wGwYD
+VQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7McXA0
+ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLX
+l18xoS830r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVB
+HfCuuCkslFJgNJQ72uA40Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B
+5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/RuFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3
+WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0PAQH/BAQD
+AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLP
+gcIV1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+
+DKhQ7SLHrQVMdvvt7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKu
+BctN518fV4bVIJwo+28TOPX2EZL2fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHs
+h7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5wwDX3OaJdZtB7WZ+oRxKaJyOk
+LY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEd
+MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3Mg
+Q2xhc3MgMyBDQSAxMB4XDTA1MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzEL
+MAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MR0wGwYD
+VQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKxifZg
+isRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//z
+NIqeKNc0n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI
++MkcVyzwPX6UvCWThOiaAJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2R
+hzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+
+mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0PAQH/BAQD
+AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFP
+Bdy7pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27s
+EzNxZy5p+qksP2bAEllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2
+mSlf56oBzKwzqBwKu5HEA6BvtjT5htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yC
+e/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQjel/wroQk5PMr+4okoyeYZdow
+dXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzET
+MBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UE
+AxMIQ0EgRGlzaWcwHhcNMDYwMzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQsw
+CQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcg
+YS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgmGErE
+Nx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnX
+mjxUizkDPw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYD
+XcDtab86wYqg6I7ZuUUohwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhW
+S8+2rT+MitcE5eN4TPWGqvWP+j1scaMtymfraHtuM6kMgiioTGohQBUgDCZbg8Kp
+FhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8wgfwwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0PAQH/BAQD
+AgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cu
+ZGlzaWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5z
+ay9jYS9jcmwvY2FfZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2sv
+Y2EvY3JsL2NhX2Rpc2lnLmNybDAaBgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEw
+DQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59tWDYcPQuBDRIrRhCA/ec8J9B6
+yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3mkkp7M5+cTxq
+EEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/
+CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeB
+EicTXxChds6KezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFN
+PGO+I++MzVpQuGhU+QqZMxEA4Z7CRneC9VkGjCFMhwnN5ag=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV
+BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X
+DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ
+BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4
+QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny
+gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw
+zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q
+130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2
+JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw
+DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw
+ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT
+AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj
+AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG
+9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h
+bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc
+fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu
+HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w
+t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw
+WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT
+AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD
+QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP
+MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do
+0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ
+UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d
+RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ
+OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv
+JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C
+AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O
+BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ
+LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY
+MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ
+44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I
+Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw
+i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN
+9u6wWk5JRFRYX0KD
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD
+VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0
+IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3
+MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz
+IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz
+MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj
+dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw
+EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp
+MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G
+CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9
+28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq
+VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q
+DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR
+5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL
+ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a
+Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl
+UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s
++12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5
+Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj
+ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx
+hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV
+HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1
++HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN
+YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t
+L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy
+ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt
+IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV
+HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w
+DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW
+PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF
+5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1
+glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH
+FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2
+pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD
+xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG
+tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq
+jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De
+fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg
+OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ
+d0jQ
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJD
+TjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2
+MDcwOTE0WhcNMjcwNDE2MDcwOTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMF
+Q05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzDo+/hn7E7SIX1mlwh
+IhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tizVHa6
+dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZO
+V/kbZKKTVrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrC
+GHn2emU1z5DrvTOTn1OrczvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gN
+v7Sg2Ca+I19zN38m5pIEo3/PIKe38zrKy5nLAgMBAAGjczBxMBEGCWCGSAGG+EIB
+AQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscCwQ7vptU7ETAPBgNVHRMB
+Af8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991SlgrHAsEO
+76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnK
+OOK5Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvH
+ugDnuL8BV8F3RTIMO/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7Hgvi
+yJA/qIYM/PmLXoXLT1tLYhFHxUV8BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fL
+buXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2G8kS1sHNzYDzAgE8yGnLRUhj
+2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5mmxE=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDkzCCAnugAwIBAgIQFBOWgxRVjOp7Y+X8NId3RDANBgkqhkiG9w0BAQUFADA0
+MRMwEQYDVQQDEwpDb21TaWduIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQG
+EwJJTDAeFw0wNDAzMjQxMTMyMThaFw0yOTAzMTkxNTAyMThaMDQxEzARBgNVBAMT
+CkNvbVNpZ24gQ0ExEDAOBgNVBAoTB0NvbVNpZ24xCzAJBgNVBAYTAklMMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8ORUaSvTx49qROR+WCf4C9DklBKK
+8Rs4OC8fMZwG1Cyn3gsqrhqg455qv588x26i+YtkbDqthVVRVKU4VbirgwTyP2Q2
+98CNQ0NqZtH3FyrV7zb6MBBC11PN+fozc0yz6YQgitZBJzXkOPqUm7h65HkfM/sb
+2CEJKHxNGGleZIp6GZPKfuzzcuc3B1hZKKxC+cX/zT/npfo4sdAMx9lSGlPWgcxC
+ejVb7Us6eva1jsz/D3zkYDaHL63woSV9/9JLEYhwVKZBqGdTUkJe5DSe5L6j7Kpi
+Xd3DTKaCQeQzC6zJMw9kglcq/QytNuEMrkvF7zuZ2SOzW120V+x0cAwqTwIDAQAB
+o4GgMIGdMAwGA1UdEwQFMAMBAf8wPQYDVR0fBDYwNDAyoDCgLoYsaHR0cDovL2Zl
+ZGlyLmNvbXNpZ24uY28uaWwvY3JsL0NvbVNpZ25DQS5jcmwwDgYDVR0PAQH/BAQD
+AgGGMB8GA1UdIwQYMBaAFEsBmz5WGmU2dst7l6qSBe4y5ygxMB0GA1UdDgQWBBRL
+AZs+VhplNnbLe5eqkgXuMucoMTANBgkqhkiG9w0BAQUFAAOCAQEA0Nmlfv4pYEWd
+foPPbrxHbvUanlR2QnG0PFg/LUAlQvaBnPGJEMgOqnhPOAlXsDzACPw1jvFIUY0M
+cXS6hMTXcpuEfDhOZAYnKuGntewImbQKDdSFc8gS4TXt8QUxHXOZDOuWyt3T5oWq
+8Ir7dcHyCTxlZWTzTNity4hp8+SDtwy9F1qWF8pb/627HOkthIDYIb6FUtnUdLlp
+hbpN7Sgy6/lhSuTENh4Z3G+EER+V9YMoGKgzkkMn3V0TBEVPh9VGzT2ouvDzuFYk
+Res3x+F2T3I5GN9+dHLHcy056mDmrRGiVod7w2ia/viMcKjfZTL0pECMocJEAw6U
+AGegcQCCSA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAw
+PDEbMBkGA1UEAxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWdu
+MQswCQYDVQQGEwJJTDAeFw0wNDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwx
+GzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBDQTEQMA4GA1UEChMHQ29tU2lnbjEL
+MAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGtWhf
+HZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs49oh
+gHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sW
+v+bznkqH7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ue
+Mv5WJDmyVIRD9YTC2LxBkMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr
+9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d19guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt
+6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUwAwEB/zBEBgNVHR8EPTA7
+MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29tU2lnblNl
+Y3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58
+ADsAj8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkq
+hkiG9w0BAQUFAAOCAQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7p
+iL1DRYHjZiM/EoZNGeQFsOY3wo3aBijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtC
+dsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtpFhpFfTMDZflScZAmlaxMDPWL
+kz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP51qJThRv4zdL
+hfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz
+OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG
+A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh
+bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE
+ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS
+b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5
+7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS
+J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y
+HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP
+t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz
+FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY
+XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/
+MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw
+hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js
+MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA
+A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj
+Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx
+XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o
+omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc
+A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW
+WL1WMRJOEcgh4LMRkWXbtKaIOM5V
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc
+MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj
+IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB
+IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE
+RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl
+U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290
+IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU
+ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC
+QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr
+rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S
+NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc
+QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH
+txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP
+BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC
+AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp
+tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa
+IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl
+6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+
+xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU
+Cm26OWMohpLzGITY+9HPBVZkVw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNV
+BAMML0VCRyBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
+c8SxMTcwNQYDVQQKDC5FQkcgQmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXpt
+ZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAeFw0wNjA4MTcwMDIxMDlaFw0xNjA4
+MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25payBTZXJ0aWZpa2Eg
+SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2ltIFRl
+a25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h
+4fuXd7hxlugTlkaDT7byX3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAk
+tiHq6yOU/im/+4mRDGSaBUorzAzu8T2bgmmkTPiab+ci2hC6X5L8GCcKqKpE+i4s
+tPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfreYteIAbTdgtsApWjluTL
+dlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZTqNGFav4
+c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8Um
+TDGyY5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z
++kI2sSXFCjEmN1ZnuqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0O
+Lna9XvNRiYuoP1Vzv9s6xiQFlpJIqkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMW
+OeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vmExH8nYQKE3vwO9D8owrXieqW
+fo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0Nokb+Clsi7n2
+l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
+/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgw
+FoAU587GT/wWZ5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+
+8ygjdsZs93/mQJ7ANtyVDR2tFcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI
+6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgmzJNSroIBk5DKd8pNSe/iWtkqvTDO
+TLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64kXPBfrAowzIpAoHME
+wfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqTbCmY
+Iai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJn
+xk1Gj7sURT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4Q
+DgZxGhBM/nV+/x5XOULK1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9q
+Kd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11t
+hie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQY9iJSrSq3RZj9W6+YKH4
+7ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9AahH3eU7
+QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDtjCCAp6gAwIBAgIQRJmNPMADJ72cdpW56tustTANBgkqhkiG9w0BAQUFADB1
+MQswCQYDVQQGEwJUUjEoMCYGA1UEChMfRWxla3Ryb25payBCaWxnaSBHdXZlbmxp
+Z2kgQS5TLjE8MDoGA1UEAxMzZS1HdXZlbiBLb2sgRWxla3Ryb25payBTZXJ0aWZp
+a2EgSGl6bWV0IFNhZ2xheWljaXNpMB4XDTA3MDEwNDExMzI0OFoXDTE3MDEwNDEx
+MzI0OFowdTELMAkGA1UEBhMCVFIxKDAmBgNVBAoTH0VsZWt0cm9uaWsgQmlsZ2kg
+R3V2ZW5saWdpIEEuUy4xPDA6BgNVBAMTM2UtR3V2ZW4gS29rIEVsZWt0cm9uaWsg
+U2VydGlmaWthIEhpem1ldCBTYWdsYXlpY2lzaTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAMMSIJ6wXgBljU5Gu4Bc6SwGl9XzcslwuedLZYDBS75+PNdU
+MZTe1RK6UxYC6lhj71vY8+0qGqpxSKPcEC1fX+tcS5yWCEIlKBHMilpiAVDV6wlT
+L/jDj/6z/P2douNffb7tC+Bg62nsM+3YjfsSSYMAyYuXjDtzKjKzEve5TfL0TW3H
+5tYmNwjy2f1rXKPlSFxYvEK+A1qBuhw1DADT9SN+cTAIJjjcJRFHLfO6IxClv7wC
+90Nex/6wN1CZew+TzuZDLMN+DfIcQ2Zgy2ExR4ejT669VmxMvLz4Bcpk9Ok0oSy1
+c+HCPujIyTQlCFzz7abHlJ+tiEMl1+E5YP6sOVkCAwEAAaNCMEAwDgYDVR0PAQH/
+BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ/uRLOU1fqRTy7ZVZoE
+VtstxNulMA0GCSqGSIb3DQEBBQUAA4IBAQB/X7lTW2M9dTLn+sR0GstG30ZpHFLP
+qk/CaOv/gKlR6D1id4k9CnU58W5dF4dvaAXBlGzZXd/aslnLpRCKysw5zZ/rTt5S
+/wzw9JKp8mxTq5vSR6AfdPebmvEvFZ96ZDAYBzwqD2fK/A+JYZ1lpTzlvBNbCNvj
+/+27BrtqBrF6T2XGgv0enIu1De5Iu7i9qgi0+6N8y5/NkHZchpZ4Vwpm+Vganf2X
+KWDeEaaQHBkc7gGWIjQ0LpH5t8Qn0Xvmv/uARFoW5evg1Ao4vOSR49XrXMGs3xtq
+fJ7lddK2l4fbzIcrQzqECK+rPNv3PGYxhrCdU3nt+CPeQuMtgvEP5fqX
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe
+MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
+ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
+Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw
+IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL
+SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF
+AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH
+SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh
+ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X
+DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1
+TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ
+fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA
+sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU
+WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS
+nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH
+dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip
+NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC
+AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF
+MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
+ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB
+uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl
+PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP
+JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/
+gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2
+j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6
+5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB
+o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS
+/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z
+Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE
+W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D
+hNQ+IIX3Sj0rnP0qCglN6oH4EZw=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL
+MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj
+KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2
+MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
+eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV
+BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw
+NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV
+BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH
+MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL
+So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal
+tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
+BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG
+CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT
+qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz
+rD6ogRLQy7rQkgu2npaqBA+K
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB
+mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT
+MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s
+eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv
+cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ
+BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg
+MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0
+BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
+LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz
++uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm
+hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn
+5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W
+JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL
+DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC
+huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
+HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB
+AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB
+zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN
+kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD
+AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH
+SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G
+spki4cErx5z481+oghLrGREt
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD
+VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0
+IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3
+MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD
+aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx
+MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy
+cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG
+A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl
+BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI
+hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed
+KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7
+G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2
+zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4
+ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG
+HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2
+Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V
+yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e
+beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r
+6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh
+wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog
+zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW
+BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr
+ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp
+ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk
+cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt
+YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC
+CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow
+KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI
+hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ
+UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz
+X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x
+fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz
+a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd
+Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd
+SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O
+AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso
+M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge
+v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z
+09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
+A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
+Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
+MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG
+A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8
+RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT
+gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm
+KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd
+QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ
+XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw
+DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o
+LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU
+RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp
+jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK
+6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX
+mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs
+Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH
+WD9f
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx
+FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg
+Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG
+A1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdr
+b25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1ApzQ
+jVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEn
+PzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjh
+ZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9
+nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/h
+q5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgED
+MA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsC
+mEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI3
+7piol7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clB
+oiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJs
+EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO
+fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi
+AmvZWg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYT
+AkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQ
+TS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG
+9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMB4XDTAyMTIxMzE0MjkyM1oXDTIw
+MTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAM
+BgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEO
+MAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2
+LmZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaI
+s9z4iPf930Pfeo2aSVz2TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2
+xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCWSo7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4
+u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYyHF2fYPepraX/z9E0+X1b
+F8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNdfrGoRpAx
+Vs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGd
+PDPQtQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNV
+HSAEDjAMMAoGCCqBegF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAx
+NjAfBgNVHSMEGDAWgBSjBS8YYFDCiQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUF
+AAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RKq89toB9RlPhJy3Q2FLwV3duJ
+L92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3QMZsyK10XZZOY
+YLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg
+Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2a
+NjSaTFR+FwNIlQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R
+0982gaEbeC9xs/FZTEYYKKuF0mBWWg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4
+MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6
+ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD
+VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j
+b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq
+scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO
+xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H
+LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX
+uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD
+yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+
+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q
+rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN
+BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L
+hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB
+QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+
+HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu
+Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg
+QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB
+BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
+MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA
+A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb
+laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56
+awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo
+JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw
+LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT
+VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk
+LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb
+UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/
+QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+
+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls
+QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcN
+AQkBFglwa2lAc2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZp
+dHNlZXJpbWlza2Vza3VzMRAwDgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMw
+MVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMQsw
+CQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEQ
+MA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOB
+SvZiF3tfTQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkz
+ABpTpyHhOEvWgxutr2TC+Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvH
+LCu3GFH+4Hv2qEivbDtPL+/40UceJlfwUR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMP
+PbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDaTpxt4brNj3pssAki14sL
+2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQFMAMBAf8w
+ggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwIC
+MIHDHoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDk
+AGwAagBhAHMAdABhAHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0
+AHMAZQBlAHIAaQBtAGkAcwBrAGUAcwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABz
+AGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABrAGkAbgBuAGkAdABhAG0AaQBz
+AGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nwcy8wKwYDVR0f
+BCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE
+FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcY
+P2/v6X2+MA4GA1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOi
+CfP+JmeaUOTDBS8rNXiRTHyoERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+g
+kcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyLabVAyJRld/JXIWY7zoVAtjNjGr95
+HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678IIbsSt4beDI3poHS
+na9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkhMp6q
+qIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0Z
+TbvGRNs2yyqcjg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD
+VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0
+ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G
+CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y
+OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx
+FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp
+Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o
+dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP
+kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc
+cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U
+fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7
+N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC
+xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1
++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
+A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM
+Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG
+SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h
+mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk
+ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775
+tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c
+2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t
+HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAw
+cjELMAkGA1UEBhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNy
+b3NlYyBMdGQuMRQwEgYDVQQLEwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9z
+ZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0MDYxMjI4NDRaFw0xNzA0MDYxMjI4
+NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEWMBQGA1UEChMN
+TWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMTGU1p
+Y3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2u
+uO/TEdyB5s87lozWbxXGd36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+
+LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/NoqdNAoI/gqyFxuEPkEeZlApxcpMqyabA
+vjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjcQR/Ji3HWVBTji1R4P770
+Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJPqW+jqpx
+62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcB
+AQRbMFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3Aw
+LQYIKwYBBQUHMAKGIWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAP
+BgNVHRMBAf8EBTADAQH/MIIBcwYDVR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIB
+AQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3LmUtc3ppZ25vLmh1L1NaU1ov
+MIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0AdAB2AOEAbgB5
+ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn
+AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABT
+AHoAbwBsAGcA4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABh
+ACAAcwB6AGUAcgBpAG4AdAAgAGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABo
+AHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMAegBpAGcAbgBvAC4AaAB1AC8AUwBa
+AFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6Ly93d3cuZS1zemln
+bm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NOPU1p
+Y3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxP
+PU1pY3Jvc2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZv
+Y2F0aW9uTGlzdDtiaW5hcnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuB
+EGluZm9AZS1zemlnbm8uaHWkdzB1MSMwIQYDVQQDDBpNaWNyb3NlYyBlLVN6aWdu
+w7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhTWjEWMBQGA1UEChMNTWlj
+cm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhVMIGsBgNV
+HSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJI
+VTERMA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDAS
+BgNVBAsTC2UtU3ppZ25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBS
+b290IENBghEAzLjnv04pGv2i3GalHCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS
+8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMTnGZjWS7KXHAM/IO8VbH0jgds
+ZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FEaGAHQzAxQmHl
+7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a
+86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfR
+hUZLphK3dehKyVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/
+MPMMNz7UwiiAc7EBt51alhQBS6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG
+EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3
+MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl
+cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR
+dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB
+pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM
+b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm
+aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz
+IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT
+lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz
+AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5
+VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG
+ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2
+BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG
+AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M
+U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh
+bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C
++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
+bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F
+uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2
+XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB
+ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly
+aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl
+ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w
+NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G
+A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD
+VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX
+SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR
+VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2
+w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF
+mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg
+4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9
+4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw
+DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw
+EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx
+SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2
+ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8
+vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa
+hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi
+Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ
+/L7fCg0=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr
+MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG
+A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0
+MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp
+Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD
+QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz
+i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8
+h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV
+MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9
+UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni
+8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC
+h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD
+VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB
+AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm
+KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ
+X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr
+QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5
+pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN
+QSdJQO7e5iNEOdyhIta6A/I=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDEl
+MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMh
+U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIz
+MloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09N
+IFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNlY3VyaXR5IENvbW11
+bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSE
+RMqm4miO/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gO
+zXppFodEtZDkBp2uoQSXWHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5
+bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4zZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDF
+MxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4bepJz11sS6/vmsJWXMY1
+VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK9U2vP9eC
+OKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HW
+tWS3irO4G8za+6xmiEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZ
+q51ihPZRwSzJIxXYKLerJRO1RuGGAv8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDb
+EJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnWmHyojf6GPgcWkuF75x3sM3Z+
+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEWT1MKZPlO9L9O
+VL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO
+TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh
+dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX
+DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl
+ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv
+b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291
+qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp
+uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU
+Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE
+pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp
+5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M
+UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN
+GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy
+5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv
+6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK
+eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6
+B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/
+BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov
+L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV
+HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG
+SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS
+CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen
+5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897
+IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK
+gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL
++63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL
+vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm
+bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk
+N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC
+Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z
+ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEezCCA2OgAwIBAgIQNxkY5lNUfBq1uMtZWts1tzANBgkqhkiG9w0BAQUFADCB
+rjELMAkGA1UEBhMCREUxIDAeBgNVBAgTF0JhZGVuLVd1ZXJ0dGVtYmVyZyAoQlcp
+MRIwEAYDVQQHEwlTdHV0dGdhcnQxKTAnBgNVBAoTIERldXRzY2hlciBTcGFya2Fz
+c2VuIFZlcmxhZyBHbWJIMT4wPAYDVQQDEzVTLVRSVVNUIEF1dGhlbnRpY2F0aW9u
+IGFuZCBFbmNyeXB0aW9uIFJvb3QgQ0EgMjAwNTpQTjAeFw0wNTA2MjIwMDAwMDBa
+Fw0zMDA2MjEyMzU5NTlaMIGuMQswCQYDVQQGEwJERTEgMB4GA1UECBMXQmFkZW4t
+V3VlcnR0ZW1iZXJnIChCVykxEjAQBgNVBAcTCVN0dXR0Z2FydDEpMCcGA1UEChMg
+RGV1dHNjaGVyIFNwYXJrYXNzZW4gVmVybGFnIEdtYkgxPjA8BgNVBAMTNVMtVFJV
+U1QgQXV0aGVudGljYXRpb24gYW5kIEVuY3J5cHRpb24gUm9vdCBDQSAyMDA1OlBO
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2bVKwdMz6tNGs9HiTNL1
+toPQb9UY6ZOvJ44TzbUlNlA0EmQpoVXhOmCTnijJ4/Ob4QSwI7+Vio5bG0F/WsPo
+TUzVJBY+h0jUJ67m91MduwwA7z5hca2/OnpYH5Q9XIHV1W/fuJvS9eXLg3KSwlOy
+ggLrra1fFi2SU3bxibYs9cEv4KdKb6AwajLrmnQDaHgTncovmwsdvs91DSaXm8f1
+XgqfeN+zvOyauu9VjxuapgdjKRdZYgkqeQd3peDRF2npW932kKvimAoA0SVtnteF
+hy+S8dF2g08LOlk3KC8zpxdQ1iALCvQm+Z845y2kuJuJja2tyWp9iRe79n+Ag3rm
+7QIDAQABo4GSMIGPMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEG
+MCkGA1UdEQQiMCCkHjAcMRowGAYDVQQDExFTVFJvbmxpbmUxLTIwNDgtNTAdBgNV
+HQ4EFgQUD8oeXHngovMpttKFswtKtWXsa1IwHwYDVR0jBBgwFoAUD8oeXHngovMp
+ttKFswtKtWXsa1IwDQYJKoZIhvcNAQEFBQADggEBAK8B8O0ZPCjoTVy7pWMciDMD
+pwCHpB8gq9Yc4wYfl35UvbfRssnV2oDsF9eK9XvCAPbpEW+EoFolMeKJ+aQAPzFo
+LtU96G7m1R08P7K9n3frndOMusDXtk3sU5wPBG7qNWdX4wple5A64U8+wwCSersF
+iXOMy6ZNwPv2AtawB6MDwidAnwzkhYItr5pCHdDHjfhA7p0GVxzZotiAFP7hYy0y
+h9WUUpY6RsZxlj33mA6ykaqP2vROJAA5VeitF7nTNCtKqUDMFypVZUF0Qn71wK/I
+k63yGFs9iQzbRzkk+OBM8h+wPQrKBU6JIRrjKpms/H+h8Q8bHz2eBIPdltkdOpQ=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjEL
+MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV
+BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0
+Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYwMTEyMTQzODQzWhcNMjUxMjMxMjI1
+OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i
+SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UEAxMc
+VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jf
+tMjWQ+nEdVl//OEd+DFwIxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKg
+uNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2J
+XjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQXa7pIXSSTYtZgo+U4+lK
+8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7uSNQZu+99
+5OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1Ud
+EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3
+kUrL84J6E1wIqzCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy
+dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6
+Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz
+JTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290
+Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
+TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iS
+GNn3Bzn1LL4GdXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprt
+ZjluS5TmVfwLG4t3wVMTZonZKNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8
+au0WOB9/WIFaGusyiC2y8zl3gK9etmF1KdsjTYjKUCjLhdLTEKJZbtOTVAB6okaV
+hgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kPJOzHdiEoZa5X6AeI
+dUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfkvQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjEL
+MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV
+BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0
+Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYwMTEyMTQ0MTU3WhcNMjUxMjMxMjI1
+OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i
+SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UEAxMc
+VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJW
+Ht4bNwcwIi9v8Qbxq63WyKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+Q
+Vl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo6SI7dYnWRBpl8huXJh0obazovVkdKyT2
+1oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZuV3bOx4a+9P/FRQI2Alq
+ukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk2ZyqBwi1
+Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1Ud
+EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NX
+XAek0CSnwPIA1DCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy
+dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6
+Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz
+JTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290
+Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
+TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlN
+irTzwppVMXzEO2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8
+TtXqluJucsG7Kv5sbviRmEb8yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6
+g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9IJqDnxrcOfHFcqMRA/07QlIp2+gB
+95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal092Y+tTmBvTwtiBj
+S+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc5A==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTEL
+MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNV
+BAsTG1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1
+c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcNMDYwMzIyMTU1NDI4WhcNMjUxMjMx
+MjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIg
+R21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYwJAYD
+VQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSR
+JJZ4Hgmgm5qVSkr1YnwCqMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3T
+fCZdzHd55yx4Oagmcw6iXSVphU9VDprvxrlE4Vc93x9UIuVvZaozhDrzznq+VZeu
+jRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtwag+1m7Z3W0hZneTvWq3z
+wZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9OgdwZu5GQ
+fezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYD
+VR0jBBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAO
+BgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0G
+CSqGSIb3DQEBBQUAA4IBAQAo0uCG1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X1
+7caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/CyvwbZ71q+s2IhtNerNXxTPqYn
+8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3ghUJGooWMNjs
+ydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT
+ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/
+2TYcuiUaUj0a7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIID4TCCAsmgAwIBAgIOYyUAAQACFI0zFQLkbPQwDQYJKoZIhvcNAQEFBQAwezEL
+MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNV
+BAsTG1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQTEoMCYGA1UEAxMfVEMgVHJ1
+c3RDZW50ZXIgVW5pdmVyc2FsIENBIElJSTAeFw0wOTA5MDkwODE1MjdaFw0yOTEy
+MzEyMzU5NTlaMHsxCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNUQyBUcnVzdENlbnRl
+ciBHbWJIMSQwIgYDVQQLExtUQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0ExKDAm
+BgNVBAMTH1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQSBJSUkwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDC2pxisLlxErALyBpXsq6DFJmzNEubkKLF
+5+cvAqBNLaT6hdqbJYUtQCggbergvbFIgyIpRJ9Og+41URNzdNW88jBmlFPAQDYv
+DIRlzg9uwliT6CwLOunBjvvya8o84pxOjuT5fdMnnxvVZ3iHLX8LR7PH6MlIfK8v
+zArZQe+f/prhsq75U7Xl6UafYOPfjdN/+5Z+s7Vy+EutCHnNaYlAJ/Uqwa1D7KRT
+yGG299J5KmcYdkhtWyUB0SbFt1dpIxVbYYqt8Bst2a9c8SaQaanVDED1M4BDj5yj
+dipFtK+/fz6HP3bFzSreIMUWWMv5G/UPyw0RUmS40nZid4PxWJ//AgMBAAGjYzBh
+MB8GA1UdIwQYMBaAFFbn4VslQ4Dg9ozhcbyO5YAvxEjiMA8GA1UdEwEB/wQFMAMB
+Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRW5+FbJUOA4PaM4XG8juWAL8RI
+4jANBgkqhkiG9w0BAQUFAAOCAQEAg8ev6n9NCjw5sWi+e22JLumzCecYV42Fmhfz
+dkJQEw/HkG8zrcVJYCtsSVgZ1OK+t7+rSbyUyKu+KGwWaODIl0YgoGhnYIg5IFHY
+aAERzqf2EQf27OysGh+yZm5WZ2B6dF7AbZc2rrUNXWZzwCUyRdhKBgePxLcHsU0G
+DeGl6/R1yrqc0L2z0zIkTO5+4nYES0lT2PLpVDP85XEfPRRclkvxOvIAu2y0+pZV
+CIgJwcyRGSmwIC3/yzikQOEXvnlhgP8HA4ZMTnsGnxGGjYnuJ8Tb4rwZjgvDwxPH
+LQNjO9Po5KIqwoIIlBZU8O8fJ5AluA0OKBtHd0e9HKgl8ZS0Zg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp
+IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi
+BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw
+MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh
+d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig
+YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v
+dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/
+BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6
+papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K
+DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3
+KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox
+XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB
+rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
+Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
+MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV
+BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa
+Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl
+LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u
+MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl
+ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm
+gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8
+YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf
+b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9
+9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S
+zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk
+OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV
+HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA
+2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW
+oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu
+t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c
+KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM
+m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu
+MdRAGmI0Nj81Aa6sY6A=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRS
+MRgwFgYDVQQHDA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJp
+bGltc2VsIHZlIFRla25vbG9qaWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSw
+VEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ryb25payB2ZSBLcmlwdG9sb2ppIEFy
+YcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNVBAsMGkthbXUgU2Vy
+dGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUgS8O2
+ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAe
+Fw0wNzA4MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIx
+GDAWBgNVBAcMD0dlYnplIC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmls
+aW1zZWwgdmUgVGVrbm9sb2ppayBBcmHFn3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBU
+QUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZlIEtyaXB0b2xvamkgQXJh
+xZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2FtdSBTZXJ0
+aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7Zr
+IFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4h
+gb46ezzb8R1Sf1n68yJMlaCQvEhOEav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yK
+O7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1xnnRFDDtG1hba+818qEhTsXO
+fJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR6Oqeyjh1jmKw
+lZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL
+hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQID
+AQABo0IwQDAdBgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/
+BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmP
+NOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4N5EY3ATIZJkrGG2AA1nJrvhY0D7t
+wyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLTy9LQQfMmNkqblWwM
+7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYhLBOh
+gLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5n
+oN+J1q2MdqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUs
+yZyQ2uypQjyttgI=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
+ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln
+biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
+U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
+aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG
+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp
+U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg
+SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln
+biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5
+IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm
+GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve
+fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw
+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ
+aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj
+aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW
+kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC
+4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga
+FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB
+vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W
+ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
+Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX
+MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0
+IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y
+IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh
+bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF
+9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH
+H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H
+LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN
+/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT
+rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud
+EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw
+WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs
+exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud
+DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4
+sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+
+seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz
+4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+
+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR
+lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3
+7M2CYfE45k+XmCpajQ==
+-----END CERTIFICATE-----
level2/test/lib/error.py
@@ -0,0 +1,6 @@
+# Exceptions
+class StripeError(Exception):
+    pass
+
+class HTTPConnectionError(StripeError):
+    pass
level2/test/lib/error.pyc
Binary file
level2/test/lib/http_client.py
@@ -0,0 +1,231 @@
+import os
+import sys
+import textwrap
+
+# From this package
+import lib.error as error
+import lib.util as util
+
+# This is a port of http_client from [Stripe-Python](https://github.com/stripe/stripe-python)
+
+# - Requests is the preferred HTTP library
+# - Use Pycurl if it's there (at least it verifies SSL certs)
+# - Fall back to urllib2 with a warning if needed
+
+try:
+    if sys.version_info < (3,0):
+        import urllib2 as urllib_request
+    else:
+        import urllib.request as urllib_request
+except ImportError:
+    pass
+
+try:
+    import pycurl
+except ImportError:
+    pycurl = None
+
+try:
+    import requests
+except ImportError:
+    requests = None
+else:
+    try:
+        # Require version 0.8.8, but don't want to depend on distutils
+        version = requests.__version__
+        major, minor, patch = [int(i) for i in version.split('.')]
+    except Exception:
+        # Probably some new-fangled version, so it should support verify
+        pass
+    else:
+        if (major, minor, patch) < (0, 8, 8):
+            util.logger.warn(
+                'Warning: the test harness will only use your Python "requests"'
+                'library if it is version 0.8.8 or newer, but your '
+                '"requests" library is version %s. We will fall back to '
+                'an alternate HTTP library so everything should work. We '
+                'recommend upgrading your "requests" library. (HINT: running '
+                '"pip install -U requests" should upgrade your requests '
+                'library to the latest version.)' % (version,))
+            requests = None
+
+def certs_path():
+    return os.path.join(os.path.dirname(__file__), 'ca-certificates.crt')
+
+
+def new_default_http_client(*args, **kwargs):
+    if requests:
+        impl = RequestsClient
+    elif pycurl and sys.version_info < (3,0):
+        # Officially supports in 3.1-3.3 but not 3.0. The idea is that for >=2.6
+        # you should use requests
+        impl = PycurlClient
+    else:
+        impl = Urllib2Client
+        if sys.version_info < (2,6):
+            reccomendation = "pycurl"
+        else:
+            reccomendation = "requests"
+        util.logger.info(
+            "Warning: The test harness is falling back to *urllib2*. "
+            "Its SSL implementation doesn't verify server "
+            "certificates (how's that for a distributed systems problem?). "
+            "We recommend instead installing %(rec)s via `pip install %(rec)s`.",
+            {'rec': reccomendation})
+
+    return impl(*args, **kwargs)
+
+
+class HTTPClient(object):
+
+    def __init__(self, headers={}, verify_ssl_certs=True):
+        self._verify_ssl_certs = verify_ssl_certs
+        self.headers = headers
+
+    def request(self, method, url, post_data=None):
+        raise NotImplementedError(
+            'HTTPClient subclasses must implement `request`')
+
+
+class RequestsClient(HTTPClient):
+    name = 'requests'
+
+    def request(self, method, url, post_data=None):
+        kwargs = {}
+
+        if self._verify_ssl_certs:
+            kwargs['verify'] = certs_path()
+        else:
+            kwargs['verify'] = False
+
+        try:
+            try:
+                result = requests.request(method,
+                                          url,
+                                          headers=self.headers,
+                                          data=post_data,
+                                          timeout=80,
+                                          **kwargs)
+            except TypeError:
+                e = util.exception_as()
+                raise TypeError(
+                    'Warning: It looks like your installed version of the '
+                    '"requests" library is not compatible with Stripe\'s '
+                    'usage thereof. (HINT: The most likely cause is that '
+                    'your "requests" library is out of date. You can fix '
+                    'that by running "pip install -U requests".) The '
+                    'underlying error was: %s' % (e,))
+
+            # This causes the content to actually be read, which could cause
+            # e.g. a socket timeout. TODO: The other fetch methods probably
+            # are succeptible to the same and should be updated.
+            content = result.content
+            status_code = result.status_code
+        except Exception:
+            # Would catch just requests.exceptions.RequestException, but can
+            # also raise ValueError, RuntimeError, etc.
+            e = util.exception_as()
+            self._handle_request_error(e)
+        if sys.version_info >= (3, 0):
+            content = content.decode('utf-8')
+        return content, status_code
+
+    def _handle_request_error(self, e):
+        if isinstance(e, requests.exceptions.RequestException):
+            err = "%s: %s" % (type(e).__name__, str(e))
+        else:
+            err = "A %s was raised" % (type(e).__name__,)
+            if str(e):
+                err += " with error message %s" % (str(e),)
+            else:
+                err += " with no error message"
+        msg = "Network error: %s" % (err,)
+        raise error.HTTPConnectionError(msg)
+
+class PycurlClient(HTTPClient):
+    name = 'pycurl'
+
+    def request(self, method, url, post_data=None):
+        s = util.StringIO.StringIO()
+        curl = pycurl.Curl()
+
+        if method == 'get':
+            curl.setopt(pycurl.HTTPGET, 1)
+        elif method == 'post':
+            curl.setopt(pycurl.POST, 1)
+            curl.setopt(pycurl.POSTFIELDS, post_data)
+        else:
+            curl.setopt(pycurl.CUSTOMREQUEST, method.upper())
+
+        # pycurl doesn't like unicode URLs
+        curl.setopt(pycurl.URL, util.utf8(url))
+
+        curl.setopt(pycurl.WRITEFUNCTION, s.write)
+        curl.setopt(pycurl.NOSIGNAL, 1)
+        curl.setopt(pycurl.CONNECTTIMEOUT, 30)
+        curl.setopt(pycurl.TIMEOUT, 80)
+        curl.setopt(pycurl.HTTPHEADER, ['%s: %s' % (k, v)
+                    for k, v in self.headers.iteritems()])
+        if self._verify_ssl_certs:
+            curl.setopt(pycurl.CAINFO, certs_path())
+        else:
+            curl.setopt(pycurl.SSL_VERIFYHOST, False)
+
+        try:
+            curl.perform()
+        except pycurl.error:
+            e = util.exception_as()
+            self._handle_request_error(e)
+        rbody = s.getvalue()
+        rcode = curl.getinfo(pycurl.RESPONSE_CODE)
+        return rbody, rcode
+
+    def _handle_request_error(self, e):
+        error_code = e[0]
+        if error_code in [pycurl.E_COULDNT_CONNECT,
+                          pycurl.E_COULDNT_RESOLVE_HOST,
+                          pycurl.E_OPERATION_TIMEOUTED]:
+            msg = ("Test harness could not connect to Stripe. Please check "
+                   "your internet connection and try again.")
+        elif (error_code in [pycurl.E_SSL_CACERT,
+                             pycurl.E_SSL_PEER_CERTIFICATE]):
+            msg = "Could not verify host's SSL certificate."
+        else:
+            msg = ""
+        msg = textwrap.fill(msg) + "\n\nNetwork error: %s" % e[1]
+        raise error.HTTPConnectionError(msg)
+
+
+class Urllib2Client(HTTPClient):
+    if sys.version_info >= (3, 0):
+        name = 'urllib.request'
+    else:
+        name = 'urllib2'
+
+    def request(self, method, url, post_data=None):
+        if sys.version_info >= (3, 0) and isinstance(post_data, str):
+            post_data = post_data.encode('utf-8')
+
+        req = urllib_request.Request(url, post_data, self.headers)
+
+        if method not in ('get', 'post'):
+            req.get_method = lambda: method.upper()
+
+        try:
+            response = urllib_request.urlopen(req)
+            rbody = response.read()
+            rcode = response.code
+        except urllib_request.HTTPError:
+            e = util.exception_as()
+            rcode = e.code
+            rbody = e.read()
+        except (urllib_request.URLError, ValueError):
+            e = util.exception_as()
+            self._handle_request_error(e)
+        if sys.version_info >= (3, 0):
+            rbody = rbody.decode('utf-8')
+        return rbody, rcode
+
+    def _handle_request_error(self, e):
+        msg = "Network error: %s" % str(e)
+        raise error.HTTPConnectionError(msg)
level2/test/lib/http_client.pyc
Binary file
level2/test/lib/test_framework.py
@@ -0,0 +1,227 @@
+import difflib
+import os.path
+from random import SystemRandom
+import re
+import subprocess
+import sys
+import time
+
+# From this package
+import lib.error as error
+import lib.http_client as http_client
+import lib.util as util
+
+data_directory = os.path.join(os.path.dirname(__file__), "..", "data")
+
+class TestCase(object):
+    def __init__(self, harness, id_or_url):
+        self.harness = harness
+        self.id, self.url = self.normalize_id_and_url(id_or_url)
+        self.json = None
+
+    def normalize_id_and_url(self, id_or_url):
+        if re.match("\Ahttps?:", id_or_url):
+            url = id_or_url
+            # Look at the last component and remove extension
+            id = id_or_url.split('/')[-1].split('.')[0]
+        else:
+            id = id_or_url
+            level = self.harness.LEVEL
+            url = "https://stripe-ctf-3.s3.amazonaws.com/level%s/%s.json" % (level, id)
+        return id, url
+
+    def dump_path(self):
+        return os.path.join(self.harness.test_cases_path(), self.id + ".json")
+
+    def load(self):
+        if self.json: return self.json
+        try:
+            f = open(self.dump_path(), "r")
+            self.json = util.json.load(f)
+            f.close()
+            return self.json
+        except IOError:
+            pass
+        util.logger.info('Fetching. URL: %s', self.url)
+        content = self.harness.fetch_s3_resource(self.url)
+        try:
+            self.json = util.json.loads(content)
+        except ValueError:
+            # Decoding the JSON failed.
+            msg = ("There was a problem parsing the test case. We expected "
+                   "JSON. We received: %s" % (content,))
+            raise error.StripeError(msg)
+        return self.json
+
+    def flush(self):
+        f = open(os.path.join(self.harness.test_cases_path(), self.id + ".json"), "w")
+        util.json.dump(self.json, f)
+        f.close()
+
+class AbstractHarness(object):
+    def __init__(self, ids_or_urls=[], options={}):
+        util.mkdir_p(self.test_cases_path())
+        if not os.path.isfile(http_client.certs_path()):
+            msg = ("You seem to have deleted the file of certificates "
+                   "that shipped with this repo. It should exist "
+                   "at %s" % http_client.certs_path())
+            raise error.StripeError(msg)
+        if ids_or_urls == []:
+            util.logger.info('No test case supplied. Randomly choosing among defaults.')
+            ids_or_urls = [SystemRandom().choice(self.DEFAULT_TEST_CASES)]
+        self.test_cases = map(lambda token: TestCase(self, token), ids_or_urls)
+        self.options = options
+        headers = {
+            'User-Agent': 'Stripe TestHarness/%s' % (self.VERSION,),
+        }
+        self.http_client = http_client.new_default_http_client(headers=headers, verify_ssl_certs=True)
+
+    def fetch_s3_resource(self, url):
+        try:
+            content, status_code = self.http_client.request("get", url)
+        except error.HTTPConnectionError:
+            err = util.exception_as()
+            msg = ("There was an error while connecting to fetch "
+                   "the url %s. Please check your connectivity. If there "
+                   "continues to be an issue, please let us know at "
+                   "ctf@stripe.com. The specific error is:\n" % (url,))
+            raise error.StripeError(msg + str(err))
+        if status_code == 200:
+            return content
+        elif status_code == 403:
+            msg = ("We received a 403 while fetching the url %s. "
+                   "This probably means that you are trying to get "
+                   "something that doesn't actually exist." % (url,))
+            raise error.StripeError(msg)
+        else:
+            msg = ("We received the unexpected response code %i while "
+                   "fetching the url %s." % (status_code, url,))
+            raise error.StripeError(msg)
+
+    def run(self):
+        task = self.options["task"]
+
+        if task == "execute":
+            test_cases_to_execute = self.load_test_cases()
+            self.execute(test_cases_to_execute)
+        else:
+            raise StandardError("Unrecognized task " +  task)
+
+    def test_cases_path(self):
+        return os.path.join(
+            data_directory,
+            "downloaded_test_cases",
+            "version%i" % self.VERSION)
+
+    def flush_test_cases(self):
+        util.logger.info('Flushing. Path: %s', self.test_cases_path())
+        for test_case in self.test_cases:
+            test_case.flush(self.test_cases_path())
+
+    def add_test_case(self, test_case):
+        self.test_cases.append(test_case)
+
+    def load_test_cases(self):
+        loaded_test_cases = []
+        for test_case in self.test_cases:
+            result = test_case.load()
+            if not result: continue
+            test_case.flush()
+            loaded_test_cases.append(test_case)
+        return loaded_test_cases
+
+    def hook_preexecute(self):
+        # May override
+        pass
+
+    def execute(self, test_cases_to_execute):
+        self.hook_preexecute()
+        runner = self.hook_create_runner()
+
+        for test_case in test_cases_to_execute:
+            if self.options["raw"]:
+                util.logger.info(runner.run_test_case_raw(test_case.json))
+            else:
+                runner.run_test_case(test_case.json)
+
+class AbstractRunner(object):
+    def __init__(self, options):
+        pass
+
+    # may override
+    def code_directory(self):
+        return os.path.join(os.path.dirname(__file__), "..")
+
+    def log_diff(self, benchmark_output, user_output):
+        util.logger.info("Here is the head of your output:")
+        util.logger.info(user_output[0:1000])
+        diff = list(difflib.Differ().compare(benchmark_output.splitlines(True),
+                                             user_output.splitlines(True)))
+        lines = filter(lambda line: line[0] != "?", diff[0:20])
+        util.logger.info("\n***********\n")
+        util.logger.info("Here is the head of the diff between your output and the benchmark:")
+        util.logger.info("".join(lines))
+
+    def run_build_sh(self):
+        util.logger.info("Building your code via `build.sh`.")
+        build_runner = subprocess.Popen([
+            os.path.join(self.code_directory(), "build.sh")],
+            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        # Blocks
+        stdout, stderr = build_runner.communicate()
+        if build_runner.returncode == 0:
+            util.logger.info("Done building your code.")
+        else:
+            util.logger.info("Build failed with code %i. Stderr:", build_runner.returncode)
+            util.logger.info(stderr)
+
+    # may override
+    def hook_prerun(self):
+        pass
+
+    def run_test_case(self, test_case):
+        self.hook_prerun()
+        id = test_case['id']
+        util.logger.info("About to run test case: %s" % id)
+        input = test_case['input']
+        result = self.run_input(input)
+        return self.report_result(test_case, result)
+
+    def run_test_case_raw(self, test_case):
+        self.hook_prerun()
+        input = test_case['input']
+        result = self.run_input(input)
+        return result['output']
+
+    def run_input(self, input):
+        util.logger.info("Beginning run.")
+        output = self.run_subprocess_command(self.subprocess_command(), input)
+        util.logger.info('Finished run')
+        return output
+
+    def report_stderr(self, stderr):
+        if not stderr: return
+        util.logger.info("Standard error from trial run:")
+        util.logger.info(stderr)
+
+    def subprocess_communicate(self, runner, input):
+        if sys.version_info >= (3, 0):
+            input = input.encode('utf-8')
+        stdout, stderr = runner.communicate(input)
+        if sys.version_info >= (3, 0):
+            stderr = stderr.decode('utf-8')
+            stdout = stdout.decode('utf-8')
+        return stdout, stderr
+
+    def run_subprocess_command(self, command, input):
+        start_time = time.time()
+        runner = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        stdout, stderr = self.subprocess_communicate(runner, input)
+        end_time = time.time()
+        return {
+            'wall_clock_time': end_time - start_time,
+            'output': stdout,
+            'input': input,
+            'level': self.LEVEL,
+            'exitstatus': runner.returncode,
+            }
level2/test/lib/test_framework.pyc
Binary file
level2/test/lib/util.py
@@ -0,0 +1,68 @@
+import logging
+import os
+from random import SystemRandom
+import sys
+
+logger = logging.getLogger('stripe')
+logger.addHandler(logging.StreamHandler(sys.stdout))
+logger.setLevel(logging.INFO)
+
+__all__ = ['StringIO', 'json', 'utf8', 'random_letters', 'mkdir_p']
+
+if sys.version_info < (3,0):
+    # Used to interface with pycurl, which we only make available for
+    # those Python versions
+    try:
+        import cStringIO as StringIO
+    except ImportError:
+        import StringIO
+
+try:
+    import json
+except ImportError:
+    json = None
+
+if not (json and hasattr(json, 'loads')):
+    try:
+        import simplejson as json
+    except ImportError:
+        if not json:
+            raise ImportError(
+                "Stripe requires a JSON library, such as simplejson. "
+                "HINT: Try installing the "
+                "python simplejson library via 'pip install simplejson' or "
+                "'easy_install simplejson'.")
+        else:
+            raise ImportError(
+                "Stripe requires a JSON library with the same interface as "
+                "the Python 2.6 'json' library.  You appear to have a 'json' "
+                "library with a different interface.  Please install "
+                "the simplejson library.  HINT: Try installing the "
+                "python simplejson library via 'pip install simplejson' "
+                "or 'easy_install simplejson'.")
+
+
+def utf8(value):
+    if sys.version_info < (3, 0) and isinstance(value, unicode):
+        return value.encode('utf-8')
+    else:
+        return value
+
+def random_letters(count=4):
+    LETTERS = "abcdefghijklmnopqrstuvwxyz"
+    output = []
+    for i in range(0, count):
+        output.append(SystemRandom().choice(LETTERS))
+    return "".join(output)
+
+# TODO: Python >2.5 ?
+def mkdir_p(path):
+    try:
+        os.makedirs(path)
+    except OSError:
+        if os.path.isdir(path): pass
+        else: raise
+
+def exception_as():
+    _, err, _ = sys.exc_info()
+    return err
level2/test/lib/util.pyc
Binary file
level2/test/__init__.py
level2/test/harness
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+
+import subprocess
+
+# From this package
+import runner
+import lib.test_framework
+
+# Deprecated in 2.7 in favor of argparse but not yet removed.
+from optparse import OptionParser
+
+class Harness(lib.test_framework.AbstractHarness):
+    LEVEL = 2
+    VERSION = 1
+    DEFAULT_TEST_CASES = [
+        "level2-aUSeyGpde8",
+        "level2-x9NNVGpudm",
+        "level2-SqSgpc5BeY",
+        "level2-Fi8KAtxHBe",
+        "level2-DsuFanfu0x"
+    ]
+
+    def __init__(self, ids_or_urls=[], options={}):
+        super(Harness, self).__init__(ids_or_urls, options)
+
+    def hook_create_runner(self):
+        return runner.Runner({})
+
+def main():
+    default_options = {"task": "execute", "raw": False}
+    usage = "usage: %prog [options] [test case URL or id]"
+    parser = OptionParser(usage=usage)
+    parser.add_option("-r", "--raw", action="store_true", dest="raw", help="Print the raw output of your solution.")
+    (options, args) = parser.parse_args()
+    options_dict = vars(options)
+
+    for key in default_options:
+        if options_dict.get(key) == None:
+            options_dict[key] = default_options[key]
+
+    harness = Harness(args, options_dict)
+    harness.run()
+
+if __name__ == "__main__":
+  main()
+
level2/test/runner.py
@@ -0,0 +1,115 @@
+import os.path
+import subprocess
+
+# From this package
+import lib.test_framework as test_framework
+import lib.util as util
+
+class Runner(test_framework.AbstractRunner):
+    LEVEL = 2
+
+    def __init__(self, options):
+        super(Runner, self).__init__(options)
+        self.secret = util.random_letters(16)
+        self.client_port = "3000"
+        self.backend_ports = ["3001", "3002"]
+        self.results_path = os.path.join(
+            test_framework.data_directory,
+            "results-%s.json" % self.secret)
+
+    def code_directory(self):
+        return os.path.join(os.path.dirname(__file__), "..")
+
+    def hook_prerun(self):
+        self.run_build_sh()
+
+    def read_result_file(self, path):
+        try:
+            f = open(path)
+        except IOError:
+            return None
+        results = util.json.load(f)
+        f.close()
+        return results
+
+    def score(self, results):
+        return max(0.01, results['good_responses'] - results['backend_deficit'] / 8.0)
+
+    def spinup_backend(self, port):
+        return subprocess.Popen([
+                os.path.join(self.code_directory(), "network_simulation", "backend.js"),
+                "--secret", self.secret,
+                "--in-port", port])
+
+    # overrides
+    def run_input(self, input):
+        util.logger.info("Beginning run.")
+        backend_runners = []
+        for port in self.backend_ports:
+            backend_runners.append(self.spinup_backend(port))
+        shield_runner = subprocess.Popen([
+            os.path.join(self.code_directory(), "shield"),
+            "--in-port", self.client_port,
+            "--out-ports", ",".join(self.backend_ports)])
+        sword_runner = subprocess.Popen([
+            os.path.join(self.code_directory(), "network_simulation", "sword.js"),
+            "--secret", self.secret,
+            "--out-port", self.client_port,
+            "--results-path", self.results_path, input],
+            stdin=subprocess.PIPE)
+        # Blocks:
+        stdout, stderr = sword_runner.communicate()
+        for br in backend_runners: br.terminate()
+        shield_runner.terminate()
+        util.logger.info('Finished run')
+        results = self.read_result_file(self.results_path)
+        if results != None:
+            output_dictionary = {
+                'score': self.score(results),
+                'good_responses': results['good_responses'],
+                'backend_deficit': results['backend_deficit'],
+                'correct': results['correct'],
+                'results': results
+                }
+        else:
+            output_dictionary = {
+                'correct': False,
+                'unclean_description': "`sword.js` did not write a results file"
+                }
+        output_dictionary.update({
+            'input': input,
+            'level': self.LEVEL,
+            'exitstatus': sword_runner.returncode,
+            })
+        return output_dictionary
+
+    def report_result(self, test_case, result):
+        returncode = result['exitstatus']
+
+        if returncode != 0:
+            util.logger.info('Your `shield` exited uncleanly. Exit code: %i',
+                             returncode)
+        elif not result['correct']:
+            util.logger.error("Test case failed. Reason: %s", result['unclean_description'])
+        else:
+            benchmark_score = test_case['score']
+            your_score = result['score']
+            score_ratio = (your_score + 0.0) / benchmark_score
+            your_good_responses = result['results']['good_responses']
+            your_deficit = round(result['results']['backend_deficit'] / 8.0, 2)
+            benchmark_good_responses = test_case['results']['good_responses']
+            benchmark_deficit = round(test_case['results']['backend_deficit'] / 8.0, 2)
+            msg = ("Test case passed. Your score: %(your_score)f. Benchmark score: "
+                   "%(benchmark_score)f. You/Benchmark: %(score_ratio)f."
+                   "You handled %(your_good_responses)s legitimate responses and "
+                   "you received %(your_deficit)s negative points for idle time "
+                   "on the backends. The benchmark handled %(benchmark_good_responses)s "
+                   "and received %(benchmark_deficit)s negative points.")
+            util.logger.info(msg,
+                             {"your_score": your_score,
+                              "benchmark_score": benchmark_score,
+                              "score_ratio": score_ratio,
+                              "your_good_responses": your_good_responses,
+                              "your_deficit": your_deficit,
+                              "benchmark_good_responses": benchmark_good_responses,
+                              "benchmark_deficit": benchmark_deficit})
level2/test/runner.pyc
Binary file
level2/build.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+# Add any build steps you need here
+
+npm install underscore
+npm install nopt
+npm install seed-random
level2/package.json
@@ -0,0 +1,19 @@
+{
+  "name": "stripectf3-level2-ddos",
+  "version": "0.1.0",
+  "description": "Stripe CTF-3: Distributed Systems. Balance distributed traffic.",
+  "main": "shield",
+  "scripts": {
+    "test": "echo \"No tests supplied.\" && exit 1"
+  },
+  "author": {
+    "name": "Christian Anderson"
+  },
+  "license": "BSD",
+  "dependencies": {
+    "http-proxy": "*",
+    "nopt": "*",
+    "underscore": "*",
+    "seed-random": "*"
+  }
+}
level2/README.md
@@ -0,0 +1,84 @@
+# Level 2
+
+## Getting started
+
+As in Level 0, you can run this level using `test/harness` and you can it
+submit for scoring using `git push`. Your goal is to modify the
+reverse proxy in `shield` to create a defensive proxy that mitigates
+floods of malicious traffic. When you submit your `shield` for
+scoring, we will run it using a copy of the network simulation code
+that is available in `network_simulation/`.
+
+To run the provide code, you will need a Node.js installation. See:
+
+  http://nodejs.org
+
+## Building
+
+The provided `./build.sh` will install the level's Node.js
+dependencies using the Node Package Manager (`npm`). Remember that
+this build script will be run on our scoring servers to build your
+submitted code, and you can modify it however you want.
+
+## Included files
+
+* `./shield` and `./build.sh`: You should modify these!
+* `./network_simulation/`: a copy of the simulation code that we run on
+  our servers. They are provided for your reference and for local
+  simulations. We maintain our own copies of them for purposes of
+  scoring.
+** `backend.js`: the code for the fragile backend servers
+** `sword.js` the code that simulates the malicious and legitimate
+  traffic against the backends
+* `./network_simulation/lib/`: some Node.js modules that are shared by the rest of the
+  included code. Yours to modify (although you often won't need to).
+
+## The simulation and scoring
+
+### The layout of the simulated network
+
+The network has three components: your proxy (`shield`), backends, and
+clients. The standard configuration, used for scoring and used by
+`test/harness`, is to have a backend listen on port 3001 and a second
+on 3002. Then `shield` connects to the two backends and listens on
+port `3000`. Then `sword.js` is run, and it simulates a swarm of many
+clients (some legitimate and some malicious) connecting to the proxy.
+Although there are two backends in the scoring simulation, the stub
+code in `shield` does not perform any load balancing unless you modify
+it to do so.
+
+Technical note: the simulation framework uses HEAD requests to check
+the upness of the `shield` and backends (it won't run until they're
+up). If you are doing a major re-write, you should preserve the
+current semantics around HEAD (see the `sword.js` source for more).
+
+## The scoring
+
+In the simulated environment, there are a large number of legitimate
+clients making just a few requests each. There are also a small
+number of malicious clients making an enormous number of requests
+each. Think of these as mice and elephants: the goal of the level is
+to let the mice through while keeping the elephants out.
+
+The scoring simulation runs for 20 seconds. During that period, you
+receive one point each time that you successfully proxy a response to
+a request that was made by a mouse. At the end of the 20 seconds, you
+lose points in proportion to the idleness of your backend boxes
+(i.e. if you don't have an opportunity to proxy a mouse request, it is
+better to proxy an elephant request than do nothing) [1]. The lowest
+official score is 0.01 (there are no negative scores).
+
+When the requests are coming in they are not labeled as
+legitimate or malicious. However, the originating IP is
+identified by the 'X-Forwarded-For' header on each packet. You can
+determine, by watching the network whether an IP is malicious. A
+given IP is either always malicious or always legitimate
+
+[1] The gory details of scoring follow: You get +1 point for proxying a
+response to a request that was made my a mouse. Then there's the issue
+of backend downtime. The backends working together at full capacity
+can process a theoretical maximum of N requests per simulation (the
+value of N depends on the simulation constants, which you can
+investigate in `sword.js`). If you instead handle M < N requests, you
+receive negative (N - M) / 8 points. It does not matter, for this part
+of the scoring, whether you are handling mice or elephants.
level2/shield
@@ -0,0 +1,161 @@
+#!/usr/bin/env node
+
+"use strict";
+
+var http = require('http');
+var httpProxy = require('./network_simulation/lib/proxy');
+var checkServer = require('./network_simulation/lib/check_server');
+var nopt = require('nopt');
+var url = require('url');
+var pending = 0; // gotta love global vars
+
+var RequestData = function (request, response, buffer) {
+  this.request = request;
+  this.response = response;
+  this.buffer = buffer;
+};
+
+function ipFromRequest(reqData) {
+  return reqData.request.headers['x-forwarded-for'];
+}
+
+function rejectRequest(reqData) {
+  reqData.response.writeHead(400);
+  reqData.response.end();
+}
+
+var Queue = function (proxies, parameters, users, last_sent) {
+  this.proxies = proxies;
+  this.parameters = parameters;
+  this.users = users;
+  this.last_sent = last_sent;
+};
+
+Queue.prototype.takeRequest = function (reqData) {
+  // Reject traffic as necessary:
+  var req_ip = ipFromRequest(reqData);
+  var now = new Date().getTime();
+  var bursty_threshold = 120;
+  var idle_threshold = 4;
+
+  // maximize utility always
+  if (pending < idle_threshold) {
+    // proxy it through
+    var rand = Math.floor(Math.random()*this.proxies.length);
+    this.proxies[rand].proxyRequest(reqData.request, reqData.response, reqData.buffer);
+    pending += 1;
+
+  } else {
+    if (req_ip in this.users) {
+     
+      // check for blacklist
+      if (this.users[req_ip]["blacklist"]) {
+        rejectRequest(reqData);
+        return;
+      }
+
+      // check for bad behavior 
+      else if (now - this.users[req_ip]["last"] < bursty_threshold) {
+        //console.log("BLACKLISTED! %s, last:%d(ms), total:%d(requests)", req_ip.substring(0,8),  now - this.users[req_ip]["last"], this.users[req_ip]["total"]+1);
+        this.users[req_ip]["blacklist"] = true;
+        this.users[req_ip]["total"] = 0;
+        rejectRequest(reqData);
+        return;
+      }
+
+    } else {
+      // new user to track
+      this.users[req_ip] = new Object();
+      this.users[req_ip]["total"] =  0;
+      this.users[req_ip]["blacklist"] = false;
+    } 
+
+    // update user stats
+    this.users[req_ip]["total"] = this.users[req_ip]["total"] + 1;
+    this.users[req_ip]["last"] = new Date().getTime();
+    
+    // proxy it through
+    var rand = Math.floor(Math.random()*this.proxies.length);
+    this.proxies[rand].proxyRequest(reqData.request, reqData.response, reqData.buffer);
+    pending += 1;
+  }
+};
+
+Queue.prototype.requestFinished = function () {
+  // remove one from the pending counter
+  if (pending > 0){
+    pending -= 1;
+  }
+  return;
+};
+
+function checkBackends(targets, path, response) {
+  var toCheck = targets.map(function (target) {
+    var output = {};
+    output['host'] = target['host'];
+    output['port'] = target['port'];
+    output['path'] = path;
+    return output;
+  });
+  var success = function () {
+    response.writeHead(200, {"Content-Type": "application/json"});
+    response.end()
+  };
+  var error = function () {
+    response.writeHead(500, {"Content-Type": "application/json"});
+    response.end()
+  };
+  checkServer.checkServers(toCheck, success, error);
+}
+
+function main() {
+  var opts = {
+    "out-ports": String,
+    "in-port": String,
+  };
+  var parsed = nopt(opts),
+      inPort = parsed['in-port'] || '3000',
+      outPorts = parsed['out-ports'] ? parsed['out-ports'].split(",") : ['3001'],
+      targets = [],
+      target,
+      proxies = [],
+      proxy,
+      i;
+
+  for (i = 0; i < outPorts.length; i++) {
+    target = {'host': 'localhost', 'port': outPorts[i]};
+    targets.push(target);
+    proxy = new httpProxy.HttpProxy({'target': target});
+    proxy.identifier = i;
+    proxies.push(proxy);
+  }
+ 
+  var users = new Object();
+  var queue = new Queue(proxies, {}, users, new Date().getTime());
+  for (i = 0; i < proxies.length; i++) {
+    proxy = proxies[i];
+    proxy.on("end", queue.requestFinished);
+  }
+
+  var server = http.createServer(function (req, res) {
+    if (req.method === "HEAD") {
+      // HEAD requests are used to monitor the status of the simulation
+      // proxies[0].proxyRequest(reqData.request, reqData.response, reqData.buffer);
+      checkBackends(targets, url.parse(req.url)['pathname'], res);
+    } else {
+      var buffer = httpProxy.buffer(req);
+      var reqData = new RequestData(req, res, buffer);
+      queue.takeRequest(reqData);
+    }
+  });
+
+  server.on('close', function () {
+    for (i = 0; i < proxies.length; i++) {
+      proxies[i].close();
+    }
+  });
+  console.log("The shield is up and listening.");
+  server.listen(inPort);
+}
+
+main();