text: // Load modules
  
  var Fs = require('fs');
  var Escape = require('./escape');
  
  // Declare internals
  
  var internals = {};
  
  // Clone object or array
  
  exports.clone = function (obj, seen) {
  
      if (typeof obj !== 'object' ||
          obj === null) {
  
          return obj;
      }
  
      seen = seen || {orig: [], copy: []};
  
      var lookup = seen.orig.indexOf(obj);
      if (lookup !== -1) {
          return seen.copy[lookup];
      }
  
      var newObj = (obj instanceof Array) ? [] : {};
  
      seen.orig.push(obj);
      seen.copy.push(newObj);
  
      for (var i in obj) {
          if (obj.hasOwnProperty(i)) {
              if (obj[i] instanceof Buffer) {
                  newObj[i] = new Buffer(obj[i]);
              }
              else if (obj[i] instanceof Date) {
                  newObj[i] = new Date(obj[i].getTime());
              }
              else if (obj[i] instanceof RegExp) {
                  var flags = '' + (obj[i].global ? 'g' : '') + (obj[i].ignoreCase ? 'i' : '') + (obj[i].multiline ? 'm'
                          : '');
                  newObj[i] = new RegExp(obj[i].source, flags);
              }
              else {
                  newObj[i] = exports.clone(obj[i], seen);
              }
          }
      }
  
      return newObj;
  };
  
  // Merge all the properties of source into target, source wins in conflic, and by default null and undefined from source are applied
  
  exports.merge = function (target, source, isNullOverride /* = true */, isMergeArrays /* = true */) {
  
      exports.assert(target && typeof target == 'object', 'Invalid target value: must be an object');
      exports.assert(source === null || source === undefined || typeof source === 'object',
          'Invalid source value: must be null, undefined, or an object');
  
      if (!source) {
          return target;
      }
  
      if (source instanceof Array) {
          exports.assert(target instanceof Array, 'Cannot merge array onto an object');
          if (isMergeArrays === false) {                                                  // isMergeArrays defaults to true
              target.length = 0;                                                          // Must not change target assignment
          }
  
          for (var i = 0, il = source.length; i < il; ++i) {
              target.push(source[i]);
          }
  
          return target;
      }
  
      var keys = Object.keys(source);
      for (var k = 0, kl = keys.length; k < kl; ++k) {
          var key = keys[k];
          var value = source[key];
          if (value &&
              typeof value === 'object') {
  
              if (!target[key] ||
                  typeof target[key] !== 'object') {
  
                  target[key] = exports.clone(value);
              }
              else {
                  exports.merge(target[key], source[key], isNullOverride, isMergeArrays);
              }
          }
          else {
              if (value !== null && value !== undefined) {            // Explicit to preserve empty strings
                  target[key] = value;
              }
              else if (isNullOverride !== false) {                    // Defaults to true
                  target[key] = value;
              }
          }
      }
  
      return target;
  };
  
  // Apply options to a copy of the defaults
  
  exports.applyToDefaults = function (defaults, options) {
  
      exports.assert(defaults && typeof defaults == 'object', 'Invalid defaults value: must be an object');
      exports.assert(!options || options === true || typeof options === 'object',
          'Invalid options value: must be true, falsy or an object');
  
      if (!options) {                                                 // If no options, return null
          return null;
      }
  
      var copy = exports.clone(defaults);
  
      if (options === true) {                                         // If options is set to true, use defaults
          return copy;
      }
  
      return exports.merge(copy, options, false, false);
  };
  
  // Remove duplicate items from array
  
  exports.unique = function (array, key) {
  
      var index = {};
      var result = [];
  
      for (var i = 0, il = array.length; i < il; ++i) {
          var id = (key ? array[i][key] : array[i]);
          if (index[id] !== true) {
  
              result.push(array[i]);
              index[id] = true;
          }
      }
  
      return result;
  };
  
  // Convert array into object
  
  exports.mapToObject = function (array, key) {
  
      if (!array) {
          return null;
      }
  
      var obj = {};
      for (var i = 0, il = array.length; i < il; ++i) {
          if (key) {
              if (array[i][key]) {
                  obj[array[i][key]] = true;
              }
          }
          else {
              obj[array[i]] = true;
          }
      }
  
      return obj;
  };
  
  // Find the common unique items in two arrays
  
  exports.intersect = function (array1, array2, justFirst) {
  
      if (!array1 || !array2) {
          return [];
      }
  
      var common = [];
      var hash = (array1 instanceof Array ? exports.mapToObject(array1) : array1);
      var found = {};
      for (var i = 0, il = array2.length; i < il; ++i) {
          if (hash[array2[i]] && !found[array2[i]]) {
              if (justFirst) {
                  return array2[i];
              }
  
              common.push(array2[i]);
              found[array2[i]] = true;
          }
      }
  
      return (justFirst ? null : common);
  };
  
  // Find which keys are present
  
  exports.matchKeys = function (obj, keys) {
  
      var matched = [];
      for (var i = 0, il = keys.length; i < il; ++i) {
          if (obj.hasOwnProperty(keys[i])) {
              matched.push(keys[i]);
          }
      }
      return matched;
  };
  
  // Flatten array
  
  exports.flatten = function (array, target) {
  
      var result = target || [];
  
      for (var i = 0, il = array.length; i < il; ++i) {
          if (Array.isArray(array[i])) {
              exports.flatten(array[i], result);
          }
          else {
              result.push(array[i]);
          }
      }
  
      return result;
  };
  
  // Remove keys
  
  exports.removeKeys = function (object, keys) {
  
      for (var i = 0, il = keys.length; i < il; i++) {
          delete object[keys[i]];
      }
  };
  
  // Convert an object key chain string ('a.b.c') to reference (object[a][b][c])
  
  exports.reach = function (obj, chain) {
  
      var path = chain.split('.');
      var ref = obj;
      for (var i = 0, il = path.length; i < il; ++i) {
          if (ref) {
              ref = ref[path[i]];
          }
      }
  
      return ref;
  };
  
  // Inherits a selected set of methods from an object, wrapping functions in asynchronous syntax and catching errors
  
  exports.inheritAsync = function (self, obj, keys) {
  
      keys = keys || null;
  
      for (var i in obj) {
          if (obj.hasOwnProperty(i)) {
              if (keys instanceof Array &&
                  keys.indexOf(i) < 0) {
  
                  continue;
              }
  
              self.prototype[i] = (function (fn) {
  
                  return function (next) {
  
                      var result = null;
                      try {
                          result = fn();
                      }
                      catch (err) {
                          return next(err);
                      }
  
                      return next(null, result);
                  };
              })(obj[i]);
          }
      }
  };
  
  exports.formatStack = function (stack) {
  
      var trace = [];
      for (var i = 0, il = stack.length; i < il; ++i) {
          var item = stack[i];
          trace.push([item.getFileName(), item.getLineNumber(), item.getColumnNumber(), item.getFunctionName(),
              item.isConstructor()]);
      }
  
      return trace;
  };
  
  exports.formatTrace = function (trace) {
  
      var display = [];
  
      for (var i = 0, il = trace.length; i < il; ++i) {
          var row = trace[i];
          display.push((row[4] ? 'new ' : '') + row[3] + ' (' + row[0] + ':' + row[1] + ':' + row[2] + ')');
      }
  
      return display;
  };
  
  exports.callStack = function (slice) {
  
      // http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
  
      var v8 = Error.prepareStackTrace;
      Error.prepareStackTrace = function (err, stack) {
  
          return stack;
      };
  
      var capture = {};
      Error.captureStackTrace(capture, arguments.callee);
      var stack = capture.stack;
  
      Error.prepareStackTrace = v8;
  
      var trace = exports.formatStack(stack);
  
      if (slice) {
          return trace.slice(slice);
      }
  
      return trace;
  };
  
  exports.displayStack = function (slice) {
  
      var trace = exports.callStack(slice === undefined ? 1 : slice + 1);
  
      return exports.formatTrace(trace);
  };
  
  exports.abortThrow = false;
  
  exports.abort = function (message, hideStack) {
  
      if (process.env.NODE_ENV === 'test' || exports.abortThrow === true) {
          throw new Error(message || 'Unknown error');
      }
  
      var stack = '';
      if (!hideStack) {
          stack = exports.displayStack(1).join('\n\t');
      }
      console.log('ABORT: ' + message + '\n\t' + stack);
      process.exit(1);
  };
  
  exports.assert = function (condition /*, msg1, msg2, msg3 */) {
  
      if (condition) {
          return;
      }
  
      var msgs = Array.prototype.slice.call(arguments, 1);
      msgs = msgs.map(function (msg) {
  
          return typeof msg === 'string' ? msg : msg instanceof Error ? msg.message : JSON.stringify(msg);
      });
      throw new Error(msgs.join(' ') || 'Unknown error');
  };
  
  exports.loadDirModules = function (path, excludeFiles, target) {      // target(filename, name, capName)
  
      var exclude = {};
      for (var i = 0, il = excludeFiles.length; i < il; ++i) {
          exclude[excludeFiles[i] + '.js'] = true;
      }
  
      var files = Fs.readdirSync(path);
      for (i = 0, il = files.length; i < il; ++i) {
          var filename = files[i];
          if (/\.js$/.test(filename) && !exclude[filename]) {
  
              var name = filename.substr(0, filename.lastIndexOf('.'));
              var capName = name.charAt(0).toUpperCase() + name.substr(1).toLowerCase();
  
              if (typeof target !== 'function') {
                  target[capName] = require(path + '/' + name);
              }
              else {
                  target(path + '/' + name, name, capName);
              }
          }
      }
  };
  
  exports.rename = function (obj, from, to) {
  
      obj[to] = obj[from];
      delete obj[from];
  };
  
  exports.Timer = function () {
  
      this.reset();
  };
  
  exports.Timer.prototype.reset = function () {
  
      this.ts = Date.now();
  };
  
  exports.Timer.prototype.elapsed = function () {
  
      return Date.now() - this.ts;
  };
  
  // Load and parse package.json process root or given directory
  
  exports.loadPackage = function (dir) {
  
      var result = {};
      var filepath = (dir || process.env.PWD) + '/package.json';
      if (Fs.existsSync(filepath)) {
          try {
              result = JSON.parse(Fs.readFileSync(filepath));
          }
          catch (e) {
          }
      }
  
      return result;
  };
  
  // Escape string for Regex construction
  
  exports.escapeRegex = function (string) {
  
      // Escape ^$.*+-?=!:|\/()[]{},
      return string.replace(/[\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}\,]/g, '\\$&');
  };
  
  // Return an error as first argument of a callback
  
  exports.toss = function (condition /*, [message], next */) {
  
      var message = (arguments.length === 3 ? arguments[1] : '');
      var next = (arguments.length === 3 ? arguments[2] : arguments[1]);
  
      var err = (message instanceof Error ? message : (message ? new Error(message) : (condition instanceof Error
                  ? condition : new Error())));
  
      if (condition instanceof Error || !condition) {
  
          return next(err);
      }
  };
  
  // Base64url (RFC 4648) encode
  
  exports.base64urlEncode = function (value) {
  
      return (new Buffer(value, 'binary')).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
  };
  
  // Base64url (RFC 4648) decode
  
  exports.base64urlDecode = function (encoded) {
  
      if (encoded && !encoded.match(/^[\w\-]*$/)) {
  
          return new Error('Invalid character');
      }
  
      try {
          return (new Buffer(encoded.replace(/-/g, '+').replace(/:/g, '/'), 'base64')).toString('binary');
      }
      catch (err) {
          return err;
      }
  };
  
  // Escape attribute value for use in HTTP header
  
  exports.escapeHeaderAttribute = function (attribute) {
  
      // Allowed value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9, \, "
  
      exports.assert(attribute.match(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~\"\\]*$/),
          'Bad attribute value (' + attribute + ')');
  
      return attribute.replace(/\\/g, '\\\\').replace(/\"/g, '\\"');                             // Escape quotes and slash
  };
  
  exports.escapeHtml = function (string) {
  
      return Escape.escapeHtml(string);
  };
  
  exports.escapeJavaScript = function (string) {
  
      return Escape.escapeJavaScript(string);
  };
  
  /*
   var event = {
   timestamp: now.getTime(),
   tags: ['tag'],
   data: { some: 'data' }
   };
   */
  
  exports.consoleFunc = console.log;
  
  exports.printEvent = function (event) {
  
      var pad = function (value) {
  
          return (value < 10 ? '0' : '') + value;
      };
  
      var now = new Date(event.timestamp);
      var timestring = (now.getYear() - 100).toString() +
          pad(now.getMonth() + 1) +
          pad(now.getDate()) +
          '/' +
          pad(now.getHours()) +
          pad(now.getMinutes()) +
          pad(now.getSeconds()) +
          '.' +
          now.getMilliseconds();
  
      var data = event.data;
      if (typeof event.data !== 'string') {
          try {
              data = JSON.stringify(event.data);
          }
          catch (e) {
              data = 'JSON Error: ' + e.message;
          }
      }
  
      var output = timestring + ', ' + event.tags[0] + ', ' + data;
      exports.consoleFunc(output);
  };
  
  exports.nextTick = function (callback) {
  
      return function () {
  
          var args = arguments;
          process.nextTick(function () {
  
              callback.apply(null, args);
          });
      };
  };
  