Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81154 views
1
// Load modules
2
3
var Crypto = require('crypto');
4
var Path = require('path');
5
var Util = require('util');
6
var Escape = require('./escape');
7
8
9
// Declare internals
10
11
var internals = {};
12
13
14
// Clone object or array
15
16
exports.clone = function (obj, seen) {
17
18
if (typeof obj !== 'object' ||
19
obj === null) {
20
21
return obj;
22
}
23
24
seen = seen || { orig: [], copy: [] };
25
26
var lookup = seen.orig.indexOf(obj);
27
if (lookup !== -1) {
28
return seen.copy[lookup];
29
}
30
31
var newObj;
32
var cloneDeep = false;
33
34
if (!Array.isArray(obj)) {
35
if (Buffer.isBuffer(obj)) {
36
newObj = new Buffer(obj);
37
}
38
else if (obj instanceof Date) {
39
newObj = new Date(obj.getTime());
40
}
41
else if (obj instanceof RegExp) {
42
newObj = new RegExp(obj);
43
}
44
else {
45
var proto = Object.getPrototypeOf(obj);
46
if (proto &&
47
proto.isImmutable) {
48
49
newObj = obj;
50
}
51
else {
52
newObj = Object.create(proto);
53
cloneDeep = true;
54
}
55
}
56
}
57
else {
58
newObj = [];
59
cloneDeep = true;
60
}
61
62
seen.orig.push(obj);
63
seen.copy.push(newObj);
64
65
if (cloneDeep) {
66
var keys = Object.getOwnPropertyNames(obj);
67
for (var i = 0, il = keys.length; i < il; ++i) {
68
var key = keys[i];
69
var descriptor = Object.getOwnPropertyDescriptor(obj, key);
70
if (descriptor.get ||
71
descriptor.set) {
72
73
Object.defineProperty(newObj, key, descriptor);
74
}
75
else {
76
newObj[key] = exports.clone(obj[key], seen);
77
}
78
}
79
}
80
81
return newObj;
82
};
83
84
85
// Merge all the properties of source into target, source wins in conflict, and by default null and undefined from source are applied
86
/*eslint-disable */
87
exports.merge = function (target, source, isNullOverride /* = true */, isMergeArrays /* = true */) {
88
/*eslint-enable */
89
exports.assert(target && typeof target === 'object', 'Invalid target value: must be an object');
90
exports.assert(source === null || source === undefined || typeof source === 'object', 'Invalid source value: must be null, undefined, or an object');
91
92
if (!source) {
93
return target;
94
}
95
96
if (Array.isArray(source)) {
97
exports.assert(Array.isArray(target), 'Cannot merge array onto an object');
98
if (isMergeArrays === false) { // isMergeArrays defaults to true
99
target.length = 0; // Must not change target assignment
100
}
101
102
for (var i = 0, il = source.length; i < il; ++i) {
103
target.push(exports.clone(source[i]));
104
}
105
106
return target;
107
}
108
109
var keys = Object.keys(source);
110
for (var k = 0, kl = keys.length; k < kl; ++k) {
111
var key = keys[k];
112
var value = source[key];
113
if (value &&
114
typeof value === 'object') {
115
116
if (!target[key] ||
117
typeof target[key] !== 'object' ||
118
(Array.isArray(target[key]) ^ Array.isArray(value)) ||
119
value instanceof Date ||
120
Buffer.isBuffer(value) ||
121
value instanceof RegExp) {
122
123
target[key] = exports.clone(value);
124
}
125
else {
126
exports.merge(target[key], value, isNullOverride, isMergeArrays);
127
}
128
}
129
else {
130
if (value !== null &&
131
value !== undefined) { // Explicit to preserve empty strings
132
133
target[key] = value;
134
}
135
else if (isNullOverride !== false) { // Defaults to true
136
target[key] = value;
137
}
138
}
139
}
140
141
return target;
142
};
143
144
145
// Apply options to a copy of the defaults
146
147
exports.applyToDefaults = function (defaults, options, isNullOverride) {
148
149
exports.assert(defaults && typeof defaults === 'object', 'Invalid defaults value: must be an object');
150
exports.assert(!options || options === true || typeof options === 'object', 'Invalid options value: must be true, falsy or an object');
151
152
if (!options) { // If no options, return null
153
return null;
154
}
155
156
var copy = exports.clone(defaults);
157
158
if (options === true) { // If options is set to true, use defaults
159
return copy;
160
}
161
162
return exports.merge(copy, options, isNullOverride === true, false);
163
};
164
165
166
// Clone an object except for the listed keys which are shallow copied
167
168
exports.cloneWithShallow = function (source, keys) {
169
170
if (!source ||
171
typeof source !== 'object') {
172
173
return source;
174
}
175
176
var storage = internals.store(source, keys); // Move shallow copy items to storage
177
var copy = exports.clone(source); // Deep copy the rest
178
internals.restore(copy, source, storage); // Shallow copy the stored items and restore
179
return copy;
180
};
181
182
183
internals.store = function (source, keys) {
184
185
var storage = {};
186
for (var i = 0, il = keys.length; i < il; ++i) {
187
var key = keys[i];
188
var value = exports.reach(source, key);
189
if (value !== undefined) {
190
storage[key] = value;
191
internals.reachSet(source, key, undefined);
192
}
193
}
194
195
return storage;
196
};
197
198
199
internals.restore = function (copy, source, storage) {
200
201
var keys = Object.keys(storage);
202
for (var i = 0, il = keys.length; i < il; ++i) {
203
var key = keys[i];
204
internals.reachSet(copy, key, storage[key]);
205
internals.reachSet(source, key, storage[key]);
206
}
207
};
208
209
210
internals.reachSet = function (obj, key, value) {
211
212
var path = key.split('.');
213
var ref = obj;
214
for (var i = 0, il = path.length; i < il; ++i) {
215
var segment = path[i];
216
if (i + 1 === il) {
217
ref[segment] = value;
218
}
219
220
ref = ref[segment];
221
}
222
};
223
224
225
// Apply options to defaults except for the listed keys which are shallow copied from option without merging
226
227
exports.applyToDefaultsWithShallow = function (defaults, options, keys) {
228
229
exports.assert(defaults && typeof defaults === 'object', 'Invalid defaults value: must be an object');
230
exports.assert(!options || options === true || typeof options === 'object', 'Invalid options value: must be true, falsy or an object');
231
exports.assert(keys && Array.isArray(keys), 'Invalid keys');
232
233
if (!options) { // If no options, return null
234
return null;
235
}
236
237
var copy = exports.cloneWithShallow(defaults, keys);
238
239
if (options === true) { // If options is set to true, use defaults
240
return copy;
241
}
242
243
var storage = internals.store(options, keys); // Move shallow copy items to storage
244
exports.merge(copy, options, false, false); // Deep copy the rest
245
internals.restore(copy, options, storage); // Shallow copy the stored items and restore
246
return copy;
247
};
248
249
250
// Deep object or array comparison
251
252
exports.deepEqual = function (obj, ref, options, seen) {
253
254
options = options || { prototype: true };
255
256
var type = typeof obj;
257
258
if (type !== typeof ref) {
259
return false;
260
}
261
262
if (type !== 'object' ||
263
obj === null ||
264
ref === null) {
265
266
if (obj === ref) { // Copied from Deep-eql, copyright(c) 2013 Jake Luer, [email protected], MIT Licensed, https://github.com/chaijs/deep-eql
267
return obj !== 0 || 1 / obj === 1 / ref; // -0 / +0
268
}
269
270
return obj !== obj && ref !== ref; // NaN
271
}
272
273
seen = seen || [];
274
if (seen.indexOf(obj) !== -1) {
275
return true; // If previous comparison failed, it would have stopped execution
276
}
277
278
seen.push(obj);
279
280
if (Array.isArray(obj)) {
281
if (!Array.isArray(ref)) {
282
return false;
283
}
284
285
if (!options.part && obj.length !== ref.length) {
286
return false;
287
}
288
289
for (var i = 0, il = obj.length; i < il; ++i) {
290
if (options.part) {
291
var found = false;
292
for (var r = 0, rl = ref.length; r < rl; ++r) {
293
if (exports.deepEqual(obj[i], ref[r], options, seen)) {
294
found = true;
295
break;
296
}
297
}
298
299
return found;
300
}
301
302
if (!exports.deepEqual(obj[i], ref[i], options, seen)) {
303
return false;
304
}
305
}
306
307
return true;
308
}
309
310
if (Buffer.isBuffer(obj)) {
311
if (!Buffer.isBuffer(ref)) {
312
return false;
313
}
314
315
if (obj.length !== ref.length) {
316
return false;
317
}
318
319
for (var j = 0, jl = obj.length; j < jl; ++j) {
320
if (obj[j] !== ref[j]) {
321
return false;
322
}
323
}
324
325
return true;
326
}
327
328
if (obj instanceof Date) {
329
return (ref instanceof Date && obj.getTime() === ref.getTime());
330
}
331
332
if (obj instanceof RegExp) {
333
return (ref instanceof RegExp && obj.toString() === ref.toString());
334
}
335
336
if (options.prototype) {
337
if (Object.getPrototypeOf(obj) !== Object.getPrototypeOf(ref)) {
338
return false;
339
}
340
}
341
342
var keys = Object.getOwnPropertyNames(obj);
343
344
if (!options.part && keys.length !== Object.getOwnPropertyNames(ref).length) {
345
return false;
346
}
347
348
for (var k = 0, kl = keys.length; k < kl; ++k) {
349
var key = keys[k];
350
var descriptor = Object.getOwnPropertyDescriptor(obj, key);
351
if (descriptor.get) {
352
if (!exports.deepEqual(descriptor, Object.getOwnPropertyDescriptor(ref, key), options, seen)) {
353
return false;
354
}
355
}
356
else if (!exports.deepEqual(obj[key], ref[key], options, seen)) {
357
return false;
358
}
359
}
360
361
return true;
362
};
363
364
365
// Remove duplicate items from array
366
367
exports.unique = function (array, key) {
368
369
var index = {};
370
var result = [];
371
372
for (var i = 0, il = array.length; i < il; ++i) {
373
var id = (key ? array[i][key] : array[i]);
374
if (index[id] !== true) {
375
376
result.push(array[i]);
377
index[id] = true;
378
}
379
}
380
381
return result;
382
};
383
384
385
// Convert array into object
386
387
exports.mapToObject = function (array, key) {
388
389
if (!array) {
390
return null;
391
}
392
393
var obj = {};
394
for (var i = 0, il = array.length; i < il; ++i) {
395
if (key) {
396
if (array[i][key]) {
397
obj[array[i][key]] = true;
398
}
399
}
400
else {
401
obj[array[i]] = true;
402
}
403
}
404
405
return obj;
406
};
407
408
409
// Find the common unique items in two arrays
410
411
exports.intersect = function (array1, array2, justFirst) {
412
413
if (!array1 || !array2) {
414
return [];
415
}
416
417
var common = [];
418
var hash = (Array.isArray(array1) ? exports.mapToObject(array1) : array1);
419
var found = {};
420
for (var i = 0, il = array2.length; i < il; ++i) {
421
if (hash[array2[i]] && !found[array2[i]]) {
422
if (justFirst) {
423
return array2[i];
424
}
425
426
common.push(array2[i]);
427
found[array2[i]] = true;
428
}
429
}
430
431
return (justFirst ? null : common);
432
};
433
434
435
// Test if the reference contains the values
436
437
exports.contain = function (ref, values, options) {
438
439
/*
440
string -> string(s)
441
array -> item(s)
442
object -> key(s)
443
object -> object (key:value)
444
*/
445
446
var valuePairs = null;
447
if (typeof ref === 'object' &&
448
typeof values === 'object' &&
449
!Array.isArray(ref) &&
450
!Array.isArray(values)) {
451
452
valuePairs = values;
453
values = Object.keys(values);
454
}
455
else {
456
values = [].concat(values);
457
}
458
459
options = options || {}; // deep, once, only, part
460
461
exports.assert(arguments.length >= 2, 'Insufficient arguments');
462
exports.assert(typeof ref === 'string' || typeof ref === 'object', 'Reference must be string or an object');
463
exports.assert(values.length, 'Values array cannot be empty');
464
465
var compare, compareFlags;
466
if (options.deep) {
467
compare = exports.deepEqual;
468
469
var hasOnly = options.hasOwnProperty('only'), hasPart = options.hasOwnProperty('part');
470
471
compareFlags = {
472
prototype: hasOnly ? options.only : hasPart ? !options.part : false,
473
part: hasOnly ? !options.only : hasPart ? options.part : true
474
};
475
}
476
else {
477
compare = function (a, b) {
478
479
return a === b;
480
};
481
}
482
483
var misses = false;
484
var matches = new Array(values.length);
485
for (var i = 0, il = matches.length; i < il; ++i) {
486
matches[i] = 0;
487
}
488
489
if (typeof ref === 'string') {
490
var pattern = '(';
491
for (i = 0, il = values.length; i < il; ++i) {
492
var value = values[i];
493
exports.assert(typeof value === 'string', 'Cannot compare string reference to non-string value');
494
pattern += (i ? '|' : '') + exports.escapeRegex(value);
495
}
496
497
var regex = new RegExp(pattern + ')', 'g');
498
var leftovers = ref.replace(regex, function ($0, $1) {
499
500
var index = values.indexOf($1);
501
++matches[index];
502
return ''; // Remove from string
503
});
504
505
misses = !!leftovers;
506
}
507
else if (Array.isArray(ref)) {
508
for (i = 0, il = ref.length; i < il; ++i) {
509
for (var j = 0, jl = values.length, matched = false; j < jl && matched === false; ++j) {
510
matched = compare(values[j], ref[i], compareFlags) && j;
511
}
512
513
if (matched !== false) {
514
++matches[matched];
515
}
516
else {
517
misses = true;
518
}
519
}
520
}
521
else {
522
var keys = Object.keys(ref);
523
for (i = 0, il = keys.length; i < il; ++i) {
524
var key = keys[i];
525
var pos = values.indexOf(key);
526
if (pos !== -1) {
527
if (valuePairs &&
528
!compare(valuePairs[key], ref[key], compareFlags)) {
529
530
return false;
531
}
532
533
++matches[pos];
534
}
535
else {
536
misses = true;
537
}
538
}
539
}
540
541
var result = false;
542
for (i = 0, il = matches.length; i < il; ++i) {
543
result = result || !!matches[i];
544
if ((options.once && matches[i] > 1) ||
545
(!options.part && !matches[i])) {
546
547
return false;
548
}
549
}
550
551
if (options.only &&
552
misses) {
553
554
return false;
555
}
556
557
return result;
558
};
559
560
561
// Flatten array
562
563
exports.flatten = function (array, target) {
564
565
var result = target || [];
566
567
for (var i = 0, il = array.length; i < il; ++i) {
568
if (Array.isArray(array[i])) {
569
exports.flatten(array[i], result);
570
}
571
else {
572
result.push(array[i]);
573
}
574
}
575
576
return result;
577
};
578
579
580
// Convert an object key chain string ('a.b.c') to reference (object[a][b][c])
581
582
exports.reach = function (obj, chain, options) {
583
584
options = options || {};
585
if (typeof options === 'string') {
586
options = { separator: options };
587
}
588
589
var path = chain.split(options.separator || '.');
590
var ref = obj;
591
for (var i = 0, il = path.length; i < il; ++i) {
592
var key = path[i];
593
if (key[0] === '-' && Array.isArray(ref)) {
594
key = key.slice(1, key.length);
595
key = ref.length - key;
596
}
597
598
if (!ref ||
599
!ref.hasOwnProperty(key) ||
600
(typeof ref !== 'object' && options.functions === false)) { // Only object and function can have properties
601
602
exports.assert(!options.strict || i + 1 === il, 'Missing segment', key, 'in reach path ', chain);
603
exports.assert(typeof ref === 'object' || options.functions === true || typeof ref !== 'function', 'Invalid segment', key, 'in reach path ', chain);
604
ref = options.default;
605
break;
606
}
607
608
ref = ref[key];
609
}
610
611
return ref;
612
};
613
614
615
exports.reachTemplate = function (obj, template, options) {
616
617
return template.replace(/{([^}]+)}/g, function ($0, chain) {
618
619
var value = exports.reach(obj, chain, options);
620
return (value === undefined || value === null ? '' : value);
621
});
622
};
623
624
625
exports.formatStack = function (stack) {
626
627
var trace = [];
628
for (var i = 0, il = stack.length; i < il; ++i) {
629
var item = stack[i];
630
trace.push([item.getFileName(), item.getLineNumber(), item.getColumnNumber(), item.getFunctionName(), item.isConstructor()]);
631
}
632
633
return trace;
634
};
635
636
637
exports.formatTrace = function (trace) {
638
639
var display = [];
640
641
for (var i = 0, il = trace.length; i < il; ++i) {
642
var row = trace[i];
643
display.push((row[4] ? 'new ' : '') + row[3] + ' (' + row[0] + ':' + row[1] + ':' + row[2] + ')');
644
}
645
646
return display;
647
};
648
649
650
exports.callStack = function (slice) {
651
652
// http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
653
654
var v8 = Error.prepareStackTrace;
655
Error.prepareStackTrace = function (err, stack) {
656
657
return stack;
658
};
659
660
var capture = {};
661
Error.captureStackTrace(capture, arguments.callee); /*eslint no-caller:0 */
662
var stack = capture.stack;
663
664
Error.prepareStackTrace = v8;
665
666
var trace = exports.formatStack(stack);
667
668
if (slice) {
669
return trace.slice(slice);
670
}
671
672
return trace;
673
};
674
675
676
exports.displayStack = function (slice) {
677
678
var trace = exports.callStack(slice === undefined ? 1 : slice + 1);
679
680
return exports.formatTrace(trace);
681
};
682
683
684
exports.abortThrow = false;
685
686
687
exports.abort = function (message, hideStack) {
688
689
if (process.env.NODE_ENV === 'test' || exports.abortThrow === true) {
690
throw new Error(message || 'Unknown error');
691
}
692
693
var stack = '';
694
if (!hideStack) {
695
stack = exports.displayStack(1).join('\n\t');
696
}
697
console.log('ABORT: ' + message + '\n\t' + stack);
698
process.exit(1);
699
};
700
701
702
exports.assert = function (condition /*, msg1, msg2, msg3 */) {
703
704
if (condition) {
705
return;
706
}
707
708
if (arguments.length === 2 && arguments[1] instanceof Error) {
709
throw arguments[1];
710
}
711
712
var msgs = [];
713
for (var i = 1, il = arguments.length; i < il; ++i) {
714
if (arguments[i] !== '') {
715
msgs.push(arguments[i]); // Avoids Array.slice arguments leak, allowing for V8 optimizations
716
}
717
}
718
719
msgs = msgs.map(function (msg) {
720
721
return typeof msg === 'string' ? msg : msg instanceof Error ? msg.message : exports.stringify(msg);
722
});
723
throw new Error(msgs.join(' ') || 'Unknown error');
724
};
725
726
727
exports.Timer = function () {
728
729
this.ts = 0;
730
this.reset();
731
};
732
733
734
exports.Timer.prototype.reset = function () {
735
736
this.ts = Date.now();
737
};
738
739
740
exports.Timer.prototype.elapsed = function () {
741
742
return Date.now() - this.ts;
743
};
744
745
746
exports.Bench = function () {
747
748
this.ts = 0;
749
this.reset();
750
};
751
752
753
exports.Bench.prototype.reset = function () {
754
755
this.ts = exports.Bench.now();
756
};
757
758
759
exports.Bench.prototype.elapsed = function () {
760
761
return exports.Bench.now() - this.ts;
762
};
763
764
765
exports.Bench.now = function () {
766
767
var ts = process.hrtime();
768
return (ts[0] * 1e3) + (ts[1] / 1e6);
769
};
770
771
772
// Escape string for Regex construction
773
774
exports.escapeRegex = function (string) {
775
776
// Escape ^$.*+-?=!:|\/()[]{},
777
return string.replace(/[\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}\,]/g, '\\$&');
778
};
779
780
781
// Base64url (RFC 4648) encode
782
783
exports.base64urlEncode = function (value, encoding) {
784
785
var buf = (Buffer.isBuffer(value) ? value : new Buffer(value, encoding || 'binary'));
786
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
787
};
788
789
790
// Base64url (RFC 4648) decode
791
792
exports.base64urlDecode = function (value, encoding) {
793
794
if (value &&
795
!/^[\w\-]*$/.test(value)) {
796
797
return new Error('Invalid character');
798
}
799
800
try {
801
var buf = new Buffer(value, 'base64');
802
return (encoding === 'buffer' ? buf : buf.toString(encoding || 'binary'));
803
}
804
catch (err) {
805
return err;
806
}
807
};
808
809
810
// Escape attribute value for use in HTTP header
811
812
exports.escapeHeaderAttribute = function (attribute) {
813
814
// Allowed value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9, \, "
815
816
exports.assert(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~\"\\]*$/.test(attribute), 'Bad attribute value (' + attribute + ')');
817
818
return attribute.replace(/\\/g, '\\\\').replace(/\"/g, '\\"'); // Escape quotes and slash
819
};
820
821
822
exports.escapeHtml = function (string) {
823
824
return Escape.escapeHtml(string);
825
};
826
827
828
exports.escapeJavaScript = function (string) {
829
830
return Escape.escapeJavaScript(string);
831
};
832
833
834
exports.nextTick = function (callback) {
835
836
return function () {
837
838
var args = arguments;
839
process.nextTick(function () {
840
841
callback.apply(null, args);
842
});
843
};
844
};
845
846
847
exports.once = function (method) {
848
849
if (method._hoekOnce) {
850
return method;
851
}
852
853
var once = false;
854
var wrapped = function () {
855
856
if (!once) {
857
once = true;
858
method.apply(null, arguments);
859
}
860
};
861
862
wrapped._hoekOnce = true;
863
864
return wrapped;
865
};
866
867
868
exports.isAbsolutePath = function (path, platform) {
869
870
if (!path) {
871
return false;
872
}
873
874
if (Path.isAbsolute) { // node >= 0.11
875
return Path.isAbsolute(path);
876
}
877
878
platform = platform || process.platform;
879
880
// Unix
881
882
if (platform !== 'win32') {
883
return path[0] === '/';
884
}
885
886
// Windows
887
888
return !!/^(?:[a-zA-Z]:[\\\/])|(?:[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/])/.test(path); // C:\ or \\something\something
889
};
890
891
892
exports.isInteger = function (value) {
893
894
return (typeof value === 'number' &&
895
parseFloat(value) === parseInt(value, 10) &&
896
!isNaN(value));
897
};
898
899
900
exports.ignore = function () { };
901
902
903
exports.inherits = Util.inherits;
904
905
906
exports.format = Util.format;
907
908
909
exports.transform = function (source, transform, options) {
910
911
exports.assert(source === null || source === undefined || typeof source === 'object', 'Invalid source object: must be null, undefined, or an object');
912
913
var result = {};
914
var keys = Object.keys(transform);
915
916
for (var k = 0, kl = keys.length; k < kl; ++k) {
917
var key = keys[k];
918
var path = key.split('.');
919
var sourcePath = transform[key];
920
921
exports.assert(typeof sourcePath === 'string', 'All mappings must be "." delineated strings');
922
923
var segment;
924
var res = result;
925
926
while (path.length > 1) {
927
segment = path.shift();
928
if (!res[segment]) {
929
res[segment] = {};
930
}
931
res = res[segment];
932
}
933
segment = path.shift();
934
res[segment] = exports.reach(source, sourcePath, options);
935
}
936
937
return result;
938
};
939
940
941
exports.uniqueFilename = function (path, extension) {
942
943
if (extension) {
944
extension = extension[0] !== '.' ? '.' + extension : extension;
945
}
946
else {
947
extension = '';
948
}
949
950
path = Path.resolve(path);
951
var name = [Date.now(), process.pid, Crypto.randomBytes(8).toString('hex')].join('-') + extension;
952
return Path.join(path, name);
953
};
954
955
956
exports.stringify = function () {
957
958
try {
959
return JSON.stringify.apply(null, arguments);
960
}
961
catch (err) {
962
return '[Cannot display object: ' + err.message + ']';
963
}
964
};
965
966
967
exports.shallow = function (source) {
968
969
var target = {};
970
var keys = Object.keys(source);
971
for (var i = 0, il = keys.length; i < il; ++i) {
972
var key = keys[i];
973
target[key] = source[key];
974
}
975
976
return target;
977
};
978
979