text: // Load modules
  
  var Crypto = require('crypto');
  var Path = require('path');
  var Util = require('util');
  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;
      var cloneDeep = false;
  
      if (!Array.isArray(obj)) {
          if (Buffer.isBuffer(obj)) {
              newObj = new Buffer(obj);
          }
          else if (obj instanceof Date) {
              newObj = new Date(obj.getTime());
          }
          else if (obj instanceof RegExp) {
              newObj = new RegExp(obj);
          }
          else {
              var proto = Object.getPrototypeOf(obj);
              if (!proto || proto.isImmutable) {
                  newObj = obj;
              }
              else {
                  newObj = Object.create(proto);
                  cloneDeep = true;
              }
          }
      }
      else {
          newObj = [];
          cloneDeep = true;
      }
  
      seen.orig.push(obj);
      seen.copy.push(newObj);
  
      if (cloneDeep) {
          for (var i in obj) {
              if (obj.hasOwnProperty(i)) {
                  var descriptor = Object.getOwnPropertyDescriptor(obj, i);
                  if (descriptor.get ||
                      descriptor.set) {
  
                      Object.defineProperty(newObj, i, descriptor);
                  }
                  else {
                      newObj[i] = exports.clone(obj[i], seen);
                  }
              }
          }
      }
  
      return newObj;
  };
  
  
  // Merge all the properties of source into target, source wins in conflict, 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 (Array.isArray(source)) {
          exports.assert(Array.isArray(target), '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(exports.clone(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' ||
                  (Array.isArray(target[key]) ^ Array.isArray(value)) ||
                  value instanceof Date ||
                  Buffer.isBuffer(value) ||
                  value instanceof RegExp) {
  
                  target[key] = exports.clone(value);
              }
              else {
                  exports.merge(target[key], value, 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);
  };
  
  
  // Clone an object except for the listed keys which are shallow copied
  
  exports.cloneWithShallow = function (source, keys) {
  
      if (!source ||
          typeof source !== 'object') {
  
          return source;
      }
  
      return internals.withShallow(source, keys, function () {
  
          return exports.clone(source);
      });
  };
  
  
  // Apply options to defaults except for the listed keys which are shallow copied from option without merging
  
  exports.applyToDefaultsWithShallow = function (defaults, options, keys) {
  
      return internals.withShallow(options, keys, function () {
  
          return exports.applyToDefaults(defaults, options);
      });
  };
  
  
  internals.withShallow = function (source, keys, clone) {
  
      // Move shallow copy items to storage
  
      var storage = {};
      for (var i = 0, il = keys.length; i < il; ++i) {
          var key = keys[i];
          if (source.hasOwnProperty(key)) {
              storage[key] = source[key];
              source[key] = undefined;
          }
      }
  
      // Deep copy the rest
  
      var copy = clone();
  
      // Shallow copy the stored items
  
      for (i = 0; i < il; ++i) {
          var key = keys[i];
          if (storage.hasOwnProperty(key)) {
              source[key] = storage[key];
              copy[key] = storage[key];
          }
      }
  
      return copy;
  };
  
  
  // Deep object or array comparison
  
  exports.deepEqual = function (obj, ref, seen) {
  
      var type = typeof obj;
      if (type !== typeof ref) {
          return false;
      }
  
      if (type !== 'object' ||
          obj === null ||
          ref === null) {
  
          if (obj === ref) {                                                      // Copied from Deep-eql, copyright(c) 2013 Jake Luer, jake@alogicalparadox.com, MIT Licensed, https://github.com/chaijs/deep-eql
              return obj !== 0 || 1 / obj === 1 / ref;        // -0 / +0
          }
  
          return obj !== obj && ref !== ref;                  // NaN
      }
  
      seen = seen || [];
      if (seen.indexOf(obj) !== -1) {
          return true;                            // If previous comparison failed, it would have stopped execution
      }
  
      seen.push(obj);
  
      if (Array.isArray(obj)) {
          if (!Array.isArray(ref)) {
              return false;
          }
  
          if (obj.length !== ref.length) {
              return false;
          }
  
          for (var i = 0, il = obj.length; i < il; ++i) {
              if (!exports.deepEqual(obj[i], ref[i])) {
                  return false;
              }
          }
  
          return true;
      }
  
      if (Buffer.isBuffer(obj)) {
          if (!Buffer.isBuffer(ref)) {
              return false;
          }
  
          if (obj.length !== ref.length) {
              return false;
          }
  
          for (var i = 0, il = obj.length; i < il; ++i) {
              if (obj[i] !== ref[i]) {
                  return false;
              }
          }
  
          return true;
      }
  
      if (obj instanceof Date) {
          return (ref instanceof Date && obj.getTime() === ref.getTime());
      }
  
      if (obj instanceof RegExp) {
          return (ref instanceof RegExp && obj.toString() === ref.toString());
      }
  
      if (Object.getPrototypeOf(obj) !== Object.getPrototypeOf(ref)) {
          return false;
      }
  
      var keys = Object.keys(obj);
      for (var i = 0, l = keys.length; i < l; ++i) {
          var key = keys[i];
          var descriptor = Object.getOwnPropertyDescriptor(obj, key);
          if (descriptor.get) {
              if (!exports.deepEqual(descriptor, Object.getOwnPropertyDescriptor(ref, key), seen)) {
                  return false;
              }
          }
          else if (!exports.deepEqual(obj[key], ref[key], seen)) {
              return false;
          }
      }
  
      return true;
  };
  
  
  // 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 = (Array.isArray(array1) ? 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);
  };
  
  
  // Test if the reference contains the values
  
  exports.contain = function (ref, values, options) {
  
      /*
          string -> string(s)
          array -> item(s)
          object -> key(s)
          object -> object (key:value)
      */
  
      var valuePairs = null;
      if (typeof ref === 'object' &&
          typeof values === 'object' &&
          !Array.isArray(ref) &&
          !Array.isArray(values)) {
  
          valuePairs = values;
          values = Object.keys(values);
      }
      else {
          values = [].concat(values);
      }
  
      options = options || {};            // deep, once, only, part
  
      exports.assert(arguments.length >= 2, 'Insufficient arguments');
      exports.assert(typeof ref === 'string' || typeof ref === 'object', 'Reference must be string or an object');
      exports.assert(values.length, 'Values array cannot be empty');
  
      var compare = options.deep ? exports.deepEqual : function (a, b) { return a === b; };
  
      var misses = false;
      var matches = new Array(values.length);
      for (var i = 0, il = matches.length; i < il; ++i) {
          matches[i] = 0;
      }
  
      if (typeof ref === 'string') {
          var pattern = '(';
          for (i = 0, il = values.length; i < il; ++i) {
              var value = values[i];
              exports.assert(typeof value === 'string', 'Cannot compare string reference to non-string value');
              pattern += (i ? '|' : '') + exports.escapeRegex(value);
          }
  
          var regex = new RegExp(pattern + ')', 'g');
          var leftovers = ref.replace(regex, function ($0, $1) {
  
              var index = values.indexOf($1);
              ++matches[index];
              return '';          // Remove from string
          });
  
          misses = !!leftovers;
      }
      else if (Array.isArray(ref)) {
          for (i = 0, il = ref.length; i < il; ++i) {
              for (var j = 0, jl = values.length, found = false; j < jl && found === false; ++j) {
                  found = compare(ref[i], values[j]) && j;
              }
  
              if (found !== false) {
                  ++matches[found]
              }
              else {
                  misses = true;
              }
          }
      }
      else {
          var keys = Object.keys(ref);
          for (i = 0, il = keys.length; i < il; ++i) {
              var key = keys[i];
              var found = values.indexOf(key);
              if (found !== -1) {
                  if (valuePairs &&
                      !compare(ref[key], valuePairs[key])) {
  
                      return false;
                  }
  
                  ++matches[found]
              }
              else {
                  misses = true;
              }
          }
      }
  
      var result = false;
      for (var i = 0, il = matches.length; i < il; ++i) {
          result = result || !!matches[i];
          if ((options.once && matches[i] > 1) ||
              (!options.part && !matches[i])) {
  
              return false;
          }
      }
  
      if (options.only &&
          misses) {
  
          return false;
      }
  
      return result;
  };
  
  
  // 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;
  };
  
  
  // Convert an object key chain string ('a.b.c') to reference (object[a][b][c])
  
  exports.reach = function (obj, chain, options) {
  
      options = options || {};
      if (typeof options === 'string') {
          options = { separator: options };
      }
  
      var path = chain.split(options.separator || '.');
      var ref = obj;
      for (var i = 0, il = path.length; i < il; ++i) {
          if (!ref ||
              !ref.hasOwnProperty(path[i]) ||
              (typeof ref !== 'object' && options.functions === false)) {         // Only object and function can have properties
  
              exports.assert(!options.strict || i + 1 === il, 'Missing segment', path[i], 'in reach path ', chain);
              exports.assert(typeof ref === 'object' || options.functions === true || typeof ref !== 'function', 'Invalid segment', path[i], 'in reach path ', chain);
              ref = options.default;
              break;
          }
  
          ref = ref[path[i]];
      }
  
      return ref;
  };
  
  
  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 = [];
      for (var i = 1, il = arguments.length; i < il; ++i) {
          if (arguments[i] !== '') {
              msgs.push(arguments[i]);            // Avoids Array.slice arguments leak, allowing for V8 optimizations
          }
      }
  
      msgs = msgs.map(function (msg) {
  
          return typeof msg === 'string' ? msg : msg instanceof Error ? msg.message : exports.stringify(msg);
      });
      throw new Error(msgs.join(' ') || 'Unknown error');
  };
  
  
  exports.Timer = function () {
  
      this.ts = 0;
      this.reset();
  };
  
  
  exports.Timer.prototype.reset = function () {
  
      this.ts = Date.now();
  };
  
  
  exports.Timer.prototype.elapsed = function () {
  
      return Date.now() - this.ts;
  };
  
  
  exports.Bench = function () {
  
      this.ts = 0;
      this.reset();
  };
  
  
  exports.Bench.prototype.reset = function () {
  
      this.ts = exports.Bench.now();
  };
  
  
  exports.Bench.prototype.elapsed = function () {
  
      return exports.Bench.now() - this.ts;
  };
  
  
  exports.Bench.now = function () {
  
      var ts = process.hrtime();
      return (ts[0] * 1e3) + (ts[1] / 1e6);
  };
  
  
  // Escape string for Regex construction
  
  exports.escapeRegex = function (string) {
  
      // Escape ^$.*+-?=!:|\/()[]{},
      return string.replace(/[\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}\,]/g, '\\$&');
  };
  
  
  // Base64url (RFC 4648) encode
  
  exports.base64urlEncode = function (value, encoding) {
  
      var buf = (Buffer.isBuffer(value) ? value : new Buffer(value, encoding || 'binary'));
      return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
  };
  
  
  // Base64url (RFC 4648) decode
  
  exports.base64urlDecode = function (value, encoding) {
  
      if (value &&
          !/^[\w\-]*$/.test(value)) {
  
          return new Error('Invalid character');
      }
  
      try {
          var buf = new Buffer(value, 'base64');
          return (encoding === 'buffer' ? buf : buf.toString(encoding || '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(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~\"\\]*$/.test(attribute), '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);
  };
  
  
  exports.nextTick = function (callback) {
  
      return function () {
  
          var args = arguments;
          process.nextTick(function () {
  
              callback.apply(null, args);
          });
      };
  };
  
  
  exports.once = function (method) {
  
      if (method._hoekOnce) {
          return method;
      }
  
      var once = false;
      var wrapped = function () {
  
          if (!once) {
              once = true;
              method.apply(null, arguments);
          }
      };
  
      wrapped._hoekOnce = true;
  
      return wrapped;
  };
  
  
  exports.isAbsolutePath = function (path, platform) {
  
      if (!path) {
          return false;
      }
  
      if (Path.isAbsolute) {                      // node >= 0.11
          return Path.isAbsolute(path);
      }
  
      platform = platform || process.platform;
  
      // Unix
  
      if (platform !== 'win32') {
          return path[0] === '/';
      }
  
      // Windows
  
      return !!/^(?:[a-zA-Z]:[\\\/])|(?:[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/])/.test(path);        // C:\ or \\something\something
  };
  
  
  exports.isInteger = function (value) {
  
      return (typeof value === 'number' &&
              parseFloat(value) === parseInt(value, 10) &&
              !isNaN(value));
  };
  
  
  exports.ignore = function () { };
  
  
  exports.inherits = Util.inherits;
  
  
  exports.format = Util.format;
  
  
  exports.transform = function (source, transform, options) {
  
      exports.assert(source == null || typeof source === 'object', 'Invalid source object: must be null, undefined, or an object');
  
      var result = {};
      var keys = Object.keys(transform);
  
      for (var k = 0, kl = keys.length; k < kl; ++k) {
          var key = keys[k];
          var path = key.split('.');
          var sourcePath = transform[key];
  
          exports.assert(typeof sourcePath === 'string', 'All mappings must be "." delineated strings');
  
          var segment;
          var res = result;
  
          while (path.length > 1) {
              segment = path.shift();
              if (!res[segment]) {
                  res[segment] = {};
              }
              res = res[segment];
          }
          segment = path.shift();
          res[segment] = exports.reach(source, sourcePath, options);
      }
  
      return result;
  };
  
  
  exports.uniqueFilename = function (path, extension) {
  
      if (extension) {
          extension = extension[0] !== '.' ? '.' + extension : extension;
      }
      else {
          extension = '';
      }
  
      path = Path.resolve(path);
      var name = [Date.now(), process.pid, Crypto.randomBytes(8).toString('hex')].join('-') + extension;
      return Path.join(path, name);
  };
  
  
  exports.stringify = function () {
  
      try {
          return JSON.stringify.apply(null, arguments);
      }
      catch (err) {
          return '[Cannot display object: ' + err.message + ']';
      }
  };
  
  
  exports.shallow = function (source) {
  
      var target = {};
      var keys = Object.keys(source);
      for (var i = 0, il = keys.length; i < il; ++i) {
          var key = keys[i];
          target[key] = source[key];
      }
  
      return target;
  };
  