You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
974 lines
24 KiB
974 lines
24 KiB
'use strict'; |
|
|
|
// Load modules |
|
|
|
const Crypto = require('crypto'); |
|
const Path = require('path'); |
|
const Util = require('util'); |
|
const Escape = require('./escape'); |
|
|
|
|
|
// Declare internals |
|
|
|
const internals = {}; |
|
|
|
|
|
// Clone object or array |
|
|
|
exports.clone = function (obj, seen) { |
|
|
|
if (typeof obj !== 'object' || |
|
obj === null) { |
|
|
|
return obj; |
|
} |
|
|
|
seen = seen || new Map(); |
|
|
|
const lookup = seen.get(obj); |
|
if (lookup) { |
|
return lookup; |
|
} |
|
|
|
let newObj; |
|
let 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 { |
|
const proto = Object.getPrototypeOf(obj); |
|
if (proto && |
|
proto.isImmutable) { |
|
|
|
newObj = obj; |
|
} |
|
else { |
|
newObj = Object.create(proto); |
|
cloneDeep = true; |
|
} |
|
} |
|
} |
|
else { |
|
newObj = []; |
|
cloneDeep = true; |
|
} |
|
|
|
seen.set(obj, newObj); |
|
|
|
if (cloneDeep) { |
|
const keys = Object.getOwnPropertyNames(obj); |
|
for (let i = 0; i < keys.length; ++i) { |
|
const key = keys[i]; |
|
const descriptor = Object.getOwnPropertyDescriptor(obj, key); |
|
if (descriptor && |
|
(descriptor.get || |
|
descriptor.set)) { |
|
|
|
Object.defineProperty(newObj, key, descriptor); |
|
} |
|
else { |
|
newObj[key] = exports.clone(obj[key], 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 |
|
|
|
/*eslint-disable */ |
|
exports.merge = function (target, source, isNullOverride /* = true */, isMergeArrays /* = true */) { |
|
/*eslint-enable */ |
|
|
|
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 (let i = 0; i < source.length; ++i) { |
|
target.push(exports.clone(source[i])); |
|
} |
|
|
|
return target; |
|
} |
|
|
|
const keys = Object.keys(source); |
|
for (let i = 0; i < keys.length; ++i) { |
|
const key = keys[i]; |
|
const 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, isNullOverride) { |
|
|
|
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; |
|
} |
|
|
|
const copy = exports.clone(defaults); |
|
|
|
if (options === true) { // If options is set to true, use defaults |
|
return copy; |
|
} |
|
|
|
return exports.merge(copy, options, isNullOverride === true, 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; |
|
} |
|
|
|
const storage = internals.store(source, keys); // Move shallow copy items to storage |
|
const copy = exports.clone(source); // Deep copy the rest |
|
internals.restore(copy, source, storage); // Shallow copy the stored items and restore |
|
return copy; |
|
}; |
|
|
|
|
|
internals.store = function (source, keys) { |
|
|
|
const storage = {}; |
|
for (let i = 0; i < keys.length; ++i) { |
|
const key = keys[i]; |
|
const value = exports.reach(source, key); |
|
if (value !== undefined) { |
|
storage[key] = value; |
|
internals.reachSet(source, key, undefined); |
|
} |
|
} |
|
|
|
return storage; |
|
}; |
|
|
|
|
|
internals.restore = function (copy, source, storage) { |
|
|
|
const keys = Object.keys(storage); |
|
for (let i = 0; i < keys.length; ++i) { |
|
const key = keys[i]; |
|
internals.reachSet(copy, key, storage[key]); |
|
internals.reachSet(source, key, storage[key]); |
|
} |
|
}; |
|
|
|
|
|
internals.reachSet = function (obj, key, value) { |
|
|
|
const path = key.split('.'); |
|
let ref = obj; |
|
for (let i = 0; i < path.length; ++i) { |
|
const segment = path[i]; |
|
if (i + 1 === path.length) { |
|
ref[segment] = value; |
|
} |
|
|
|
ref = ref[segment]; |
|
} |
|
}; |
|
|
|
|
|
// Apply options to defaults except for the listed keys which are shallow copied from option without merging |
|
|
|
exports.applyToDefaultsWithShallow = function (defaults, options, keys) { |
|
|
|
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'); |
|
exports.assert(keys && Array.isArray(keys), 'Invalid keys'); |
|
|
|
if (!options) { // If no options, return null |
|
return null; |
|
} |
|
|
|
const copy = exports.cloneWithShallow(defaults, keys); |
|
|
|
if (options === true) { // If options is set to true, use defaults |
|
return copy; |
|
} |
|
|
|
const storage = internals.store(options, keys); // Move shallow copy items to storage |
|
exports.merge(copy, options, false, false); // Deep copy the rest |
|
internals.restore(copy, options, storage); // Shallow copy the stored items and restore |
|
return copy; |
|
}; |
|
|
|
|
|
// Deep object or array comparison |
|
|
|
exports.deepEqual = function (obj, ref, options, seen) { |
|
|
|
options = options || { prototype: true }; |
|
|
|
const 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 (!options.part && obj.length !== ref.length) { |
|
return false; |
|
} |
|
|
|
for (let i = 0; i < obj.length; ++i) { |
|
if (options.part) { |
|
let found = false; |
|
for (let j = 0; j < ref.length; ++j) { |
|
if (exports.deepEqual(obj[i], ref[j], options)) { |
|
found = true; |
|
break; |
|
} |
|
} |
|
|
|
return found; |
|
} |
|
|
|
if (!exports.deepEqual(obj[i], ref[i], options)) { |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
if (Buffer.isBuffer(obj)) { |
|
if (!Buffer.isBuffer(ref)) { |
|
return false; |
|
} |
|
|
|
if (obj.length !== ref.length) { |
|
return false; |
|
} |
|
|
|
for (let i = 0; i < obj.length; ++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 (options.prototype) { |
|
if (Object.getPrototypeOf(obj) !== Object.getPrototypeOf(ref)) { |
|
return false; |
|
} |
|
} |
|
|
|
const keys = Object.getOwnPropertyNames(obj); |
|
|
|
if (!options.part && keys.length !== Object.getOwnPropertyNames(ref).length) { |
|
return false; |
|
} |
|
|
|
for (let i = 0; i < keys.length; ++i) { |
|
const key = keys[i]; |
|
const descriptor = Object.getOwnPropertyDescriptor(obj, key); |
|
if (descriptor.get) { |
|
if (!exports.deepEqual(descriptor, Object.getOwnPropertyDescriptor(ref, key), options, seen)) { |
|
return false; |
|
} |
|
} |
|
else if (!exports.deepEqual(obj[key], ref[key], options, seen)) { |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
}; |
|
|
|
|
|
// Remove duplicate items from array |
|
|
|
exports.unique = (array, key) => { |
|
|
|
let result; |
|
if (key) { |
|
result = []; |
|
const index = new Set(); |
|
array.forEach((item) => { |
|
|
|
const identifier = item[key]; |
|
if (!index.has(identifier)) { |
|
index.add(identifier); |
|
result.push(item); |
|
} |
|
}); |
|
} |
|
else { |
|
result = Array.from(new Set(array)); |
|
} |
|
|
|
return result; |
|
}; |
|
|
|
|
|
// Convert array into object |
|
|
|
exports.mapToObject = function (array, key) { |
|
|
|
if (!array) { |
|
return null; |
|
} |
|
|
|
const obj = {}; |
|
for (let i = 0; i < array.length; ++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 []; |
|
} |
|
|
|
const common = []; |
|
const hash = (Array.isArray(array1) ? exports.mapToObject(array1) : array1); |
|
const found = {}; |
|
for (let i = 0; i < array2.length; ++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) |
|
*/ |
|
|
|
let 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'); |
|
|
|
let compare; |
|
let compareFlags; |
|
if (options.deep) { |
|
compare = exports.deepEqual; |
|
|
|
const hasOnly = options.hasOwnProperty('only'); |
|
const hasPart = options.hasOwnProperty('part'); |
|
|
|
compareFlags = { |
|
prototype: hasOnly ? options.only : hasPart ? !options.part : false, |
|
part: hasOnly ? !options.only : hasPart ? options.part : true |
|
}; |
|
} |
|
else { |
|
compare = (a, b) => a === b; |
|
} |
|
|
|
let misses = false; |
|
const matches = new Array(values.length); |
|
for (let i = 0; i < matches.length; ++i) { |
|
matches[i] = 0; |
|
} |
|
|
|
if (typeof ref === 'string') { |
|
let pattern = '('; |
|
for (let i = 0; i < values.length; ++i) { |
|
const value = values[i]; |
|
exports.assert(typeof value === 'string', 'Cannot compare string reference to non-string value'); |
|
pattern += (i ? '|' : '') + exports.escapeRegex(value); |
|
} |
|
|
|
const regex = new RegExp(pattern + ')', 'g'); |
|
const leftovers = ref.replace(regex, ($0, $1) => { |
|
|
|
const index = values.indexOf($1); |
|
++matches[index]; |
|
return ''; // Remove from string |
|
}); |
|
|
|
misses = !!leftovers; |
|
} |
|
else if (Array.isArray(ref)) { |
|
for (let i = 0; i < ref.length; ++i) { |
|
let matched = false; |
|
for (let j = 0; j < values.length && matched === false; ++j) { |
|
matched = compare(values[j], ref[i], compareFlags) && j; |
|
} |
|
|
|
if (matched !== false) { |
|
++matches[matched]; |
|
} |
|
else { |
|
misses = true; |
|
} |
|
} |
|
} |
|
else { |
|
const keys = Object.getOwnPropertyNames(ref); |
|
for (let i = 0; i < keys.length; ++i) { |
|
const key = keys[i]; |
|
const pos = values.indexOf(key); |
|
if (pos !== -1) { |
|
if (valuePairs && |
|
!compare(valuePairs[key], ref[key], compareFlags)) { |
|
|
|
return false; |
|
} |
|
|
|
++matches[pos]; |
|
} |
|
else { |
|
misses = true; |
|
} |
|
} |
|
} |
|
|
|
let result = false; |
|
for (let i = 0; i < matches.length; ++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) { |
|
|
|
const result = target || []; |
|
|
|
for (let i = 0; i < array.length; ++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) { |
|
|
|
if (chain === false || |
|
chain === null || |
|
typeof chain === 'undefined') { |
|
|
|
return obj; |
|
} |
|
|
|
options = options || {}; |
|
if (typeof options === 'string') { |
|
options = { separator: options }; |
|
} |
|
|
|
const path = chain.split(options.separator || '.'); |
|
let ref = obj; |
|
for (let i = 0; i < path.length; ++i) { |
|
let key = path[i]; |
|
if (key[0] === '-' && Array.isArray(ref)) { |
|
key = key.slice(1, key.length); |
|
key = ref.length - key; |
|
} |
|
|
|
if (!ref || |
|
!((typeof ref === 'object' || typeof ref === 'function') && key in ref) || |
|
(typeof ref !== 'object' && options.functions === false)) { // Only object and function can have properties |
|
|
|
exports.assert(!options.strict || i + 1 === path.length, 'Missing segment', key, 'in reach path ', chain); |
|
exports.assert(typeof ref === 'object' || options.functions === true || typeof ref !== 'function', 'Invalid segment', key, 'in reach path ', chain); |
|
ref = options.default; |
|
break; |
|
} |
|
|
|
ref = ref[key]; |
|
} |
|
|
|
return ref; |
|
}; |
|
|
|
|
|
exports.reachTemplate = function (obj, template, options) { |
|
|
|
return template.replace(/{([^}]+)}/g, ($0, chain) => { |
|
|
|
const value = exports.reach(obj, chain, options); |
|
return (value === undefined || value === null ? '' : value); |
|
}); |
|
}; |
|
|
|
|
|
exports.formatStack = function (stack) { |
|
|
|
const trace = []; |
|
for (let i = 0; i < stack.length; ++i) { |
|
const item = stack[i]; |
|
trace.push([item.getFileName(), item.getLineNumber(), item.getColumnNumber(), item.getFunctionName(), item.isConstructor()]); |
|
} |
|
|
|
return trace; |
|
}; |
|
|
|
|
|
exports.formatTrace = function (trace) { |
|
|
|
const display = []; |
|
|
|
for (let i = 0; i < trace.length; ++i) { |
|
const 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 |
|
|
|
const v8 = Error.prepareStackTrace; |
|
Error.prepareStackTrace = function (_, stack) { |
|
|
|
return stack; |
|
}; |
|
|
|
const capture = {}; |
|
Error.captureStackTrace(capture, this); // arguments.callee is not supported in strict mode so we use this and slice the trace of this off the result |
|
const stack = capture.stack; |
|
|
|
Error.prepareStackTrace = v8; |
|
|
|
const trace = exports.formatStack(stack); |
|
|
|
return trace.slice(1 + slice); |
|
}; |
|
|
|
|
|
exports.displayStack = function (slice) { |
|
|
|
const 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'); |
|
} |
|
|
|
let 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; |
|
} |
|
|
|
if (arguments.length === 2 && arguments[1] instanceof Error) { |
|
throw arguments[1]; |
|
} |
|
|
|
let msgs = []; |
|
for (let i = 1; i < arguments.length; ++i) { |
|
if (arguments[i] !== '') { |
|
msgs.push(arguments[i]); // Avoids Array.slice arguments leak, allowing for V8 optimizations |
|
} |
|
} |
|
|
|
msgs = msgs.map((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 () { |
|
|
|
const 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) { |
|
|
|
exports.assert(typeof value === 'string' || Buffer.isBuffer(value), 'value must be string or buffer'); |
|
const 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 (typeof value !== 'string') { |
|
|
|
return new Error('Value not a string'); |
|
} |
|
|
|
if (!/^[\w\-]*$/.test(value)) { |
|
|
|
return new Error('Invalid character'); |
|
} |
|
|
|
const buf = new Buffer(value, 'base64'); |
|
return (encoding === 'buffer' ? buf : buf.toString(encoding || 'binary')); |
|
}; |
|
|
|
|
|
// 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.escapeJson = function (string) { |
|
|
|
return Escape.escapeJson(string); |
|
}; |
|
|
|
exports.nextTick = function (callback) { |
|
|
|
return function () { |
|
|
|
const args = arguments; |
|
process.nextTick(() => { |
|
|
|
callback.apply(null, args); |
|
}); |
|
}; |
|
}; |
|
|
|
|
|
exports.once = function (method) { |
|
|
|
if (method._hoekOnce) { |
|
return method; |
|
} |
|
|
|
let once = false; |
|
const wrapped = function () { |
|
|
|
if (!once) { |
|
once = true; |
|
method.apply(null, arguments); |
|
} |
|
}; |
|
|
|
wrapped._hoekOnce = true; |
|
|
|
return wrapped; |
|
}; |
|
|
|
|
|
exports.isInteger = Number.isSafeInteger; |
|
|
|
|
|
exports.ignore = function () { }; |
|
|
|
|
|
exports.inherits = Util.inherits; |
|
|
|
|
|
exports.format = Util.format; |
|
|
|
|
|
exports.transform = function (source, transform, options) { |
|
|
|
exports.assert(source === null || source === undefined || typeof source === 'object' || Array.isArray(source), 'Invalid source object: must be null, undefined, an object, or an array'); |
|
const separator = (typeof options === 'object' && options !== null) ? (options.separator || '.') : '.'; |
|
|
|
if (Array.isArray(source)) { |
|
const results = []; |
|
for (let i = 0; i < source.length; ++i) { |
|
results.push(exports.transform(source[i], transform, options)); |
|
} |
|
return results; |
|
} |
|
|
|
const result = {}; |
|
const keys = Object.keys(transform); |
|
|
|
for (let i = 0; i < keys.length; ++i) { |
|
const key = keys[i]; |
|
const path = key.split(separator); |
|
const sourcePath = transform[key]; |
|
|
|
exports.assert(typeof sourcePath === 'string', 'All mappings must be "." delineated strings'); |
|
|
|
let segment; |
|
let 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); |
|
const 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) { |
|
|
|
const target = {}; |
|
const keys = Object.keys(source); |
|
for (let i = 0; i < keys.length; ++i) { |
|
const key = keys[i]; |
|
target[key] = source[key]; |
|
} |
|
|
|
return target; |
|
};
|
|
|