Initial commit
[yaffs-website] / node_modules / jsprim / lib / jsprim.js
1 /*
2  * lib/jsprim.js: utilities for primitive JavaScript types
3  */
4
5 var mod_assert = require('assert');
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.iso8601 = iso8601;
33 exports.rfc1123 = rfc1123;
34 exports.parseDateTime = parseDateTime;
35
36 exports.hrtimediff = hrtimeDiff;
37 exports.hrtimeDiff = hrtimeDiff;
38 exports.hrtimeAccum = hrtimeAccum;
39 exports.hrtimeAdd = hrtimeAdd;
40 exports.hrtimeNanosec = hrtimeNanosec;
41 exports.hrtimeMicrosec = hrtimeMicrosec;
42 exports.hrtimeMillisec = hrtimeMillisec;
43
44
45 /*
46  * Deep copy an acyclic *basic* Javascript object.  This only handles basic
47  * scalars (strings, numbers, booleans) and arbitrarily deep arrays and objects
48  * containing these.  This does *not* handle instances of other classes.
49  */
50 function deepCopy(obj)
51 {
52         var ret, key;
53         var marker = '__deepCopy';
54
55         if (obj && obj[marker])
56                 throw (new Error('attempted deep copy of cyclic object'));
57
58         if (obj && obj.constructor == Object) {
59                 ret = {};
60                 obj[marker] = true;
61
62                 for (key in obj) {
63                         if (key == marker)
64                                 continue;
65
66                         ret[key] = deepCopy(obj[key]);
67                 }
68
69                 delete (obj[marker]);
70                 return (ret);
71         }
72
73         if (obj && obj.constructor == Array) {
74                 ret = [];
75                 obj[marker] = true;
76
77                 for (key = 0; key < obj.length; key++)
78                         ret.push(deepCopy(obj[key]));
79
80                 delete (obj[marker]);
81                 return (ret);
82         }
83
84         /*
85          * It must be a primitive type -- just return it.
86          */
87         return (obj);
88 }
89
90 function deepEqual(obj1, obj2)
91 {
92         if (typeof (obj1) != typeof (obj2))
93                 return (false);
94
95         if (obj1 === null || obj2 === null || typeof (obj1) != 'object')
96                 return (obj1 === obj2);
97
98         if (obj1.constructor != obj2.constructor)
99                 return (false);
100
101         var k;
102         for (k in obj1) {
103                 if (!obj2.hasOwnProperty(k))
104                         return (false);
105
106                 if (!deepEqual(obj1[k], obj2[k]))
107                         return (false);
108         }
109
110         for (k in obj2) {
111                 if (!obj1.hasOwnProperty(k))
112                         return (false);
113         }
114
115         return (true);
116 }
117
118 function isEmpty(obj)
119 {
120         var key;
121         for (key in obj)
122                 return (false);
123         return (true);
124 }
125
126 function hasKey(obj, key)
127 {
128         mod_assert.equal(typeof (key), 'string');
129         return (Object.prototype.hasOwnProperty.call(obj, key));
130 }
131
132 function forEachKey(obj, callback)
133 {
134         for (var key in obj) {
135                 if (hasKey(obj, key)) {
136                         callback(key, obj[key]);
137                 }
138         }
139 }
140
141 function pluck(obj, key)
142 {
143         mod_assert.equal(typeof (key), 'string');
144         return (pluckv(obj, key));
145 }
146
147 function pluckv(obj, key)
148 {
149         if (obj === null || typeof (obj) !== 'object')
150                 return (undefined);
151
152         if (obj.hasOwnProperty(key))
153                 return (obj[key]);
154
155         var i = key.indexOf('.');
156         if (i == -1)
157                 return (undefined);
158
159         var key1 = key.substr(0, i);
160         if (!obj.hasOwnProperty(key1))
161                 return (undefined);
162
163         return (pluckv(obj[key1], key.substr(i + 1)));
164 }
165
166 /*
167  * Invoke callback(row) for each entry in the array that would be returned by
168  * flattenObject(data, depth).  This is just like flattenObject(data,
169  * depth).forEach(callback), except that the intermediate array is never
170  * created.
171  */
172 function flattenIter(data, depth, callback)
173 {
174         doFlattenIter(data, depth, [], callback);
175 }
176
177 function doFlattenIter(data, depth, accum, callback)
178 {
179         var each;
180         var key;
181
182         if (depth === 0) {
183                 each = accum.slice(0);
184                 each.push(data);
185                 callback(each);
186                 return;
187         }
188
189         mod_assert.ok(data !== null);
190         mod_assert.equal(typeof (data), 'object');
191         mod_assert.equal(typeof (depth), 'number');
192         mod_assert.ok(depth >= 0);
193
194         for (key in data) {
195                 each = accum.slice(0);
196                 each.push(key);
197                 doFlattenIter(data[key], depth - 1, each, callback);
198         }
199 }
200
201 function flattenObject(data, depth)
202 {
203         if (depth === 0)
204                 return ([ data ]);
205
206         mod_assert.ok(data !== null);
207         mod_assert.equal(typeof (data), 'object');
208         mod_assert.equal(typeof (depth), 'number');
209         mod_assert.ok(depth >= 0);
210
211         var rv = [];
212         var key;
213
214         for (key in data) {
215                 flattenObject(data[key], depth - 1).forEach(function (p) {
216                         rv.push([ key ].concat(p));
217                 });
218         }
219
220         return (rv);
221 }
222
223 function startsWith(str, prefix)
224 {
225         return (str.substr(0, prefix.length) == prefix);
226 }
227
228 function endsWith(str, suffix)
229 {
230         return (str.substr(
231             str.length - suffix.length, suffix.length) == suffix);
232 }
233
234 function iso8601(d)
235 {
236         if (typeof (d) == 'number')
237                 d = new Date(d);
238         mod_assert.ok(d.constructor === Date);
239         return (mod_extsprintf.sprintf('%4d-%02d-%02dT%02d:%02d:%02d.%03dZ',
240             d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate(),
241             d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(),
242             d.getUTCMilliseconds()));
243 }
244
245 var RFC1123_MONTHS = [
246     'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
247     'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
248 var RFC1123_DAYS = [
249     'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
250
251 function rfc1123(date) {
252         return (mod_extsprintf.sprintf('%s, %02d %s %04d %02d:%02d:%02d GMT',
253             RFC1123_DAYS[date.getUTCDay()], date.getUTCDate(),
254             RFC1123_MONTHS[date.getUTCMonth()], date.getUTCFullYear(),
255             date.getUTCHours(), date.getUTCMinutes(),
256             date.getUTCSeconds()));
257 }
258
259 /*
260  * Parses a date expressed as a string, as either a number of milliseconds since
261  * the epoch or any string format that Date accepts, giving preference to the
262  * former where these two sets overlap (e.g., small numbers).
263  */
264 function parseDateTime(str)
265 {
266         /*
267          * This is irritatingly implicit, but significantly more concise than
268          * alternatives.  The "+str" will convert a string containing only a
269          * number directly to a Number, or NaN for other strings.  Thus, if the
270          * conversion succeeds, we use it (this is the milliseconds-since-epoch
271          * case).  Otherwise, we pass the string directly to the Date
272          * constructor to parse.
273          */
274         var numeric = +str;
275         if (!isNaN(numeric)) {
276                 return (new Date(numeric));
277         } else {
278                 return (new Date(str));
279         }
280 }
281
282 function validateJsonObjectJS(schema, input)
283 {
284         var report = mod_jsonschema.validate(input, schema);
285
286         if (report.errors.length === 0)
287                 return (null);
288
289         /* Currently, we only do anything useful with the first error. */
290         var error = report.errors[0];
291
292         /* The failed property is given by a URI with an irrelevant prefix. */
293         var propname = error['property'];
294         var reason = error['message'].toLowerCase();
295         var i, j;
296
297         /*
298          * There's at least one case where the property error message is
299          * confusing at best.  We work around this here.
300          */
301         if ((i = reason.indexOf('the property ')) != -1 &&
302             (j = reason.indexOf(' is not defined in the schema and the ' +
303             'schema does not allow additional properties')) != -1) {
304                 i += 'the property '.length;
305                 if (propname === '')
306                         propname = reason.substr(i, j - i);
307                 else
308                         propname = propname + '.' + reason.substr(i, j - i);
309
310                 reason = 'unsupported property';
311         }
312
313         var rv = new mod_verror.VError('property "%s": %s', propname, reason);
314         rv.jsv_details = error;
315         return (rv);
316 }
317
318 function randElt(arr)
319 {
320         mod_assert.ok(Array.isArray(arr) && arr.length > 0,
321             'randElt argument must be a non-empty array');
322
323         return (arr[Math.floor(Math.random() * arr.length)]);
324 }
325
326 function assertHrtime(a)
327 {
328         mod_assert.ok(a[0] >= 0 && a[1] >= 0,
329             'negative numbers not allowed in hrtimes');
330         mod_assert.ok(a[1] < 1e9, 'nanoseconds column overflow');
331 }
332
333 /*
334  * Compute the time elapsed between hrtime readings A and B, where A is later
335  * than B.  hrtime readings come from Node's process.hrtime().  There is no
336  * defined way to represent negative deltas, so it's illegal to diff B from A
337  * where the time denoted by B is later than the time denoted by A.  If this
338  * becomes valuable, we can define a representation and extend the
339  * implementation to support it.
340  */
341 function hrtimeDiff(a, b)
342 {
343         assertHrtime(a);
344         assertHrtime(b);
345         mod_assert.ok(a[0] > b[0] || (a[0] == b[0] && a[1] >= b[1]),
346             'negative differences not allowed');
347
348         var rv = [ a[0] - b[0], 0 ];
349
350         if (a[1] >= b[1]) {
351                 rv[1] = a[1] - b[1];
352         } else {
353                 rv[0]--;
354                 rv[1] = 1e9 - (b[1] - a[1]);
355         }
356
357         return (rv);
358 }
359
360 /*
361  * Convert a hrtime reading from the array format returned by Node's
362  * process.hrtime() into a scalar number of nanoseconds.
363  */
364 function hrtimeNanosec(a)
365 {
366         assertHrtime(a);
367
368         return (Math.floor(a[0] * 1e9 + a[1]));
369 }
370
371 /*
372  * Convert a hrtime reading from the array format returned by Node's
373  * process.hrtime() into a scalar number of microseconds.
374  */
375 function hrtimeMicrosec(a)
376 {
377         assertHrtime(a);
378
379         return (Math.floor(a[0] * 1e6 + a[1] / 1e3));
380 }
381
382 /*
383  * Convert a hrtime reading from the array format returned by Node's
384  * process.hrtime() into a scalar number of milliseconds.
385  */
386 function hrtimeMillisec(a)
387 {
388         assertHrtime(a);
389
390         return (Math.floor(a[0] * 1e3 + a[1] / 1e6));
391 }
392
393 /*
394  * Add two hrtime readings A and B, overwriting A with the result of the
395  * addition.  This function is useful for accumulating several hrtime intervals
396  * into a counter.  Returns A.
397  */
398 function hrtimeAccum(a, b)
399 {
400         assertHrtime(a);
401         assertHrtime(b);
402
403         /*
404          * Accumulate the nanosecond component.
405          */
406         a[1] += b[1];
407         if (a[1] >= 1e9) {
408                 /*
409                  * The nanosecond component overflowed, so carry to the seconds
410                  * field.
411                  */
412                 a[0]++;
413                 a[1] -= 1e9;
414         }
415
416         /*
417          * Accumulate the seconds component.
418          */
419         a[0] += b[0];
420
421         return (a);
422 }
423
424 /*
425  * Add two hrtime readings A and B, returning the result as a new hrtime array.
426  * Does not modify either input argument.
427  */
428 function hrtimeAdd(a, b)
429 {
430         assertHrtime(a);
431
432         var rv = [ a[0], a[1] ];
433
434         return (hrtimeAccum(rv, b));
435 }
436
437
438 /*
439  * Check an object for unexpected properties.  Accepts the object to check, and
440  * an array of allowed property names (strings).  Returns an array of key names
441  * that were found on the object, but did not appear in the list of allowed
442  * properties.  If no properties were found, the returned array will be of
443  * zero length.
444  */
445 function extraProperties(obj, allowed)
446 {
447         mod_assert.ok(typeof (obj) === 'object' && obj !== null,
448             'obj argument must be a non-null object');
449         mod_assert.ok(Array.isArray(allowed),
450             'allowed argument must be an array of strings');
451         for (var i = 0; i < allowed.length; i++) {
452                 mod_assert.ok(typeof (allowed[i]) === 'string',
453                     'allowed argument must be an array of strings');
454         }
455
456         return (Object.keys(obj).filter(function (key) {
457                 return (allowed.indexOf(key) === -1);
458         }));
459 }
460
461 /*
462  * Given three sets of properties "provided" (may be undefined), "overrides"
463  * (required), and "defaults" (may be undefined), construct an object containing
464  * the union of these sets with "overrides" overriding "provided", and
465  * "provided" overriding "defaults".  None of the input objects are modified.
466  */
467 function mergeObjects(provided, overrides, defaults)
468 {
469         var rv, k;
470
471         rv = {};
472         if (defaults) {
473                 for (k in defaults)
474                         rv[k] = defaults[k];
475         }
476
477         if (provided) {
478                 for (k in provided)
479                         rv[k] = provided[k];
480         }
481
482         if (overrides) {
483                 for (k in overrides)
484                         rv[k] = overrides[k];
485         }
486
487         return (rv);
488 }