UNPKG

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