thoughts/node_modules/mongoose/lib/utils.js
node_modules/mongoose/lib/utils.jsRaw
1/*!
2 * Module dependencies.
3 */
4
5var ReadPref = require('mongodb').ReadPreference
6 , ObjectId = require('./types/objectid')
7 , cloneRegExp = require('regexp-clone')
8 , sliced = require('sliced')
9 , mpath = require('mpath')
10 , ms = require('ms')
11 , MongooseBuffer
12 , MongooseArray
13 , Document
14
15/*!
16 * Produces a collection name from model `name`.
17 *
18 * @param {String} name a model name
19 * @return {String} a collection name
20 * @api private
21 */
22
23exports.toCollectionName = function (name) {
24 if ('system.profile' === name) return name;
25 if ('system.indexes' === name) return name;
26 return pluralize(name.toLowerCase());
27};
28
29/**
30 * Pluralization rules.
31 *
32 * These rules are applied while processing the argument to `toCollectionName`.
33 *
34 * @deprecated remove in 4.x gh-1350
35 */
36
37exports.pluralization = [
38 [/(m)an$/gi, '$1en'],
39 [/(pe)rson$/gi, '$1ople'],
40 [/(child)$/gi, '$1ren'],
41 [/^(ox)$/gi, '$1en'],
42 [/(ax|test)is$/gi, '$1es'],
43 [/(octop|vir)us$/gi, '$1i'],
44 [/(alias|status)$/gi, '$1es'],
45 [/(bu)s$/gi, '$1ses'],
46 [/(buffal|tomat|potat)o$/gi, '$1oes'],
47 [/([ti])um$/gi, '$1a'],
48 [/sis$/gi, 'ses'],
49 [/(?:([^f])fe|([lr])f)$/gi, '$1$2ves'],
50 [/(hive)$/gi, '$1s'],
51 [/([^aeiouy]|qu)y$/gi, '$1ies'],
52 [/(x|ch|ss|sh)$/gi, '$1es'],
53 [/(matr|vert|ind)ix|ex$/gi, '$1ices'],
54 [/([m|l])ouse$/gi, '$1ice'],
55 [/(quiz)$/gi, '$1zes'],
56 [/s$/gi, 's'],
57 [/$/gi, 's']
58];
59var rules = exports.pluralization;
60
61/**
62 * Uncountable words.
63 *
64 * These words are applied while processing the argument to `toCollectionName`.
65 * @api public
66 */
67
68exports.uncountables = [
69 'advice',
70 'energy',
71 'excretion',
72 'digestion',
73 'cooperation',
74 'health',
75 'justice',
76 'labour',
77 'machinery',
78 'equipment',
79 'information',
80 'pollution',
81 'sewage',
82 'paper',
83 'money',
84 'species',
85 'series',
86 'rain',
87 'rice',
88 'fish',
89 'sheep',
90 'moose',
91 'deer',
92 'news',
93 'expertise',
94 'status',
95 'media'
96];
97var uncountables = exports.uncountables;
98
99/*!
100 * Pluralize function.
101 *
102 * @author TJ Holowaychuk (extracted from _ext.js_)
103 * @param {String} string to pluralize
104 * @api private
105 */
106
107function pluralize (str) {
108 var rule, found;
109 if (!~uncountables.indexOf(str.toLowerCase())){
110 found = rules.filter(function(rule){
111 return str.match(rule[0]);
112 });
113 if (found[0]) return str.replace(found[0][0], found[0][1]);
114 }
115 return str;
116};
117
118/*!
119 * Determines if `a` and `b` are deep equal.
120 *
121 * Modified from node/lib/assert.js
122 *
123 * @param {any} a a value to compare to `b`
124 * @param {any} b a value to compare to `a`
125 * @return {Boolean}
126 * @api private
127 */
128
129exports.deepEqual = function deepEqual (a, b) {
130 if (a === b) return true;
131
132 if (a instanceof Date && b instanceof Date)
133 return a.getTime() === b.getTime();
134
135 if (a instanceof ObjectId && b instanceof ObjectId) {
136 return a.toString() === b.toString();
137 }
138
139 if (a instanceof RegExp && b instanceof RegExp) {
140 return a.source == b.source &&
141 a.ignoreCase == b.ignoreCase &&
142 a.multiline == b.multiline &&
143 a.global == b.global;
144 }
145
146 if (typeof a !== 'object' && typeof b !== 'object')
147 return a == b;
148
149 if (a === null || b === null || a === undefined || b === undefined)
150 return false
151
152 if (a.prototype !== b.prototype) return false;
153
154 // Handle MongooseNumbers
155 if (a instanceof Number && b instanceof Number) {
156 return a.valueOf() === b.valueOf();
157 }
158
159 if (Buffer.isBuffer(a)) {
160 if (!Buffer.isBuffer(b)) return false;
161 if (a.length !== b.length) return false;
162 for (var i = 0, len = a.length; i < len; ++i) {
163 if (a[i] !== b[i]) return false;
164 }
165 return true;
166 }
167
168 if (isMongooseObject(a)) a = a.toObject();
169 if (isMongooseObject(b)) b = b.toObject();
170
171 try {
172 var ka = Object.keys(a),
173 kb = Object.keys(b),
174 key, i;
175 } catch (e) {//happens when one is a string literal and the other isn't
176 return false;
177 }
178
179 // having the same number of owned properties (keys incorporates
180 // hasOwnProperty)
181 if (ka.length != kb.length)
182 return false;
183
184 //the same set of keys (although not necessarily the same order),
185 ka.sort();
186 kb.sort();
187
188 //~~~cheap key test
189 for (i = ka.length - 1; i >= 0; i--) {
190 if (ka[i] != kb[i])
191 return false;
192 }
193
194 //equivalent values for every corresponding key, and
195 //~~~possibly expensive deep test
196 for (i = ka.length - 1; i >= 0; i--) {
197 key = ka[i];
198 if (!deepEqual(a[key], b[key])) return false;
199 }
200
201 return true;
202};
203
204/*!
205 * Object clone with Mongoose natives support.
206 *
207 * If options.minimize is true, creates a minimal data object. Empty objects and undefined values will not be cloned. This makes the data payload sent to MongoDB as small as possible.
208 *
209 * Functions are never cloned.
210 *
211 * @param {Object} obj the object to clone
212 * @param {Object} options
213 * @return {Object} the cloned object
214 * @api private
215 */
216
217exports.clone = function clone (obj, options) {
218 if (obj === undefined || obj === null)
219 return obj;
220
221 if (Array.isArray(obj))
222 return cloneArray(obj, options);
223
224 if (isMongooseObject(obj)) {
225 if (options && options.json && 'function' === typeof obj.toJSON) {
226 return obj.toJSON(options);
227 } else {
228 return obj.toObject(options);
229 }
230 }
231
232 if (obj.constructor) {
233 switch (obj.constructor.name) {
234 case 'Object':
235 return cloneObject(obj, options);
236 case 'Date':
237 return new obj.constructor(+obj);
238 case 'RegExp':
239 return cloneRegExp(obj);
240 default:
241 // ignore
242 break;
243 }
244 }
245
246 if (obj instanceof ObjectId)
247 return new ObjectId(obj.id);
248
249 if (!obj.constructor && exports.isObject(obj)) {
250 // object created with Object.create(null)
251 return cloneObject(obj, options);
252 }
253
254 if (obj.valueOf)
255 return obj.valueOf();
256};
257var clone = exports.clone;
258
259/*!
260 * ignore
261 */
262
263function cloneObject (obj, options) {
264 var retainKeyOrder = options && options.retainKeyOrder
265 , minimize = options && options.minimize
266 , ret = {}
267 , hasKeys
268 , keys
269 , val
270 , k
271 , i
272
273 if (retainKeyOrder) {
274 for (k in obj) {
275 val = clone(obj[k], options);
276
277 if (!minimize || ('undefined' !== typeof val)) {
278 hasKeys || (hasKeys = true);
279 ret[k] = val;
280 }
281 }
282 } else {
283 // faster
284
285 keys = Object.keys(obj);
286 i = keys.length;
287
288 while (i--) {
289 k = keys[i];
290 val = clone(obj[k], options);
291
292 if (!minimize || ('undefined' !== typeof val)) {
293 if (!hasKeys) hasKeys = true;
294 ret[k] = val;
295 }
296 }
297 }
298
299 return minimize
300 ? hasKeys && ret
301 : ret;
302};
303
304function cloneArray (arr, options) {
305 var ret = [];
306 for (var i = 0, l = arr.length; i < l; i++)
307 ret.push(clone(arr[i], options));
308 return ret;
309};
310
311/*!
312 * Shallow copies defaults into options.
313 *
314 * @param {Object} defaults
315 * @param {Object} options
316 * @return {Object} the merged object
317 * @api private
318 */
319
320exports.options = function (defaults, options) {
321 var keys = Object.keys(defaults)
322 , i = keys.length
323 , k ;
324
325 options = options || {};
326
327 while (i--) {
328 k = keys[i];
329 if (!(k in options)) {
330 options[k] = defaults[k];
331 }
332 }
333
334 return options;
335};
336
337/*!
338 * Generates a random string
339 *
340 * @api private
341 */
342
343exports.random = function () {
344 return Math.random().toString().substr(3);
345};
346
347/*!
348 * Merges `from` into `to` without overwriting existing properties.
349 *
350 * @param {Object} to
351 * @param {Object} from
352 * @api private
353 */
354
355exports.merge = function merge (to, from) {
356 var keys = Object.keys(from)
357 , i = keys.length
358 , key
359
360 while (i--) {
361 key = keys[i];
362 if ('undefined' === typeof to[key]) {
363 to[key] = from[key];
364 } else {
365 if (exports.isObject(from[key])) {
366 merge(to[key], from[key]);
367 } else {
368 to[key] = from[key];
369 }
370 }
371 }
372};
373
374/*!
375 * toString helper
376 */
377
378var toString = Object.prototype.toString;
379
380/*!
381 * Determines if `arg` is an object.
382 *
383 * @param {Object|Array|String|Function|RegExp|any} arg
384 * @api private
385 * @return {Boolean}
386 */
387
388exports.isObject = function (arg) {
389 return '[object Object]' == toString.call(arg);
390}
391
392/*!
393 * A faster Array.prototype.slice.call(arguments) alternative
394 * @api private
395 */
396
397exports.args = sliced;
398
399/*!
400 * process.nextTick helper.
401 *
402 * Wraps `callback` in a try/catch + nextTick.
403 *
404 * node-mongodb-native has a habit of state corruption when an error is immediately thrown from within a collection callback.
405 *
406 * @param {Function} callback
407 * @api private
408 */
409
410exports.tick = function tick (callback) {
411 if ('function' !== typeof callback) return;
412 return function () {
413 try {
414 callback.apply(this, arguments);
415 } catch (err) {
416 // only nextTick on err to get out of
417 // the event loop and avoid state corruption.
418 process.nextTick(function () {
419 throw err;
420 });
421 }
422 }
423}
424
425/*!
426 * Returns if `v` is a mongoose object that has a `toObject()` method we can use.
427 *
428 * This is for compatibility with libs like Date.js which do foolish things to Natives.
429 *
430 * @param {any} v
431 * @api private
432 */
433
434exports.isMongooseObject = function (v) {
435 Document || (Document = require('./document'));
436 MongooseArray || (MongooseArray = require('./types').Array);
437 MongooseBuffer || (MongooseBuffer = require('./types').Buffer);
438
439 return v instanceof Document ||
440 v instanceof MongooseArray ||
441 v instanceof MongooseBuffer
442}
443var isMongooseObject = exports.isMongooseObject;
444
445/*!
446 * Converts `expires` options of index objects to `expiresAfterSeconds` options for MongoDB.
447 *
448 * @param {Object} object
449 * @api private
450 */
451
452exports.expires = function expires (object) {
453 if (!(object && 'Object' == object.constructor.name)) return;
454 if (!('expires' in object)) return;
455
456 var when;
457 if ('string' != typeof object.expires) {
458 when = object.expires;
459 } else {
460 when = Math.round(ms(object.expires) / 1000);
461 }
462 object.expireAfterSeconds = when;
463 delete object.expires;
464}
465
466/*!
467 * Converts arguments to ReadPrefs the driver
468 * can understand.
469 *
470 * @TODO move this into the driver layer
471 * @param {String|Array} pref
472 * @param {Array} [tags]
473 */
474
475exports.readPref = function readPref (pref, tags) {
476 if (Array.isArray(pref)) {
477 tags = pref[1];
478 pref = pref[0];
479 }
480
481 switch (pref) {
482 case 'p':
483 pref = 'primary';
484 break;
485 case 'pp':
486 pref = 'primaryPreferred';
487 break;
488 case 's':
489 pref = 'secondary';
490 break;
491 case 'sp':
492 pref = 'secondaryPreferred';
493 break;
494 case 'n':
495 pref = 'nearest';
496 break;
497 }
498
499 return new ReadPref(pref, tags);
500}
501
502/*!
503 * Populate options constructor
504 */
505
506function PopulateOptions (path, select, match, options, model) {
507 this.path = path;
508 this.match = match;
509 this.select = select;
510 this.options = options;
511 this.model = model;
512 this._docs = {};
513}
514
515// make it compatible with utils.clone
516PopulateOptions.prototype.constructor = Object;
517
518// expose
519exports.PopulateOptions = PopulateOptions;
520
521/*!
522 * populate helper
523 */
524
525exports.populate = function populate (path, select, model, match, options) {
526 // The order of select/conditions args is opposite Model.find but
527 // necessary to keep backward compatibility (select could be
528 // an array, string, or object literal).
529
530 // might have passed an object specifying all arguments
531 if (1 === arguments.length) {
532 if (path instanceof PopulateOptions) {
533 return [path];
534 }
535
536 if (Array.isArray(path)) {
537 return path.map(function(o){
538 return exports.populate(o)[0];
539 });
540 }
541
542 if (exports.isObject(path)) {
543 match = path.match;
544 options = path.options;
545 select = path.select;
546 model = path.model;
547 path = path.path;
548 }
549 } else if ('string' !== typeof model) {
550 options = match;
551 match = model;
552 model = undefined;
553 }
554
555 if ('string' != typeof path) {
556 throw new TypeError('utils.populate: invalid path. Expected string. Got typeof `' + typeof path + '`');
557 }
558
559 var ret = [];
560 var paths = path.split(' ');
561 for (var i = 0; i < paths.length; ++i) {
562 ret.push(new PopulateOptions(paths[i], select, match, options, model));
563 }
564
565 return ret;
566}
567
568/*!
569 * Return the value of `obj` at the given `path`.
570 *
571 * @param {String} path
572 * @param {Object} obj
573 */
574
575exports.getValue = function (path, obj, map) {
576 return mpath.get(path, obj, '_doc', map);
577}
578
579/*!
580 * Sets the value of `obj` at the given `path`.
581 *
582 * @param {String} path
583 * @param {Anything} val
584 * @param {Object} obj
585 */
586
587exports.setValue = function (path, val, obj, map) {
588 mpath.set(path, val, obj, '_doc', map);
589}
590
591/*!
592 * Returns an array of values from object `o`.
593 *
594 * @param {Object} o
595 * @return {Array}
596 * @private
597 */
598
599exports.object = {};
600exports.object.vals = function vals (o) {
601 var keys = Object.keys(o)
602 , i = keys.length
603 , ret = [];
604
605 while (i--) {
606 ret.push(o[keys[i]]);
607 }
608
609 return ret;
610}
611
612/*!
613 * @see exports.options
614 */
615
616exports.object.shallowCopy = exports.options;
617
618/*!
619 * Safer helper for hasOwnProperty checks
620 *
621 * @param {Object} obj
622 * @param {String} prop
623 */
624
625exports.object.hasOwnProperty = function (obj, prop) {
626 return Object.prototype.hasOwnProperty.call(obj, prop);
627}
628
629/*!
630 * Determine if `val` is null or undefined
631 *
632 * @return {Boolean}
633 */
634
635exports.isNullOrUndefined = function (val) {
636 return null == val
637}
638
639/*!
640 * ignore
641 */
642
643exports.array = {};
644
645/*!
646 * Flattens an array.
647 *
648 * [ 1, [ 2, 3, [4] ]] -> [1,2,3,4]
649 *
650 * @param {Array} arr
651 * @param {Function} [filter] If passed, will be invoked with each item in the array. If `filter` returns a falsey value, the item will not be included in the results.
652 * @return {Array}
653 * @private
654 */
655
656exports.array.flatten = function flatten (arr, filter, ret) {
657 ret || (ret = []);
658
659 arr.forEach(function (item) {
660 if (Array.isArray(item)) {
661 flatten(item, filter, ret);
662 } else {
663 if (!filter || filter(item)) {
664 ret.push(item);
665 }
666 }
667 });
668
669 return ret;
670}
671