|
|
|
|
|
|
|
|
|
var mod_assert = require('assert-plus'); |
|
var mod_util = require('util'); |
|
|
|
var mod_extsprintf = require('extsprintf'); |
|
var mod_verror = require('verror'); |
|
var mod_jsonschema = require('json-schema'); |
|
|
|
|
|
|
|
|
|
exports.deepCopy = deepCopy; |
|
exports.deepEqual = deepEqual; |
|
exports.isEmpty = isEmpty; |
|
exports.hasKey = hasKey; |
|
exports.forEachKey = forEachKey; |
|
exports.pluck = pluck; |
|
exports.flattenObject = flattenObject; |
|
exports.flattenIter = flattenIter; |
|
exports.validateJsonObject = validateJsonObjectJS; |
|
exports.validateJsonObjectJS = validateJsonObjectJS; |
|
exports.randElt = randElt; |
|
exports.extraProperties = extraProperties; |
|
exports.mergeObjects = mergeObjects; |
|
|
|
exports.startsWith = startsWith; |
|
exports.endsWith = endsWith; |
|
|
|
exports.parseInteger = parseInteger; |
|
|
|
exports.iso8601 = iso8601; |
|
exports.rfc1123 = rfc1123; |
|
exports.parseDateTime = parseDateTime; |
|
|
|
exports.hrtimediff = hrtimeDiff; |
|
exports.hrtimeDiff = hrtimeDiff; |
|
exports.hrtimeAccum = hrtimeAccum; |
|
exports.hrtimeAdd = hrtimeAdd; |
|
exports.hrtimeNanosec = hrtimeNanosec; |
|
exports.hrtimeMicrosec = hrtimeMicrosec; |
|
exports.hrtimeMillisec = hrtimeMillisec; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function deepCopy(obj) |
|
{ |
|
var ret, key; |
|
var marker = '__deepCopy'; |
|
|
|
if (obj && obj[marker]) |
|
throw (new Error('attempted deep copy of cyclic object')); |
|
|
|
if (obj && obj.constructor == Object) { |
|
ret = {}; |
|
obj[marker] = true; |
|
|
|
for (key in obj) { |
|
if (key == marker) |
|
continue; |
|
|
|
ret[key] = deepCopy(obj[key]); |
|
} |
|
|
|
delete (obj[marker]); |
|
return (ret); |
|
} |
|
|
|
if (obj && obj.constructor == Array) { |
|
ret = []; |
|
obj[marker] = true; |
|
|
|
for (key = 0; key < obj.length; key++) |
|
ret.push(deepCopy(obj[key])); |
|
|
|
delete (obj[marker]); |
|
return (ret); |
|
} |
|
|
|
|
|
|
|
|
|
return (obj); |
|
} |
|
|
|
function deepEqual(obj1, obj2) |
|
{ |
|
if (typeof (obj1) != typeof (obj2)) |
|
return (false); |
|
|
|
if (obj1 === null || obj2 === null || typeof (obj1) != 'object') |
|
return (obj1 === obj2); |
|
|
|
if (obj1.constructor != obj2.constructor) |
|
return (false); |
|
|
|
var k; |
|
for (k in obj1) { |
|
if (!(k in obj2)) |
|
return (false); |
|
|
|
if (!deepEqual(obj1[k], obj2[k])) |
|
return (false); |
|
} |
|
|
|
for (k in obj2) { |
|
if (!(k in obj1)) |
|
return (false); |
|
} |
|
|
|
return (true); |
|
} |
|
|
|
function isEmpty(obj) |
|
{ |
|
var key; |
|
for (key in obj) |
|
return (false); |
|
return (true); |
|
} |
|
|
|
function hasKey(obj, key) |
|
{ |
|
mod_assert.equal(typeof (key), 'string'); |
|
return (Object.prototype.hasOwnProperty.call(obj, key)); |
|
} |
|
|
|
function forEachKey(obj, callback) |
|
{ |
|
for (var key in obj) { |
|
if (hasKey(obj, key)) { |
|
callback(key, obj[key]); |
|
} |
|
} |
|
} |
|
|
|
function pluck(obj, key) |
|
{ |
|
mod_assert.equal(typeof (key), 'string'); |
|
return (pluckv(obj, key)); |
|
} |
|
|
|
function pluckv(obj, key) |
|
{ |
|
if (obj === null || typeof (obj) !== 'object') |
|
return (undefined); |
|
|
|
if (obj.hasOwnProperty(key)) |
|
return (obj[key]); |
|
|
|
var i = key.indexOf('.'); |
|
if (i == -1) |
|
return (undefined); |
|
|
|
var key1 = key.substr(0, i); |
|
if (!obj.hasOwnProperty(key1)) |
|
return (undefined); |
|
|
|
return (pluckv(obj[key1], key.substr(i + 1))); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function flattenIter(data, depth, callback) |
|
{ |
|
doFlattenIter(data, depth, [], callback); |
|
} |
|
|
|
function doFlattenIter(data, depth, accum, callback) |
|
{ |
|
var each; |
|
var key; |
|
|
|
if (depth === 0) { |
|
each = accum.slice(0); |
|
each.push(data); |
|
callback(each); |
|
return; |
|
} |
|
|
|
mod_assert.ok(data !== null); |
|
mod_assert.equal(typeof (data), 'object'); |
|
mod_assert.equal(typeof (depth), 'number'); |
|
mod_assert.ok(depth >= 0); |
|
|
|
for (key in data) { |
|
each = accum.slice(0); |
|
each.push(key); |
|
doFlattenIter(data[key], depth - 1, each, callback); |
|
} |
|
} |
|
|
|
function flattenObject(data, depth) |
|
{ |
|
if (depth === 0) |
|
return ([ data ]); |
|
|
|
mod_assert.ok(data !== null); |
|
mod_assert.equal(typeof (data), 'object'); |
|
mod_assert.equal(typeof (depth), 'number'); |
|
mod_assert.ok(depth >= 0); |
|
|
|
var rv = []; |
|
var key; |
|
|
|
for (key in data) { |
|
flattenObject(data[key], depth - 1).forEach(function (p) { |
|
rv.push([ key ].concat(p)); |
|
}); |
|
} |
|
|
|
return (rv); |
|
} |
|
|
|
function startsWith(str, prefix) |
|
{ |
|
return (str.substr(0, prefix.length) == prefix); |
|
} |
|
|
|
function endsWith(str, suffix) |
|
{ |
|
return (str.substr( |
|
str.length - suffix.length, suffix.length) == suffix); |
|
} |
|
|
|
function iso8601(d) |
|
{ |
|
if (typeof (d) == 'number') |
|
d = new Date(d); |
|
mod_assert.ok(d.constructor === Date); |
|
return (mod_extsprintf.sprintf('%4d-%02d-%02dT%02d:%02d:%02d.%03dZ', |
|
d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate(), |
|
d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), |
|
d.getUTCMilliseconds())); |
|
} |
|
|
|
var RFC1123_MONTHS = [ |
|
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', |
|
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; |
|
var RFC1123_DAYS = [ |
|
'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; |
|
|
|
function rfc1123(date) { |
|
return (mod_extsprintf.sprintf('%s, %02d %s %04d %02d:%02d:%02d GMT', |
|
RFC1123_DAYS[date.getUTCDay()], date.getUTCDate(), |
|
RFC1123_MONTHS[date.getUTCMonth()], date.getUTCFullYear(), |
|
date.getUTCHours(), date.getUTCMinutes(), |
|
date.getUTCSeconds())); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function parseDateTime(str) |
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var numeric = +str; |
|
if (!isNaN(numeric)) { |
|
return (new Date(numeric)); |
|
} else { |
|
return (new Date(str)); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; |
|
var MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991; |
|
|
|
|
|
|
|
|
|
|
|
var PI_DEFAULTS = { |
|
base: 10, |
|
allowSign: true, |
|
allowPrefix: false, |
|
allowTrailing: false, |
|
allowImprecise: false, |
|
trimWhitespace: false, |
|
leadingZeroIsOctal: false |
|
}; |
|
|
|
var CP_0 = 0x30; |
|
var CP_9 = 0x39; |
|
|
|
var CP_A = 0x41; |
|
var CP_B = 0x42; |
|
var CP_O = 0x4f; |
|
var CP_T = 0x54; |
|
var CP_X = 0x58; |
|
var CP_Z = 0x5a; |
|
|
|
var CP_a = 0x61; |
|
var CP_b = 0x62; |
|
var CP_o = 0x6f; |
|
var CP_t = 0x74; |
|
var CP_x = 0x78; |
|
var CP_z = 0x7a; |
|
|
|
var PI_CONV_DEC = 0x30; |
|
var PI_CONV_UC = 0x37; |
|
var PI_CONV_LC = 0x57; |
|
|
|
|
|
|
|
|
|
|
|
|
|
function parseInteger(str, uopts) |
|
{ |
|
mod_assert.string(str, 'str'); |
|
mod_assert.optionalObject(uopts, 'options'); |
|
|
|
var baseOverride = false; |
|
var options = PI_DEFAULTS; |
|
|
|
if (uopts) { |
|
baseOverride = hasKey(uopts, 'base'); |
|
options = mergeObjects(options, uopts); |
|
mod_assert.number(options.base, 'options.base'); |
|
mod_assert.ok(options.base >= 2, 'options.base >= 2'); |
|
mod_assert.ok(options.base <= 36, 'options.base <= 36'); |
|
mod_assert.bool(options.allowSign, 'options.allowSign'); |
|
mod_assert.bool(options.allowPrefix, 'options.allowPrefix'); |
|
mod_assert.bool(options.allowTrailing, |
|
'options.allowTrailing'); |
|
mod_assert.bool(options.allowImprecise, |
|
'options.allowImprecise'); |
|
mod_assert.bool(options.trimWhitespace, |
|
'options.trimWhitespace'); |
|
mod_assert.bool(options.leadingZeroIsOctal, |
|
'options.leadingZeroIsOctal'); |
|
|
|
if (options.leadingZeroIsOctal) { |
|
mod_assert.ok(!baseOverride, |
|
'"base" and "leadingZeroIsOctal" are ' + |
|
'mutually exclusive'); |
|
} |
|
} |
|
|
|
var c; |
|
var pbase = -1; |
|
var base = options.base; |
|
var start; |
|
var mult = 1; |
|
var value = 0; |
|
var idx = 0; |
|
var len = str.length; |
|
|
|
|
|
if (options.trimWhitespace) { |
|
while (idx < len && isSpace(str.charCodeAt(idx))) { |
|
++idx; |
|
} |
|
} |
|
|
|
|
|
if (options.allowSign) { |
|
if (str[idx] === '-') { |
|
idx += 1; |
|
mult = -1; |
|
} else if (str[idx] === '+') { |
|
idx += 1; |
|
} |
|
} |
|
|
|
|
|
if (str[idx] === '0') { |
|
if (options.allowPrefix) { |
|
pbase = prefixToBase(str.charCodeAt(idx + 1)); |
|
if (pbase !== -1 && (!baseOverride || pbase === base)) { |
|
base = pbase; |
|
idx += 2; |
|
} |
|
} |
|
|
|
if (pbase === -1 && options.leadingZeroIsOctal) { |
|
base = 8; |
|
} |
|
} |
|
|
|
|
|
for (start = idx; idx < len; ++idx) { |
|
c = translateDigit(str.charCodeAt(idx)); |
|
if (c !== -1 && c < base) { |
|
value *= base; |
|
value += c; |
|
} else { |
|
break; |
|
} |
|
} |
|
|
|
|
|
if (start === idx) { |
|
return (new Error('invalid number: ' + JSON.stringify(str))); |
|
} |
|
|
|
|
|
if (options.trimWhitespace) { |
|
while (idx < len && isSpace(str.charCodeAt(idx))) { |
|
++idx; |
|
} |
|
} |
|
|
|
|
|
if (idx < len && !options.allowTrailing) { |
|
return (new Error('trailing characters after number: ' + |
|
JSON.stringify(str.slice(idx)))); |
|
} |
|
|
|
|
|
if (value === 0) { |
|
return (0); |
|
} |
|
|
|
|
|
var result = value * mult; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!options.allowImprecise && |
|
(value > MAX_SAFE_INTEGER || result < MIN_SAFE_INTEGER)) { |
|
return (new Error('number is outside of the supported range: ' + |
|
JSON.stringify(str.slice(start, idx)))); |
|
} |
|
|
|
return (result); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function translateDigit(d) |
|
{ |
|
if (d >= CP_0 && d <= CP_9) { |
|
|
|
return (d - PI_CONV_DEC); |
|
} else if (d >= CP_A && d <= CP_Z) { |
|
|
|
return (d - PI_CONV_UC); |
|
} else if (d >= CP_a && d <= CP_z) { |
|
|
|
return (d - PI_CONV_LC); |
|
} else { |
|
|
|
return (-1); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function isSpace(c) |
|
{ |
|
return (c === 0x20) || |
|
(c >= 0x0009 && c <= 0x000d) || |
|
(c === 0x00a0) || |
|
(c === 0x1680) || |
|
(c === 0x180e) || |
|
(c >= 0x2000 && c <= 0x200a) || |
|
(c === 0x2028) || |
|
(c === 0x2029) || |
|
(c === 0x202f) || |
|
(c === 0x205f) || |
|
(c === 0x3000) || |
|
(c === 0xfeff); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function prefixToBase(c) |
|
{ |
|
if (c === CP_b || c === CP_B) { |
|
|
|
return (2); |
|
} else if (c === CP_o || c === CP_O) { |
|
|
|
return (8); |
|
} else if (c === CP_t || c === CP_T) { |
|
|
|
return (10); |
|
} else if (c === CP_x || c === CP_X) { |
|
|
|
return (16); |
|
} else { |
|
|
|
return (-1); |
|
} |
|
} |
|
|
|
|
|
function validateJsonObjectJS(schema, input) |
|
{ |
|
var report = mod_jsonschema.validate(input, schema); |
|
|
|
if (report.errors.length === 0) |
|
return (null); |
|
|
|
|
|
var error = report.errors[0]; |
|
|
|
|
|
var propname = error['property']; |
|
var reason = error['message'].toLowerCase(); |
|
var i, j; |
|
|
|
|
|
|
|
|
|
|
|
if ((i = reason.indexOf('the property ')) != -1 && |
|
(j = reason.indexOf(' is not defined in the schema and the ' + |
|
'schema does not allow additional properties')) != -1) { |
|
i += 'the property '.length; |
|
if (propname === '') |
|
propname = reason.substr(i, j - i); |
|
else |
|
propname = propname + '.' + reason.substr(i, j - i); |
|
|
|
reason = 'unsupported property'; |
|
} |
|
|
|
var rv = new mod_verror.VError('property "%s": %s', propname, reason); |
|
rv.jsv_details = error; |
|
return (rv); |
|
} |
|
|
|
function randElt(arr) |
|
{ |
|
mod_assert.ok(Array.isArray(arr) && arr.length > 0, |
|
'randElt argument must be a non-empty array'); |
|
|
|
return (arr[Math.floor(Math.random() * arr.length)]); |
|
} |
|
|
|
function assertHrtime(a) |
|
{ |
|
mod_assert.ok(a[0] >= 0 && a[1] >= 0, |
|
'negative numbers not allowed in hrtimes'); |
|
mod_assert.ok(a[1] < 1e9, 'nanoseconds column overflow'); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function hrtimeDiff(a, b) |
|
{ |
|
assertHrtime(a); |
|
assertHrtime(b); |
|
mod_assert.ok(a[0] > b[0] || (a[0] == b[0] && a[1] >= b[1]), |
|
'negative differences not allowed'); |
|
|
|
var rv = [ a[0] - b[0], 0 ]; |
|
|
|
if (a[1] >= b[1]) { |
|
rv[1] = a[1] - b[1]; |
|
} else { |
|
rv[0]--; |
|
rv[1] = 1e9 - (b[1] - a[1]); |
|
} |
|
|
|
return (rv); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function hrtimeNanosec(a) |
|
{ |
|
assertHrtime(a); |
|
|
|
return (Math.floor(a[0] * 1e9 + a[1])); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function hrtimeMicrosec(a) |
|
{ |
|
assertHrtime(a); |
|
|
|
return (Math.floor(a[0] * 1e6 + a[1] / 1e3)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function hrtimeMillisec(a) |
|
{ |
|
assertHrtime(a); |
|
|
|
return (Math.floor(a[0] * 1e3 + a[1] / 1e6)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function hrtimeAccum(a, b) |
|
{ |
|
assertHrtime(a); |
|
assertHrtime(b); |
|
|
|
|
|
|
|
|
|
a[1] += b[1]; |
|
if (a[1] >= 1e9) { |
|
|
|
|
|
|
|
|
|
a[0]++; |
|
a[1] -= 1e9; |
|
} |
|
|
|
|
|
|
|
|
|
a[0] += b[0]; |
|
|
|
return (a); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function hrtimeAdd(a, b) |
|
{ |
|
assertHrtime(a); |
|
|
|
var rv = [ a[0], a[1] ]; |
|
|
|
return (hrtimeAccum(rv, b)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function extraProperties(obj, allowed) |
|
{ |
|
mod_assert.ok(typeof (obj) === 'object' && obj !== null, |
|
'obj argument must be a non-null object'); |
|
mod_assert.ok(Array.isArray(allowed), |
|
'allowed argument must be an array of strings'); |
|
for (var i = 0; i < allowed.length; i++) { |
|
mod_assert.ok(typeof (allowed[i]) === 'string', |
|
'allowed argument must be an array of strings'); |
|
} |
|
|
|
return (Object.keys(obj).filter(function (key) { |
|
return (allowed.indexOf(key) === -1); |
|
})); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function mergeObjects(provided, overrides, defaults) |
|
{ |
|
var rv, k; |
|
|
|
rv = {}; |
|
if (defaults) { |
|
for (k in defaults) |
|
rv[k] = defaults[k]; |
|
} |
|
|
|
if (provided) { |
|
for (k in provided) |
|
rv[k] = provided[k]; |
|
} |
|
|
|
if (overrides) { |
|
for (k in overrides) |
|
rv[k] = overrides[k]; |
|
} |
|
|
|
return (rv); |
|
} |
|
|