arnaucube
arnaucube/thoughtsPublic Self-hosted repository
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