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