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

View file

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

View file

@ -6,7 +6,7 @@ Ox.DocPage = function(options, self) {
item: {} item: {}
}) })
.options(options || {}) .options(options || {})
.addClass('OxText') .addClass('OxDocPage OxText')
.css({ .css({
overflow: 'auto' overflow: 'auto'
}); });
@ -42,8 +42,13 @@ Ox.DocPage = function(options, self) {
if (item[section]) { if (item[section]) {
if (section == 'description') { if (section == 'description') {
$elements.push($('<div>') $elements.push($('<div>')
.css({paddingLeft: (level * 32) + 'px'}) .css({
.html(item.description) 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 { } else {
$elements.push($('<div>') $elements.push($('<div>')
@ -108,7 +113,7 @@ Ox.DocPage = function(options, self) {
.addClass(className) .addClass(className)
.css({marginLeft: (level * 32 + 16) + 'px'}) .css({marginLeft: (level * 32 + 16) + 'px'})
.html( .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') !(self.options.stripComments && token.type == 'comment')
) { ) {
classNames = 'Ox' + Ox.toTitleCase(token.type); classNames = 'Ox' + Ox.toTitleCase(token.type);
if (token.type == 'whitespace') { if (self.options.showWhitespace && token.type == 'whitespace') {
if (isAfterLinebreak() && hasIrregularSpaces()) { if (isAfterLinebreak() && hasIrregularSpaces()) {
classNames += ' OxLeading' classNames += ' OxLeading'
} else if (isBeforeLinebreak()) { } else if (isBeforeLinebreak()) {
@ -65,27 +65,15 @@ Ox.SyntaxHighlighter = function(options, self) {
} }
}); });
self.lines = self.source.split('<br/>'); self.lines = self.source.split('<br/>');
self.lineNumbersWidth = (self.lines.length + self.options.offset - 1).toString().length * 7; self.lineNumbersWidth = (
self.sourceCodeWidth = 80 * 7 + ( self.lines.length + self.options.offset - 1
self.lines.length > 40 ? Ox.UI.SCROLLBAR_SIZE : 0 ).toString().length * 7;
);
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.$lineNumbers = new Ox.Element() self.$lineNumbers = new Ox.Element()
.addClass('OxLineNumbers') .addClass('OxLineNumbers')
.css({ .css({
display: 'table-cell', display: 'table-cell',
width: self.lineNumbersWidth + 'px', width: self.lineNumbersWidth + 'px',
//height: (self.lines.length * 14) + 8 + 'px',
padding: '4px', padding: '4px',
}) })
.html( .html(
@ -98,8 +86,6 @@ Ox.SyntaxHighlighter = function(options, self) {
.addClass('OxSourceCode') .addClass('OxSourceCode')
.css({ .css({
display: 'table-cell', display: 'table-cell',
//width: self.sourceCodeWidth + 'px',
//height: (self.lines.length * 14) + 'px',
padding: '4px' padding: '4px'
}) })
.html(self.source) .html(self.source)

View file

@ -2,6 +2,34 @@
// OxJS (c) 2011 Ox2620, dual-licensed GPL/MIT, see http://oxjs.org for details // 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/ // 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 // also see https://github.com/tlrobinson/narwhal/blob/master/lib/util.js
@ -164,6 +192,41 @@ Ox.merge = function(arr) {
return 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 <f> Returns an array without duplicate values
> Ox.unique([1, 2, 3, 2, 1]) > 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([]) > Ox.isEmpty([])
true true
> Ox.isEmpty({}) > 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, Unlike <code>Object.keys()</code>, <code>Ox.keys()</code> works for arrays,
objects and strings. objects and strings.
> Ox.keys([1, 2, 3]) > 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?? // fixme: is this really needed? arrays... ok... but strings??
Ox.keys = function(obj) { Ox.keys = function(obj) {
var keys = []; var keys = [];
Ox.forEach(obj, function(v, k) { Ox.forEach(obj, function(v, k) {
@ -727,27 +789,20 @@ Ox.range = function() {
return arr; return arr;
}; };
Ox.serialize = function(obj) { /*@
/* Ox.setPropertyOnce <f> Sets a property once
>>> Ox.serialize({a: 1, b: 2, c: 3}) Given a array of objects, each of which has a property with a boolean
'a=1&b=2&c=3' value, this sets exactly one of these to true, and returns the index
*/ of the object whose property is true.
var arr = []; > Ox.setPropertyOnce([{selected: false}, {selected: false}], 'selected')
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')
0 0
>>> Ox.setPropertyOnce([{selected: false}, {selected: true}], 'selected') > Ox.setPropertyOnce([{selected: false}, {selected: true}], 'selected')
1 1
>>> Ox.setPropertyOnce([{selected: true}, {selected: true}], 'selected') > Ox.setPropertyOnce([{selected: true}, {selected: true}], 'selected')
0 0
*/ @*/
// fixme: strange name, and shouldn't it return the full array?
Ox.setPropertyOnce = function(arr, str) {
var pos = -1; var pos = -1;
Ox.forEach(arr, function(v, i) { Ox.forEach(arr, function(v, i) {
if (pos == -1 && arr[i][str]) { if (pos == -1 && arr[i][str]) {
@ -763,74 +818,72 @@ Ox.setPropertyOnce = function(arr, str) {
return pos; return pos;
}; };
Ox.shuffle = function(arr) { /*@
/* Ox.shuffle <f> Randomizes the order of values within a collection
>>> Ox.shuffle([1, 2, 3]).length > Ox.shuffle([1, 2, 3]).length
3 3
*/ > Ox.len(Ox.shuffle({a: 1, b: 2, c: 3}))
var shuffle = arr; 3
return shuffle.sort(function() { > Ox.shuffle('123').length
3
@*/
Ox.shuffle = function(col) {
var keys, ret, type = Ox.typeOf(col), values;
function sort() {
return Math.random() - 0.5; 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 = 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; return Ox.filter(Ox.values(obj), fn).length > 0;
}; };
Ox.sort = function(arr) { /*@
/* Ox.sum <f> Returns the sum of the values of a collection
>>> Ox.sort(['10', '9', 'B', 'a']) > Ox.sum(1, 2, 3)
['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})
6 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; var sum = 0;
Ox.forEach(obj, function(val) { col = arguments.length > 1 ? Ox.makeArray(arguments) : col;
sum += val; Ox.forEach(col, function(val) {
val = +val;
sum += Ox.isNumber(val) ? val : 0;
}); });
return sum; return sum;
}; };
@ -854,43 +907,39 @@ Ox.toArray = function(obj) {
return arr; return arr;
}; };
Ox.unserialize = function(str) { /*@
/* Ox.values <f> Returns the values of a collection
>>> Ox.unserialize('a=1&b=2&c=3').c > Ox.values([1, 2, 3])
'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])
[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] [1, 2, 3]
>>> Ox.values('abc') > Ox.values('abc')
['a', 'b', 'c'] ['a', 'b', 'c']
>>> Ox.values([1,]) > Ox.values([1,,3])
[1] [1, 3]
*/ @*/
// fixme: why doesn't this use map? 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 = []; var values = [];
Ox.forEach(obj, function(val) { Ox.forEach(col, function(val) {
values.push(val); values.push(val);
}); });
return values; return values;
}; };
Ox.walk = function(obj, fn) { /*@
/* Ox.walk <f> Recursively walk a tree-like key/value store
>>> a = 0; Ox.walk({a: 1, b: {c: 2, d: 3}}, function(v, k) { a += Ox.isNumber(v) ? v : 0}); a <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 6
*/ @*/
Ox.walk = function(obj, fn) {
Ox.forEach(obj, function(val, key) { Ox.forEach(obj, function(val, key) {
fn(val, key, obj); fn(val, key, obj);
Ox.walk(obj[key], fn); Ox.walk(obj[key], fn);
@ -2788,6 +2837,7 @@ Ox.doc <f> Generates documentation for annotated JavaScript
@*/ @*/
Ox.doc = (function() { Ox.doc = (function() {
// fixme: dont require the trailing '@'
var re = { var re = {
item: /^(.+?) <(.+?)> (.+)$/, item: /^(.+?) <(.+?)> (.+)$/,
multiline: /^\/\*\@.*?\n([\w\W]+)\n.*?\@\*\/$/, multiline: /^\/\*\@.*?\n([\w\W]+)\n.*?\@\*\/$/,
@ -2886,6 +2936,7 @@ Ox.doc = (function() {
}; };
} }
function parseTokens(tokens, includeLeading) { function parseTokens(tokens, includeLeading) {
// fixme: do not strip whitespace from the beginning of the first line of the items' source
var leading = [], var leading = [],
tokens_ = []; tokens_ = [];
tokens.forEach(function(token) { tokens.forEach(function(token) {
@ -3079,7 +3130,7 @@ Ox.test = function(file, callback) {
var actual = eval(example.statement); var actual = eval(example.statement);
if (example.result) { if (example.result) {
tests.push({ tests.push({
actual: actual, actual: JSON.stringify(actual),
expected: example.result, expected: example.result,
name: item.name, name: item.name,
section: item.section, section: item.section,
@ -3174,18 +3225,19 @@ Ox.tokenize = (function() {
'shift', 'slice', 'some', 'sort', 'splice', 'shift', 'slice', 'some', 'sort', 'splice',
'unshift', 'unshift',
// Date // Date
'getDate', 'getDay', 'getFullYear', 'getHours', 'getMilliseconds', 'getDate', 'getDay', 'getFullYear', 'getHours',
'getMinutes', 'getMonth', 'getSeconds', 'getTime', 'getTimezoneOffset', 'getMilliseconds', 'getMinutes', 'getMonth', 'getSeconds',
'getUTCDate', 'getUTCDay', 'getUTCFullYear', 'getUTCHours', 'getUTCMilliseconds', 'getTime', 'getTimezoneOffset',
'getUTCMinutes', 'getUTCMonth', 'getUTCSeconds', 'getUTCDate', 'getUTCDay', 'getUTCFullYear', 'getUTCHours',
'getUTCMilliseconds', 'getUTCMinutes', 'getUTCMonth', 'getUTCSeconds',
'now', 'now',
'parse', 'parse',
'setDate', 'setFullYear', 'setHours', 'setMilliseconds', 'setMinutes', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
'setMonth', 'setSeconds', 'setTime', 'setMinutes', 'setMonth', 'setSeconds', 'setTime',
'setUTCDate', 'setUTCFullYear', 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCDate', 'setUTCFullYear', 'setUTCHours', 'setUTCMilliseconds',
'setUTCMonth', 'setUTCSeconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
'toDateString', 'toJSON', 'toLocaleDateString', 'toLocaleString', 'toLocaleTimeString', 'toDateString', 'toJSON', 'toLocaleDateString', 'toLocaleString',
'toTimeString', 'toUTCString', 'toLocaleTimeString', 'toTimeString', 'toUTCString',
'UTC', 'UTC',
// Function // Function
'apply', 'bind', 'call', 'isGenerator', 'apply', 'bind', 'call', 'isGenerator',
@ -3226,7 +3278,22 @@ Ox.tokenize = (function() {
'match', 'match',
'replace', 'replace',
'search', 'slice', 'split', 'substr', 'substring', '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: [ object: [
'Array', 'Array',
@ -3250,8 +3317,24 @@ Ox.tokenize = (function() {
// Function // Function
'constructor', 'length', 'prototype', 'constructor', 'length', 'prototype',
// RegExp // 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) { return function(source) {
@ -3272,7 +3355,7 @@ Ox.tokenize = (function() {
}, },
identifier: function() { identifier: function() {
var str; var str;
while (identifier.indexOf(source[++cursor]) > -1) {} while ((identifier + number).indexOf(source[++cursor]) > -1) {}
str = source.substring(start, cursor); str = source.substring(start, cursor);
Ox.forEach(word, function(value, key) { Ox.forEach(word, function(value, key) {
if (value.indexOf(str) > -1) { 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] [16, 16, 17, 17, 17, 17]
@*/ @*/
Ox.divideInt = function(num, by) { Ox.divideInt = function(num, by) {
// fixme: for loops are so C ;)
var arr = [], var arr = [],
div = parseInt(num / by), div = parseInt(num / by),
mod = num % by, mod = num % by,
@ -3550,6 +3634,34 @@ Ox.extend = function() {
return obj; 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 RegExp functions
@ -3684,7 +3796,7 @@ Ox.loadFile = (function() {
Ox.basename = function(str) { Ox.basename = function(str) {
/* /*
fixme: this should go into Path functions fixme: deprecate
>>> Ox.basename("foo/bar/foo.bar") >>> Ox.basename("foo/bar/foo.bar")
"foo.bar" "foo.bar"
>>> Ox.basename("foo.bar") >>> Ox.basename("foo.bar")
@ -3693,6 +3805,10 @@ Ox.basename = function(str) {
return str.replace(/^.*[\/\\]/g, ''); 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.char = String.fromCharCode;
Ox.clean = function(str) { Ox.clean = function(str) {
@ -3723,11 +3839,15 @@ Ox.contains = function(str, chr) {
return str.indexOf(chr) > -1; return str.indexOf(chr) > -1;
}; };
Ox.endsWith = function(str, sub) { /*@
/* Ox.endsWith <f> Checks if a string ends with a given substring
>>> Ox.endsWith("foobar", "bar") 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 true
*/ @*/
Ox.endsWith = function(str, sub) {
return new RegExp(sub + '$').test(str); return new RegExp(sub + '$').test(str);
}; };
@ -3739,34 +3859,40 @@ Ox.highlight = function(txt, str) {
) : txt; ) : 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 = 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); 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) { Ox.pad = function(str, len, pad, pos) {
// fixme: slighly obscure signature // fixme: slighly obscure signature
// fixme: weird for negative numbers // 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); str = str.toString().substr(0, len);
pad = Ox.repeat(pad || '0', len - str.length); pad = Ox.repeat(pad || '0', len - str.length);
@ -3778,6 +3904,30 @@ Ox.pad = function(str, len, pad, pos) {
return str; 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) { Ox.repeat = function(obj, num) {
/* /*
works for arrays, numbers and strings works for arrays, numbers and strings
@ -3807,6 +3957,14 @@ Ox.reverse = function(str) {
return str.toString().split('').reverse().join(''); 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 = function(str, sub) {
/* /*
>>> Ox.startsWith("foobar", "foo") >>> Ox.startsWith("foobar", "foo")
@ -3819,12 +3977,13 @@ Ox.startsWith = function(str, sub) {
return new RegExp('^' + sub).test(str); 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 = function(str) {
/* return str.replace(/(<.*?>)/g, '');
>>> Ox.stripTags("f<span>o</span>o")
"foo"
*/
return str.replace(/(<.*?>)/gi, '');
}; };
Ox.substr = function(str, start, stop) { 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 = 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.replace(/[\-_\/][a-z]/g, function(str) {
return str[1].toUpperCase(); 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 = function(str) {
/*
>>> Ox.toDashes("fooBarBaz")
"foo-bar-baz"
*/
return str.replace(/[A-Z]/g, function(str) { return str.replace(/[A-Z]/g, function(str) {
return '-' + str.toLowerCase(); 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 = function(str) {
/* /*
>>> Ox.toSlashes("fooBarBaz")
"foo/bar/baz"
*/ */
return str.replace(/[A-Z]/g, function(str) { return str.replace(/[A-Z]/g, function(str) {
return '/' + str.toLowerCase(); 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 = 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) { return Ox.map(str.split(' '), function(v) {
var sub = v.substr(1), var sub = v.substr(1),
low = sub.toLowerCase(); low = sub.toLowerCase();
@ -3903,11 +4069,12 @@ Ox.toTitleCase = function(str) {
}).join(" "); }).join(" ");
}; };
/*@
Ox.toUnderscores <f> Takes a camelCase string, returns string with underscores
> Ox.toUnderscores('fooBarBaz')
'foo_bar_baz'
@*/
Ox.toUnderscores = function(str) { Ox.toUnderscores = function(str) {
/*
>>> Ox.toUnderscores("fooBarBaz")
"foo_bar_baz"
*/
return str.replace(/[A-Z]/g, function(str) { return str.replace(/[A-Z]/g, function(str) {
return '_' + str.toLowerCase(); return '_' + str.toLowerCase();
}); });
@ -3921,10 +4088,23 @@ Ox.trim = function(str) { // is in jQuery, and in JavaScript itself
return str.replace(/^\s+|\s+$/g, ""); 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 = function(str, len, pad, pos) {
/* /*
>>> Ox.truncate("anticonstitutionellement", 16, "...", "center")
"anticon...lement"
*/ */
var pad = pad || {}, var pad = pad || {},
pos = pos || "right", pos = pos || "right",
@ -3945,11 +4125,14 @@ Ox.truncate = function(str, len, pad, pos) {
return str; 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 = 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/), var arr = str.toLowerCase().split(/\b/),
chr = "-'", chr = "-'",
len = arr.length, len = arr.length,
@ -3978,23 +4161,21 @@ Ox.words = function(str) {
/*@ /*@
Ox.wordwrap <f> Wrap a string at word boundaries 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) { Ox.wordwrap = function(str, len, sep, bal, spa) {
// fixme: bad API, sep/bal/spa should be in options object // 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(), var str = str === null ? '' : str.toString(),
len = len || 80, len = len || 80,
sep = sep || "<br/>", sep = sep || '<br/>',
bal = bal || false, bal = bal || false,
spa = Ox.isUndefined(spa) ? true : spa, spa = Ox.isUndefined(spa) ? true : spa,
words = str.split(" "), words = str.split(' '),
lines; lines;
if (bal) { if (bal) {
// balance lines: test if same number of lines // 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) { 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 // word fits in current line
lines[lines.length - 1] += word + " "; lines[lines.length - 1] += word + ' ';
} else { } else {
if (word.length <= len) { if (word.length <= len) {
// word fits in next line // word fits in next line
lines.push(word + " "); lines.push(word + ' ');
} else { } else {
// word is longer than line // word is longer than line
var chr = len - lines[lines.length - 1].length; 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) { for (var pos = chr; pos < word.length; pos += len) {
lines.push(word.substr(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 <function> Returns true if two values are equal
> Ox.isEqual(false, false) > Ox.isEqual((function() { return arguments; }()), (function() { return arguments; }()))
true
> Ox.isEqual(0, 0)
true
> Ox.isEqual(NaN, NaN)
false
> Ox.isEqual('', '')
true true
> Ox.isEqual([1, 2, 3], [1, 2, 3]) > Ox.isEqual([1, 2, 3], [1, 2, 3])
true 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 true
> Ox.isEqual({a: 1, b: 2, c: 3}, {c: 3, b: 2, a: 1}) > 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 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 false
> Ox.isEqual('', '')
true
> Ox.isEqual(void 0, void 0)
true
@*/ @*/
Ox.isEqual = function(obj0, obj1) { Ox.isEqual = function(a, b) {
var ret = false; var isEqual = false, type = Ox.typeOf(a);
if (obj0 === obj1) { if (a === b) {
ret = true; isEqual = true;
} else if (typeof(obj0) == typeof(obj1)) { } else if (type == Ox.typeOf(b)) {
if (obj0 == obj1) { if (a == b) {
ret = true; isEqual = true;
} else if (Ox.isArray(obj0) && obj0.length == obj1.length) { } else if (type == 'date') {
ret = true; isEqual = a.getTime() == b.getTime();
Ox.forEach(obj0, function(v, i) { } else if (['element', 'function'].indexOf(type) > -1) {
ret = Ox.isEqual(v, obj1[i]); isEqual = a.toString() == b.toString();
return ret; } 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;
}; };
/*@ /*@