twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

jsprim.js (17071B)


      1 /*
      2  * lib/jsprim.js: utilities for primitive JavaScript types
      3  */
      4 
      5 var mod_assert = require('assert-plus');
      6 var mod_util = require('util');
      7 
      8 var mod_extsprintf = require('extsprintf');
      9 var mod_verror = require('verror');
     10 var mod_jsonschema = require('json-schema');
     11 
     12 /*
     13  * Public interface
     14  */
     15 exports.deepCopy = deepCopy;
     16 exports.deepEqual = deepEqual;
     17 exports.isEmpty = isEmpty;
     18 exports.hasKey = hasKey;
     19 exports.forEachKey = forEachKey;
     20 exports.pluck = pluck;
     21 exports.flattenObject = flattenObject;
     22 exports.flattenIter = flattenIter;
     23 exports.validateJsonObject = validateJsonObjectJS;
     24 exports.validateJsonObjectJS = validateJsonObjectJS;
     25 exports.randElt = randElt;
     26 exports.extraProperties = extraProperties;
     27 exports.mergeObjects = mergeObjects;
     28 
     29 exports.startsWith = startsWith;
     30 exports.endsWith = endsWith;
     31 
     32 exports.parseInteger = parseInteger;
     33 
     34 exports.iso8601 = iso8601;
     35 exports.rfc1123 = rfc1123;
     36 exports.parseDateTime = parseDateTime;
     37 
     38 exports.hrtimediff = hrtimeDiff;
     39 exports.hrtimeDiff = hrtimeDiff;
     40 exports.hrtimeAccum = hrtimeAccum;
     41 exports.hrtimeAdd = hrtimeAdd;
     42 exports.hrtimeNanosec = hrtimeNanosec;
     43 exports.hrtimeMicrosec = hrtimeMicrosec;
     44 exports.hrtimeMillisec = hrtimeMillisec;
     45 
     46 
     47 /*
     48  * Deep copy an acyclic *basic* Javascript object.  This only handles basic
     49  * scalars (strings, numbers, booleans) and arbitrarily deep arrays and objects
     50  * containing these.  This does *not* handle instances of other classes.
     51  */
     52 function deepCopy(obj)
     53 {
     54 	var ret, key;
     55 	var marker = '__deepCopy';
     56 
     57 	if (obj && obj[marker])
     58 		throw (new Error('attempted deep copy of cyclic object'));
     59 
     60 	if (obj && obj.constructor == Object) {
     61 		ret = {};
     62 		obj[marker] = true;
     63 
     64 		for (key in obj) {
     65 			if (key == marker)
     66 				continue;
     67 
     68 			ret[key] = deepCopy(obj[key]);
     69 		}
     70 
     71 		delete (obj[marker]);
     72 		return (ret);
     73 	}
     74 
     75 	if (obj && obj.constructor == Array) {
     76 		ret = [];
     77 		obj[marker] = true;
     78 
     79 		for (key = 0; key < obj.length; key++)
     80 			ret.push(deepCopy(obj[key]));
     81 
     82 		delete (obj[marker]);
     83 		return (ret);
     84 	}
     85 
     86 	/*
     87 	 * It must be a primitive type -- just return it.
     88 	 */
     89 	return (obj);
     90 }
     91 
     92 function deepEqual(obj1, obj2)
     93 {
     94 	if (typeof (obj1) != typeof (obj2))
     95 		return (false);
     96 
     97 	if (obj1 === null || obj2 === null || typeof (obj1) != 'object')
     98 		return (obj1 === obj2);
     99 
    100 	if (obj1.constructor != obj2.constructor)
    101 		return (false);
    102 
    103 	var k;
    104 	for (k in obj1) {
    105 		if (!obj2.hasOwnProperty(k))
    106 			return (false);
    107 
    108 		if (!deepEqual(obj1[k], obj2[k]))
    109 			return (false);
    110 	}
    111 
    112 	for (k in obj2) {
    113 		if (!obj1.hasOwnProperty(k))
    114 			return (false);
    115 	}
    116 
    117 	return (true);
    118 }
    119 
    120 function isEmpty(obj)
    121 {
    122 	var key;
    123 	for (key in obj)
    124 		return (false);
    125 	return (true);
    126 }
    127 
    128 function hasKey(obj, key)
    129 {
    130 	mod_assert.equal(typeof (key), 'string');
    131 	return (Object.prototype.hasOwnProperty.call(obj, key));
    132 }
    133 
    134 function forEachKey(obj, callback)
    135 {
    136 	for (var key in obj) {
    137 		if (hasKey(obj, key)) {
    138 			callback(key, obj[key]);
    139 		}
    140 	}
    141 }
    142 
    143 function pluck(obj, key)
    144 {
    145 	mod_assert.equal(typeof (key), 'string');
    146 	return (pluckv(obj, key));
    147 }
    148 
    149 function pluckv(obj, key)
    150 {
    151 	if (obj === null || typeof (obj) !== 'object')
    152 		return (undefined);
    153 
    154 	if (obj.hasOwnProperty(key))
    155 		return (obj[key]);
    156 
    157 	var i = key.indexOf('.');
    158 	if (i == -1)
    159 		return (undefined);
    160 
    161 	var key1 = key.substr(0, i);
    162 	if (!obj.hasOwnProperty(key1))
    163 		return (undefined);
    164 
    165 	return (pluckv(obj[key1], key.substr(i + 1)));
    166 }
    167 
    168 /*
    169  * Invoke callback(row) for each entry in the array that would be returned by
    170  * flattenObject(data, depth).  This is just like flattenObject(data,
    171  * depth).forEach(callback), except that the intermediate array is never
    172  * created.
    173  */
    174 function flattenIter(data, depth, callback)
    175 {
    176 	doFlattenIter(data, depth, [], callback);
    177 }
    178 
    179 function doFlattenIter(data, depth, accum, callback)
    180 {
    181 	var each;
    182 	var key;
    183 
    184 	if (depth === 0) {
    185 		each = accum.slice(0);
    186 		each.push(data);
    187 		callback(each);
    188 		return;
    189 	}
    190 
    191 	mod_assert.ok(data !== null);
    192 	mod_assert.equal(typeof (data), 'object');
    193 	mod_assert.equal(typeof (depth), 'number');
    194 	mod_assert.ok(depth >= 0);
    195 
    196 	for (key in data) {
    197 		each = accum.slice(0);
    198 		each.push(key);
    199 		doFlattenIter(data[key], depth - 1, each, callback);
    200 	}
    201 }
    202 
    203 function flattenObject(data, depth)
    204 {
    205 	if (depth === 0)
    206 		return ([ data ]);
    207 
    208 	mod_assert.ok(data !== null);
    209 	mod_assert.equal(typeof (data), 'object');
    210 	mod_assert.equal(typeof (depth), 'number');
    211 	mod_assert.ok(depth >= 0);
    212 
    213 	var rv = [];
    214 	var key;
    215 
    216 	for (key in data) {
    217 		flattenObject(data[key], depth - 1).forEach(function (p) {
    218 			rv.push([ key ].concat(p));
    219 		});
    220 	}
    221 
    222 	return (rv);
    223 }
    224 
    225 function startsWith(str, prefix)
    226 {
    227 	return (str.substr(0, prefix.length) == prefix);
    228 }
    229 
    230 function endsWith(str, suffix)
    231 {
    232 	return (str.substr(
    233 	    str.length - suffix.length, suffix.length) == suffix);
    234 }
    235 
    236 function iso8601(d)
    237 {
    238 	if (typeof (d) == 'number')
    239 		d = new Date(d);
    240 	mod_assert.ok(d.constructor === Date);
    241 	return (mod_extsprintf.sprintf('%4d-%02d-%02dT%02d:%02d:%02d.%03dZ',
    242 	    d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate(),
    243 	    d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(),
    244 	    d.getUTCMilliseconds()));
    245 }
    246 
    247 var RFC1123_MONTHS = [
    248     'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
    249     'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
    250 var RFC1123_DAYS = [
    251     'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
    252 
    253 function rfc1123(date) {
    254 	return (mod_extsprintf.sprintf('%s, %02d %s %04d %02d:%02d:%02d GMT',
    255 	    RFC1123_DAYS[date.getUTCDay()], date.getUTCDate(),
    256 	    RFC1123_MONTHS[date.getUTCMonth()], date.getUTCFullYear(),
    257 	    date.getUTCHours(), date.getUTCMinutes(),
    258 	    date.getUTCSeconds()));
    259 }
    260 
    261 /*
    262  * Parses a date expressed as a string, as either a number of milliseconds since
    263  * the epoch or any string format that Date accepts, giving preference to the
    264  * former where these two sets overlap (e.g., small numbers).
    265  */
    266 function parseDateTime(str)
    267 {
    268 	/*
    269 	 * This is irritatingly implicit, but significantly more concise than
    270 	 * alternatives.  The "+str" will convert a string containing only a
    271 	 * number directly to a Number, or NaN for other strings.  Thus, if the
    272 	 * conversion succeeds, we use it (this is the milliseconds-since-epoch
    273 	 * case).  Otherwise, we pass the string directly to the Date
    274 	 * constructor to parse.
    275 	 */
    276 	var numeric = +str;
    277 	if (!isNaN(numeric)) {
    278 		return (new Date(numeric));
    279 	} else {
    280 		return (new Date(str));
    281 	}
    282 }
    283 
    284 
    285 /*
    286  * Number.*_SAFE_INTEGER isn't present before node v0.12, so we hardcode
    287  * the ES6 definitions here, while allowing for them to someday be higher.
    288  */
    289 var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
    290 var MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
    291 
    292 
    293 /*
    294  * Default options for parseInteger().
    295  */
    296 var PI_DEFAULTS = {
    297 	base: 10,
    298 	allowSign: true,
    299 	allowPrefix: false,
    300 	allowTrailing: false,
    301 	allowImprecise: false,
    302 	trimWhitespace: false,
    303 	leadingZeroIsOctal: false
    304 };
    305 
    306 var CP_0 = 0x30;
    307 var CP_9 = 0x39;
    308 
    309 var CP_A = 0x41;
    310 var CP_B = 0x42;
    311 var CP_O = 0x4f;
    312 var CP_T = 0x54;
    313 var CP_X = 0x58;
    314 var CP_Z = 0x5a;
    315 
    316 var CP_a = 0x61;
    317 var CP_b = 0x62;
    318 var CP_o = 0x6f;
    319 var CP_t = 0x74;
    320 var CP_x = 0x78;
    321 var CP_z = 0x7a;
    322 
    323 var PI_CONV_DEC = 0x30;
    324 var PI_CONV_UC = 0x37;
    325 var PI_CONV_LC = 0x57;
    326 
    327 
    328 /*
    329  * A stricter version of parseInt() that provides options for changing what
    330  * is an acceptable string (for example, disallowing trailing characters).
    331  */
    332 function parseInteger(str, uopts)
    333 {
    334 	mod_assert.string(str, 'str');
    335 	mod_assert.optionalObject(uopts, 'options');
    336 
    337 	var baseOverride = false;
    338 	var options = PI_DEFAULTS;
    339 
    340 	if (uopts) {
    341 		baseOverride = hasKey(uopts, 'base');
    342 		options = mergeObjects(options, uopts);
    343 		mod_assert.number(options.base, 'options.base');
    344 		mod_assert.ok(options.base >= 2, 'options.base >= 2');
    345 		mod_assert.ok(options.base <= 36, 'options.base <= 36');
    346 		mod_assert.bool(options.allowSign, 'options.allowSign');
    347 		mod_assert.bool(options.allowPrefix, 'options.allowPrefix');
    348 		mod_assert.bool(options.allowTrailing,
    349 		    'options.allowTrailing');
    350 		mod_assert.bool(options.allowImprecise,
    351 		    'options.allowImprecise');
    352 		mod_assert.bool(options.trimWhitespace,
    353 		    'options.trimWhitespace');
    354 		mod_assert.bool(options.leadingZeroIsOctal,
    355 		    'options.leadingZeroIsOctal');
    356 
    357 		if (options.leadingZeroIsOctal) {
    358 			mod_assert.ok(!baseOverride,
    359 			    '"base" and "leadingZeroIsOctal" are ' +
    360 			    'mutually exclusive');
    361 		}
    362 	}
    363 
    364 	var c;
    365 	var pbase = -1;
    366 	var base = options.base;
    367 	var start;
    368 	var mult = 1;
    369 	var value = 0;
    370 	var idx = 0;
    371 	var len = str.length;
    372 
    373 	/* Trim any whitespace on the left side. */
    374 	if (options.trimWhitespace) {
    375 		while (idx < len && isSpace(str.charCodeAt(idx))) {
    376 			++idx;
    377 		}
    378 	}
    379 
    380 	/* Check the number for a leading sign. */
    381 	if (options.allowSign) {
    382 		if (str[idx] === '-') {
    383 			idx += 1;
    384 			mult = -1;
    385 		} else if (str[idx] === '+') {
    386 			idx += 1;
    387 		}
    388 	}
    389 
    390 	/* Parse the base-indicating prefix if there is one. */
    391 	if (str[idx] === '0') {
    392 		if (options.allowPrefix) {
    393 			pbase = prefixToBase(str.charCodeAt(idx + 1));
    394 			if (pbase !== -1 && (!baseOverride || pbase === base)) {
    395 				base = pbase;
    396 				idx += 2;
    397 			}
    398 		}
    399 
    400 		if (pbase === -1 && options.leadingZeroIsOctal) {
    401 			base = 8;
    402 		}
    403 	}
    404 
    405 	/* Parse the actual digits. */
    406 	for (start = idx; idx < len; ++idx) {
    407 		c = translateDigit(str.charCodeAt(idx));
    408 		if (c !== -1 && c < base) {
    409 			value *= base;
    410 			value += c;
    411 		} else {
    412 			break;
    413 		}
    414 	}
    415 
    416 	/* If we didn't parse any digits, we have an invalid number. */
    417 	if (start === idx) {
    418 		return (new Error('invalid number: ' + JSON.stringify(str)));
    419 	}
    420 
    421 	/* Trim any whitespace on the right side. */
    422 	if (options.trimWhitespace) {
    423 		while (idx < len && isSpace(str.charCodeAt(idx))) {
    424 			++idx;
    425 		}
    426 	}
    427 
    428 	/* Check for trailing characters. */
    429 	if (idx < len && !options.allowTrailing) {
    430 		return (new Error('trailing characters after number: ' +
    431 		    JSON.stringify(str.slice(idx))));
    432 	}
    433 
    434 	/* If our value is 0, we return now, to avoid returning -0. */
    435 	if (value === 0) {
    436 		return (0);
    437 	}
    438 
    439 	/* Calculate our final value. */
    440 	var result = value * mult;
    441 
    442 	/*
    443 	 * If the string represents a value that cannot be precisely represented
    444 	 * by JavaScript, then we want to check that:
    445 	 *
    446 	 * - We never increased the value past MAX_SAFE_INTEGER
    447 	 * - We don't make the result negative and below MIN_SAFE_INTEGER
    448 	 *
    449 	 * Because we only ever increment the value during parsing, there's no
    450 	 * chance of moving past MAX_SAFE_INTEGER and then dropping below it
    451 	 * again, losing precision in the process. This means that we only need
    452 	 * to do our checks here, at the end.
    453 	 */
    454 	if (!options.allowImprecise &&
    455 	    (value > MAX_SAFE_INTEGER || result < MIN_SAFE_INTEGER)) {
    456 		return (new Error('number is outside of the supported range: ' +
    457 		    JSON.stringify(str.slice(start, idx))));
    458 	}
    459 
    460 	return (result);
    461 }
    462 
    463 
    464 /*
    465  * Interpret a character code as a base-36 digit.
    466  */
    467 function translateDigit(d)
    468 {
    469 	if (d >= CP_0 && d <= CP_9) {
    470 		/* '0' to '9' -> 0 to 9 */
    471 		return (d - PI_CONV_DEC);
    472 	} else if (d >= CP_A && d <= CP_Z) {
    473 		/* 'A' - 'Z' -> 10 to 35 */
    474 		return (d - PI_CONV_UC);
    475 	} else if (d >= CP_a && d <= CP_z) {
    476 		/* 'a' - 'z' -> 10 to 35 */
    477 		return (d - PI_CONV_LC);
    478 	} else {
    479 		/* Invalid character code */
    480 		return (-1);
    481 	}
    482 }
    483 
    484 
    485 /*
    486  * Test if a value matches the ECMAScript definition of trimmable whitespace.
    487  */
    488 function isSpace(c)
    489 {
    490 	return (c === 0x20) ||
    491 	    (c >= 0x0009 && c <= 0x000d) ||
    492 	    (c === 0x00a0) ||
    493 	    (c === 0x1680) ||
    494 	    (c === 0x180e) ||
    495 	    (c >= 0x2000 && c <= 0x200a) ||
    496 	    (c === 0x2028) ||
    497 	    (c === 0x2029) ||
    498 	    (c === 0x202f) ||
    499 	    (c === 0x205f) ||
    500 	    (c === 0x3000) ||
    501 	    (c === 0xfeff);
    502 }
    503 
    504 
    505 /*
    506  * Determine which base a character indicates (e.g., 'x' indicates hex).
    507  */
    508 function prefixToBase(c)
    509 {
    510 	if (c === CP_b || c === CP_B) {
    511 		/* 0b/0B (binary) */
    512 		return (2);
    513 	} else if (c === CP_o || c === CP_O) {
    514 		/* 0o/0O (octal) */
    515 		return (8);
    516 	} else if (c === CP_t || c === CP_T) {
    517 		/* 0t/0T (decimal) */
    518 		return (10);
    519 	} else if (c === CP_x || c === CP_X) {
    520 		/* 0x/0X (hexadecimal) */
    521 		return (16);
    522 	} else {
    523 		/* Not a meaningful character */
    524 		return (-1);
    525 	}
    526 }
    527 
    528 
    529 function validateJsonObjectJS(schema, input)
    530 {
    531 	var report = mod_jsonschema.validate(input, schema);
    532 
    533 	if (report.errors.length === 0)
    534 		return (null);
    535 
    536 	/* Currently, we only do anything useful with the first error. */
    537 	var error = report.errors[0];
    538 
    539 	/* The failed property is given by a URI with an irrelevant prefix. */
    540 	var propname = error['property'];
    541 	var reason = error['message'].toLowerCase();
    542 	var i, j;
    543 
    544 	/*
    545 	 * There's at least one case where the property error message is
    546 	 * confusing at best.  We work around this here.
    547 	 */
    548 	if ((i = reason.indexOf('the property ')) != -1 &&
    549 	    (j = reason.indexOf(' is not defined in the schema and the ' +
    550 	    'schema does not allow additional properties')) != -1) {
    551 		i += 'the property '.length;
    552 		if (propname === '')
    553 			propname = reason.substr(i, j - i);
    554 		else
    555 			propname = propname + '.' + reason.substr(i, j - i);
    556 
    557 		reason = 'unsupported property';
    558 	}
    559 
    560 	var rv = new mod_verror.VError('property "%s": %s', propname, reason);
    561 	rv.jsv_details = error;
    562 	return (rv);
    563 }
    564 
    565 function randElt(arr)
    566 {
    567 	mod_assert.ok(Array.isArray(arr) && arr.length > 0,
    568 	    'randElt argument must be a non-empty array');
    569 
    570 	return (arr[Math.floor(Math.random() * arr.length)]);
    571 }
    572 
    573 function assertHrtime(a)
    574 {
    575 	mod_assert.ok(a[0] >= 0 && a[1] >= 0,
    576 	    'negative numbers not allowed in hrtimes');
    577 	mod_assert.ok(a[1] < 1e9, 'nanoseconds column overflow');
    578 }
    579 
    580 /*
    581  * Compute the time elapsed between hrtime readings A and B, where A is later
    582  * than B.  hrtime readings come from Node's process.hrtime().  There is no
    583  * defined way to represent negative deltas, so it's illegal to diff B from A
    584  * where the time denoted by B is later than the time denoted by A.  If this
    585  * becomes valuable, we can define a representation and extend the
    586  * implementation to support it.
    587  */
    588 function hrtimeDiff(a, b)
    589 {
    590 	assertHrtime(a);
    591 	assertHrtime(b);
    592 	mod_assert.ok(a[0] > b[0] || (a[0] == b[0] && a[1] >= b[1]),
    593 	    'negative differences not allowed');
    594 
    595 	var rv = [ a[0] - b[0], 0 ];
    596 
    597 	if (a[1] >= b[1]) {
    598 		rv[1] = a[1] - b[1];
    599 	} else {
    600 		rv[0]--;
    601 		rv[1] = 1e9 - (b[1] - a[1]);
    602 	}
    603 
    604 	return (rv);
    605 }
    606 
    607 /*
    608  * Convert a hrtime reading from the array format returned by Node's
    609  * process.hrtime() into a scalar number of nanoseconds.
    610  */
    611 function hrtimeNanosec(a)
    612 {
    613 	assertHrtime(a);
    614 
    615 	return (Math.floor(a[0] * 1e9 + a[1]));
    616 }
    617 
    618 /*
    619  * Convert a hrtime reading from the array format returned by Node's
    620  * process.hrtime() into a scalar number of microseconds.
    621  */
    622 function hrtimeMicrosec(a)
    623 {
    624 	assertHrtime(a);
    625 
    626 	return (Math.floor(a[0] * 1e6 + a[1] / 1e3));
    627 }
    628 
    629 /*
    630  * Convert a hrtime reading from the array format returned by Node's
    631  * process.hrtime() into a scalar number of milliseconds.
    632  */
    633 function hrtimeMillisec(a)
    634 {
    635 	assertHrtime(a);
    636 
    637 	return (Math.floor(a[0] * 1e3 + a[1] / 1e6));
    638 }
    639 
    640 /*
    641  * Add two hrtime readings A and B, overwriting A with the result of the
    642  * addition.  This function is useful for accumulating several hrtime intervals
    643  * into a counter.  Returns A.
    644  */
    645 function hrtimeAccum(a, b)
    646 {
    647 	assertHrtime(a);
    648 	assertHrtime(b);
    649 
    650 	/*
    651 	 * Accumulate the nanosecond component.
    652 	 */
    653 	a[1] += b[1];
    654 	if (a[1] >= 1e9) {
    655 		/*
    656 		 * The nanosecond component overflowed, so carry to the seconds
    657 		 * field.
    658 		 */
    659 		a[0]++;
    660 		a[1] -= 1e9;
    661 	}
    662 
    663 	/*
    664 	 * Accumulate the seconds component.
    665 	 */
    666 	a[0] += b[0];
    667 
    668 	return (a);
    669 }
    670 
    671 /*
    672  * Add two hrtime readings A and B, returning the result as a new hrtime array.
    673  * Does not modify either input argument.
    674  */
    675 function hrtimeAdd(a, b)
    676 {
    677 	assertHrtime(a);
    678 
    679 	var rv = [ a[0], a[1] ];
    680 
    681 	return (hrtimeAccum(rv, b));
    682 }
    683 
    684 
    685 /*
    686  * Check an object for unexpected properties.  Accepts the object to check, and
    687  * an array of allowed property names (strings).  Returns an array of key names
    688  * that were found on the object, but did not appear in the list of allowed
    689  * properties.  If no properties were found, the returned array will be of
    690  * zero length.
    691  */
    692 function extraProperties(obj, allowed)
    693 {
    694 	mod_assert.ok(typeof (obj) === 'object' && obj !== null,
    695 	    'obj argument must be a non-null object');
    696 	mod_assert.ok(Array.isArray(allowed),
    697 	    'allowed argument must be an array of strings');
    698 	for (var i = 0; i < allowed.length; i++) {
    699 		mod_assert.ok(typeof (allowed[i]) === 'string',
    700 		    'allowed argument must be an array of strings');
    701 	}
    702 
    703 	return (Object.keys(obj).filter(function (key) {
    704 		return (allowed.indexOf(key) === -1);
    705 	}));
    706 }
    707 
    708 /*
    709  * Given three sets of properties "provided" (may be undefined), "overrides"
    710  * (required), and "defaults" (may be undefined), construct an object containing
    711  * the union of these sets with "overrides" overriding "provided", and
    712  * "provided" overriding "defaults".  None of the input objects are modified.
    713  */
    714 function mergeObjects(provided, overrides, defaults)
    715 {
    716 	var rv, k;
    717 
    718 	rv = {};
    719 	if (defaults) {
    720 		for (k in defaults)
    721 			rv[k] = defaults[k];
    722 	}
    723 
    724 	if (provided) {
    725 		for (k in provided)
    726 			rv[k] = provided[k];
    727 	}
    728 
    729 	if (overrides) {
    730 		for (k in overrides)
    731 			rv[k] = overrides[k];
    732 	}
    733 
    734 	return (rv);
    735 }