better Ox.isEqual(), more tests, more documentation

This commit is contained in:
rolux 2011-05-08 14:14:07 +02:00
parent a1ed6a44c5
commit 37219bfbe9
5 changed files with 466 additions and 252 deletions

View file

@ -63,8 +63,8 @@ Ox.load('UI', {
.html(
Ox.repeat(' ', 4) +
'<b>&gt; ' + Ox.encodeHTML(test.statement) + ' </b> ==&gt; ' +
Ox.encodeHTML(test.actual) +
(test.success ? '' : ' !=&gt; ' + Ox.encodeHTML(test.expected))
(test.success ? '' : Ox.encodeHTML(test.actual) + ' !=&gt; ') +
Ox.encodeHTML(test.expected)
)
.hide()
.appendTo($foo);
@ -78,7 +78,10 @@ Ox.load('UI', {
height: '15px',
padding: '4px 8px 4px 8px',
fontFamily: 'Menlo, Monaco, Courier',
fontSize: '12px'
fontSize: '12px',
whiteSpace: 'nowrap',
MozUserSelect: 'text',
WebkitUserSelect: 'text'
});
gradients.forEach(function(gradient) {
$div.css({

View file

@ -254,6 +254,18 @@ Dialog
cursor: se-resize;
}
/*
================================================================================
Documentation
================================================================================
*/
.OxDocPage code {
//border: 1px solid rgb(232, 232, 232);
//background: rgb(248, 248, 248);
white-space: nowrap;
}
/*
================================================================================
Drag & Drop

View file

@ -6,7 +6,7 @@ Ox.DocPage = function(options, self) {
item: {}
})
.options(options || {})
.addClass('OxText')
.addClass('OxDocPage OxText')
.css({
overflow: 'auto'
});
@ -42,8 +42,13 @@ Ox.DocPage = function(options, self) {
if (item[section]) {
if (section == 'description') {
$elements.push($('<div>')
.css({paddingLeft: (level * 32) + 'px'})
.html(item.description)
.css({
paddingTop: (level ? 0 : 8) + 'px',
borderTop: level ? '': '1px solid rgb(192, 192, 192)',
marginTop: (level ? 0 : 8) + 'px',
marginLeft: (level * 32) + 'px',
})
.html(Ox.parseHTML(item.description))
);
} else {
$elements.push($('<div>')
@ -108,7 +113,7 @@ Ox.DocPage = function(options, self) {
.addClass(className)
.css({marginLeft: (level * 32 + 16) + 'px'})
.html(
'<code>' + Ox.parseHTML(example.result) + '</code>'
'<code>' + Ox.encodeHTML(example.result) + '</code>'
)
)
});

View file

@ -39,7 +39,7 @@ Ox.SyntaxHighlighter = function(options, self) {
!(self.options.stripComments && token.type == 'comment')
) {
classNames = 'Ox' + Ox.toTitleCase(token.type);
if (token.type == 'whitespace') {
if (self.options.showWhitespace && token.type == 'whitespace') {
if (isAfterLinebreak() && hasIrregularSpaces()) {
classNames += ' OxLeading'
} else if (isBeforeLinebreak()) {
@ -65,27 +65,15 @@ Ox.SyntaxHighlighter = function(options, self) {
}
});
self.lines = self.source.split('<br/>');
self.lineNumbersWidth = (self.lines.length + self.options.offset - 1).toString().length * 7;
self.sourceCodeWidth = 80 * 7 + (
self.lines.length > 40 ? Ox.UI.SCROLLBAR_SIZE : 0
);
self.height = 40 * 14 + (
Math.max.apply(null, self.lines.map(function(line) {
return line.length;
})) > 80 ? Ox.UI.SCROLLBAR_SIZE : 0
);
that.css({
//width: self.lineNumbersWidth + self.sourceCodeWidth,
//height: self.height
});
self.lineNumbersWidth = (
self.lines.length + self.options.offset - 1
).toString().length * 7;
self.$lineNumbers = new Ox.Element()
.addClass('OxLineNumbers')
.css({
display: 'table-cell',
width: self.lineNumbersWidth + 'px',
//height: (self.lines.length * 14) + 8 + 'px',
padding: '4px',
})
.html(
@ -98,8 +86,6 @@ Ox.SyntaxHighlighter = function(options, self) {
.addClass('OxSourceCode')
.css({
display: 'table-cell',
//width: self.sourceCodeWidth + 'px',
//height: (self.lines.length * 14) + 'px',
padding: '4px'
})
.html(self.source)

View file

@ -2,6 +2,34 @@
// OxJS (c) 2011 Ox2620, dual-licensed GPL/MIT, see http://oxjs.org for details
/*
Some conventions:
Functions
- only one var statement, in the first line of the function
- return only once, from the last line of the function body
Variable names
arg argument
args arguments
arr array
callback callback function
col collection (array, string or object)
date date
fn function
hasFoo boolean
i index (integer key)
isFoo boolean
k key (of a key/value pair)
key key (of a key/value pair)
max maximum value
min minumum value
num number
obj object
re regexp
ret return value
v value (of a key/value pair)
val value (of a key/value pair)
*/
// todo: check http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
// also see https://github.com/tlrobinson/narwhal/blob/master/lib/util.js
@ -164,6 +192,41 @@ Ox.merge = function(arr) {
return arr;
};
/*@
Ox.sort <f> Sorts an array, handling leading digits and ignoring capitalization
> Ox.sort(['10', '9', 'B', 'a'])
['9', '10', 'a', 'B']
@*/
Ox.sort = function(arr) {
var len, matches = {}, sort = {};
// find leading numbers
arr.forEach(function(val, i) {
var match = /^\d+/(val);
matches[val] = match ? match[0] : '';
});
// get length of longest leading number
len = Ox.max(Ox.map(matches, function(val) {
return val.length;
}));
// pad leading numbers, and make lower case
arr.forEach(function(val) {
sort[val] = (
matches[val] ?
Ox.pad(matches[val], len) + val.toString().substr(matches[val].length) :
val
).toLowerCase();
});
return arr.sort(function(a, b) {
var ret = 0;
if (sort[a] < sort[b]) {
ret = -1;
} else if (sort[a] > sort[b]) {
ret = 1;
}
return ret;
});
};
/*@
Ox.unique <f> Returns an array without duplicate values
> Ox.unique([1, 2, 3, 2, 1])
@ -473,7 +536,7 @@ Ox.getset = function(obj, args, callback, context) {
}
/*@
Ox.isEmpty <f> Returns true if an array, object or string is empty
Ox.isEmpty <f> Returns true if a collection is empty
> Ox.isEmpty([])
true
> Ox.isEmpty({})
@ -486,7 +549,7 @@ Ox.isEmpty = function(val) {
};
/*@
Ox.keys <f> Returns the keys of an array, object or string
Ox.keys <f> Returns the keys of a collection
Unlike <code>Object.keys()</code>, <code>Ox.keys()</code> works for arrays,
objects and strings.
> Ox.keys([1, 2, 3])
@ -502,7 +565,6 @@ Ox.keys <f> Returns the keys of an array, object or string
@*/
// fixme: is this really needed? arrays... ok... but strings??
Ox.keys = function(obj) {
var keys = [];
Ox.forEach(obj, function(v, k) {
@ -727,27 +789,20 @@ Ox.range = function() {
return arr;
};
Ox.serialize = function(obj) {
/*
>>> Ox.serialize({a: 1, b: 2, c: 3})
'a=1&b=2&c=3'
*/
var arr = [];
Ox.forEach(obj, function(val, key) {
val !== '' && arr.push(key + '=' + val);
});
return arr.join('&');
};
Ox.setPropertyOnce = function(arr, str) {
/*
>>> Ox.setPropertyOnce([{selected: false}, {selected: false}], 'selected')
/*@
Ox.setPropertyOnce <f> Sets a property once
Given a array of objects, each of which has a property with a boolean
value, this sets exactly one of these to true, and returns the index
of the object whose property is true.
> Ox.setPropertyOnce([{selected: false}, {selected: false}], 'selected')
0
>>> Ox.setPropertyOnce([{selected: false}, {selected: true}], 'selected')
> Ox.setPropertyOnce([{selected: false}, {selected: true}], 'selected')
1
>>> Ox.setPropertyOnce([{selected: true}, {selected: true}], 'selected')
0
*/
> Ox.setPropertyOnce([{selected: true}, {selected: true}], 'selected')
0
@*/
// fixme: strange name, and shouldn't it return the full array?
Ox.setPropertyOnce = function(arr, str) {
var pos = -1;
Ox.forEach(arr, function(v, i) {
if (pos == -1 && arr[i][str]) {
@ -763,74 +818,72 @@ Ox.setPropertyOnce = function(arr, str) {
return pos;
};
Ox.shuffle = function(arr) {
/*
>>> Ox.shuffle([1, 2, 3]).length
/*@
Ox.shuffle <f> Randomizes the order of values within a collection
> Ox.shuffle([1, 2, 3]).length
3
*/
var shuffle = arr;
return shuffle.sort(function() {
> Ox.len(Ox.shuffle({a: 1, b: 2, c: 3}))
3
> Ox.shuffle('123').length
3
@*/
Ox.shuffle = function(col) {
var keys, ret, type = Ox.typeOf(col), values;
function sort() {
return Math.random() - 0.5;
});
}
if (type == 'array') {
ret = col.sort(sort);
} else if (type == 'object') {
keys = Object.keys(col);
values = Ox.values(col).sort(sort);
ret = {};
keys.forEach(function(key, i) {
ret[key] = values[i]
});
} else if (type == 'string') {
ret = col.split('').sort(sort).join('');
}
return ret;
};
/*@
Ox.some <f> Tests if one or more elements of a collection satisfy a given condition
Unlike <code>[].some()</code>, <code>Ox.some()</code> works for arrays,
objects and strings.
> Ox.some([2, 1, 0], function(i, v) { return i == v; })
true
> Ox.some({a: 1, b: 2, c: 3}, function(v) { return v == 1; })
true
> Ox.some("foo", function(v) { return v == 'f'; })
true
@*/
Ox.some = function(obj, fn) {
/*
Ox.some() works for arrays, objects and strings, unlike [].some()
>>> Ox.some([2, 1, 0], function(i, v) { return i == v; })
true
>>> Ox.some({a: 1, b: 2, c: 3}, function(v) { return v == 1; })
true
>>> Ox.some("foo", function(v) { return v == 'f'; })
true
*/
return Ox.filter(Ox.values(obj), fn).length > 0;
};
Ox.sort = function(arr) {
/*
>>> Ox.sort(['10', '9', 'B', 'a'])
['9', '10', 'a', 'B']
*/
var len, matches = {}, sort = {};
// find leading numbers
arr.forEach(function(val, i) {
var match = /^\d+/(val);
matches[val] = match ? match[0] : '';
});
// get length of longest leading number
len = Ox.max(Ox.map(matches, function(val) {
return val.length;
}));
// pad leading numbers, and make lower case
arr.forEach(function(val) {
sort[val] = (
matches[val] ?
Ox.pad(matches[val], len) + val.toString().substr(matches[val].length) :
val
).toLowerCase();
});
return arr.sort(function(a, b) {
var ret = 0;
if (sort[a] < sort[b]) {
ret = -1;
} else if (sort[a] > sort[b]) {
ret = 1;
}
return ret;
});
};
Ox.sum = function(obj) {
/*
>>> Ox.sum([-1, 0, 1])
0
>>> Ox.sum({a: 1, b: 2, c: 3})
/*@
Ox.sum <f> Returns the sum of the values of a collection
> Ox.sum(1, 2, 3)
6
*/
> Ox.sum([1, 2, 3])
6
> Ox.sum({a: 1, b: 2, c: 3})
6
> Ox.sum('123')
6
> Ox.sum('123foo')
6
> Ox.sum('08', -2, 'foo')
6
@*/
Ox.sum = function(col) {
var sum = 0;
Ox.forEach(obj, function(val) {
sum += val;
col = arguments.length > 1 ? Ox.makeArray(arguments) : col;
Ox.forEach(col, function(val) {
val = +val;
sum += Ox.isNumber(val) ? val : 0;
});
return sum;
};
@ -854,43 +907,39 @@ Ox.toArray = function(obj) {
return arr;
};
Ox.unserialize = function(str) {
/*
>>> Ox.unserialize('a=1&b=2&c=3').c
'3'
*/
var arr, obj = {};
Ox.forEach(str.split('&'), function(val) {
arr = val.split('=');
obj[arr[0]] = arr[1];
});
return obj;
};
Ox.values = function(obj) {
/*
>>> Ox.values([1, 2, 3])
/*@
Ox.values <f> Returns the values of a collection
> Ox.values([1, 2, 3])
[1, 2, 3]
>>> Ox.values({a: 1, b: 2, c: 3})
> Ox.values({a: 1, b: 2, c: 3})
[1, 2, 3]
>>> Ox.values('abc')
> Ox.values('abc')
['a', 'b', 'c']
>>> Ox.values([1,])
[1]
*/
// fixme: why doesn't this use map?
> Ox.values([1,,3])
[1, 3]
@*/
Ox.values = function(col) {
// this happens to works for arrays and strings, but still:
// Ox.values(arr) -> arr, Ox.values(str) -> str.split('')
var values = [];
Ox.forEach(obj, function(val) {
Ox.forEach(col, function(val) {
values.push(val);
});
return values;
};
Ox.walk = function(obj, fn) {
/*
>>> a = 0; Ox.walk({a: 1, b: {c: 2, d: 3}}, function(v, k) { a += Ox.isNumber(v) ? v : 0}); a
/*@
Ox.walk <f> Recursively walk a tree-like key/value store
<script>
Ox.test.number = 0;
Ox.walk({a: 1, b: {c: 2, d: 3}}, function (v) {
Ox.test.number += Ox.isNumber(v) ? v : 0;
});
</script>
> Ox.test.number
6
*/
@*/
Ox.walk = function(obj, fn) {
Ox.forEach(obj, function(val, key) {
fn(val, key, obj);
Ox.walk(obj[key], fn);
@ -2788,6 +2837,7 @@ Ox.doc <f> Generates documentation for annotated JavaScript
@*/
Ox.doc = (function() {
// fixme: dont require the trailing '@'
var re = {
item: /^(.+?) <(.+?)> (.+)$/,
multiline: /^\/\*\@.*?\n([\w\W]+)\n.*?\@\*\/$/,
@ -2886,6 +2936,7 @@ Ox.doc = (function() {
};
}
function parseTokens(tokens, includeLeading) {
// fixme: do not strip whitespace from the beginning of the first line of the items' source
var leading = [],
tokens_ = [];
tokens.forEach(function(token) {
@ -3079,7 +3130,7 @@ Ox.test = function(file, callback) {
var actual = eval(example.statement);
if (example.result) {
tests.push({
actual: actual,
actual: JSON.stringify(actual),
expected: example.result,
name: item.name,
section: item.section,
@ -3174,18 +3225,19 @@ Ox.tokenize = (function() {
'shift', 'slice', 'some', 'sort', 'splice',
'unshift',
// Date
'getDate', 'getDay', 'getFullYear', 'getHours', 'getMilliseconds',
'getMinutes', 'getMonth', 'getSeconds', 'getTime', 'getTimezoneOffset',
'getUTCDate', 'getUTCDay', 'getUTCFullYear', 'getUTCHours', 'getUTCMilliseconds',
'getUTCMinutes', 'getUTCMonth', 'getUTCSeconds',
'getDate', 'getDay', 'getFullYear', 'getHours',
'getMilliseconds', 'getMinutes', 'getMonth', 'getSeconds',
'getTime', 'getTimezoneOffset',
'getUTCDate', 'getUTCDay', 'getUTCFullYear', 'getUTCHours',
'getUTCMilliseconds', 'getUTCMinutes', 'getUTCMonth', 'getUTCSeconds',
'now',
'parse',
'setDate', 'setFullYear', 'setHours', 'setMilliseconds', 'setMinutes',
'setMonth', 'setSeconds', 'setTime',
'setUTCDate', 'setUTCFullYear', 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes',
'setUTCMonth', 'setUTCSeconds',
'toDateString', 'toJSON', 'toLocaleDateString', 'toLocaleString', 'toLocaleTimeString',
'toTimeString', 'toUTCString',
'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
'setMinutes', 'setMonth', 'setSeconds', 'setTime',
'setUTCDate', 'setUTCFullYear', 'setUTCHours', 'setUTCMilliseconds',
'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
'toDateString', 'toJSON', 'toLocaleDateString', 'toLocaleString',
'toLocaleTimeString', 'toTimeString', 'toUTCString',
'UTC',
// Function
'apply', 'bind', 'call', 'isGenerator',
@ -3226,7 +3278,22 @@ Ox.tokenize = (function() {
'match',
'replace',
'search', 'slice', 'split', 'substr', 'substring',
'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toUpperCase', 'trim'
'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toUpperCase', 'trim',
// Window
'addEventListener', 'alert', 'atob',
'blur', 'btoa',
'clearInterval', 'clearTimeout', 'close', 'confirm',
'dispatchEvent',
'escape',
'find', 'focus',
'getComputedStyle', 'getSelection',
'moveBy', 'moveTo',
'open',
'postMessage', 'print', 'prompt',
'removeEventListener', 'resizeBy', 'resizeTo',
'scroll', 'scrollBy', 'scrollTo',
'setCursor', 'setInterval', 'setTimeout', 'stop',
'unescape'
],
object: [
'Array',
@ -3250,8 +3317,24 @@ Ox.tokenize = (function() {
// Function
'constructor', 'length', 'prototype',
// RegExp
'global', 'ignoreCase', 'lastIndex', 'multiline', 'source'
'global', 'ignoreCase', 'lastIndex', 'multiline', 'source',
// Window
'applicationCache',
'closed', 'content', 'crypto',
'defaultStatus', 'document',
'frameElement', 'frames',
'history',
'innerHeight', 'innerWidth',
'length', 'location', 'locationbar', 'localStorage',
'menubar',
'name', 'navigator',
'opener', 'outerHeight', 'outerWidth',
'pageXOffset', 'pageYOffset', 'parent', 'personalbar',
'screen', 'screenX', 'screenY', 'scrollbars', 'scrollX', 'scrollY',
'self', 'sessionStorage', 'status', 'statusbar',
'toolbar', 'top'
]
// Window stuff? 'atob', 'btoa', 'console', 'document' ...
};
return function(source) {
@ -3272,7 +3355,7 @@ Ox.tokenize = (function() {
},
identifier: function() {
var str;
while (identifier.indexOf(source[++cursor]) > -1) {}
while ((identifier + number).indexOf(source[++cursor]) > -1) {}
str = source.substring(start, cursor);
Ox.forEach(word, function(value, key) {
if (value.indexOf(str) > -1) {
@ -3419,6 +3502,7 @@ Ox.divideInt <f> Divides a number by another and returns an array of integers
[16, 16, 17, 17, 17, 17]
@*/
Ox.divideInt = function(num, by) {
// fixme: for loops are so C ;)
var arr = [],
div = parseInt(num / by),
mod = num % by,
@ -3550,6 +3634,34 @@ Ox.extend = function() {
return obj;
};
/*@
Ox.serialize <f> Parses an object into query parameters
> Ox.serialize({a: 1, b: 2, c: 3})
'a=1&b=2&c=3'
@*/
Ox.serialize = function(obj) {
var arr = [];
Ox.forEach(obj, function(val, key) {
arr.push(key + '=' + val);
});
return arr.join('&');
};
/*@
Ox.unserialize <f> Parses query parameters into an object
> Ox.unserialize('a=1&b=2&c=3')
{a: 1, b: 2, c: 3}
@*/
Ox.unserialize = function(str) {
var obj = {};
Ox.forEach(str.split('&'), function(val) {
var arr = val.split('='),
num = +arr[1];
obj[arr[0]] = Ox.isNumber(num) ? num : arr[1];
});
return obj;
};
/*
================================================================================
RegExp functions
@ -3684,7 +3796,7 @@ Ox.loadFile = (function() {
Ox.basename = function(str) {
/*
fixme: this should go into Path functions
fixme: deprecate
>>> Ox.basename("foo/bar/foo.bar")
"foo.bar"
>>> Ox.basename("foo.bar")
@ -3693,6 +3805,10 @@ Ox.basename = function(str) {
return str.replace(/^.*[\/\\]/g, '');
};
/*@
Ox.char <f> Alias for String.fromCharCode
@*/
// fixme: add some mapping? like Ox.char(9, 13) or Ox.char([9, 13])?
Ox.char = String.fromCharCode;
Ox.clean = function(str) {
@ -3723,11 +3839,15 @@ Ox.contains = function(str, chr) {
return str.indexOf(chr) > -1;
};
Ox.endsWith = function(str, sub) {
/*
>>> Ox.endsWith("foobar", "bar")
/*@
Ox.endsWith <f> Checks if a string ends with a given substring
While <code>Ox.endsWith('foobar', 'bar')</code> is longer than
<code>/bar$/.test('foobar')</code>, <code>Ox.endsWith('foobar', bar)</code>
is shorter than <code>new RegExp(bar + '$').test('foobar')</code>.
> Ox.endsWith('foobar', 'bar')
true
*/
@*/
Ox.endsWith = function(str, sub) {
return new RegExp(sub + '$').test(str);
};
@ -3739,34 +3859,40 @@ Ox.highlight = function(txt, str) {
) : txt;
};
/*@
Ox.isValidEmail <f> Tests if a string is a valid e-mail address
(str) -> <b> True if the string is a valid e-mail address
str <s> Any string
> Ox.isValidEmail("foo@bar.com")
true
> Ox.isValidEmail("foo.bar@foobar.co.uk")
true
> Ox.isValidEmail("foo@bar")
false
> Ox.isValidEmail("foo@bar..com")
false
@*/
Ox.isValidEmail = function(str) {
/*
>>> Ox.isValidEmail("foo@bar.com")
true
>>> Ox.isValidEmail("foo.bar@foobar.co.uk")
true
>>> Ox.isValidEmail("foo@bar")
false
>>> Ox.isValidEmail("foo@bar..com")
false
*/
return !!/^[0-9A-Z\.\+\-_]+@(?:[0-9A-Z\-]+\.)+[A-Z]{2,6}$/i(str);
}
/*@
Ox.pad <f> Pad a string to a given length
> Ox.pad(1, 2)
"01"
> Ox.pad("abc", 6, ".", "right")
"abc..."
> Ox.pad("foobar", 3, ".", "right")
"foo"
> Ox.pad("abc", 6, "123456", "right")
"abc123"
> Ox.pad("abc", 6, "123456", "left")
"456abc"
@*/
Ox.pad = function(str, len, pad, pos) {
// fixme: slighly obscure signature
// fixme: weird for negative numbers
/*
>>> Ox.pad(1, 2)
"01"
>>> Ox.pad("abc", 6, ".", "right")
"abc..."
>>> Ox.pad("foobar", 3, ".", "right")
"foo"
>>> Ox.pad("abc", 6, "123456", "right")
"abc123"
>>> Ox.pad("abc", 6, "123456", "left")
"456abc"
*/
str = str.toString().substr(0, len);
pad = Ox.repeat(pad || '0', len - str.length);
@ -3778,6 +3904,30 @@ Ox.pad = function(str, len, pad, pos) {
return str;
};
/*@
Ox.parsePath <f> Returns the components of a path
(str) -> <o> Path
extension <s> File extension
filename <s> Filename
pathname <s> Pathname
> Ox.parsePath('/foo/bar/foo.bar')
{extension: 'bar', filename: 'foo.bar', pathname: '/foo/bar/'}
> Ox.parsePath('foo/')
{extension: '', filename: '', pathname: 'foo/'}
> Ox.parsePath('foo')
{extension: '', filename: 'foo', pathname: ''}
> Ox.parsePath('.foo')
{extension: '', filename: '.foo', pathname: ''}
@*/
Ox.parsePath = function(str) {
var matches = /^(.+\/)?(.+?(\..+)?)?$/(str);
return {
pathname: matches[1] || '',
filename: matches[2] || '',
extension: matches[3] ? matches[3].substr(1) : ''
};
}
Ox.repeat = function(obj, num) {
/*
works for arrays, numbers and strings
@ -3807,6 +3957,14 @@ Ox.reverse = function(str) {
return str.toString().split('').reverse().join('');
};
/*@
Ox.startsWith <f> Checks if a string starts with a given substring
While <code>Ox.startsWith('foobar', 'foo')</code> is longer than
<code>/^foo/.test('foobar')</code>, <code>Ox.startsWith('foobar', foo)</code>
is shorter than <code>new RegExp('^' + foo).test('foobar')</code>.
> Ox.endsWith('foobar', 'bar')
true
@*/
Ox.startsWith = function(str, sub) {
/*
>>> Ox.startsWith("foobar", "foo")
@ -3819,12 +3977,13 @@ Ox.startsWith = function(str, sub) {
return new RegExp('^' + sub).test(str);
};
/*@
Ox.stripTags <f> Strips HTML tags from a string
> Ox.stripTags('f<span>o</span>o')
'foo'
@*/
Ox.stripTags = function(str) {
/*
>>> Ox.stripTags("f<span>o</span>o")
"foo"
*/
return str.replace(/(<.*?>)/gi, '');
return str.replace(/(<.*?>)/g, '');
};
Ox.substr = function(str, start, stop) {
@ -3852,47 +4011,54 @@ Ox.substr = function(str, start, stop) {
);
};
/*@
Ox.toCamelCase <f> Takes a string with '-', '/' or '_', returns a camelCase string
> Ox.toCamelCase('foo-bar-baz')
'fooBarBaz'
> Ox.toCamelCase('foo/bar/baz')
'fooBarBaz'
> Ox.toCamelCase('foo_bar_baz')
'fooBarBaz'
@*/
Ox.toCamelCase = function(str) {
/*
>>> Ox.toCamelCase("foo-bar-baz")
"fooBarBaz"
>>> Ox.toCamelCase("foo/bar/baz")
"fooBarBaz"
>>> Ox.toCamelCase("foo_bar_baz")
"fooBarBaz"
*/
return str.replace(/[\-_\/][a-z]/g, function(str) {
return str[1].toUpperCase();
});
};
/*@
Ox.toDashes <f> Takes a camelCase string, returns a string with dashes
> Ox.toDashes('fooBarBaz')
'foo-bar-baz'
@*/
Ox.toDashes = function(str) {
/*
>>> Ox.toDashes("fooBarBaz")
"foo-bar-baz"
*/
return str.replace(/[A-Z]/g, function(str) {
return '-' + str.toLowerCase();
});
};
/*@
Ox.toSlashes <f> Takes a camelCase string, returns a string with slashes
> Ox.toSlashes('fooBarBaz')
'foo/bar/baz'
@*/
Ox.toSlashes = function(str) {
/*
>>> Ox.toSlashes("fooBarBaz")
"foo/bar/baz"
*/
return str.replace(/[A-Z]/g, function(str) {
return '/' + str.toLowerCase();
});
};
/*@
Ox.toTitleCase <f> Returns a string with capitalized words
> Ox.toTitleCase('foo')
'Foo'
> Ox.toTitleCase('Apple releases iPhone, IBM stock plummets')
'Apple Releases iPhone, IBM Stock Plummets'
@*/
Ox.toTitleCase = function(str) {
/*
>>> Ox.toTitleCase("foo")
"Foo"
>>> Ox.toTitleCase("Apple releases iPhone, IBM stock plummets")
"Apple Releases iPhone, IBM Stock Plummets"
*/
return Ox.map(str.split(' '), function(v) {
var sub = v.substr(1),
low = sub.toLowerCase();
@ -3903,11 +4069,12 @@ Ox.toTitleCase = function(str) {
}).join(" ");
};
/*@
Ox.toUnderscores <f> Takes a camelCase string, returns string with underscores
> Ox.toUnderscores('fooBarBaz')
'foo_bar_baz'
@*/
Ox.toUnderscores = function(str) {
/*
>>> Ox.toUnderscores("fooBarBaz")
"foo_bar_baz"
*/
return str.replace(/[A-Z]/g, function(str) {
return '_' + str.toLowerCase();
});
@ -3921,10 +4088,23 @@ Ox.trim = function(str) { // is in jQuery, and in JavaScript itself
return str.replace(/^\s+|\s+$/g, "");
};
/*@
Ox.truncate <f> Truncate a string to a given length
(string, length) <s> Truncated string
(string, length, position) -> <s> Truncated string
(string, length, placeholder) -> <s> Truncated string
(string, length, position, placeholder) -> <s> Truncated string
> Ox.truncate('anticonstitutionellement', 16)
'anticonstitut...'
> Ox.truncate('anticonstitutionellement', 16, -1)
'...utionellement'
> Ox.truncate('anticonstitutionellement', 16, '>')
'anticonstitutio>'
> Ox.truncate("anticonstitutionellement", 16, "...", "center")
'anticon...lement'
@*/
Ox.truncate = function(str, len, pad, pos) {
/*
>>> Ox.truncate("anticonstitutionellement", 16, "...", "center")
"anticon...lement"
*/
var pad = pad || {},
pos = pos || "right",
@ -3945,11 +4125,14 @@ Ox.truncate = function(str, len, pad, pos) {
return str;
};
/*@
Ox.words <f> Splits a string into words, removing punctuation
(string) -> <[s]> Array of words
string <s> Any string
> Ox.words('Let\'s "walk" a tree-like key/value store--okay?')
["let's", "walk", "a", "tree-like", "key", "value", "store", "okay"]
@*/
Ox.words = function(str) {
/*
> Ox.words("The key/value pairs are read-only--aren't they?")
["the", "key", "value", "pairs", "are", "read-only", "aren't", "they"]
*/
var arr = str.toLowerCase().split(/\b/),
chr = "-'",
len = arr.length,
@ -3978,23 +4161,21 @@ Ox.words = function(str) {
/*@
Ox.wordwrap <f> Wrap a string at word boundaries
> Ox.wordwrap("Anticonstitutionellement, Paris s'eveille", 25, '<br/>')
"Anticonstitutionellement, <br/>Paris s'eveille"
> Ox.wordwrap("Anticonstitutionellement, Paris s'eveille", 16, '<br/>')
"Anticonstitution<br/>ellement, Paris <br/>s'eveille"
> Ox.wordwrap('These are short words', 16, '<br/>', true)
'These are <br/>short words'
@*/
Ox.wordwrap = function(str, len, sep, bal, spa) {
// fixme: bad API, sep/bal/spa should be in options object
/*
>>> Ox.wordwrap("Anticonstitutionellement, Paris s'eveille", 25, "<br/>")
"Anticonstitutionellement, <br/>Paris s'eveille"
>>> Ox.wordwrap("Anticonstitutionellement, Paris s'eveille", 16, "<br/>")
"Anticonstitution<br/>ellement, Paris <br/>s'eveille"
>>> Ox.wordwrap("These are short words", 16, "<br/>", true)
"These are <br/>short words"
*/
var str = str === null ? '' : str.toString(),
len = len || 80,
sep = sep || "<br/>",
sep = sep || '<br/>',
bal = bal || false,
spa = Ox.isUndefined(spa) ? true : spa,
words = str.split(" "),
words = str.split(' '),
lines;
if (bal) {
// balance lines: test if same number of lines
@ -4015,15 +4196,15 @@ Ox.wordwrap = function(str, len, sep, bal, spa) {
}
}
}
lines = [""];
lines = [''];
Ox.forEach(words, function(word) {
if ((lines[lines.length - 1] + word + " ").length <= len + 1) {
if ((lines[lines.length - 1] + word + ' ').length <= len + 1) {
// word fits in current line
lines[lines.length - 1] += word + " ";
lines[lines.length - 1] += word + ' ';
} else {
if (word.length <= len) {
// word fits in next line
lines.push(word + " ");
lines.push(word + ' ');
} else {
// word is longer than line
var chr = len - lines[lines.length - 1].length;
@ -4031,7 +4212,7 @@ Ox.wordwrap = function(str, len, sep, bal, spa) {
for (var pos = chr; pos < word.length; pos += len) {
lines.push(word.substr(pos, len));
}
lines[lines.length - 1] += " ";
lines[lines.length - 1] += ' ';
}
}
});
@ -4106,48 +4287,75 @@ Ox.isElement = function(val) {
/*@
Ox.isEqual <function> Returns true if two values are equal
> Ox.isEqual(false, false)
true
> Ox.isEqual(0, 0)
true
> Ox.isEqual(NaN, NaN)
false
> Ox.isEqual('', '')
> Ox.isEqual((function() { return arguments; }()), (function() { return arguments; }()))
true
> Ox.isEqual([1, 2, 3], [1, 2, 3])
true
> Ox.isEqual({a: 1, b: [2, 3], c: {d: '4'}}, {a: 1, b: [2, 3], c: {d: '4'}})
> Ox.isEqual([1, 2, 3], [3, 2, 1])
false
> Ox.isEqual(false, false)
true
> Ox.isEqual(new Date(0), new Date(0))
true
> Ox.isEqual(new Date(0), new Date(1))
false
> Ox.isEqual(document.createElement('a'), document.createElement('a'))
true
> Ox.isEqual(document.createElement('a'), document.createElement('b'))
false
> Ox.isEqual(function(a) { return a; }, function(a) { return a; })
true
> Ox.isEqual(function(a) { return a; }, function(b) { return b; })
false
> Ox.isEqual(Infinity, Infinity)
true
> Ox.isEqual(-Infinity, Infinity)
false
> Ox.isEqual(NaN, NaN)
false
> Ox.isEqual(0, 0)
true
> Ox.isEqual({a: 1, b: 2, c: 3}, {c: 3, b: 2, a: 1})
true // FIXME: is false
> Ox.isEqual(function(arg) { return arg; }, function(arg) { return arg; })
true
> Ox.isEqual(function(foo) { return foo; }, function(bar) { return bar; })
> Ox.isEqual({a: 1, b: [2, 3], c: {d: '4'}}, {a: 1, b: [2, 3], c: {d: '4'}})
true
> Ox.isEqual(/ /, / /)
true
> Ox.isEqual(/ /g, / /i)
false
> Ox.isEqual('', '')
true
> Ox.isEqual(void 0, void 0)
true
@*/
Ox.isEqual = function(obj0, obj1) {
var ret = false;
if (obj0 === obj1) {
ret = true;
} else if (typeof(obj0) == typeof(obj1)) {
if (obj0 == obj1) {
ret = true;
} else if (Ox.isArray(obj0) && obj0.length == obj1.length) {
ret = true;
Ox.forEach(obj0, function(v, i) {
ret = Ox.isEqual(v, obj1[i]);
return ret;
Ox.isEqual = function(a, b) {
var isEqual = false, type = Ox.typeOf(a);
if (a === b) {
isEqual = true;
} else if (type == Ox.typeOf(b)) {
if (a == b) {
isEqual = true;
} else if (type == 'date') {
isEqual = a.getTime() == b.getTime();
} else if (['element', 'function'].indexOf(type) > -1) {
isEqual = a.toString() == b.toString();
} else if (type == 'regexp') {
isEqual = a.global == b.global &&
a.ignore == b.ignore &&
a.multiline == b.multiline &&
a.source == b.source;
} else if (
['arguments', 'array', 'object'].indexOf(type) > -1 &&
Ox.len(a) == Ox.len(b)
) {
isEqual = true;
Ox.forEach(a, function(v, k) {
isEqual = Ox.isEqual(v, b[k]);
return isEqual;
});
} else if (Ox.isDate(obj0)) {
ret = obj0.getTime() == obj1.getTime();
} else if (Ox.isObject(obj0)) {
ret = Ox.isEqual(Ox.keys(obj0), Ox.keys(obj1)) &&
Ox.isEqual(Ox.values(obj0), Ox.values(obj1));
} else if (Ox.isFunction(obj0)) {
ret = obj0.toString() == obj1.toString();
}
}
return ret;
return isEqual;
};
/*@