Comparing version 1.3.9 to 1.4.0
@@ -12,2 +12,72 @@ ## Caution! | ||
v1.4.0+ | ||
======= | ||
- Level: Major | ||
- `pad`, `padLeft`, and `padRight` now pad to the exact character. This means that `padLeft(20)` will produce a string exactly 20 characters long, instead of adding 20 characters worth of padding to the left side of the string as before. You can use `String#repeat` for the same effect as the old functionality. | ||
- Level: Major | ||
- `Object.fromQueryString` now does not cast values by default. This means that all values in the resulting object are strings, unless `castBoolean` is true, which will cast boolean values of "true" and "false" only. Digits are no longer cast to numbers at all. Additionally, the "deep" argument is now removed. Deep parameters will always be parsed if they can be. | ||
- Level: Major | ||
- `Function#lazy` now has different arguments. `limit` is now the third argument with `immediate` taking its place as second. Additionally `immediate` -- which determines whether lazy functions are executed immediately then lock or lock then execute after a timeout -- is now false by default. | ||
- Level: Major | ||
- Date range methods `eachDay`, `eachMonth`, etc. are deprecated in favor of the syntax `every("day")`, etc. | ||
- Level: Major | ||
- Date range method `duration` is deprecated in favor of `span`. Additionally it will add 1 to the duration to include the starting number itself. In effect for date ranges this means that `duration` will be 1 ms longer. | ||
- Level: Major | ||
- `Range#step` alias was removed. Use `Range#every` instead. | ||
- Level: Major | ||
- Date formatting tokens `z` and `zz` are now `Z` and `ZZ`. Additionally `zzz` was removed. | ||
- Level: Moderate | ||
- `Array#find` now works according to the ES6 spec. This means that it will no longer take a `fromIndex` or `loop` arguments. Instead, the second argument is the context in which the function matcher will be run. If you need the previous functionality, use `Array#findFrom` and `Array#findIndexFrom` instead. | ||
- Level: Moderate | ||
- `Array.sortBy` now performs a natural sort by default. This means numbers (any consecutive numbers, so this will include currency formatting, etc.) will sort as numbers, (2 before 100). If you do not want this behavior, set the flag `Array.AlphanumericSortNatural` to `false`. | ||
- Level: Moderate | ||
- `Object.clone` now will error if being called on a user-created class instance or host object (DOM Elements, Events, etc). A number of complex issues tie in here, but in the end it is unreliable to call `clone` on an object that is not a standard data types as 1) hidden properties cannot be cloned 2) the original arguments to the constructor cannot be known 3) even if they could be known the issue of whether or not the constructor should actually be called again is not clear. | ||
- Level: Moderate | ||
- The `split` argument was removed from `String#truncate`. For truncating without splitting words, use `String#truncateOnWords` instead. Argument position is adjusted accordingly. | ||
- Level: Moderate | ||
- Class instances are now internally matched by reference only. This means that `Object.equal(new Person, new Person)` will now be `false`. This was in fact the original intended behavior but a bug had not been closed here leading to it not actually being `false`. Although a case can be made for matching class instances by value, in the end it is too expensive and tricky to distinguish them from host objects, which should never be matched by value. Instead it is better to check for equality of class instances on a unique identifier or the like. | ||
- Level: Moderate | ||
- `Object.isObject` will now no longer return true for class instances for the same reasons listed above. This also was intended behavior but was defective. | ||
- Level: Moderate | ||
- `String#normalize` is now deprecated, but still available as a separate script in the `lib/plugins` directory. | ||
- Level: Moderate | ||
- Date ranges are now their own package (the "range" package), are not dependent on the Date package, and work on numbers and strings as well. | ||
- Level: Minor | ||
- Enumerable methods on object will now coerce primitive types. This means that `Object.findAll('foo')` will now treat `'foo'` as `new String('foo')`. This is reversed from the previous behavior which would error on primitive types and coerce objects to primitive types where possible. | ||
- Level: Minor | ||
- `String#capitalize` passing the `all` flag now will not capitalize after an apostrophe. | ||
- Level: Very Minor | ||
- Date ranges that have an end that is less than the start are now no longer considered invalid, and can be iterated across in exactly the same manner. This means that ranges can now be iterated in reverse and .start and .end are no longer equivalent to .min and .max. | ||
- Level: Very Minor | ||
- Removed `Number#upto` and `Number#downto` will now work on inverse ranges. In other words (1).downto(5) if represented as an array will now produce [1,2,3,4,5] even though 1 is less than 5 and the operator was "downto". It will also step through the range accordingly. | ||
- Level: Very Minor | ||
- Passing a regex to array matching methods like `findAll` will now match it directly against the element in the array, regardless of whether or not the matched element is a string or not. This makes the logic more straightforward but it also means that it will stringify the element before attempting to match. If, for example, you have instances of classes in the array and the regex is /t/, the /t/ will return true for that element as it will match the stringified "[object Object]" of the instance, which is likely not what you want, so caution is needed here. | ||
- Level: Very Minor | ||
- Passing `null` to `.map` will now have the same effect as `undefined` (or no arguments), that is, no mapping will occur. This will apply to any method making use of the internal `transformArgument`, so `Array#min`, `Array#max`, and `Array#groupBy` are affected as well. | ||
- Level: Very Minor | ||
- `String#pad/padLeft/padRight` will now raise an error on padding to a negative number. Conversely, they will no longer raise an error on undefined/null/NaN. | ||
v1.3.9+ | ||
@@ -14,0 +84,0 @@ ======= |
@@ -0,1 +1,64 @@ | ||
v1.4.0 | ||
====== | ||
### API Changes ### | ||
- Adding generalized ranges for Numbers and Strings in addition to Dates. | ||
- Date ranges are now part of the Range package and are no longer dependent on the Date package. | ||
- Adding `clamp` for ranges and an alias for Number. | ||
- Adding `cap` for ranges and an alias for Number. | ||
- Added `String#truncateOnWords`. Part of the `String#truncate` functionality is now here. | ||
- `Array.create` will understand ranges and can build an array from one. | ||
- `DateRange#duration` is deprecated in favor of `Range#span`. | ||
- Fix for relative times with "4 weeks" that are actually past the single month threshold. | ||
- `Number#upto` and `Number#downto` will now work on inverse ranges. | ||
- `pad`, `padLeft`, and `padRight` now pad to the specified length, instead of simply adding to string. | ||
- Fuzzy matching methods like `findAll` now directly match regexes against elements, regardless of whether or not they are strings. | ||
- Instances of classes are now entirely matched by reference only, as originally intended. This means that any equality checking inside Sugar will consider them equal only if they are `===`. | ||
- `Object.clone` now only works on known object types and does not work on instances of user-created classes. | ||
- `String#assign` now can be passed an array as well as enumerated arguments. | ||
- Fixed global variable leak #328 | ||
- Optimization for `Array#removeAt` #324 | ||
- Fix for `isThisWeek` being false when not en locale. | ||
- Timezone formatting tokens changed to align with Moment.js better. | ||
- Major performance optimization for date formatting and more. | ||
- Added `Date#beginningOfISOWeek` and `Date#endOfISOWeek` | ||
- Fix for `Array#create` not working on argument objects of zero-length (Issue #299). | ||
- Fix for `String#capitalize` capitalizing after apostrophes (Issue #325). | ||
- Fix for extended objects `select` and `reject` returning plain objects. | ||
- Fix for `Object.merge` not merging certain deep objects. | ||
- Added Date.SugarNewDate to allow customization of internally created dates. | ||
- Removed `multiMatch` in favor of a cached matcher system. | ||
- Fix for environments where regexes are functions. | ||
- Fix for `Function#cancel` not properly clearing all timers (Issue #346). | ||
- Fix for lazy functions not being able to recursively call themselves. | ||
- Added option `immediate` to `Function#lazy`, which is now false by default. | ||
- Added `Function#every`. | ||
- Exposed `Array.AlphanumericSort` to allow its use in native `Array#sort`. | ||
- Added `Array.AlphanumericSortNatural` that is on by default and triggers a natural sort. | ||
- Fixed strings not being coerced into objects in < IE8. | ||
- `Array.find` now aligns with ES6 spec. | ||
- Fixed bug with array like objects iterated over with loop = true. | ||
- Fixed `String#truncate` not returning primitives. | ||
- `String#repeat` is now aligned more with spec. `String#pad` follows suit. | ||
- Added `Array#findFrom` and `Array#findIndexFrom`. | ||
- Removed `String#normalize`. | ||
- Removed `Range#step` alias. | ||
- Removed `deep` argument from `Object.fromQueryString` and replaced with optional boolean casting. | ||
### Performance Enhancements ### | ||
- Object.map: up to 682% faster | ||
- Date#format: up to 21,400% faster | ||
- Array#min/max/less/more up to 83% faster | ||
- Enumerable methods like findAll/findIndex/map/any/count/sum/etc.: up to 11,270% faster | ||
- isString/isNumber/isBoolean: up to 77% faster | ||
- isEqual returns up front when === (can be *much* faster). Many methods use this internally as well. | ||
- Math related functions (and internals that use them): up to 16% faster. | ||
- getRegExpFlags is up to 1000% faster. | ||
- Range#every up to 52% faster for dates, 1500% faster for numbers/strings. | ||
- Array#at and String#at up to 242% faster for single index lookups. | ||
- String#assign up to 30% faster. | ||
v1.3.9 | ||
@@ -315,2 +378,3 @@ ====== | ||
- Fixed issues with traversing months before January. | ||
- String#titleize added to inflections module. | ||
@@ -317,0 +381,0 @@ |
421
lib/array.js
'use strict'; | ||
@@ -11,30 +12,83 @@ /*** | ||
function multiMatch(el, match, scope, params) { | ||
var result = true; | ||
if(el === match) { | ||
// Match strictly equal values up front. | ||
function regexMatcher(reg) { | ||
reg = regexp(reg); | ||
return function (el) { | ||
return reg.test(el); | ||
} | ||
} | ||
function dateMatcher(d) { | ||
var ms = d.getTime(); | ||
return function (el) { | ||
return !!(el && el.getTime) && el.getTime() === ms; | ||
} | ||
} | ||
function functionMatcher(fn) { | ||
return function (el, i, arr) { | ||
// Return true up front if match by reference | ||
return el === fn || fn.call(this, el, i, arr); | ||
} | ||
} | ||
function invertedArgsFunctionMatcher(fn) { | ||
return function (value, key, obj) { | ||
// Return true up front if match by reference | ||
return value === fn || fn.call(obj, key, value, obj); | ||
} | ||
} | ||
function fuzzyMatcher(obj, isObject) { | ||
var matchers = {}; | ||
return function (el, i, arr) { | ||
var key; | ||
if(!isObjectType(el)) { | ||
return false; | ||
} | ||
for(key in obj) { | ||
matchers[key] = matchers[key] || getMatcher(obj[key], isObject); | ||
if(matchers[key].call(arr, el[key], i, arr) === false) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} else if(isRegExp(match) && isString(el)) { | ||
} | ||
} | ||
function defaultMatcher(f) { | ||
return function (el) { | ||
return el === f || isEqual(el, f); | ||
} | ||
} | ||
function getMatcher(f, isObject) { | ||
if(isPrimitiveType(f)) { | ||
// Do nothing and fall through to the | ||
// default matcher below. | ||
} else if(isRegExp(f)) { | ||
// Match against a regexp | ||
return regexp(match).test(el); | ||
} else if(isFunction(match)) { | ||
return regexMatcher(f); | ||
} else if(isDate(f)) { | ||
// Match against a date. isEqual below should also | ||
// catch this but matching directly up front for speed. | ||
return dateMatcher(f); | ||
} else if(isFunction(f)) { | ||
// Match against a filtering function | ||
return match.apply(scope, params); | ||
} else if(isObject(match) && isObjectPrimitive(el)) { | ||
// Match against a hash or array. | ||
iterateOverObject(match, function(key, value) { | ||
if(!multiMatch(el[key], match[key], scope, [el[key], el])) { | ||
result = false; | ||
} | ||
}); | ||
return result; | ||
} else { | ||
return isEqual(el, match); | ||
if(isObject) { | ||
return invertedArgsFunctionMatcher(f); | ||
} else { | ||
return functionMatcher(f); | ||
} | ||
} else if(isPlainObject(f)) { | ||
// Match against a fuzzy hash or array. | ||
return fuzzyMatcher(f, isObject); | ||
} | ||
// Default is standard isEqual | ||
return defaultMatcher(f); | ||
} | ||
function transformArgument(el, map, context, mapArgs) { | ||
if(isUndefined(map)) { | ||
if(!map) { | ||
return el; | ||
} else if(isFunction(map)) { | ||
} else if(map.apply) { | ||
return map.apply(context, mapArgs || []); | ||
@@ -51,6 +105,8 @@ } else if(isFunction(el[map])) { | ||
function arrayEach(arr, fn, startIndex, loop) { | ||
var length, index, i; | ||
var index, i, length = +arr.length; | ||
if(startIndex < 0) startIndex = arr.length + startIndex; | ||
i = isNaN(startIndex) ? 0 : startIndex; | ||
length = loop === true ? arr.length + i : arr.length; | ||
if(loop === true) { | ||
length += i; | ||
} | ||
while(i < length) { | ||
@@ -88,11 +144,14 @@ index = i % arr.length; | ||
function arrayFind(arr, f, startIndex, loop, returnIndex) { | ||
var result, index; | ||
arrayEach(arr, function(el, i, arr) { | ||
if(multiMatch(el, f, arr, [el, i, arr])) { | ||
result = el; | ||
index = i; | ||
return false; | ||
} | ||
}, startIndex, loop); | ||
function arrayFind(arr, f, startIndex, loop, returnIndex, context) { | ||
var result, index, matcher; | ||
if(arr.length > 0) { | ||
matcher = getMatcher(f); | ||
arrayEach(arr, function(el, i) { | ||
if(matcher.call(context, el, i, arr)) { | ||
result = el; | ||
index = i; | ||
return false; | ||
} | ||
}, startIndex, loop); | ||
} | ||
return returnIndex ? index : result; | ||
@@ -123,3 +182,3 @@ } | ||
// 2. It exists in the compared array and we're adding, or it doesn't exist and we're removing. | ||
if(elementExistsInHash(o, stringified, el, isReference) != subtract) { | ||
if(elementExistsInHash(o, stringified, el, isReference) !== subtract) { | ||
discardElementFromHash(o, stringified, el, isReference); | ||
@@ -146,2 +205,11 @@ result.push(el); | ||
function isArrayLike(obj) { | ||
return hasProperty(obj, 'length') && !isString(obj) && !isPlainObject(obj); | ||
} | ||
function isArgumentsObject(obj) { | ||
// .callee exists on Arguments objects in < IE8 | ||
return hasProperty(obj, 'length') && (className(obj) === '[object Arguments]' || !!obj.callee); | ||
} | ||
function flatArguments(args) { | ||
@@ -169,3 +237,3 @@ var result = []; | ||
isReference = !objectIsMatchedByValue(element), | ||
exists = elementExistsInHash(hash, stringified, element, isReference); | ||
exists = elementExistsInHash(hash, stringified, element, isReference); | ||
if(isReference) { | ||
@@ -198,10 +266,14 @@ hash[stringified].push(element); | ||
function getMinOrMax(obj, map, which, all) { | ||
var edge, | ||
var el, | ||
key, | ||
edge, | ||
test, | ||
result = [], | ||
max = which === 'max', | ||
min = which === 'min', | ||
isArray = Array.isArray(obj); | ||
iterateOverObject(obj, function(key) { | ||
var el = obj[key], | ||
test = transformArgument(el, map, obj, isArray ? [el, parseInt(key), obj] : []); | ||
isArray = array.isArray(obj); | ||
for(key in obj) { | ||
if(!obj.hasOwnProperty(key)) continue; | ||
el = obj[key]; | ||
test = transformArgument(el, map, obj, isArray ? [el, parseInt(key), obj] : []); | ||
if(isUndefined(test)) { | ||
@@ -216,3 +288,3 @@ throw new TypeError('Cannot compare with undefined'); | ||
} | ||
}); | ||
} | ||
if(!isArray) result = arrayFlatten(result, 1); | ||
@@ -227,45 +299,64 @@ return all ? result : result[0]; | ||
var aValue, bValue, aChar, bChar, aEquiv, bEquiv, index = 0, tiebreaker = 0; | ||
a = getCollationReadyString(a); | ||
b = getCollationReadyString(b); | ||
var sortIgnore = array[AlphanumericSortIgnore]; | ||
var sortIgnoreCase = array[AlphanumericSortIgnoreCase]; | ||
var sortEquivalents = array[AlphanumericSortEquivalents]; | ||
var sortOrder = array[AlphanumericSortOrder]; | ||
var naturalSort = array[AlphanumericSortNatural]; | ||
a = getCollationReadyString(a, sortIgnore, sortIgnoreCase); | ||
b = getCollationReadyString(b, sortIgnore, sortIgnoreCase); | ||
do { | ||
aChar = getCollationCharacter(a, index); | ||
bChar = getCollationCharacter(b, index); | ||
aValue = getCollationValue(aChar); | ||
bValue = getCollationValue(bChar); | ||
aChar = getCollationCharacter(a, index, sortEquivalents); | ||
bChar = getCollationCharacter(b, index, sortEquivalents); | ||
aValue = getSortOrderIndex(aChar, sortOrder); | ||
bValue = getSortOrderIndex(bChar, sortOrder); | ||
if(aValue === -1 || bValue === -1) { | ||
aValue = a.charCodeAt(index) || null; | ||
bValue = b.charCodeAt(index) || null; | ||
if(naturalSort && codeIsNumeral(aValue) && codeIsNumeral(bValue)) { | ||
aValue = stringToNumber(a.slice(index)); | ||
bValue = stringToNumber(b.slice(index)); | ||
} | ||
} else { | ||
aEquiv = aChar !== a.charAt(index); | ||
bEquiv = bChar !== b.charAt(index); | ||
if(aEquiv !== bEquiv && tiebreaker === 0) { | ||
tiebreaker = aEquiv - bEquiv; | ||
} | ||
} | ||
aEquiv = aChar !== a.charAt(index); | ||
bEquiv = bChar !== b.charAt(index); | ||
if(aEquiv !== bEquiv && tiebreaker === 0) { | ||
tiebreaker = aEquiv - bEquiv; | ||
} | ||
index += 1; | ||
} while(aValue != null && bValue != null && aValue === bValue); | ||
if(aValue === bValue) return tiebreaker; | ||
return aValue < bValue ? -1 : 1; | ||
return aValue - bValue; | ||
} | ||
function getCollationReadyString(str) { | ||
if(array[AlphanumericSortIgnoreCase]) { | ||
function getCollationReadyString(str, sortIgnore, sortIgnoreCase) { | ||
if(!isString(str)) str = string(str); | ||
if(sortIgnoreCase) { | ||
str = str.toLowerCase(); | ||
} | ||
return str.replace(array[AlphanumericSortIgnore], ''); | ||
if(sortIgnore) { | ||
str = str.replace(sortIgnore, ''); | ||
} | ||
return str; | ||
} | ||
function getCollationCharacter(str, index) { | ||
var chr = str.charAt(index), eq = array[AlphanumericSortEquivalents] || {}; | ||
return eq[chr] || chr; | ||
function getCollationCharacter(str, index, sortEquivalents) { | ||
var chr = str.charAt(index); | ||
return sortEquivalents[chr] || chr; | ||
} | ||
function getCollationValue(chr) { | ||
var order = array[AlphanumericSortOrder]; | ||
function getSortOrderIndex(chr, sortOrder) { | ||
if(!chr) { | ||
return null; | ||
} else { | ||
return order.indexOf(chr); | ||
return sortOrder.indexOf(chr); | ||
} | ||
} | ||
var AlphanumericSort = 'AlphanumericSort'; | ||
var AlphanumericSortOrder = 'AlphanumericSortOrder'; | ||
@@ -275,2 +366,3 @@ var AlphanumericSortIgnore = 'AlphanumericSortIgnore'; | ||
var AlphanumericSortEquivalents = 'AlphanumericSortEquivalents'; | ||
var AlphanumericSortNatural = 'AlphanumericSortNatural'; | ||
@@ -280,14 +372,23 @@ | ||
function buildEnhancements() { | ||
var callbackCheck = function() { var a = arguments; return a.length > 0 && !isFunction(a[0]); }; | ||
extendSimilar(array, true, callbackCheck, 'map,every,all,some,any,none,filter', function(methods, name) { | ||
var nativeMap = array.prototype.map; | ||
var callbackCheck = function() { | ||
var args = arguments; | ||
return args.length > 0 && !isFunction(args[0]); | ||
}; | ||
extendSimilar(array, true, callbackCheck, 'every,all,some,filter,any,none,find,findIndex', function(methods, name) { | ||
var nativeFn = array.prototype[name] | ||
methods[name] = function(f) { | ||
return this[name](function(el, index) { | ||
if(name === 'map') { | ||
return transformArgument(el, f, this, [el, index, this]); | ||
} else { | ||
return multiMatch(el, f, this, [el, index, this]); | ||
} | ||
var matcher = getMatcher(f); | ||
return nativeFn.call(this, function(el, index) { | ||
return matcher(el, index, this); | ||
}); | ||
} | ||
}); | ||
extend(array, true, callbackCheck, { | ||
'map': function(f) { | ||
return nativeMap.call(this, function(el, index) { | ||
return transformArgument(el, f, this, [el, index, this]); | ||
}); | ||
} | ||
}); | ||
} | ||
@@ -309,2 +410,3 @@ | ||
}); | ||
array[AlphanumericSortNatural] = true; | ||
array[AlphanumericSortIgnoreCase] = true; | ||
@@ -314,3 +416,3 @@ array[AlphanumericSortEquivalents] = equivalents; | ||
extend(array, false, false, { | ||
extend(array, false, true, { | ||
@@ -333,11 +435,6 @@ /*** | ||
'create': function() { | ||
var result = [], tmp; | ||
var result = []; | ||
multiArgs(arguments, function(a) { | ||
if(isObjectPrimitive(a)) { | ||
try { | ||
tmp = array.prototype.slice.call(a, 0); | ||
if(tmp.length > 0) { | ||
a = tmp; | ||
} | ||
} catch(e) {}; | ||
if(isArgumentsObject(a) || isArrayLike(a)) { | ||
a = array.prototype.slice.call(a, 0); | ||
} | ||
@@ -354,6 +451,6 @@ result = result.concat(a); | ||
/*** | ||
* @method find(<f>, [index] = 0, [loop] = false) | ||
* @method find(<f>, [context] = undefined) | ||
* @returns Mixed | ||
* @short Returns the first element that matches <f>. | ||
* @extra <f> will match a string, number, array, object, or alternately test against a function or regex. Starts at [index], and will continue once from index = 0 if [loop] is true. This method implements @array_matching. | ||
* @extra [context] is the %this% object if passed. When <f> is a function, will use native implementation if it exists. <f> will also match a string, number, array, object, or alternately test against a function or regex. This method implements @array_matching. | ||
* @example | ||
@@ -363,7 +460,48 @@ * | ||
* return n['a'] == 1; | ||
* }); -> {a:1,b:3} | ||
* ['cuba','japan','canada'].find(/^c/, 2) -> 'canada' | ||
* }); -> {a:1,b:3} | ||
* ['cuba','japan','canada'].find(/^c/) -> 'cuba' | ||
* | ||
***/ | ||
'find': function(f, index, loop) { | ||
'find': function(f, context) { | ||
checkCallback(f); | ||
return arrayFind(this, f, 0, false, false, context); | ||
}, | ||
/*** | ||
* @method findIndex(<f>, [context] = undefined) | ||
* @returns Number | ||
* @short Returns the index of the first element that matches <f> or -1 if not found. | ||
* @extra [context] is the %this% object if passed. When <f> is a function, will use native implementation if it exists. <f> will also match a string, number, array, object, or alternately test against a function or regex. This method implements @array_matching. | ||
* | ||
* @example | ||
* | ||
+ [1,2,3,4].findIndex(function(n) { | ||
* return n % 2 == 0; | ||
* }); -> 1 | ||
+ [1,2,3,4].findIndex(3); -> 2 | ||
+ ['one','two','three'].findIndex(/t/); -> 1 | ||
* | ||
***/ | ||
'findIndex': function(f, context) { | ||
var index; | ||
checkCallback(f); | ||
index = arrayFind(this, f, 0, false, true, context); | ||
return isUndefined(index) ? -1 : index; | ||
} | ||
}); | ||
extend(array, true, true, { | ||
/*** | ||
* @method findFrom(<f>, [index] = 0, [loop] = false) | ||
* @returns Array | ||
* @short Returns any element that matches <f>, beginning from [index]. | ||
* @extra <f> will match a string, number, array, object, or alternately test against a function or regex. Will continue from index = 0 if [loop] is true. This method implements @array_matching. | ||
* @example | ||
* | ||
* ['cuba','japan','canada'].findFrom(/^c/, 2) -> 'canada' | ||
* | ||
***/ | ||
'findFrom': function(f, index, loop) { | ||
return arrayFind(this, f, index, loop); | ||
@@ -373,2 +511,17 @@ }, | ||
/*** | ||
* @method findIndexFrom(<f>, [index] = 0, [loop] = false) | ||
* @returns Array | ||
* @short Returns the index of any element that matches <f>, beginning from [index]. | ||
* @extra <f> will match a string, number, array, object, or alternately test against a function or regex. Will continue from index = 0 if [loop] is true. This method implements @array_matching. | ||
* @example | ||
* | ||
* ['cuba','japan','canada'].findIndexFrom(/^c/, 2) -> 2 | ||
* | ||
***/ | ||
'findIndexFrom': function(f, index, loop) { | ||
var index = arrayFind(this, f, index, loop, true); | ||
return isUndefined(index) ? -1 : index; | ||
}, | ||
/*** | ||
* @method findAll(<f>, [index] = 0, [loop] = false) | ||
@@ -388,8 +541,11 @@ * @returns Array | ||
'findAll': function(f, index, loop) { | ||
var result = []; | ||
arrayEach(this, function(el, i, arr) { | ||
if(multiMatch(el, f, arr, [el, i, arr])) { | ||
result.push(el); | ||
} | ||
}, index, loop); | ||
var result = [], matcher; | ||
if(this.length > 0) { | ||
matcher = getMatcher(f); | ||
arrayEach(this, function(el, i, arr) { | ||
if(matcher(el, i, arr)) { | ||
result.push(el); | ||
} | ||
}, index, loop); | ||
} | ||
return result; | ||
@@ -399,21 +555,2 @@ }, | ||
/*** | ||
* @method findIndex(<f>, [startIndex] = 0, [loop] = false) | ||
* @returns Number | ||
* @short Returns the index of the first element that matches <f> or -1 if not found. | ||
* @extra This method has a few notable differences to native %indexOf%. Although <f> will similarly match a primitive such as a string or number, it will also match deep objects and arrays that are not equal by reference (%===%). Additionally, if a function is passed it will be run as a matching function (similar to the behavior of %Array#filter%) rather than attempting to find that function itself by reference in the array. Starts at [index], and will continue once from index = 0 if [loop] is true. This method implements @array_matching. | ||
* @example | ||
* | ||
+ [1,2,3,4].findIndex(3); -> 2 | ||
+ [1,2,3,4].findIndex(function(n) { | ||
* return n % 2 == 0; | ||
* }); -> 1 | ||
+ ['one','two','three'].findIndex(/th/); -> 2 | ||
* | ||
***/ | ||
'findIndex': function(f, startIndex, loop) { | ||
var index = arrayFind(this, f, startIndex, loop, true); | ||
return isUndefined(index) ? -1 : index; | ||
}, | ||
/*** | ||
* @method count(<f>) | ||
@@ -448,8 +585,5 @@ * @returns Number | ||
'removeAt': function(start, end) { | ||
var i, len; | ||
if(isUndefined(start)) return this; | ||
if(isUndefined(end)) end = start; | ||
for(i = 0, len = end - start; i <= len; i++) { | ||
this.splice(start, 1); | ||
} | ||
if(isUndefined(end)) end = start; | ||
this.splice(start, end - start + 1); | ||
return this; | ||
@@ -601,3 +735,3 @@ }, | ||
'at': function() { | ||
return entryAtIndex(this, arguments); | ||
return getEntriesForIndexes(this, arguments); | ||
}, | ||
@@ -767,3 +901,3 @@ | ||
* @returns Number | ||
* @short Averages all values in the array. | ||
* @short Gets the mean average for all values in the array. | ||
* @extra [map] may be a function mapping the value to be averaged or a string acting as a shortcut. | ||
@@ -800,7 +934,7 @@ * @example | ||
var divisor = ceil(this.length / num); | ||
getRange(0, num - 1, function(i) { | ||
simpleRepeat(num, function(i) { | ||
var index = i * divisor; | ||
var group = arr.slice(index, index + divisor); | ||
if(pad && group.length < divisor) { | ||
getRange(1, divisor - group.length, function() { | ||
simpleRepeat(divisor - group.length, function() { | ||
group = group.add(padding); | ||
@@ -830,3 +964,3 @@ }); | ||
if(isUndefined(padding)) padding = null; | ||
getRange(0, ceil(len / num) - 1, function(i) { | ||
simpleRepeat(ceil(len / num), function(i) { | ||
group = arr.slice(num * i, num * i + num); | ||
@@ -1000,7 +1134,7 @@ while(group.length < num) { | ||
'remove': function() { | ||
var i, arr = this; | ||
var arr = this; | ||
multiArgs(arguments, function(f) { | ||
i = 0; | ||
var i = 0, matcher = getMatcher(f); | ||
while(i < arr.length) { | ||
if(multiMatch(arr[i], f, arr, [arr[i], i, arr])) { | ||
if(matcher(arr[i], i, arr)) { | ||
arr.splice(i, 1); | ||
@@ -1088,5 +1222,7 @@ } else { | ||
// Aliases | ||
extend(array, true, false, { | ||
extend(array, true, true, { | ||
/*** | ||
@@ -1121,7 +1257,4 @@ * @method all() | ||
function keysWithCoercion(obj) { | ||
if(obj && obj.valueOf) { | ||
obj = obj.valueOf(); | ||
} | ||
return object.keys(obj); | ||
function keysWithObjectCoercion(obj) { | ||
return object.keys(coercePrimitiveToObject(obj)); | ||
} | ||
@@ -1163,10 +1296,14 @@ | ||
function buildEnumerableMethods(names, mapping) { | ||
extendSimilar(object, false, false, names, function(methods, name) { | ||
extendSimilar(object, false, true, names, function(methods, name) { | ||
methods[name] = function(obj, arg1, arg2) { | ||
var result, coerced = keysWithCoercion(obj); | ||
var result, coerced = keysWithObjectCoercion(obj), matcher; | ||
if(!mapping) { | ||
matcher = getMatcher(arg1, true); | ||
} | ||
result = array.prototype[name].call(coerced, function(key) { | ||
var value = obj[key]; | ||
if(mapping) { | ||
return transformArgument(obj[key], arg1, obj, [key, obj[key], obj]); | ||
return transformArgument(value, arg1, obj, [key, value, obj]); | ||
} else { | ||
return multiMatch(obj[key], arg1, obj, [key, obj[key], obj]); | ||
return matcher(value, key, obj); | ||
} | ||
@@ -1188,16 +1325,23 @@ }, arg2); | ||
extend(object, false, false, { | ||
function exportSortAlgorithm() { | ||
array[AlphanumericSort] = collateStrings; | ||
} | ||
extend(object, false, true, { | ||
'map': function(obj, map) { | ||
return keysWithCoercion(obj).reduce(function(result, key) { | ||
result[key] = transformArgument(obj[key], map, obj, [key, obj[key], obj]); | ||
return result; | ||
}, {}); | ||
var result = {}, key, value; | ||
for(key in obj) { | ||
if(!hasOwnProperty(obj, key)) continue; | ||
value = obj[key]; | ||
result[key] = transformArgument(value, map, obj, [key, value, obj]); | ||
} | ||
return result; | ||
}, | ||
'reduce': function(obj) { | ||
var values = keysWithCoercion(obj).map(function(key) { | ||
var values = keysWithObjectCoercion(obj).map(function(key) { | ||
return obj[key]; | ||
}); | ||
return values.reduce.apply(values, multiArgs(arguments).slice(1)); | ||
return values.reduce.apply(values, multiArgs(arguments, null, 1)); | ||
}, | ||
@@ -1222,3 +1366,3 @@ | ||
'size': function (obj) { | ||
return keysWithCoercion(obj).length; | ||
return keysWithObjectCoercion(obj).length; | ||
} | ||
@@ -1238,2 +1382,3 @@ | ||
buildObjectInstanceMethods(EnumerableOtherMethods, Hash); | ||
exportSortAlgorithm(); | ||
417
lib/core.js
'use strict'; | ||
/*** | ||
@@ -11,11 +13,11 @@ * @package Core | ||
// The global context | ||
var globalContext = typeof global !== 'undefined' ? global : this; | ||
// Internal toString | ||
var internalToString = object.prototype.toString; | ||
// The global context | ||
var globalContext = typeof global !== 'undefined' ? global : this; | ||
// Internal hasOwnProperty | ||
var internalHasOwnProperty = object.prototype.hasOwnProperty; | ||
// Type check methods need a way to be accessed dynamically outside global context. | ||
var typeChecks = {}; | ||
// defineProperty exists in IE8 but will error when trying to define a property on | ||
@@ -25,27 +27,61 @@ // native objects. IE8 does not have defineProperies, however, so this check saves a try/catch block. | ||
// Are regexes type function? | ||
var regexIsFunction = typeof regexp() === 'function'; | ||
// Do strings have no keys? | ||
var noKeysInStringObjects = !('0' in new string('a')); | ||
// Type check methods need a way to be accessed dynamically. | ||
var typeChecks = {}; | ||
// Classes that can be matched by value | ||
var matchedByValueReg = /^\[object Date|Array|String|Number|RegExp|Boolean|Arguments\]$/; | ||
// Class initializers and class helpers | ||
var ClassNames = 'Boolean,Number,String,Array,Date,RegExp,Function'.split(','); | ||
var ClassNames = 'Array,Boolean,Date,Function,Number,String,RegExp'.split(','); | ||
var isBoolean = buildPrimitiveClassCheck('boolean', ClassNames[0]); | ||
var isNumber = buildPrimitiveClassCheck('number', ClassNames[1]); | ||
var isString = buildPrimitiveClassCheck('string', ClassNames[2]); | ||
var isArray = buildClassCheck(ClassNames[0]); | ||
var isBoolean = buildClassCheck(ClassNames[1]); | ||
var isDate = buildClassCheck(ClassNames[2]); | ||
var isFunction = buildClassCheck(ClassNames[3]); | ||
var isNumber = buildClassCheck(ClassNames[4]); | ||
var isString = buildClassCheck(ClassNames[5]); | ||
var isRegExp = buildClassCheck(ClassNames[6]); | ||
var isArray = buildClassCheck(ClassNames[3]); | ||
var isDate = buildClassCheck(ClassNames[4]); | ||
var isRegExp = buildClassCheck(ClassNames[5]); | ||
function buildClassCheck(name) { | ||
var type, fn; | ||
if(/String|Number|Boolean/.test(name)) { | ||
type = name.toLowerCase(); | ||
} | ||
fn = (name === 'Array' && array.isArray) || function(obj) { | ||
if(type && typeof obj === type) { | ||
return true; | ||
// Wanted to enhance performance here by using simply "typeof" | ||
// but Firefox has two major issues that make this impossible, | ||
// one fixed, the other not. Despite being typeof "function" | ||
// the objects below still report in as [object Function], so | ||
// we need to perform a full class check here. | ||
// | ||
// 1. Regexes can be typeof "function" in FF < 3 | ||
// https://bugzilla.mozilla.org/show_bug.cgi?id=61911 (fixed) | ||
// | ||
// 2. HTMLEmbedElement and HTMLObjectElement are be typeof "function" | ||
// https://bugzilla.mozilla.org/show_bug.cgi?id=268945 (won't fix) | ||
// | ||
var isFunction = buildClassCheck(ClassNames[6]); | ||
function isClass(obj, klass, cached) { | ||
var k = cached || className(obj); | ||
return k === '[object '+klass+']'; | ||
} | ||
function buildClassCheck(klass) { | ||
var fn = (klass === 'Array' && array.isArray) || function(obj, cached) { | ||
return isClass(obj, klass, cached); | ||
}; | ||
typeChecks[klass] = fn; | ||
return fn; | ||
} | ||
function buildPrimitiveClassCheck(type, klass) { | ||
var fn = function(obj) { | ||
if(isObjectType(obj)) { | ||
return isClass(obj, klass); | ||
} | ||
return className(obj) === '[object '+name+']'; | ||
return typeof obj === type; | ||
} | ||
typeChecks[name] = fn; | ||
typeChecks[klass] = fn; | ||
return fn; | ||
@@ -68,3 +104,3 @@ } | ||
defineProperty(klass, 'SugarMethods', {}); | ||
extend(klass, false, false, { | ||
extend(klass, false, true, { | ||
'extend': function(methods, override, instance) { | ||
@@ -74,3 +110,3 @@ extend(klass, instance !== false, override, methods); | ||
'sugarRestore': function() { | ||
return batchMethodExecute(klass, arguments, function(target, name, m) { | ||
return batchMethodExecute(this, klass, arguments, function(target, name, m) { | ||
defineProperty(target, name, m.method); | ||
@@ -80,5 +116,5 @@ }); | ||
'sugarRevert': function() { | ||
return batchMethodExecute(klass, arguments, function(target, name, m) { | ||
if(m.existed) { | ||
defineProperty(target, name, m.original); | ||
return batchMethodExecute(this, klass, arguments, function(target, name, m) { | ||
if(m['existed']) { | ||
defineProperty(target, name, m['original']); | ||
} else { | ||
@@ -97,13 +133,19 @@ delete target[name]; | ||
initializeClass(klass); | ||
iterateOverObject(methods, function(name, method) { | ||
var original = extendee[name]; | ||
var existed = hasOwnProperty(extendee, name); | ||
if(typeof override === 'function') { | ||
method = wrapNative(extendee[name], method, override); | ||
iterateOverObject(methods, function(name, extendedFn) { | ||
var nativeFn = extendee[name], | ||
existed = hasOwnProperty(extendee, name); | ||
if(isFunction(override) && nativeFn) { | ||
extendedFn = wrapNative(nativeFn, extendedFn, override); | ||
} | ||
if(override !== false || !extendee[name]) { | ||
defineProperty(extendee, name, method); | ||
if(override !== false || !nativeFn) { | ||
defineProperty(extendee, name, extendedFn); | ||
} | ||
// If the method is internal to Sugar, then store a reference so it can be restored later. | ||
klass['SugarMethods'][name] = { instance: instance, method: method, original: original, existed: existed }; | ||
// If the method is internal to Sugar, then | ||
// store a reference so it can be restored later. | ||
klass['SugarMethods'][name] = { | ||
'method': extendedFn, | ||
'existed': existed, | ||
'original': nativeFn, | ||
'instance': instance | ||
}; | ||
}); | ||
@@ -121,8 +163,8 @@ } | ||
function batchMethodExecute(klass, args, fn) { | ||
function batchMethodExecute(target, klass, args, fn) { | ||
var all = args.length === 0, methods = multiArgs(args), changed = false; | ||
iterateOverObject(klass['SugarMethods'], function(name, m) { | ||
if(all || methods.indexOf(name) > -1) { | ||
if(all || methods.indexOf(name) !== -1) { | ||
changed = true; | ||
fn(m.instance ? klass.prototype : klass, name, m); | ||
fn(m['instance'] ? target.prototype : target, name, m); | ||
} | ||
@@ -134,10 +176,6 @@ }); | ||
function wrapNative(nativeFn, extendedFn, condition) { | ||
return function() { | ||
var fn; | ||
if(nativeFn && (condition === true || !condition.apply(this, arguments))) { | ||
fn = nativeFn; | ||
} else { | ||
fn = extendedFn; | ||
} | ||
return fn.apply(this, arguments); | ||
return function(a) { | ||
return condition.apply(this, arguments) ? | ||
extendedFn.apply(this, arguments) : | ||
nativeFn.apply(this, arguments); | ||
} | ||
@@ -148,3 +186,8 @@ } | ||
if(definePropertySupport) { | ||
object.defineProperty(target, name, { 'value': method, 'configurable': true, 'enumerable': false, 'writable': true }); | ||
object.defineProperty(target, name, { | ||
'value': method, | ||
'configurable': true, | ||
'enumerable': false, | ||
'writable': true | ||
}); | ||
} else { | ||
@@ -158,5 +201,5 @@ target[name] = method; | ||
function multiArgs(args, fn) { | ||
var result = [], i, len; | ||
for(i = 0, len = args.length; i < len; i++) { | ||
function multiArgs(args, fn, from) { | ||
var result = [], i = from || 0, len; | ||
for(len = args.length; i < len; i++) { | ||
result.push(args[i]); | ||
@@ -168,4 +211,9 @@ if(fn) fn.call(args, args[i], i); | ||
function flattenedArgs(obj, fn, from) { | ||
multiArgs(array.prototype.concat.apply([], array.prototype.slice.call(obj, from || 0)), fn); | ||
function flattenedArgs(args, fn, from) { | ||
var arg = args[from || 0]; | ||
if(isArray(arg)) { | ||
args = arg; | ||
from = 0; | ||
} | ||
return multiArgs(args, fn, from); | ||
} | ||
@@ -193,18 +241,41 @@ | ||
function isObjectPrimitive(obj) { | ||
// Check for null | ||
return obj && typeof obj === 'object'; | ||
function hasProperty(obj, prop) { | ||
return !isPrimitiveType(obj) && prop in obj; | ||
} | ||
function isObject(obj) { | ||
function hasOwnProperty(obj, prop) { | ||
return !!obj && internalHasOwnProperty.call(obj, prop); | ||
} | ||
function isObjectType(obj) { | ||
// 1. Check for null | ||
// 2. Check for regexes in environments where they are "functions". | ||
return !!obj && (typeof obj === 'object' || (regexIsFunction && isRegExp(obj))); | ||
} | ||
function isPrimitiveType(obj) { | ||
var type = typeof obj; | ||
return obj == null || type === 'string' || type === 'number' || type === 'boolean'; | ||
} | ||
function isPlainObject(obj, klass) { | ||
klass = klass || className(obj); | ||
try { | ||
// Not own constructor property must be Object | ||
// This code was borrowed from jQuery.isPlainObject | ||
if (obj && obj.constructor && | ||
!hasOwnProperty(obj, 'constructor') && | ||
!hasOwnProperty(obj.constructor.prototype, 'isPrototypeOf')) { | ||
return false; | ||
} | ||
} catch (e) { | ||
// IE8,9 Will throw exceptions on certain host objects. | ||
return false; | ||
} | ||
// === on the constructor is not safe across iframes | ||
// 'hasOwnProperty' ensures that the object also inherits | ||
// from Object, which is false for DOMElements in IE. | ||
return !!obj && className(obj) === '[object Object]' && 'hasOwnProperty' in obj; | ||
return !!obj && klass === '[object Object]' && 'hasOwnProperty' in obj; | ||
} | ||
function hasOwnProperty(obj, key) { | ||
return object['hasOwnProperty'].call(obj, key); | ||
} | ||
function iterateOverObject(obj, fn) { | ||
@@ -218,2 +289,8 @@ var key; | ||
function simpleRepeat(n, fn) { | ||
for(var i = 0; i < n; i++) { | ||
fn(i); | ||
} | ||
} | ||
function simpleMerge(target, source) { | ||
@@ -226,6 +303,26 @@ iterateOverObject(source, function(key) { | ||
// Make primtives types like strings into objects. | ||
function coercePrimitiveToObject(obj) { | ||
if(isPrimitiveType(obj)) { | ||
obj = object(obj); | ||
} | ||
if(noKeysInStringObjects && isString(obj)) { | ||
forceStringCoercion(obj); | ||
} | ||
return obj; | ||
} | ||
// Force strings to have their indexes set in | ||
// environments that don't do this automatically. | ||
function forceStringCoercion(obj) { | ||
var i = 0, chr; | ||
while(chr = obj.charAt(i)) { | ||
obj[i++] = chr; | ||
} | ||
} | ||
// Hash definition | ||
function Hash(obj) { | ||
simpleMerge(this, obj); | ||
simpleMerge(this, coercePrimitiveToObject(obj)); | ||
}; | ||
@@ -235,17 +332,15 @@ | ||
// Number helpers | ||
// Math helpers | ||
function getRange(start, stop, fn, step) { | ||
var arr = [], i = parseInt(start), down = step < 0; | ||
while((!down && i <= stop) || (down && i >= stop)) { | ||
arr.push(i); | ||
if(fn) fn.call(this, i); | ||
i += step || 1; | ||
} | ||
return arr; | ||
} | ||
var abs = math.abs; | ||
var pow = math.pow; | ||
var ceil = math.ceil; | ||
var floor = math.floor; | ||
var round = math.round; | ||
var min = math.min; | ||
var max = math.max; | ||
function round(val, precision, method) { | ||
var fn = math[method || 'round']; | ||
var multiplier = math.pow(10, math.abs(precision || 0)); | ||
function withPrecision(val, precision, fn) { | ||
var multiplier = pow(10, abs(precision || 0)); | ||
fn = fn || round; | ||
if(precision < 0) multiplier = 1 / multiplier; | ||
@@ -255,13 +350,82 @@ return fn(val * multiplier) / multiplier; | ||
function ceil(val, precision) { | ||
return round(val, precision, 'ceil'); | ||
// Full width number helpers | ||
var HalfWidthZeroCode = 48; | ||
var HalfWidthNineCode = 57; | ||
var FullWidthZeroCode = 65296; | ||
var FullWidthNineCode = 65305; | ||
var HalfWidthPeriod = '.'; | ||
var FullWidthPeriod = '.'; | ||
var HalfWidthComma = ','; | ||
// Used here and later in the Date package. | ||
var FullWidthDigits = ''; | ||
var NumberNormalizeMap = {}; | ||
var NumberNormalizeReg; | ||
function codeIsNumeral(code) { | ||
return (code >= HalfWidthZeroCode && code <= HalfWidthNineCode) || | ||
(code >= FullWidthZeroCode && code <= FullWidthNineCode); | ||
} | ||
function floor(val, precision) { | ||
return round(val, precision, 'floor'); | ||
function buildNumberHelpers() { | ||
var digit, i; | ||
for(i = 0; i <= 9; i++) { | ||
digit = chr(i + FullWidthZeroCode); | ||
FullWidthDigits += digit; | ||
NumberNormalizeMap[digit] = chr(i + HalfWidthZeroCode); | ||
} | ||
NumberNormalizeMap[HalfWidthComma] = ''; | ||
NumberNormalizeMap[FullWidthPeriod] = HalfWidthPeriod; | ||
// Mapping this to itself to easily be able to easily | ||
// capture it in stringToNumber to detect decimals later. | ||
NumberNormalizeMap[HalfWidthPeriod] = HalfWidthPeriod; | ||
NumberNormalizeReg = regexp('[' + FullWidthDigits + FullWidthPeriod + HalfWidthComma + HalfWidthPeriod + ']', 'g'); | ||
} | ||
// String helpers | ||
function chr(num) { | ||
return string.fromCharCode(num); | ||
} | ||
// WhiteSpace/LineTerminator as defined in ES5.1 plus Unicode characters in the Space, Separator category. | ||
function getTrimmableCharacters() { | ||
return '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF'; | ||
} | ||
function repeatString(str, num) { | ||
var result = '', str = str.toString(); | ||
while (num > 0) { | ||
if (num & 1) { | ||
result += str; | ||
} | ||
if (num >>= 1) { | ||
str += str; | ||
} | ||
} | ||
return result; | ||
} | ||
// Returns taking into account full-width characters, commas, and decimals. | ||
function stringToNumber(str, base) { | ||
var sanitized, isDecimal; | ||
sanitized = str.replace(NumberNormalizeReg, function(chr) { | ||
var replacement = NumberNormalizeMap[chr]; | ||
if(replacement === HalfWidthPeriod) { | ||
isDecimal = true; | ||
} | ||
return replacement; | ||
}); | ||
return isDecimal ? parseFloat(sanitized) : parseInt(sanitized, base || 10); | ||
} | ||
// Used by Number and Date | ||
function padNumber(num, place, sign, base) { | ||
var str = math.abs(num).toString(base || 10); | ||
str = repeatString(place - str.replace(/\.\d+/, '').length, '0') + str; | ||
var str = abs(num).toString(base || 10); | ||
str = repeatString('0', place - str.replace(/\.\d+/, '').length) + str; | ||
if(sign || num < 0) { | ||
@@ -287,21 +451,16 @@ str = (num < 0 ? '-' : '+') + str; | ||
// String helpers | ||
// WhiteSpace/LineTerminator as defined in ES5.1 plus Unicode characters in the Space, Separator category. | ||
function getTrimmableCharacters() { | ||
return '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF'; | ||
} | ||
function repeatString(times, str) { | ||
return array(math.max(0, isDefined(times) ? times : 1) + 1).join(str || ''); | ||
} | ||
// RegExp helpers | ||
function getRegExpFlags(reg, add) { | ||
var flags = reg.toString().match(/[^/]*$/)[0]; | ||
if(add) { | ||
flags = (flags + add).split('').sort().join('').replace(/([gimy])\1+/g, '$1'); | ||
var flags = ''; | ||
add = add || ''; | ||
function checkFlag(prop, flag) { | ||
if(prop || add.indexOf(flag) > -1) { | ||
flags += flag; | ||
} | ||
} | ||
checkFlag(reg.multiline, 'm'); | ||
checkFlag(reg.ignoreCase, 'i'); | ||
checkFlag(reg.global, 'g'); | ||
checkFlag(reg.sticky, 'y'); | ||
return flags; | ||
@@ -312,9 +471,16 @@ } | ||
if(!isString(str)) str = string(str); | ||
return str.replace(/([\\/'*+?|()\[\]{}.^$])/g,'\\$1'); | ||
return str.replace(/([\\/\'*+?|()\[\]{}.^$])/g,'\\$1'); | ||
} | ||
// Specialized helpers | ||
// Date helpers | ||
function callDateGet(d, method) { | ||
return d['get' + (d._utc ? 'UTC' : '') + method](); | ||
} | ||
function callDateSet(d, method, value) { | ||
return d['set' + (d._utc && method != 'ISOWeek' ? 'UTC' : '') + method](value); | ||
} | ||
// Used by Array#unique and Object.equal | ||
@@ -333,4 +499,4 @@ | ||
klass = internalToString.call(thing) | ||
thingIsObject = isObject(thing); | ||
thingIsArray = klass === '[object Array]'; | ||
thingIsObject = isPlainObject(thing, klass); | ||
thingIsArray = isArray(thing, klass); | ||
@@ -355,3 +521,3 @@ if(thing != null && thingIsObject || thingIsArray) { | ||
stack.push(thing); | ||
value = string(thing.constructor); | ||
value = thing.valueOf() + string(thing.constructor); | ||
arr = thingIsArray ? thing : object.keys(thing).sort(); | ||
@@ -372,13 +538,18 @@ for(i = 0, len = arr.length; i < len; i++) { | ||
function isEqual(a, b) { | ||
if(objectIsMatchedByValue(a) && objectIsMatchedByValue(b)) { | ||
if(a === b) { | ||
// Return quickly up front when matching by reference, | ||
// but be careful about 0 !== -0. | ||
return a !== 0 || 1 / a === 1 / b; | ||
} else if(objectIsMatchedByValue(a) && objectIsMatchedByValue(b)) { | ||
return stringify(a) === stringify(b); | ||
} else { | ||
return a === b; | ||
} | ||
return false; | ||
} | ||
function objectIsMatchedByValue(obj) { | ||
// Only known objects are matched by value. This is notably excluding functions, DOM Elements, and instances of | ||
// user-created classes. The latter can arguably be matched by value, but distinguishing between these and | ||
// host objects -- which should never be compared by value -- is very tricky so not dealing with it here. | ||
var klass = className(obj); | ||
return /^\[object Date|Array|String|Number|RegExp|Boolean|Arguments\]$/.test(klass) || | ||
isObject(obj); | ||
return matchedByValueReg.test(klass) || isPlainObject(obj, klass); | ||
} | ||
@@ -389,17 +560,28 @@ | ||
function entryAtIndex(arr, args, str) { | ||
var result = [], length = arr.length, loop = args[args.length - 1] !== false, r; | ||
function getEntriesForIndexes(obj, args, isString) { | ||
var result, | ||
length = obj.length, | ||
argsLen = args.length, | ||
overshoot = args[argsLen - 1] !== false, | ||
multiple = argsLen > (overshoot ? 1 : 2); | ||
if(!multiple) { | ||
return entryAtIndex(obj, length, args[0], overshoot, isString); | ||
} | ||
result = []; | ||
multiArgs(args, function(index) { | ||
if(isBoolean(index)) return false; | ||
if(loop) { | ||
index = index % length; | ||
if(index < 0) index = length + index; | ||
} | ||
r = str ? arr.charAt(index) || '' : arr[index]; | ||
result.push(r); | ||
result.push(entryAtIndex(obj, length, index, overshoot, isString)); | ||
}); | ||
return result.length < 2 ? result[0] : result; | ||
return result; | ||
} | ||
function entryAtIndex(obj, length, index, overshoot, isString) { | ||
if(overshoot) { | ||
index = index % length; | ||
if(index < 0) index = length + index; | ||
} | ||
return isString ? obj.charAt(index) : obj[index]; | ||
} | ||
// Object class methods implemented as instance methods | ||
@@ -416,2 +598,3 @@ | ||
initializeClasses(); | ||
buildNumberHelpers(); | ||
'use strict'; | ||
@@ -19,3 +20,3 @@ /*** | ||
var keys = []; | ||
if(!isObjectPrimitive(obj) && !isRegExp(obj) && !isFunction(obj)) { | ||
if(!isObjectType(obj) && !isRegExp(obj) && !isFunction(obj)) { | ||
throw new TypeError('Object required'); | ||
@@ -179,6 +180,6 @@ } | ||
* @short Maps the array to another array containing the values that are the result of calling <map> on each element. | ||
* @extra [scope] is the %this% object. In addition to providing this method for browsers that don't support it natively, this enhanced method also directly accepts a string, which is a shortcut for a function that gets that property (or invokes a function) on each element. | ||
* @extra [scope] is the %this% object. When <map> is a function, it receives three arguments: the current element, the current index, and a reference to the array. In addition to providing this method for browsers that don't support it natively, this enhanced method also directly accepts a string, which is a shortcut for a function that gets that property (or invokes a function) on each element. | ||
* @example | ||
* | ||
+ [1,2,3].map(function(n) { | ||
* [1,2,3].map(function(n) { | ||
* return n * 3; | ||
@@ -190,5 +191,6 @@ * }); -> [3,6,9] | ||
* ['one','two','three'].map('length') -> [3,3,5] | ||
* | ||
***/ | ||
'map': function(fn, scope) { | ||
var length = this.length, index = 0, result = new Array(length); | ||
var scope = arguments[1], length = this.length, index = 0, result = new Array(length); | ||
checkFirstArgumentExists(arguments); | ||
@@ -217,3 +219,4 @@ while(index < length) { | ||
***/ | ||
'filter': function(fn, scope) { | ||
'filter': function(fn) { | ||
var scope = arguments[1]; | ||
var length = this.length, index = 0, result = []; | ||
@@ -241,3 +244,4 @@ checkFirstArgumentExists(arguments); | ||
***/ | ||
'indexOf': function(search, fromIndex) { | ||
'indexOf': function(search) { | ||
var fromIndex = arguments[1]; | ||
if(isString(this)) return this.indexOf(search, fromIndex); | ||
@@ -258,3 +262,4 @@ return arrayIndexOf(this, search, fromIndex, 1); | ||
***/ | ||
'lastIndexOf': function(search, fromIndex) { | ||
'lastIndexOf': function(search) { | ||
var fromIndex = arguments[1]; | ||
if(isString(this)) return this.lastIndexOf(search, fromIndex); | ||
@@ -276,4 +281,4 @@ return arrayIndexOf(this, search, fromIndex, -1); | ||
***/ | ||
'forEach': function(fn, scope) { | ||
var length = this.length, index = 0; | ||
'forEach': function(fn) { | ||
var length = this.length, index = 0, scope = arguments[1]; | ||
checkCallback(fn); | ||
@@ -304,4 +309,4 @@ while(index < length) { | ||
***/ | ||
'reduce': function(fn, init) { | ||
return arrayReduce(this, fn, init); | ||
'reduce': function(fn) { | ||
return arrayReduce(this, fn, arguments[1]); | ||
}, | ||
@@ -325,4 +330,4 @@ | ||
***/ | ||
'reduceRight': function(fn, init) { | ||
return arrayReduce(this, fn, init, true); | ||
'reduceRight': function(fn) { | ||
return arrayReduce(this, fn, arguments[1], true); | ||
} | ||
@@ -408,3 +413,3 @@ | ||
'bind': function(scope) { | ||
var fn = this, args = multiArgs(arguments).slice(1), nop, bound; | ||
var fn = this, args = multiArgs(arguments, null, 1), bound; | ||
if(!isFunction(this)) { | ||
@@ -411,0 +416,0 @@ throw new TypeError('Function.prototype.bind called on a non-function'); |
'use strict'; | ||
/*** | ||
@@ -10,21 +12,24 @@ * @package Function | ||
function setDelay(fn, ms, after, scope, args) { | ||
var index; | ||
// Delay of infinity is never called of course... | ||
if(ms === Infinity) return; | ||
if(!fn.timers) fn.timers = []; | ||
if(!isNumber(ms)) ms = 0; | ||
if(!isNumber(ms)) ms = 1; | ||
// This is a workaround for <= IE8, which apparently has the | ||
// ability to call timeouts in the queue on the same tick (ms?) | ||
// even if functionally they have already been cleared. | ||
fn._canceled = false; | ||
fn.timers.push(setTimeout(function(){ | ||
fn.timers.splice(index, 1); | ||
after.apply(scope, args || []); | ||
if(!fn._canceled) { | ||
after.apply(scope, args || []); | ||
} | ||
}, ms)); | ||
index = fn.timers.length; | ||
} | ||
extend(Function, true, false, { | ||
extend(Function, true, true, { | ||
/*** | ||
* @method lazy([ms] = 1, [limit] = Infinity) | ||
* @method lazy([ms] = 1, [immediate] = false, [limit] = Infinity) | ||
* @returns Function | ||
* @short Creates a lazy function that, when called repeatedly, will queue execution and wait [ms] milliseconds to execute again. | ||
* @extra Lazy functions will always execute as many times as they are called up to [limit], after which point subsequent calls will be ignored (if it is set to a finite number). Compare this to %throttle%, which will execute only once per [ms] milliseconds. %lazy% is useful when you need to be sure that every call to a function is executed, but in a non-blocking manner. Calling %cancel% on a lazy function will clear the entire queue. Note that [ms] can also be a fraction. | ||
* @short Creates a lazy function that, when called repeatedly, will queue execution and wait [ms] milliseconds to execute. | ||
* @extra If [immediate] is %true%, first execution will happen immediately, then lock. If [limit] is a fininte number, calls past [limit] will be ignored while execution is locked. Compare this to %throttle%, which will execute only once per [ms] milliseconds. Note that [ms] can also be a fraction. Calling %cancel% on a lazy function will clear the entire queue. For more see @functions. | ||
* @example | ||
@@ -40,7 +45,7 @@ * | ||
* // Executes 50 times, with each execution 20ms later than the last. | ||
* }.lazy(20, 50)); | ||
* }.lazy(20, false, 50)); | ||
* | ||
***/ | ||
'lazy': function(ms, limit) { | ||
var fn = this, queue = [], lock = false, execute, rounded, perExecution, result; | ||
'lazy': function(ms, immediate, limit) { | ||
var fn = this, queue = [], locked = false, execute, rounded, perExecution, result; | ||
ms = ms || 1; | ||
@@ -51,23 +56,31 @@ limit = limit || Infinity; | ||
execute = function() { | ||
if(lock || queue.length == 0) return; | ||
var queueLength = queue.length, maxPerRound; | ||
if(queueLength == 0) return; | ||
// Allow fractions of a millisecond by calling | ||
// multiple times per actual timeout execution | ||
var max = math.max(queue.length - perExecution, 0); | ||
while(queue.length > max) { | ||
maxPerRound = max(queueLength - perExecution, 0); | ||
while(queueLength > maxPerRound) { | ||
// Getting uber-meta here... | ||
result = Function.prototype.apply.apply(fn, queue.shift()); | ||
queueLength--; | ||
} | ||
setDelay(lazy, rounded, function() { | ||
lock = false; | ||
locked = false; | ||
execute(); | ||
}); | ||
lock = true; | ||
} | ||
function lazy() { | ||
// The first call is immediate, so having 1 in the queue | ||
// implies two calls have already taken place. | ||
if(!lock || queue.length < limit - 1) { | ||
// If the execution has locked and it's immediate, then | ||
// allow 1 less in the queue as 1 call has already taken place. | ||
if(queue.length < limit - (locked && immediate ? 1 : 0)) { | ||
queue.push([this, arguments]); | ||
execute(); | ||
} | ||
if(!locked) { | ||
locked = true; | ||
if(immediate) { | ||
execute(); | ||
} else { | ||
setDelay(lazy, rounded, execute); | ||
} | ||
} | ||
// Return the memoized result | ||
@@ -80,25 +93,6 @@ return result; | ||
/*** | ||
* @method delay([ms] = 0, [arg1], ...) | ||
* @method throttle([ms] = 1) | ||
* @returns Function | ||
* @short Executes the function after <ms> milliseconds. | ||
* @extra Returns a reference to itself. %delay% is also a way to execute non-blocking operations that will wait until the CPU is free. Delayed functions can be canceled using the %cancel% method. Can also curry arguments passed in after <ms>. | ||
* @example | ||
* | ||
* (function(arg1) { | ||
* // called 1s later | ||
* }).delay(1000, 'arg1'); | ||
* | ||
***/ | ||
'delay': function(ms) { | ||
var fn = this; | ||
var args = multiArgs(arguments).slice(1); | ||
setDelay(fn, ms, fn, fn, args); | ||
return fn; | ||
}, | ||
/*** | ||
* @method throttle(<ms>) | ||
* @returns Function | ||
* @short Creates a "throttled" version of the function that will only be executed once per <ms> milliseconds. | ||
* @extra This is functionally equivalent to calling %lazy% with a [limit] of %1%. %throttle% is appropriate when you want to make sure a function is only executed at most once for a given duration. Compare this to %lazy%, which will queue rapid calls and execute them later. | ||
* @extra This is functionally equivalent to calling %lazy% with a [limit] of %1% and [immediate] as %true%. %throttle% is appropriate when you want to make sure a function is only executed at most once for a given duration. For more see @functions. | ||
* @example | ||
@@ -112,10 +106,10 @@ * | ||
'throttle': function(ms) { | ||
return this.lazy(ms, 1); | ||
return this.lazy(ms, true, 1); | ||
}, | ||
/*** | ||
* @method debounce(<ms>) | ||
* @method debounce([ms] = 1) | ||
* @returns Function | ||
* @short Creates a "debounced" function that postpones its execution until after <ms> milliseconds have passed. | ||
* @extra This method is useful to execute a function after things have "settled down". A good example of this is when a user tabs quickly through form fields, execution of a heavy operation should happen after a few milliseconds when they have "settled" on a field. | ||
* @extra This method is useful to execute a function after things have "settled down". A good example of this is when a user tabs quickly through form fields, execution of a heavy operation should happen after a few milliseconds when they have "settled" on a field. For more see @functions. | ||
* @example | ||
@@ -138,2 +132,44 @@ * | ||
/*** | ||
* @method delay([ms] = 1, [arg1], ...) | ||
* @returns Function | ||
* @short Executes the function after <ms> milliseconds. | ||
* @extra Returns a reference to itself. %delay% is also a way to execute non-blocking operations that will wait until the CPU is free. Delayed functions can be canceled using the %cancel% method. Can also curry arguments passed in after <ms>. | ||
* @example | ||
* | ||
* (function(arg1) { | ||
* // called 1s later | ||
* }).delay(1000, 'arg1'); | ||
* | ||
***/ | ||
'delay': function(ms) { | ||
var fn = this; | ||
var args = multiArgs(arguments, null, 1); | ||
setDelay(fn, ms, fn, fn, args); | ||
return fn; | ||
}, | ||
/*** | ||
* @method every([ms] = 1, [arg1], ...) | ||
* @returns Function | ||
* @short Executes the function every <ms> milliseconds. | ||
* @extra Returns a reference to itself. Repeating functions with %every% can be canceled using the %cancel% method. Can also curry arguments passed in after <ms>. | ||
* @example | ||
* | ||
* (function(arg1) { | ||
* // called every 1s | ||
* }).every(1000, 'arg1'); | ||
* | ||
***/ | ||
'every': function(ms) { | ||
var fn = this, args = arguments; | ||
args = args.length > 1 ? multiArgs(args, null, 1) : []; | ||
function execute () { | ||
fn.apply(fn, args); | ||
setDelay(fn, ms, execute); | ||
} | ||
setDelay(fn, ms, execute); | ||
return fn; | ||
}, | ||
/*** | ||
* @method cancel() | ||
@@ -151,7 +187,9 @@ * @returns Function | ||
'cancel': function() { | ||
if(isArray(this.timers)) { | ||
while(this.timers.length > 0) { | ||
clearTimeout(this.timers.shift()); | ||
var timers = this.timers, timer; | ||
if(isArray(timers)) { | ||
while(timer = timers.shift()) { | ||
clearTimeout(timer); | ||
} | ||
} | ||
this._canceled = true; | ||
return this; | ||
@@ -206,3 +244,3 @@ }, | ||
'once': function() { | ||
return this.throttle(Infinity); | ||
return this.throttle(Infinity, true); | ||
}, | ||
@@ -209,0 +247,0 @@ |
'use strict'; | ||
/*** | ||
@@ -292,3 +294,2 @@ * | ||
Inflector.irregular('save', 'saves'); | ||
Inflector.irregular('save', 'saves'); | ||
Inflector.irregular('cow', 'kine'); | ||
@@ -301,3 +302,3 @@ Inflector.irregular('goose', 'geese'); | ||
extend(string, true, false, { | ||
extend(string, true, true, { | ||
@@ -304,0 +305,0 @@ /*** |
'use strict'; | ||
/*** | ||
@@ -6,3 +8,3 @@ * | ||
* @dependency string | ||
* @description Normalizing accented characters, character width conversion, Hiragana and Katakana conversions. | ||
* @description Detecting language by character block. Full-width <-> half-width character conversion. Hiragana and Katakana conversions. | ||
* | ||
@@ -17,8 +19,2 @@ ***/ | ||
var NormalizeMap, | ||
NormalizeReg = '', | ||
NormalizeSource; | ||
/*** | ||
@@ -151,3 +147,4 @@ * @method has[Script]() | ||
widthConversionRanges.forEach(function(r) { | ||
getRange(r.start, r.end, function(n) { | ||
simpleRepeat(r.end - r.start + 1, function(n) { | ||
n += r.start; | ||
setWidthConversion(r.type, chr(n), chr(n + r.shift)); | ||
@@ -180,126 +177,5 @@ }); | ||
extend(string, true, true, { | ||
function buildNormalizeMap() { | ||
NormalizeMap = {}; | ||
iterateOverObject(NormalizeSource, function(normalized, str) { | ||
str.split('').forEach(function(character) { | ||
NormalizeMap[character] = normalized; | ||
}); | ||
NormalizeReg += str; | ||
}); | ||
NormalizeReg = regexp('[' + NormalizeReg + ']', 'g'); | ||
} | ||
NormalizeSource = { | ||
'A': 'AⒶAÀÁÂẦẤẪẨÃĀĂẰẮẴẲȦǠÄǞẢÅǺǍȀȂẠẬẶḀĄȺⱯ', | ||
'B': 'BⒷBḂḄḆɃƂƁ', | ||
'C': 'CⒸCĆĈĊČÇḈƇȻꜾ', | ||
'D': 'DⒹDḊĎḌḐḒḎĐƋƊƉꝹ', | ||
'E': 'EⒺEÈÉÊỀẾỄỂẼĒḔḖĔĖËẺĚȄȆẸỆȨḜĘḘḚƐƎ', | ||
'F': 'FⒻFḞƑꝻ', | ||
'G': 'GⒼGǴĜḠĞĠǦĢǤƓꞠꝽꝾ', | ||
'H': 'HⒽHĤḢḦȞḤḨḪĦⱧⱵꞍ', | ||
'I': 'IⒾIÌÍÎĨĪĬİÏḮỈǏȈȊỊĮḬƗ', | ||
'J': 'JⒿJĴɈ', | ||
'K': 'KⓀKḰǨḲĶḴƘⱩꝀꝂꝄꞢ', | ||
'L': 'LⓁLĿĹĽḶḸĻḼḺŁȽⱢⱠꝈꝆꞀ', | ||
'M': 'MⓂMḾṀṂⱮƜ', | ||
'N': 'NⓃNǸŃÑṄŇṆŅṊṈȠƝꞐꞤ', | ||
'O': 'OⓄOÒÓÔỒỐỖỔÕṌȬṎŌṐṒŎȮȰÖȪỎŐǑȌȎƠỜỚỠỞỢỌỘǪǬØǾƆƟꝊꝌ', | ||
'P': 'PⓅPṔṖƤⱣꝐꝒꝔ', | ||
'Q': 'QⓆQꝖꝘɊ', | ||
'R': 'RⓇRŔṘŘȐȒṚṜŖṞɌⱤꝚꞦꞂ', | ||
'S': 'SⓈSẞŚṤŜṠŠṦṢṨȘŞⱾꞨꞄ', | ||
'T': 'TⓉTṪŤṬȚŢṰṮŦƬƮȾꞆ', | ||
'U': 'UⓊUÙÚÛŨṸŪṺŬÜǛǗǕǙỦŮŰǓȔȖƯỪỨỮỬỰỤṲŲṶṴɄ', | ||
'V': 'VⓋVṼṾƲꝞɅ', | ||
'W': 'WⓌWẀẂŴẆẄẈⱲ', | ||
'X': 'XⓍXẊẌ', | ||
'Y': 'YⓎYỲÝŶỸȲẎŸỶỴƳɎỾ', | ||
'Z': 'ZⓏZŹẐŻŽẒẔƵȤⱿⱫꝢ', | ||
'a': 'aⓐaẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁąⱥɐ', | ||
'b': 'bⓑbḃḅḇƀƃɓ', | ||
'c': 'cⓒcćĉċčçḉƈȼꜿↄ', | ||
'd': 'dⓓdḋďḍḑḓḏđƌɖɗꝺ', | ||
'e': 'eⓔeèéêềếễểẽēḕḗĕėëẻěȅȇẹệȩḝęḙḛɇɛǝ', | ||
'f': 'fⓕfḟƒꝼ', | ||
'g': 'gⓖgǵĝḡğġǧģǥɠꞡᵹꝿ', | ||
'h': 'hⓗhĥḣḧȟḥḩḫẖħⱨⱶɥ', | ||
'i': 'iⓘiìíîĩīĭïḯỉǐȉȋịįḭɨı', | ||
'j': 'jⓙjĵǰɉ', | ||
'k': 'kⓚkḱǩḳķḵƙⱪꝁꝃꝅꞣ', | ||
'l': 'lⓛlŀĺľḷḹļḽḻſłƚɫⱡꝉꞁꝇ', | ||
'm': 'mⓜmḿṁṃɱɯ', | ||
'n': 'nⓝnǹńñṅňṇņṋṉƞɲʼnꞑꞥ', | ||
'o': 'oⓞoòóôồốỗổõṍȭṏōṑṓŏȯȱöȫỏőǒȍȏơờớỡởợọộǫǭøǿɔꝋꝍɵ', | ||
'p': 'pⓟpṕṗƥᵽꝑꝓꝕ', | ||
'q': 'qⓠqɋꝗꝙ', | ||
'r': 'rⓡrŕṙřȑȓṛṝŗṟɍɽꝛꞧꞃ', | ||
's': 'sⓢsśṥŝṡšṧṣṩșşȿꞩꞅẛ', | ||
't': 'tⓣtṫẗťṭțţṱṯŧƭʈⱦꞇ', | ||
'u': 'uⓤuùúûũṹūṻŭüǜǘǖǚủůűǔȕȗưừứữửựụṳųṷṵʉ', | ||
'v': 'vⓥvṽṿʋꝟʌ', | ||
'w': 'wⓦwẁẃŵẇẅẘẉⱳ', | ||
'x': 'xⓧxẋẍ', | ||
'y': 'yⓨyỳýŷỹȳẏÿỷẙỵƴɏỿ', | ||
'z': 'zⓩzźẑżžẓẕƶȥɀⱬꝣ', | ||
'AA': 'Ꜳ', | ||
'AE': 'ÆǼǢ', | ||
'AO': 'Ꜵ', | ||
'AU': 'Ꜷ', | ||
'AV': 'ꜸꜺ', | ||
'AY': 'Ꜽ', | ||
'DZ': 'DZDŽ', | ||
'Dz': 'DzDž', | ||
'LJ': 'LJ', | ||
'Lj': 'Lj', | ||
'NJ': 'NJ', | ||
'Nj': 'Nj', | ||
'OI': 'Ƣ', | ||
'OO': 'Ꝏ', | ||
'OU': 'Ȣ', | ||
'TZ': 'Ꜩ', | ||
'VY': 'Ꝡ', | ||
'aa': 'ꜳ', | ||
'ae': 'æǽǣ', | ||
'ao': 'ꜵ', | ||
'au': 'ꜷ', | ||
'av': 'ꜹꜻ', | ||
'ay': 'ꜽ', | ||
'dz': 'dzdž', | ||
'hv': 'ƕ', | ||
'lj': 'lj', | ||
'nj': 'nj', | ||
'oi': 'ƣ', | ||
'ou': 'ȣ', | ||
'oo': 'ꝏ', | ||
'ss': 'ß', | ||
'tz': 'ꜩ', | ||
'vy': 'ꝡ' | ||
}; | ||
extend(string, true, false, { | ||
/*** | ||
* @method normalize() | ||
* @returns String | ||
* @short Returns the string with accented and non-standard Latin-based characters converted into ASCII approximate equivalents. | ||
* @example | ||
* | ||
* 'á'.normalize() -> 'a' | ||
* 'Ménage à trois'.normalize() -> 'Menage a trois' | ||
* 'Volkswagen'.normalize() -> 'Volkswagen' | ||
* 'FULLWIDTH'.normalize() -> 'FULLWIDTH' | ||
* | ||
***/ | ||
'normalize': function() { | ||
if(!NormalizeMap) { | ||
buildNormalizeMap(); | ||
} | ||
return this.replace(NormalizeReg, function(character) { | ||
return NormalizeMap[character]; | ||
}); | ||
}, | ||
/*** | ||
* @method hankaku([mode] = 'all') | ||
@@ -306,0 +182,0 @@ * @returns String |
@@ -49,3 +49,3 @@ /* | ||
'numbers': 'uno,dos,tres,cuatro,cinco,seis,siete,ocho,nueve,diez', | ||
'tokens': 'el,de', | ||
'tokens': 'el,la,de', | ||
'short':'{d} {month} {yyyy}', | ||
@@ -55,3 +55,3 @@ 'long': '{d} {month} {yyyy} {H}:{mm}', | ||
'past': '{sign} {num} {unit}', | ||
'future': '{num} {unit} {sign}', | ||
'future': '{sign} {num} {unit}', | ||
'duration': '{num} {unit}', | ||
@@ -66,3 +66,3 @@ 'timeMarker': 'a las', | ||
{ 'name': 'sign', 'src': 'hace', 'value': -1 }, | ||
{ 'name': 'sign', 'src': 'de ahora', 'value': 1 }, | ||
{ 'name': 'sign', 'src': 'dentro de', 'value': 1 }, | ||
{ 'name': 'shift', 'src': 'pasad:o|a', 'value': -1 }, | ||
@@ -74,4 +74,4 @@ { 'name': 'shift', 'src': 'próximo|próxima|proximo|proxima', 'value': 1 } | ||
'{num} {unit} {sign}', | ||
'{0?} {unit=5-7} {shift}', | ||
'{0?} {shift} {unit=5-7}' | ||
'{0?}{1?} {unit=5-7} {shift}', | ||
'{0?}{1?} {shift} {unit=5-7}' | ||
], | ||
@@ -81,4 +81,4 @@ 'timeParse': [ | ||
'{weekday} {shift}', | ||
'{date?} {1?} {month} {1?} {year?}' | ||
'{date?} {2?} {month} {2?} {year?}' | ||
] | ||
}); |
@@ -49,3 +49,3 @@ /* | ||
'numbers': 'un:|e,deux,trois,quatre,cinq,six,sept,huit,neuf,dix', | ||
'tokens': ["l'|la|le"], | ||
'tokens': "l'|la|le", | ||
'short':'{d} {month} {yyyy}', | ||
@@ -52,0 +52,0 @@ 'long': '{d} {month} {yyyy} {H}:{mm}', |
@@ -53,3 +53,3 @@ /* | ||
'relative': function(num, unit, ms, format) { | ||
var numberWithUnit, last = num.toString().slice(-1); | ||
var numberWithUnit, last = num.toString().slice(-1), mult; | ||
switch(true) { | ||
@@ -56,0 +56,0 @@ case num >= 11 && num <= 15: mult = 3; break; |
'use strict'; | ||
/*** | ||
@@ -19,15 +21,15 @@ * @package Number | ||
} | ||
i = math.max(math.min((significant / 3).floor(), limit === false ? str.length : limit), -mid); | ||
i = max(min(floor(significant / 3), limit === false ? str.length : limit), -mid); | ||
unit = str.charAt(i + mid - 1); | ||
if(significant < -9) { | ||
i = -3; | ||
roundTo = significant.abs() - 9; | ||
roundTo = abs(significant) - 9; | ||
unit = str.slice(0,1); | ||
} | ||
divisor = bytes ? (2).pow(10 * i) : (10).pow(i * 3); | ||
return (num / divisor).round(roundTo || 0).format() + unit.trim(); | ||
divisor = bytes ? pow(2, 10 * i) : pow(10, i * 3); | ||
return withPrecision(num / divisor, roundTo || 0).format() + unit.trim(); | ||
} | ||
extend(number, false, false, { | ||
extend(number, false, true, { | ||
@@ -47,7 +49,7 @@ /*** | ||
'random': function(n1, n2) { | ||
var min, max; | ||
var minNum, maxNum; | ||
if(arguments.length == 1) n2 = n1, n1 = 0; | ||
min = math.min(n1 || 0, isUndefined(n2) ? 1 : n2); | ||
max = math.max(n1 || 0, isUndefined(n2) ? 1 : n2) + 1; | ||
return floor((math.random() * (max - min)) + min); | ||
minNum = min(n1 || 0, isUndefined(n2) ? 1 : n2); | ||
maxNum = max(n1 || 0, isUndefined(n2) ? 1 : n2) + 1; | ||
return floor((math.random() * (maxNum - minNum)) + minNum); | ||
} | ||
@@ -57,3 +59,3 @@ | ||
extend(number, true, false, { | ||
extend(number, true, true, { | ||
@@ -207,3 +209,3 @@ /*** | ||
} | ||
str = (isNumber(place) ? round(this, place || 0).toFixed(math.max(place, 0)) : this.toString()).replace(/^-/, ''); | ||
str = (isNumber(place) ? withPrecision(this, place || 0).toFixed(max(place, 0)) : this.toString()).replace(/^-/, ''); | ||
split = str.split('.'); | ||
@@ -216,6 +218,6 @@ integer = split[0]; | ||
} | ||
result = integer.slice(math.max(0, i - 3), i) + result; | ||
result = integer.slice(max(0, i - 3), i) + result; | ||
} | ||
if(fraction) { | ||
result += decimal + repeatString((place || 0) - fraction.length, '0') + fraction; | ||
result += decimal + repeatString('0', (place || 0) - fraction.length) + fraction; | ||
} | ||
@@ -242,38 +244,2 @@ return (this < 0 ? '-' : '') + result; | ||
/*** | ||
* @method upto(<num>, [fn], [step] = 1) | ||
* @returns Array | ||
* @short Returns an array containing numbers from the number up to <num>. | ||
* @extra Optionally calls [fn] callback for each number in that array. [step] allows multiples greater than 1. | ||
* @example | ||
* | ||
* (2).upto(6) -> [2, 3, 4, 5, 6] | ||
* (2).upto(6, function(n) { | ||
* // This function is called 5 times receiving n as the value. | ||
* }); | ||
* (2).upto(8, null, 2) -> [2, 4, 6, 8] | ||
* | ||
***/ | ||
'upto': function(num, fn, step) { | ||
return getRange(this, num, fn, step || 1); | ||
}, | ||
/*** | ||
* @method downto(<num>, [fn], [step] = 1) | ||
* @returns Array | ||
* @short Returns an array containing numbers from the number down to <num>. | ||
* @extra Optionally calls [fn] callback for each number in that array. [step] allows multiples greater than 1. | ||
* @example | ||
* | ||
* (8).downto(3) -> [8, 7, 6, 5, 4, 3] | ||
* (8).downto(3, function(n) { | ||
* // This function is called 6 times receiving n as the value. | ||
* }); | ||
* (8).downto(2, null, 2) -> [8, 6, 4, 2] | ||
* | ||
***/ | ||
'downto': function(num, fn, step) { | ||
return getRange(this, num, fn, -(step || 1)); | ||
}, | ||
/*** | ||
* @method times(<fn>) | ||
@@ -340,3 +306,3 @@ * @returns Number | ||
'ordinalize': function() { | ||
var suffix, num = this.abs(), last = parseInt(num.toString().slice(-2)); | ||
var suffix, num = abs(this), last = parseInt(num.toString().slice(-2)); | ||
return this + getOrdinalizedSuffix(last); | ||
@@ -422,8 +388,13 @@ }, | ||
function buildNumber() { | ||
extendSimilar(number, true, false, 'round,floor,ceil', function(methods, name) { | ||
methods[name] = function(precision) { | ||
return round(this, precision, name); | ||
function createRoundingFunction(fn) { | ||
return function (precision) { | ||
return precision ? withPrecision(this, precision, fn) : fn(this); | ||
} | ||
} | ||
extend(number, true, true, { | ||
'ceil': createRoundingFunction(ceil), | ||
'round': createRoundingFunction(round), | ||
'floor': createRoundingFunction(floor) | ||
}); | ||
extendSimilar(number, true, false, 'abs,pow,sin,asin,cos,acos,tan,atan,exp,pow,sqrt', function(methods, name) { | ||
extendSimilar(number, true, true, 'abs,pow,sin,asin,cos,acos,tan,atan,exp,pow,sqrt', function(methods, name) { | ||
methods[name] = function(a, b) { | ||
@@ -430,0 +401,0 @@ return math[name](this, a, b); |
@@ -0,1 +1,4 @@ | ||
'use strict'; | ||
/*** | ||
@@ -14,5 +17,5 @@ * @package Object | ||
function setParamsObject(obj, param, value, deep) { | ||
function setParamsObject(obj, param, value, castBoolean) { | ||
var reg = /^(.+?)(\[.*\])$/, paramIsArray, match, allKeys, key; | ||
if(deep !== false && (match = param.match(reg))) { | ||
if(match = param.match(reg)) { | ||
key = match[1]; | ||
@@ -30,8 +33,6 @@ allKeys = match[2].replace(/^\[|\]$/g, '').split(']['); | ||
if(!key && paramIsArray) key = obj.length.toString(); | ||
setParamsObject(obj, key, value); | ||
} else if(value.match(/^[+-]?\d+(\.\d+)?$/)) { | ||
obj[param] = parseFloat(value); | ||
} else if(value === 'true') { | ||
setParamsObject(obj, key, value, castBoolean); | ||
} else if(castBoolean && value === 'true') { | ||
obj[param] = true; | ||
} else if(value === 'false') { | ||
} else if(castBoolean && value === 'false') { | ||
obj[param] = false; | ||
@@ -46,3 +47,3 @@ } else { | ||
// If a custom toString exists bail here and use that instead | ||
if(isArray(obj) || (isObject(obj) && obj.toString === internalToString)) { | ||
if(isArray(obj) || (isObjectType(obj) && obj.toString === internalToString)) { | ||
tmp = []; | ||
@@ -71,3 +72,3 @@ iterateOverObject(obj, function(key, value) { | ||
return match.test(key); | ||
} else if(isObjectPrimitive(match)) { | ||
} else if(isObjectType(match)) { | ||
return hasOwnProperty(match, key); | ||
@@ -80,3 +81,3 @@ } else { | ||
function selectFromObject(obj, args, select) { | ||
var result = {}, match; | ||
var match, result = obj instanceof Hash ? new Hash : {}; | ||
iterateOverObject(obj, function(key, value) { | ||
@@ -123,3 +124,3 @@ match = false; | ||
function buildTypeMethods() { | ||
extendSimilar(object, false, false, ClassNames, function(methods, name) { | ||
extendSimilar(object, false, true, ClassNames, function(methods, name) { | ||
var method = 'is' + name; | ||
@@ -148,3 +149,3 @@ ObjectTypeMethods.push(method); | ||
* @short Watches a property of <obj> and runs <fn> when it changes. | ||
* @extra <fn> is passed three arguments: the property <prop>, the old value, and the new value. The return value of [fn] will be set as the new value. This method is useful for things such as validating or cleaning the value when it is set. Warning: this method WILL NOT work in browsers that don't support %Object.defineProperty%. This notably includes IE 8 and below, and Opera. This is the only method in Sugar that is not fully compatible with all browsers. %watch% is available as an instance method on extended objects. | ||
* @extra <fn> is passed three arguments: the property <prop>, the old value, and the new value. The return value of [fn] will be set as the new value. This method is useful for things such as validating or cleaning the value when it is set. Warning: this method WILL NOT work in browsers that don't support %Object.defineProperty% (IE 8 and below). This is the only method in Sugar that is not fully compatible with all browsers. %watch% is available as an instance method on extended objects. | ||
* @example | ||
@@ -176,3 +177,3 @@ * | ||
extend(object, false, function(arg1, arg2) { return isFunction(arg2); }, { | ||
extend(object, false, function() { return arguments.length > 1; }, { | ||
@@ -203,6 +204,6 @@ /*** | ||
extend(object, false, false, { | ||
extend(object, false, true, { | ||
'isObject': function(obj) { | ||
return isObject(obj); | ||
return isPlainObject(obj); | ||
}, | ||
@@ -263,13 +264,14 @@ | ||
'merge': function(target, source, deep, resolve) { | ||
var key, val; | ||
var key, val, goDeep; | ||
// Strings cannot be reliably merged thanks to | ||
// their properties not being enumerable in < IE8. | ||
if(target && typeof source != 'string') { | ||
if(target && typeof source !== 'string') { | ||
for(key in source) { | ||
if(!hasOwnProperty(source, key) || !target) continue; | ||
val = source[key]; | ||
val = source[key]; | ||
goDeep = deep && isObjectType(val); | ||
// Conflict! | ||
if(isDefined(target[key])) { | ||
// Do not merge. | ||
if(resolve === false) { | ||
if(resolve === false && !goDeep) { | ||
continue; | ||
@@ -283,3 +285,3 @@ } | ||
// Deep merging. | ||
if(deep === true && val && isObjectPrimitive(val)) { | ||
if(goDeep) { | ||
if(isDate(val)) { | ||
@@ -337,12 +339,20 @@ val = new date(val.getTime()); | ||
'clone': function(obj, deep) { | ||
var target; | ||
// Preserve internal UTC flag when applicable. | ||
if(isDate(obj) && obj.clone) { | ||
var target, klass; | ||
if(!isObjectType(obj)) { | ||
return obj; | ||
} | ||
klass = className(obj); | ||
if(isDate(obj, klass) && obj.clone) { | ||
// Preserve internal UTC flag when applicable. | ||
return obj.clone(); | ||
} else if(!isObjectPrimitive(obj)) { | ||
return obj; | ||
} else if (obj instanceof Hash) { | ||
} else if(isDate(obj, klass) || isRegExp(obj, klass)) { | ||
return new obj.constructor(obj); | ||
} else if(obj instanceof Hash) { | ||
target = new Hash; | ||
} else if(isArray(obj, klass)) { | ||
target = []; | ||
} else if(isPlainObject(obj, klass)) { | ||
target = {}; | ||
} else { | ||
target = new obj.constructor; | ||
throw new TypeError('Clone must be a basic data type.'); | ||
} | ||
@@ -353,13 +363,14 @@ return object.merge(target, obj, deep); | ||
/*** | ||
* @method Object.fromQueryString(<str>, [deep] = true) | ||
* @method Object.fromQueryString(<str>, [booleans] = false) | ||
* @returns Object | ||
* @short Converts the query string of a URL into an object. | ||
* @extra If [deep] is %false%, conversion will only accept shallow params (ie. no object or arrays with %[]% syntax) as these are not universally supported. | ||
* @extra If [booleans] is true, then %"true"% and %"false"% will be cast into booleans. All other values, including numbers will remain their string values. | ||
* @example | ||
* | ||
* Object.fromQueryString('foo=bar&broken=wear') -> { foo: 'bar', broken: 'wear' } | ||
* Object.fromQueryString('foo[]=1&foo[]=2') -> { foo: [1,2] } | ||
* Object.fromQueryString('foo[]=1&foo[]=2') -> { foo: ['1','2'] } | ||
* Object.fromQueryString('foo=true', true) -> { foo: true } | ||
* | ||
***/ | ||
'fromQueryString': function(str, deep) { | ||
'fromQueryString': function(str, castBoolean) { | ||
var result = object.extended(), split; | ||
@@ -370,3 +381,3 @@ str = str && str.toString ? str.toString() : ''; | ||
if(split.length !== 2) return; | ||
setParamsObject(result, split[0], decodeURIComponent(split[1]), deep); | ||
setParamsObject(result, split[0], decodeURIComponent(split[1]), castBoolean); | ||
}); | ||
@@ -475,2 +486,1 @@ return result; | ||
buildObjectInstanceMethods(ObjectHashMethods, Hash); | ||
'use strict'; | ||
/*** | ||
@@ -13,3 +15,3 @@ * @package RegExp | ||
extend(regexp, false, false, { | ||
extend(regexp, false, true, { | ||
@@ -33,3 +35,3 @@ /*** | ||
extend(regexp, true, false, { | ||
extend(regexp, true, true, { | ||
@@ -36,0 +38,0 @@ /*** |
'use strict'; | ||
/*** | ||
@@ -17,22 +19,65 @@ * @package String | ||
function padString(str, p, left, right) { | ||
var padding = string(p); | ||
if(padding != p) { | ||
padding = ''; | ||
function checkRepeatRange(num) { | ||
num = +num; | ||
if(num < 0 || num === Infinity) { | ||
throw new RangeError('Invalid number'); | ||
} | ||
if(!isNumber(left)) left = 1; | ||
if(!isNumber(right)) right = 1; | ||
return padding.repeat(left) + str + padding.repeat(right); | ||
return num; | ||
} | ||
function chr(num) { | ||
return string.fromCharCode(num); | ||
function padString(num, padding) { | ||
return repeatString(isDefined(padding) ? padding : ' ', num); | ||
} | ||
function truncateString(str, length, from, ellipsis, split) { | ||
var str1, str2, len1, len2; | ||
if(str.length <= length) { | ||
return str.toString(); | ||
} | ||
ellipsis = isUndefined(ellipsis) ? '...' : ellipsis; | ||
switch(from) { | ||
case 'left': | ||
str2 = split ? truncateOnWord(str, length, true) : str.slice(str.length - length); | ||
return ellipsis + str2; | ||
case 'middle': | ||
len1 = ceil(length / 2); | ||
len2 = floor(length / 2); | ||
str1 = split ? truncateOnWord(str, len1) : str.slice(0, len1); | ||
str2 = split ? truncateOnWord(str, len2, true) : str.slice(str.length - len2); | ||
return str1 + ellipsis + str2; | ||
default: | ||
str1 = split ? truncateOnWord(str, length) : str.slice(0, length); | ||
return str1 + ellipsis; | ||
} | ||
} | ||
function truncateOnWord(str, limit, fromLeft) { | ||
if(fromLeft) { | ||
return truncateOnWord(str.reverse(), limit).reverse(); | ||
} | ||
var reg = regexp('(?=[' + getTrimmableCharacters() + '])'); | ||
var words = str.split(reg); | ||
var count = 0; | ||
return words.filter(function(word) { | ||
count += word.length; | ||
return count <= limit; | ||
}).join(''); | ||
} | ||
function numberOrIndex(str, n, from) { | ||
if(isString(n)) { | ||
n = str.indexOf(n); | ||
if(n === -1) { | ||
n = from ? str.length : 0; | ||
} | ||
} | ||
return n; | ||
} | ||
var btoa, atob; | ||
function buildBase64(key) { | ||
if(this.btoa) { | ||
btoa = this.btoa; | ||
atob = this.atob; | ||
if(globalContext.btoa) { | ||
btoa = globalContext.btoa; | ||
atob = globalContext.atob; | ||
return; | ||
@@ -113,4 +158,4 @@ } | ||
***/ | ||
'startsWith': function(reg, pos, c) { | ||
var str = this, source; | ||
'startsWith': function(reg) { | ||
var args = arguments, pos = args[1], c = args[2], str = this, source; | ||
if(pos) str = str.slice(pos); | ||
@@ -136,4 +181,4 @@ if(isUndefined(c)) c = true; | ||
***/ | ||
'endsWith': function(reg, pos, c) { | ||
var str = this, source; | ||
'endsWith': function(reg) { | ||
var args = arguments, pos = args[1], c = args[2], str = this, source; | ||
if(isDefined(pos)) str = str.slice(0, pos); | ||
@@ -148,3 +193,3 @@ if(isUndefined(c)) c = true; | ||
extend(string, true, false, { | ||
extend(string, true, true, { | ||
@@ -238,3 +283,3 @@ /*** | ||
* @short Encodes the string into base64 encoding. | ||
* @extra This method wraps the browser native %btoa% when available, and uses a custom implementation when not available. | ||
* @extra This method wraps the browser native %btoa% when available, and uses a custom implementation when not available. It can also handle Unicode string encodings. | ||
* @example | ||
@@ -247,3 +292,3 @@ * | ||
'encodeBase64': function() { | ||
return btoa(this); | ||
return btoa(unescape(encodeURIComponent(this))); | ||
}, | ||
@@ -255,3 +300,3 @@ | ||
* @short Decodes the string from base64 encoding. | ||
* @extra This method wraps the browser native %atob% when available, and uses a custom implementation when not available. | ||
* @extra This method wraps the browser native %atob% when available, and uses a custom implementation when not available. It can also handle Unicode string encodings. | ||
* @example | ||
@@ -264,3 +309,3 @@ * | ||
'decodeBase64': function() { | ||
return atob(this); | ||
return decodeURIComponent(escape(atob(this))); | ||
}, | ||
@@ -525,3 +570,3 @@ | ||
'at': function() { | ||
return entryAtIndex(this, arguments, true); | ||
return getEntriesForIndexes(this, arguments, true); | ||
}, | ||
@@ -539,4 +584,4 @@ | ||
***/ | ||
'from': function(num) { | ||
return this.slice(num); | ||
'from': function(from) { | ||
return this.slice(numberOrIndex(this, from, true)); | ||
}, | ||
@@ -554,5 +599,5 @@ | ||
***/ | ||
'to': function(num) { | ||
if(isUndefined(num)) num = this.length; | ||
return this.slice(0, num); | ||
'to': function(to) { | ||
if(isUndefined(to)) to = this.length; | ||
return this.slice(0, numberOrIndex(this, to)); | ||
}, | ||
@@ -670,55 +715,37 @@ | ||
/*** | ||
* @method truncate(<length>, [split] = true, [from] = 'right', [ellipsis] = '...') | ||
* @method truncate(<length>, [from] = 'right', [ellipsis] = '...') | ||
* @returns String | ||
* @short Truncates a string. | ||
* @extra If [split] is %false%, will not split words up, and instead discard the word where the truncation occurred. [from] can also be %"middle"% or %"left"%. | ||
* @extra [from] can be %'right'%, %'left'%, or %'middle'%. If the string is shorter than <length>, [ellipsis] will not be added. | ||
* @example | ||
* | ||
* 'just sittin on the dock of the bay'.truncate(20) -> 'just sittin on the do...' | ||
* 'just sittin on the dock of the bay'.truncate(20, false) -> 'just sittin on the...' | ||
* 'just sittin on the dock of the bay'.truncate(20, true, 'middle') -> 'just sitt...of the bay' | ||
* 'just sittin on the dock of the bay'.truncate(20, true, 'left') -> '...the dock of the bay' | ||
* 'sittin on the dock of the bay'.truncate(18) -> 'just sittin on the do...' | ||
* 'sittin on the dock of the bay'.truncate(18, 'left') -> '...the dock of the bay' | ||
* 'sittin on the dock of the bay'.truncate(18, 'middle') -> 'just sitt...of the bay' | ||
* | ||
***/ | ||
'truncate': function(length, split, from, ellipsis) { | ||
var pos, | ||
prepend = '', | ||
append = '', | ||
str = this.toString(), | ||
chars = '[' + getTrimmableCharacters() + ']+', | ||
space = '[^' + getTrimmableCharacters() + ']*', | ||
reg = regexp(chars + space + '$'); | ||
ellipsis = isUndefined(ellipsis) ? '...' : string(ellipsis); | ||
if(str.length <= length) { | ||
return str; | ||
} | ||
switch(from) { | ||
case 'left': | ||
pos = str.length - length; | ||
prepend = ellipsis; | ||
str = str.slice(pos); | ||
reg = regexp('^' + space + chars); | ||
break; | ||
case 'middle': | ||
pos = floor(length / 2); | ||
append = ellipsis + str.slice(str.length - pos).trimLeft(); | ||
str = str.slice(0, pos); | ||
break; | ||
default: | ||
pos = length; | ||
append = ellipsis; | ||
str = str.slice(0, pos); | ||
} | ||
if(split === false && this.slice(pos, pos + 1).match(/\S/)) { | ||
str = str.remove(reg); | ||
} | ||
return prepend + str + append; | ||
'truncate': function(length, from, ellipsis) { | ||
return truncateString(this, length, from, ellipsis); | ||
}, | ||
/*** | ||
* @method pad[Side](<padding> = '', [num] = 1) | ||
* @method truncateOnWord(<length>, [from] = 'right', [ellipsis] = '...') | ||
* @returns String | ||
* @short Pads either/both sides of the string. | ||
* @extra [num] is the number of characters on each side, and [padding] is the character to pad with. | ||
* @short Truncates a string without splitting up words. | ||
* @extra [from] can be %'right'%, %'left'%, or %'middle'%. If the string is shorter than <length>, [ellipsis] will not be added. | ||
* @example | ||
* | ||
* 'here we go'.truncateOnWord(5) -> 'here...' | ||
* 'here we go'.truncateOnWord(5, 'left') -> '...we go' | ||
* | ||
***/ | ||
'truncateOnWord': function(length, from, ellipsis) { | ||
return truncateString(this, length, from, ellipsis, true); | ||
}, | ||
/*** | ||
* @method pad[Side](<num> = null, [padding] = ' ') | ||
* @returns String | ||
* @short Pads the string out with [padding] to be exactly <num> characters. | ||
* | ||
* @set | ||
@@ -731,18 +758,25 @@ * pad | ||
* | ||
* 'wasabi'.pad('-') -> '-wasabi-' | ||
* 'wasabi'.pad('-', 2) -> '--wasabi--' | ||
* 'wasabi'.padLeft('-', 2) -> '--wasabi' | ||
* 'wasabi'.padRight('-', 2) -> 'wasabi--' | ||
* 'wasabi'.pad(8) -> ' wasabi ' | ||
* 'wasabi'.padLeft(8) -> ' wasabi' | ||
* 'wasabi'.padRight(8) -> 'wasabi ' | ||
* 'wasabi'.padRight(8, '-') -> 'wasabi--' | ||
* | ||
***/ | ||
'pad': function(padding, num) { | ||
return repeatString(num, padding) + this + repeatString(num, padding); | ||
'pad': function(num, padding) { | ||
var half, front, back; | ||
num = checkRepeatRange(num); | ||
half = max(0, num - this.length) / 2; | ||
front = floor(half); | ||
back = ceil(half); | ||
return padString(front, padding) + this + padString(back, padding); | ||
}, | ||
'padLeft': function(padding, num) { | ||
return repeatString(num, padding) + this; | ||
'padLeft': function(num, padding) { | ||
num = checkRepeatRange(num); | ||
return padString(max(0, num - this.length), padding) + this; | ||
}, | ||
'padRight': function(padding, num) { | ||
return this + repeatString(num, padding); | ||
'padRight': function(num, padding) { | ||
num = checkRepeatRange(num); | ||
return this + padString(max(0, num - this.length), padding); | ||
}, | ||
@@ -793,13 +827,4 @@ | ||
'repeat': function(num) { | ||
var result = '', str = this; | ||
if(!isNumber(num) || num < 1) return ''; | ||
while (num) { | ||
if (num & 1) { | ||
result += str; | ||
} | ||
if (num >>= 1) { | ||
str += str; | ||
} | ||
} | ||
return result; | ||
num = checkRepeatRange(num); | ||
return repeatString(this, num); | ||
}, | ||
@@ -821,4 +846,3 @@ | ||
'toNumber': function(base) { | ||
var str = this.replace(/,/g, ''); | ||
return str.match(/\./) ? parseFloat(str) : parseInt(str, base || 10); | ||
return stringToNumber(this, base); | ||
}, | ||
@@ -829,3 +853,3 @@ | ||
* @returns String | ||
* @short Capitalizes the first character in the string. | ||
* @short Capitalizes the first character in the string and downcases all other letters. | ||
* @extra If [all] is true, all words in the string will be capitalized. | ||
@@ -842,3 +866,3 @@ * @example | ||
var lastResponded; | ||
return this.toLowerCase().replace(all ? /[\s\S]/g : /^\S/, function(lower) { | ||
return this.toLowerCase().replace(all ? /[^']/g : /^\S/, function(lower) { | ||
var upper = lower.toUpperCase(), result; | ||
@@ -854,4 +878,4 @@ result = lastResponded ? lower : upper; | ||
* @returns String | ||
* @short Assigns variables to tokens in a string. | ||
* @extra If an object is passed, it's properties can be assigned using the object's keys. If a non-object (string, number, etc.) is passed it can be accessed by the argument number beginning with 1 (as with regex tokens). Multiple objects can be passed and will be merged together (original objects are unaffected). | ||
* @short Assigns variables to tokens in a string, demarcated with `{}`. | ||
* @extra If an object is passed, it's properties can be assigned using the object's keys (i.e. {name}). If a non-object (string, number, etc.) is passed it can be accessed by the argument number beginning with {1} (as with regex tokens). Multiple objects can be passed and will be merged together (original objects are unaffected). | ||
* @example | ||
@@ -866,4 +890,4 @@ * | ||
var assign = {}; | ||
multiArgs(arguments, function(a, i) { | ||
if(isObject(a)) { | ||
flattenedArgs(arguments, function(a, i) { | ||
if(isObjectType(a)) { | ||
simpleMerge(assign, a); | ||
@@ -884,3 +908,3 @@ } else { | ||
extend(string, true, false, { | ||
extend(string, true, true, { | ||
@@ -887,0 +911,0 @@ /*** |
{ | ||
"name": "sugar", | ||
"version": "1.3.9", | ||
"version": "1.4.0", | ||
"description": "A Javascript library for working with native objects.", | ||
@@ -12,3 +12,3 @@ "keywords": ["functional", "utility", "ender"], | ||
"engines" : {"node" : ">= 0.4.0"}, | ||
"scripts": {"test": "./unit_tests/node.sh"} | ||
"scripts": {"test": "node test/environments/node/test.js"} | ||
} |
@@ -1,3 +0,2 @@ | ||
Sugar | ||
===== | ||
# Sugar | ||
@@ -10,5 +9,11 @@ [![Build Status](https://secure.travis-ci.org/andrewplummer/Sugar.png)](http://travis-ci.org/andrewplummer/Sugar) | ||
Edge Build | ||
=============== | ||
## Upgrading | ||
If you are upgrading from an older version, please have a look at `CAUTION.md` which is a vetted changelog | ||
that details the severity of what has changed, and (sometimes) strategies for migrating. | ||
Going through this before you upgrade can make the process a lot less painful! | ||
## Edge Build | ||
Public stable releases will be made available on the site and also exist in `release/`. | ||
@@ -19,4 +24,3 @@ Any push made to `master` branch *should* have its unit tests passing, although maybe not | ||
Custom Builds | ||
=============== | ||
## Custom Builds | ||
@@ -36,12 +40,10 @@ Sugar now allows custom builds that let you opt in or out packages. This can be done [here](http://sugarjs.com/customize). | ||
Unit Tests Node | ||
=============== | ||
## Unit Tests Node | ||
Unit tests can be run through the shell script at `./unit_tests/node.sh` | ||
Use the `npm test` command to run unit tests. | ||
Date Localizations | ||
================== | ||
## Date Localizations | ||
Sugar includes 11 localizations in the main package: | ||
Sugar has the following localizations available: | ||
@@ -54,4 +56,9 @@ - English (en) | ||
- Russian (ru) | ||
- Finnish (fi) | ||
- Swedish (sv) | ||
- Danish (da) | ||
- Dutch (nl) | ||
- Polish (pl) | ||
- Portuguese (pt) | ||
- Korean (ko) | ||
- Portuguese (pt) | ||
- Japanese (ja) | ||
@@ -62,2 +69,3 @@ - Simplified Chinese (zh-CN) | ||
These files can be added separately or built into the main package on the [customize page](http://sugarjs.com/customize). | ||
In addition to these major locales, custom locales can be added using: | ||
@@ -72,19 +80,23 @@ | ||
Contributing Locales | ||
==================== | ||
If you do add a custom format for your locale, please consider forking and adding it to the repo! This especially includes the addition of new locales, but also new formats or tweaks to existing locales. Not everything can be added to the main package, but I would like to have as many languages/formats as possible available. When adding a locale contribution, the most important thing is to add unit tests that assert the correct format. These unit tests are found at `unit_tests/environments/sugar/date_LOCALE.js`. Simply add or adjust the formats for the locale (the more tests, the better!) and issue me a pull request -- I will update the code to add these locales/formats. Have a look at other unit tests files for an example of the unit testing format. | ||
## Timezones | ||
Dealing with timezones in Javascript can be tricky. Although timezones are outside the scope of Sugar, it does provide a hook that can allow timezone shifted dates to be used internally in place of normal ones. See [the date reference](http://sugarjs.com/dates#timezones) for more. | ||
Contributing Lib Comparisons | ||
============================ | ||
Lib comparisons to various other libraries can be seen at http://sugarjs.com/libs. This is one of the areas where contributions are most welcome, as I don't have extensive knowledge of many different libraries, and there is so much to cover. To contribute simply find or create the appropriate lib name in `docs/libs`, and follow the format provided. This will be an ongoing process, and I will push changes here out to the site every so often. | ||
## Contributing Locales | ||
If you do add a custom format for your locale, please consider forking and adding it to the repo! This especially includes the addition of new locales, but also new formats or tweaks to existing locales. Not everything can be added to the main package, but I would like to have as many languages/formats as possible available. When adding a locale contribution, the most important thing is to add unit tests that assert the correct format. These unit tests are found at `test/environments/sugar/date_LOCALE.js`. Simply add or adjust the formats for the locale (the more tests, the better!) and issue me a pull request -- I will update the code to add these locales/formats. Have a look at other unit tests files for an example of the unit testing format. | ||
Other Contributions | ||
=================== | ||
For other contributions, please add well formed unit tests in the Sugar environment at `unit_tests/environments/sugar/MODULE.js`. Unit tests can be run directly in the browser from `unit_tests/sugar.html`, and should all be passing in all major browsers (Webkit,Mozilla,Opera, and IE6+). Node.js unit tests should also be passing and can be run in the console with `unit_tests/node.sh`. | ||
## Contributing Lib Comparisons | ||
Lib comparisons to various other libraries can be seen at http://sugarjs.com/libs. This is one of the areas where contributions are most welcome, as I don't have extensive knowledge of many different libraries, and there is much to cover. To contribute simply find or create the appropriate lib name in `docs/libs`, and follow the format provided. This will be an ongoing process, and I will push changes here out to the site every so often. | ||
## Other Contributions | ||
For other contributions, please add well formed unit tests in the Sugar environment at `test/environments/sugar/MODULE.js`. Unit tests can be run directly in the browser from `test/default.html`, and should all be passing in all major browsers (Webkit,Mozilla,Opera, and IE6+). Node.js unit tests should also be passing and can be run in the console with `npm test`. | ||
Also note that the source code is in the `lib` directory, and `release` is automatically built, so there is no need to changes files there. | ||
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1728015
83
32924
97
1