c9d32474715037a2d34fd6fedcc298451e087e44
[aai/esr-gui.git] /
1 var mapping = require('./_mapping'),
2     fallbackHolder = require('./placeholder');
3
4 /** Built-in value reference. */
5 var push = Array.prototype.push;
6
7 /**
8  * Creates a function, with an arity of `n`, that invokes `func` with the
9  * arguments it receives.
10  *
11  * @private
12  * @param {Function} func The function to wrap.
13  * @param {number} n The arity of the new function.
14  * @returns {Function} Returns the new function.
15  */
16 function baseArity(func, n) {
17   return n == 2
18     ? function(a, b) { return func.apply(undefined, arguments); }
19     : function(a) { return func.apply(undefined, arguments); };
20 }
21
22 /**
23  * Creates a function that invokes `func`, with up to `n` arguments, ignoring
24  * any additional arguments.
25  *
26  * @private
27  * @param {Function} func The function to cap arguments for.
28  * @param {number} n The arity cap.
29  * @returns {Function} Returns the new function.
30  */
31 function baseAry(func, n) {
32   return n == 2
33     ? function(a, b) { return func(a, b); }
34     : function(a) { return func(a); };
35 }
36
37 /**
38  * Creates a clone of `array`.
39  *
40  * @private
41  * @param {Array} array The array to clone.
42  * @returns {Array} Returns the cloned array.
43  */
44 function cloneArray(array) {
45   var length = array ? array.length : 0,
46       result = Array(length);
47
48   while (length--) {
49     result[length] = array[length];
50   }
51   return result;
52 }
53
54 /**
55  * Creates a function that clones a given object using the assignment `func`.
56  *
57  * @private
58  * @param {Function} func The assignment function.
59  * @returns {Function} Returns the new cloner function.
60  */
61 function createCloner(func) {
62   return function(object) {
63     return func({}, object);
64   };
65 }
66
67 /**
68  * This function is like `_.spread` except that it includes arguments after those spread.
69  *
70  * @private
71  * @param {Function} func The function to spread arguments over.
72  * @param {number} start The start position of the spread.
73  * @returns {Function} Returns the new function.
74  */
75 function spread(func, start) {
76   return function() {
77     var length = arguments.length,
78         args = Array(length);
79
80     while (length--) {
81       args[length] = arguments[length];
82     }
83     var array = args[start],
84         lastIndex = args.length - 1,
85         otherArgs = args.slice(0, start);
86
87     if (array) {
88       push.apply(otherArgs, array);
89     }
90     if (start != lastIndex) {
91       push.apply(otherArgs, args.slice(start + 1));
92     }
93     return func.apply(this, otherArgs);
94   };
95 }
96
97 /**
98  * Creates a function that wraps `func` and uses `cloner` to clone the first
99  * argument it receives.
100  *
101  * @private
102  * @param {Function} func The function to wrap.
103  * @param {Function} cloner The function to clone arguments.
104  * @returns {Function} Returns the new immutable function.
105  */
106 function wrapImmutable(func, cloner) {
107   return function() {
108     var length = arguments.length;
109     if (!length) {
110       return;
111     }
112     var args = Array(length);
113     while (length--) {
114       args[length] = arguments[length];
115     }
116     var result = args[0] = cloner.apply(undefined, args);
117     func.apply(undefined, args);
118     return result;
119   };
120 }
121
122 /**
123  * The base implementation of `convert` which accepts a `util` object of methods
124  * required to perform conversions.
125  *
126  * @param {Object} util The util object.
127  * @param {string} name The name of the function to convert.
128  * @param {Function} func The function to convert.
129  * @param {Object} [options] The options object.
130  * @param {boolean} [options.cap=true] Specify capping iteratee arguments.
131  * @param {boolean} [options.curry=true] Specify currying.
132  * @param {boolean} [options.fixed=true] Specify fixed arity.
133  * @param {boolean} [options.immutable=true] Specify immutable operations.
134  * @param {boolean} [options.rearg=true] Specify rearranging arguments.
135  * @returns {Function|Object} Returns the converted function or object.
136  */
137 function baseConvert(util, name, func, options) {
138   var setPlaceholder,
139       isLib = typeof name == 'function',
140       isObj = name === Object(name);
141
142   if (isObj) {
143     options = func;
144     func = name;
145     name = undefined;
146   }
147   if (func == null) {
148     throw new TypeError;
149   }
150   options || (options = {});
151
152   var config = {
153     'cap': 'cap' in options ? options.cap : true,
154     'curry': 'curry' in options ? options.curry : true,
155     'fixed': 'fixed' in options ? options.fixed : true,
156     'immutable': 'immutable' in options ? options.immutable : true,
157     'rearg': 'rearg' in options ? options.rearg : true
158   };
159
160   var forceCurry = ('curry' in options) && options.curry,
161       forceFixed = ('fixed' in options) && options.fixed,
162       forceRearg = ('rearg' in options) && options.rearg,
163       placeholder = isLib ? func : fallbackHolder,
164       pristine = isLib ? func.runInContext() : undefined;
165
166   var helpers = isLib ? func : {
167     'ary': util.ary,
168     'assign': util.assign,
169     'clone': util.clone,
170     'curry': util.curry,
171     'forEach': util.forEach,
172     'isArray': util.isArray,
173     'isFunction': util.isFunction,
174     'iteratee': util.iteratee,
175     'keys': util.keys,
176     'rearg': util.rearg,
177     'toInteger': util.toInteger,
178     'toPath': util.toPath
179   };
180
181   var ary = helpers.ary,
182       assign = helpers.assign,
183       clone = helpers.clone,
184       curry = helpers.curry,
185       each = helpers.forEach,
186       isArray = helpers.isArray,
187       isFunction = helpers.isFunction,
188       keys = helpers.keys,
189       rearg = helpers.rearg,
190       toInteger = helpers.toInteger,
191       toPath = helpers.toPath;
192
193   var aryMethodKeys = keys(mapping.aryMethod);
194
195   var wrappers = {
196     'castArray': function(castArray) {
197       return function() {
198         var value = arguments[0];
199         return isArray(value)
200           ? castArray(cloneArray(value))
201           : castArray.apply(undefined, arguments);
202       };
203     },
204     'iteratee': function(iteratee) {
205       return function() {
206         var func = arguments[0],
207             arity = arguments[1],
208             result = iteratee(func, arity),
209             length = result.length;
210
211         if (config.cap && typeof arity == 'number') {
212           arity = arity > 2 ? (arity - 2) : 1;
213           return (length && length <= arity) ? result : baseAry(result, arity);
214         }
215         return result;
216       };
217     },
218     'mixin': function(mixin) {
219       return function(source) {
220         var func = this;
221         if (!isFunction(func)) {
222           return mixin(func, Object(source));
223         }
224         var pairs = [];
225         each(keys(source), function(key) {
226           if (isFunction(source[key])) {
227             pairs.push([key, func.prototype[key]]);
228           }
229         });
230
231         mixin(func, Object(source));
232
233         each(pairs, function(pair) {
234           var value = pair[1];
235           if (isFunction(value)) {
236             func.prototype[pair[0]] = value;
237           } else {
238             delete func.prototype[pair[0]];
239           }
240         });
241         return func;
242       };
243     },
244     'nthArg': function(nthArg) {
245       return function(n) {
246         var arity = n < 0 ? 1 : (toInteger(n) + 1);
247         return curry(nthArg(n), arity);
248       };
249     },
250     'rearg': function(rearg) {
251       return function(func, indexes) {
252         var arity = indexes ? indexes.length : 0;
253         return curry(rearg(func, indexes), arity);
254       };
255     },
256     'runInContext': function(runInContext) {
257       return function(context) {
258         return baseConvert(util, runInContext(context), options);
259       };
260     }
261   };
262
263   /*--------------------------------------------------------------------------*/
264
265   /**
266    * Casts `func` to a function with an arity capped iteratee if needed.
267    *
268    * @private
269    * @param {string} name The name of the function to inspect.
270    * @param {Function} func The function to inspect.
271    * @returns {Function} Returns the cast function.
272    */
273   function castCap(name, func) {
274     if (config.cap) {
275       var indexes = mapping.iterateeRearg[name];
276       if (indexes) {
277         return iterateeRearg(func, indexes);
278       }
279       var n = !isLib && mapping.iterateeAry[name];
280       if (n) {
281         return iterateeAry(func, n);
282       }
283     }
284     return func;
285   }
286
287   /**
288    * Casts `func` to a curried function if needed.
289    *
290    * @private
291    * @param {string} name The name of the function to inspect.
292    * @param {Function} func The function to inspect.
293    * @param {number} n The arity of `func`.
294    * @returns {Function} Returns the cast function.
295    */
296   function castCurry(name, func, n) {
297     return (forceCurry || (config.curry && n > 1))
298       ? curry(func, n)
299       : func;
300   }
301
302   /**
303    * Casts `func` to a fixed arity function if needed.
304    *
305    * @private
306    * @param {string} name The name of the function to inspect.
307    * @param {Function} func The function to inspect.
308    * @param {number} n The arity cap.
309    * @returns {Function} Returns the cast function.
310    */
311   function castFixed(name, func, n) {
312     if (config.fixed && (forceFixed || !mapping.skipFixed[name])) {
313       var data = mapping.methodSpread[name],
314           start = data && data.start;
315
316       return start  === undefined ? ary(func, n) : spread(func, start);
317     }
318     return func;
319   }
320
321   /**
322    * Casts `func` to an rearged function if needed.
323    *
324    * @private
325    * @param {string} name The name of the function to inspect.
326    * @param {Function} func The function to inspect.
327    * @param {number} n The arity of `func`.
328    * @returns {Function} Returns the cast function.
329    */
330   function castRearg(name, func, n) {
331     return (config.rearg && n > 1 && (forceRearg || !mapping.skipRearg[name]))
332       ? rearg(func, mapping.methodRearg[name] || mapping.aryRearg[n])
333       : func;
334   }
335
336   /**
337    * Creates a clone of `object` by `path`.
338    *
339    * @private
340    * @param {Object} object The object to clone.
341    * @param {Array|string} path The path to clone by.
342    * @returns {Object} Returns the cloned object.
343    */
344   function cloneByPath(object, path) {
345     path = toPath(path);
346
347     var index = -1,
348         length = path.length,
349         lastIndex = length - 1,
350         result = clone(Object(object)),
351         nested = result;
352
353     while (nested != null && ++index < length) {
354       var key = path[index],
355           value = nested[key];
356
357       if (value != null) {
358         nested[path[index]] = clone(index == lastIndex ? value : Object(value));
359       }
360       nested = nested[key];
361     }
362     return result;
363   }
364
365   /**
366    * Converts `lodash` to an immutable auto-curried iteratee-first data-last
367    * version with conversion `options` applied.
368    *
369    * @param {Object} [options] The options object. See `baseConvert` for more details.
370    * @returns {Function} Returns the converted `lodash`.
371    */
372   function convertLib(options) {
373     return _.runInContext.convert(options)(undefined);
374   }
375
376   /**
377    * Create a converter function for `func` of `name`.
378    *
379    * @param {string} name The name of the function to convert.
380    * @param {Function} func The function to convert.
381    * @returns {Function} Returns the new converter function.
382    */
383   function createConverter(name, func) {
384     var realName = mapping.aliasToReal[name] || name,
385         methodName = mapping.remap[realName] || realName,
386         oldOptions = options;
387
388     return function(options) {
389       var newUtil = isLib ? pristine : helpers,
390           newFunc = isLib ? pristine[methodName] : func,
391           newOptions = assign(assign({}, oldOptions), options);
392
393       return baseConvert(newUtil, realName, newFunc, newOptions);
394     };
395   }
396
397   /**
398    * Creates a function that wraps `func` to invoke its iteratee, with up to `n`
399    * arguments, ignoring any additional arguments.
400    *
401    * @private
402    * @param {Function} func The function to cap iteratee arguments for.
403    * @param {number} n The arity cap.
404    * @returns {Function} Returns the new function.
405    */
406   function iterateeAry(func, n) {
407     return overArg(func, function(func) {
408       return typeof func == 'function' ? baseAry(func, n) : func;
409     });
410   }
411
412   /**
413    * Creates a function that wraps `func` to invoke its iteratee with arguments
414    * arranged according to the specified `indexes` where the argument value at
415    * the first index is provided as the first argument, the argument value at
416    * the second index is provided as the second argument, and so on.
417    *
418    * @private
419    * @param {Function} func The function to rearrange iteratee arguments for.
420    * @param {number[]} indexes The arranged argument indexes.
421    * @returns {Function} Returns the new function.
422    */
423   function iterateeRearg(func, indexes) {
424     return overArg(func, function(func) {
425       var n = indexes.length;
426       return baseArity(rearg(baseAry(func, n), indexes), n);
427     });
428   }
429
430   /**
431    * Creates a function that invokes `func` with its first argument transformed.
432    *
433    * @private
434    * @param {Function} func The function to wrap.
435    * @param {Function} transform The argument transform.
436    * @returns {Function} Returns the new function.
437    */
438   function overArg(func, transform) {
439     return function() {
440       var length = arguments.length;
441       if (!length) {
442         return func();
443       }
444       var args = Array(length);
445       while (length--) {
446         args[length] = arguments[length];
447       }
448       var index = config.rearg ? 0 : (length - 1);
449       args[index] = transform(args[index]);
450       return func.apply(undefined, args);
451     };
452   }
453
454   /**
455    * Creates a function that wraps `func` and applys the conversions
456    * rules by `name`.
457    *
458    * @private
459    * @param {string} name The name of the function to wrap.
460    * @param {Function} func The function to wrap.
461    * @returns {Function} Returns the converted function.
462    */
463   function wrap(name, func) {
464     var result,
465         realName = mapping.aliasToReal[name] || name,
466         wrapped = func,
467         wrapper = wrappers[realName];
468
469     if (wrapper) {
470       wrapped = wrapper(func);
471     }
472     else if (config.immutable) {
473       if (mapping.mutate.array[realName]) {
474         wrapped = wrapImmutable(func, cloneArray);
475       }
476       else if (mapping.mutate.object[realName]) {
477         wrapped = wrapImmutable(func, createCloner(func));
478       }
479       else if (mapping.mutate.set[realName]) {
480         wrapped = wrapImmutable(func, cloneByPath);
481       }
482     }
483     each(aryMethodKeys, function(aryKey) {
484       each(mapping.aryMethod[aryKey], function(otherName) {
485         if (realName == otherName) {
486           var spreadData = mapping.methodSpread[realName],
487               afterRearg = spreadData && spreadData.afterRearg;
488
489           result = afterRearg
490             ? castFixed(realName, castRearg(realName, wrapped, aryKey), aryKey)
491             : castRearg(realName, castFixed(realName, wrapped, aryKey), aryKey);
492
493           result = castCap(realName, result);
494           result = castCurry(realName, result, aryKey);
495           return false;
496         }
497       });
498       return !result;
499     });
500
501     result || (result = wrapped);
502     if (result == func) {
503       result = forceCurry ? curry(result, 1) : function() {
504         return func.apply(this, arguments);
505       };
506     }
507     result.convert = createConverter(realName, func);
508     if (mapping.placeholder[realName]) {
509       setPlaceholder = true;
510       result.placeholder = func.placeholder = placeholder;
511     }
512     return result;
513   }
514
515   /*--------------------------------------------------------------------------*/
516
517   if (!isObj) {
518     return wrap(name, func);
519   }
520   var _ = func;
521
522   // Convert methods by ary cap.
523   var pairs = [];
524   each(aryMethodKeys, function(aryKey) {
525     each(mapping.aryMethod[aryKey], function(key) {
526       var func = _[mapping.remap[key] || key];
527       if (func) {
528         pairs.push([key, wrap(key, func)]);
529       }
530     });
531   });
532
533   // Convert remaining methods.
534   each(keys(_), function(key) {
535     var func = _[key];
536     if (typeof func == 'function') {
537       var length = pairs.length;
538       while (length--) {
539         if (pairs[length][0] == key) {
540           return;
541         }
542       }
543       func.convert = createConverter(key, func);
544       pairs.push([key, func]);
545     }
546   });
547
548   // Assign to `_` leaving `_.prototype` unchanged to allow chaining.
549   each(pairs, function(pair) {
550     _[pair[0]] = pair[1];
551   });
552
553   _.convert = convertLib;
554   if (setPlaceholder) {
555     _.placeholder = placeholder;
556   }
557   // Assign aliases.
558   each(keys(_), function(key) {
559     each(mapping.realToAlias[key] || [], function(alias) {
560       _[alias] = _[key];
561     });
562   });
563
564   return _;
565 }
566
567 module.exports = baseConvert;