3 var Crypto = require('crypto');
4 var Path = require('path');
5 var Util = require('util');
6 var Escape = require('./escape');
14 // Clone object or array
16 exports.clone = function (obj, seen) {
18 if (typeof obj !== 'object' ||
24 seen = seen || { orig: [], copy: [] };
26 var lookup = seen.orig.indexOf(obj);
28 return seen.copy[lookup];
32 var cloneDeep = false;
34 if (!Array.isArray(obj)) {
35 if (Buffer.isBuffer(obj)) {
36 newObj = new Buffer(obj);
38 else if (obj instanceof Date) {
39 newObj = new Date(obj.getTime());
41 else if (obj instanceof RegExp) {
42 newObj = new RegExp(obj);
45 var proto = Object.getPrototypeOf(obj);
52 newObj = Object.create(proto);
63 seen.copy.push(newObj);
66 var keys = Object.getOwnPropertyNames(obj);
67 for (var i = 0, il = keys.length; i < il; ++i) {
69 var descriptor = Object.getOwnPropertyDescriptor(obj, key);
74 Object.defineProperty(newObj, key, descriptor);
77 newObj[key] = exports.clone(obj[key], seen);
86 // Merge all the properties of source into target, source wins in conflict, and by default null and undefined from source are applied
88 exports.merge = function (target, source, isNullOverride /* = true */, isMergeArrays /* = true */) {
90 exports.assert(target && typeof target === 'object', 'Invalid target value: must be an object');
91 exports.assert(source === null || source === undefined || typeof source === 'object', 'Invalid source value: must be null, undefined, or an object');
97 if (Array.isArray(source)) {
98 exports.assert(Array.isArray(target), 'Cannot merge array onto an object');
99 if (isMergeArrays === false) { // isMergeArrays defaults to true
100 target.length = 0; // Must not change target assignment
103 for (var i = 0, il = source.length; i < il; ++i) {
104 target.push(exports.clone(source[i]));
110 var keys = Object.keys(source);
111 for (var k = 0, kl = keys.length; k < kl; ++k) {
113 var value = source[key];
115 typeof value === 'object') {
118 typeof target[key] !== 'object' ||
119 (Array.isArray(target[key]) ^ Array.isArray(value)) ||
120 value instanceof Date ||
121 Buffer.isBuffer(value) ||
122 value instanceof RegExp) {
124 target[key] = exports.clone(value);
127 exports.merge(target[key], value, isNullOverride, isMergeArrays);
131 if (value !== null &&
132 value !== undefined) { // Explicit to preserve empty strings
136 else if (isNullOverride !== false) { // Defaults to true
146 // Apply options to a copy of the defaults
148 exports.applyToDefaults = function (defaults, options, isNullOverride) {
150 exports.assert(defaults && typeof defaults === 'object', 'Invalid defaults value: must be an object');
151 exports.assert(!options || options === true || typeof options === 'object', 'Invalid options value: must be true, falsy or an object');
153 if (!options) { // If no options, return null
157 var copy = exports.clone(defaults);
159 if (options === true) { // If options is set to true, use defaults
163 return exports.merge(copy, options, isNullOverride === true, false);
167 // Clone an object except for the listed keys which are shallow copied
169 exports.cloneWithShallow = function (source, keys) {
172 typeof source !== 'object') {
177 var storage = internals.store(source, keys); // Move shallow copy items to storage
178 var copy = exports.clone(source); // Deep copy the rest
179 internals.restore(copy, source, storage); // Shallow copy the stored items and restore
184 internals.store = function (source, keys) {
187 for (var i = 0, il = keys.length; i < il; ++i) {
189 var value = exports.reach(source, key);
190 if (value !== undefined) {
191 storage[key] = value;
192 internals.reachSet(source, key, undefined);
200 internals.restore = function (copy, source, storage) {
202 var keys = Object.keys(storage);
203 for (var i = 0, il = keys.length; i < il; ++i) {
205 internals.reachSet(copy, key, storage[key]);
206 internals.reachSet(source, key, storage[key]);
211 internals.reachSet = function (obj, key, value) {
213 var path = key.split('.');
215 for (var i = 0, il = path.length; i < il; ++i) {
216 var segment = path[i];
218 ref[segment] = value;
226 // Apply options to defaults except for the listed keys which are shallow copied from option without merging
228 exports.applyToDefaultsWithShallow = function (defaults, options, keys) {
230 exports.assert(defaults && typeof defaults === 'object', 'Invalid defaults value: must be an object');
231 exports.assert(!options || options === true || typeof options === 'object', 'Invalid options value: must be true, falsy or an object');
232 exports.assert(keys && Array.isArray(keys), 'Invalid keys');
234 if (!options) { // If no options, return null
238 var copy = exports.cloneWithShallow(defaults, keys);
240 if (options === true) { // If options is set to true, use defaults
244 var storage = internals.store(options, keys); // Move shallow copy items to storage
245 exports.merge(copy, options, false, false); // Deep copy the rest
246 internals.restore(copy, options, storage); // Shallow copy the stored items and restore
251 // Deep object or array comparison
253 exports.deepEqual = function (obj, ref, options, seen) {
255 options = options || { prototype: true };
257 var type = typeof obj;
259 if (type !== typeof ref) {
263 if (type !== 'object' ||
267 if (obj === ref) { // Copied from Deep-eql, copyright(c) 2013 Jake Luer, jake@alogicalparadox.com, MIT Licensed, https://github.com/chaijs/deep-eql
268 return obj !== 0 || 1 / obj === 1 / ref; // -0 / +0
271 return obj !== obj && ref !== ref; // NaN
275 if (seen.indexOf(obj) !== -1) {
276 return true; // If previous comparison failed, it would have stopped execution
281 if (Array.isArray(obj)) {
282 if (!Array.isArray(ref)) {
286 if (!options.part && obj.length !== ref.length) {
290 for (var i = 0, il = obj.length; i < il; ++i) {
293 for (var r = 0, rl = ref.length; r < rl; ++r) {
294 if (exports.deepEqual(obj[i], ref[r], options, seen)) {
303 if (!exports.deepEqual(obj[i], ref[i], options, seen)) {
311 if (Buffer.isBuffer(obj)) {
312 if (!Buffer.isBuffer(ref)) {
316 if (obj.length !== ref.length) {
320 for (var j = 0, jl = obj.length; j < jl; ++j) {
321 if (obj[j] !== ref[j]) {
329 if (obj instanceof Date) {
330 return (ref instanceof Date && obj.getTime() === ref.getTime());
333 if (obj instanceof RegExp) {
334 return (ref instanceof RegExp && obj.toString() === ref.toString());
337 if (options.prototype) {
338 if (Object.getPrototypeOf(obj) !== Object.getPrototypeOf(ref)) {
343 var keys = Object.getOwnPropertyNames(obj);
345 if (!options.part && keys.length !== Object.getOwnPropertyNames(ref).length) {
349 for (var k = 0, kl = keys.length; k < kl; ++k) {
351 var descriptor = Object.getOwnPropertyDescriptor(obj, key);
352 if (descriptor.get) {
353 if (!exports.deepEqual(descriptor, Object.getOwnPropertyDescriptor(ref, key), options, seen)) {
357 else if (!exports.deepEqual(obj[key], ref[key], options, seen)) {
366 // Remove duplicate items from array
368 exports.unique = function (array, key) {
373 for (var i = 0, il = array.length; i < il; ++i) {
374 var id = (key ? array[i][key] : array[i]);
375 if (index[id] !== true) {
377 result.push(array[i]);
386 // Convert array into object
388 exports.mapToObject = function (array, key) {
395 for (var i = 0, il = array.length; i < il; ++i) {
398 obj[array[i][key]] = true;
402 obj[array[i]] = true;
410 // Find the common unique items in two arrays
412 exports.intersect = function (array1, array2, justFirst) {
414 if (!array1 || !array2) {
419 var hash = (Array.isArray(array1) ? exports.mapToObject(array1) : array1);
421 for (var i = 0, il = array2.length; i < il; ++i) {
422 if (hash[array2[i]] && !found[array2[i]]) {
427 common.push(array2[i]);
428 found[array2[i]] = true;
432 return (justFirst ? null : common);
436 // Test if the reference contains the values
438 exports.contain = function (ref, values, options) {
444 object -> object (key:value)
447 var valuePairs = null;
448 if (typeof ref === 'object' &&
449 typeof values === 'object' &&
450 !Array.isArray(ref) &&
451 !Array.isArray(values)) {
454 values = Object.keys(values);
457 values = [].concat(values);
460 options = options || {}; // deep, once, only, part
462 exports.assert(arguments.length >= 2, 'Insufficient arguments');
463 exports.assert(typeof ref === 'string' || typeof ref === 'object', 'Reference must be string or an object');
464 exports.assert(values.length, 'Values array cannot be empty');
466 var compare, compareFlags;
468 compare = exports.deepEqual;
470 var hasOnly = options.hasOwnProperty('only'), hasPart = options.hasOwnProperty('part');
473 prototype: hasOnly ? options.only : hasPart ? !options.part : false,
474 part: hasOnly ? !options.only : hasPart ? options.part : true
478 compare = function (a, b) {
485 var matches = new Array(values.length);
486 for (var i = 0, il = matches.length; i < il; ++i) {
490 if (typeof ref === 'string') {
492 for (i = 0, il = values.length; i < il; ++i) {
493 var value = values[i];
494 exports.assert(typeof value === 'string', 'Cannot compare string reference to non-string value');
495 pattern += (i ? '|' : '') + exports.escapeRegex(value);
498 var regex = new RegExp(pattern + ')', 'g');
499 var leftovers = ref.replace(regex, function ($0, $1) {
501 var index = values.indexOf($1);
503 return ''; // Remove from string
506 misses = !!leftovers;
508 else if (Array.isArray(ref)) {
509 for (i = 0, il = ref.length; i < il; ++i) {
510 for (var j = 0, jl = values.length, matched = false; j < jl && matched === false; ++j) {
511 matched = compare(values[j], ref[i], compareFlags) && j;
514 if (matched !== false) {
523 var keys = Object.keys(ref);
524 for (i = 0, il = keys.length; i < il; ++i) {
526 var pos = values.indexOf(key);
529 !compare(valuePairs[key], ref[key], compareFlags)) {
543 for (i = 0, il = matches.length; i < il; ++i) {
544 result = result || !!matches[i];
545 if ((options.once && matches[i] > 1) ||
546 (!options.part && !matches[i])) {
564 exports.flatten = function (array, target) {
566 var result = target || [];
568 for (var i = 0, il = array.length; i < il; ++i) {
569 if (Array.isArray(array[i])) {
570 exports.flatten(array[i], result);
573 result.push(array[i]);
581 // Convert an object key chain string ('a.b.c') to reference (object[a][b][c])
583 exports.reach = function (obj, chain, options) {
585 if (chain === false ||
587 typeof chain === 'undefined') {
592 options = options || {};
593 if (typeof options === 'string') {
594 options = { separator: options };
597 var path = chain.split(options.separator || '.');
599 for (var i = 0, il = path.length; i < il; ++i) {
601 if (key[0] === '-' && Array.isArray(ref)) {
602 key = key.slice(1, key.length);
603 key = ref.length - key;
607 !ref.hasOwnProperty(key) ||
608 (typeof ref !== 'object' && options.functions === false)) { // Only object and function can have properties
610 exports.assert(!options.strict || i + 1 === il, 'Missing segment', key, 'in reach path ', chain);
611 exports.assert(typeof ref === 'object' || options.functions === true || typeof ref !== 'function', 'Invalid segment', key, 'in reach path ', chain);
612 ref = options.default;
623 exports.reachTemplate = function (obj, template, options) {
625 return template.replace(/{([^}]+)}/g, function ($0, chain) {
627 var value = exports.reach(obj, chain, options);
628 return (value === undefined || value === null ? '' : value);
633 exports.formatStack = function (stack) {
636 for (var i = 0, il = stack.length; i < il; ++i) {
638 trace.push([item.getFileName(), item.getLineNumber(), item.getColumnNumber(), item.getFunctionName(), item.isConstructor()]);
645 exports.formatTrace = function (trace) {
649 for (var i = 0, il = trace.length; i < il; ++i) {
651 display.push((row[4] ? 'new ' : '') + row[3] + ' (' + row[0] + ':' + row[1] + ':' + row[2] + ')');
658 exports.callStack = function (slice) {
660 // http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
662 var v8 = Error.prepareStackTrace;
663 Error.prepareStackTrace = function (err, stack) {
669 Error.captureStackTrace(capture, arguments.callee); /*eslint no-caller:0 */
670 var stack = capture.stack;
672 Error.prepareStackTrace = v8;
674 var trace = exports.formatStack(stack);
677 return trace.slice(slice);
684 exports.displayStack = function (slice) {
686 var trace = exports.callStack(slice === undefined ? 1 : slice + 1);
688 return exports.formatTrace(trace);
692 exports.abortThrow = false;
695 exports.abort = function (message, hideStack) {
697 if (process.env.NODE_ENV === 'test' || exports.abortThrow === true) {
698 throw new Error(message || 'Unknown error');
703 stack = exports.displayStack(1).join('\n\t');
705 console.log('ABORT: ' + message + '\n\t' + stack);
710 exports.assert = function (condition /*, msg1, msg2, msg3 */) {
716 if (arguments.length === 2 && arguments[1] instanceof Error) {
721 for (var i = 1, il = arguments.length; i < il; ++i) {
722 if (arguments[i] !== '') {
723 msgs.push(arguments[i]); // Avoids Array.slice arguments leak, allowing for V8 optimizations
727 msgs = msgs.map(function (msg) {
729 return typeof msg === 'string' ? msg : msg instanceof Error ? msg.message : exports.stringify(msg);
731 throw new Error(msgs.join(' ') || 'Unknown error');
735 exports.Timer = function () {
742 exports.Timer.prototype.reset = function () {
744 this.ts = Date.now();
748 exports.Timer.prototype.elapsed = function () {
750 return Date.now() - this.ts;
754 exports.Bench = function () {
761 exports.Bench.prototype.reset = function () {
763 this.ts = exports.Bench.now();
767 exports.Bench.prototype.elapsed = function () {
769 return exports.Bench.now() - this.ts;
773 exports.Bench.now = function () {
775 var ts = process.hrtime();
776 return (ts[0] * 1e3) + (ts[1] / 1e6);
780 // Escape string for Regex construction
782 exports.escapeRegex = function (string) {
784 // Escape ^$.*+-?=!:|\/()[]{},
785 return string.replace(/[\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}\,]/g, '\\$&');
789 // Base64url (RFC 4648) encode
791 exports.base64urlEncode = function (value, encoding) {
793 var buf = (Buffer.isBuffer(value) ? value : new Buffer(value, encoding || 'binary'));
794 return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
798 // Base64url (RFC 4648) decode
800 exports.base64urlDecode = function (value, encoding) {
803 !/^[\w\-]*$/.test(value)) {
805 return new Error('Invalid character');
809 var buf = new Buffer(value, 'base64');
810 return (encoding === 'buffer' ? buf : buf.toString(encoding || 'binary'));
818 // Escape attribute value for use in HTTP header
820 exports.escapeHeaderAttribute = function (attribute) {
822 // Allowed value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9, \, "
824 exports.assert(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~\"\\]*$/.test(attribute), 'Bad attribute value (' + attribute + ')');
826 return attribute.replace(/\\/g, '\\\\').replace(/\"/g, '\\"'); // Escape quotes and slash
830 exports.escapeHtml = function (string) {
832 return Escape.escapeHtml(string);
836 exports.escapeJavaScript = function (string) {
838 return Escape.escapeJavaScript(string);
842 exports.nextTick = function (callback) {
846 var args = arguments;
847 process.nextTick(function () {
849 callback.apply(null, args);
855 exports.once = function (method) {
857 if (method._hoekOnce) {
862 var wrapped = function () {
866 method.apply(null, arguments);
870 wrapped._hoekOnce = true;
876 exports.isAbsolutePath = function (path, platform) {
882 if (Path.isAbsolute) { // node >= 0.11
883 return Path.isAbsolute(path);
886 platform = platform || process.platform;
890 if (platform !== 'win32') {
891 return path[0] === '/';
896 return !!/^(?:[a-zA-Z]:[\\\/])|(?:[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/])/.test(path); // C:\ or \\something\something
900 exports.isInteger = function (value) {
902 return (typeof value === 'number' &&
903 parseFloat(value) === parseInt(value, 10) &&
908 exports.ignore = function () { };
911 exports.inherits = Util.inherits;
914 exports.format = Util.format;
917 exports.transform = function (source, transform, options) {
919 exports.assert(source === null || source === undefined || typeof source === 'object' || Array.isArray(source), 'Invalid source object: must be null, undefined, an object, or an array');
921 if (Array.isArray(source)) {
923 for (var i = 0, il = source.length; i < il; ++i) {
924 results.push(exports.transform(source[i], transform, options));
930 var keys = Object.keys(transform);
932 for (var k = 0, kl = keys.length; k < kl; ++k) {
934 var path = key.split('.');
935 var sourcePath = transform[key];
937 exports.assert(typeof sourcePath === 'string', 'All mappings must be "." delineated strings');
942 while (path.length > 1) {
943 segment = path.shift();
949 segment = path.shift();
950 res[segment] = exports.reach(source, sourcePath, options);
957 exports.uniqueFilename = function (path, extension) {
960 extension = extension[0] !== '.' ? '.' + extension : extension;
966 path = Path.resolve(path);
967 var name = [Date.now(), process.pid, Crypto.randomBytes(8).toString('hex')].join('-') + extension;
968 return Path.join(path, name);
972 exports.stringify = function () {
975 return JSON.stringify.apply(null, arguments);
978 return '[Cannot display object: ' + err.message + ']';
983 exports.shallow = function (source) {
986 var keys = Object.keys(source);
987 for (var i = 0, il = keys.length; i < il; ++i) {
989 target[key] = source[key];