mirror of https://github.com/lukechilds/node.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1033 lines
28 KiB
1033 lines
28 KiB
|
|
// a decorator for functions that curry "polymorphically",
|
|
// that is, that return a function that can be tested
|
|
// against various objects if they're only "partially
|
|
// completed", or fewer arguments than needed are used.
|
|
//
|
|
// this enables the idioms:
|
|
// [1, 2, 3].every(lt(4)) eq true
|
|
// [1, 2, 3].map(add(1)) eq [2, 3, 4]
|
|
// [{}, {}, {}].forEach(set('a', 10))
|
|
//
|
|
exports.operator = function (name, length, block) {
|
|
var operator = function () {
|
|
var args = exports.array(arguments);
|
|
var completion = function (object) {
|
|
if (
|
|
typeof object == "object" &&
|
|
object !== null && // seriously? typeof null == "object"
|
|
name in object && // would throw if object === null
|
|
// not interested in literal objects:
|
|
!Object.prototype.hasOwnProperty.call(object, name)
|
|
)
|
|
return object[name].apply(object, args);
|
|
return block.apply(
|
|
this,
|
|
[object].concat(args)
|
|
);
|
|
};
|
|
if (arguments.length < length) {
|
|
// polymoprhic curry, delayed completion
|
|
return completion;
|
|
} else {
|
|
// immediate completion
|
|
return completion.call(this, args.shift());
|
|
}
|
|
};
|
|
operator.name = name;
|
|
operator.displayName = name;
|
|
operator.length = length;
|
|
operator.operator = block;
|
|
return operator;
|
|
};
|
|
|
|
exports.no = function (value) {
|
|
return value === null || value === undefined;
|
|
};
|
|
|
|
// object
|
|
|
|
exports.object = exports.operator('toObject', 1, function (object) {
|
|
var items = object;
|
|
if (!items.length)
|
|
items = exports.items(object);
|
|
var copy = {};
|
|
for (var i = 0; i < items.length; i++) {
|
|
var item = items[i];
|
|
var key = item[0];
|
|
var value = item[1];
|
|
copy[key] = value;
|
|
}
|
|
return copy;
|
|
});
|
|
|
|
exports.object.copy = function (object) {
|
|
var copy = {};
|
|
exports.object.keys(object).forEach(function (key) {
|
|
copy[key] = object[key];
|
|
});
|
|
return copy;
|
|
};
|
|
|
|
exports.object.deepCopy = function (object) {
|
|
var copy = {};
|
|
exports.object.keys(object).forEach(function (key) {
|
|
copy[key] = exports.deepCopy(object[key]);
|
|
});
|
|
return copy;
|
|
};
|
|
|
|
exports.object.eq = function (a, b, stack) {
|
|
return (
|
|
!exports.no(a) && !exports.no(b) &&
|
|
exports.array.eq(
|
|
exports.sort(exports.object.keys(a)),
|
|
exports.sort(exports.object.keys(b))
|
|
) &&
|
|
exports.object.keys(a).every(function (key) {
|
|
return exports.eq(a[key], b[key], stack);
|
|
})
|
|
);
|
|
};
|
|
|
|
exports.object.len = function (object) {
|
|
return exports.object.keys(object).length;
|
|
};
|
|
|
|
exports.object.has = function (object, key) {
|
|
return Object.prototype.hasOwnProperty.call(object, key);
|
|
};
|
|
|
|
exports.object.keys = function (object) {
|
|
var keys = [];
|
|
for (var key in object) {
|
|
if (exports.object.has(object, key))
|
|
keys.push(key);
|
|
}
|
|
return keys;
|
|
};
|
|
|
|
exports.object.values = function (object) {
|
|
var values = [];
|
|
exports.object.keys(object).forEach(function (key) {
|
|
values.push(object[key]);
|
|
});
|
|
return values;
|
|
};
|
|
|
|
exports.object.items = function (object) {
|
|
var items = [];
|
|
exports.object.keys(object).forEach(function (key) {
|
|
items.push([key, object[key]]);
|
|
});
|
|
return items;
|
|
};
|
|
|
|
/**
|
|
* Updates an object with the properties from another object.
|
|
* This function is variadic requiring a minimum of two arguments.
|
|
* The first argument is the object to update. Remaining arguments
|
|
* are considered the sources for the update. If multiple sources
|
|
* contain values for the same property, the last one with that
|
|
* property in the arguments list wins.
|
|
*
|
|
* example usage:
|
|
* util.update({}, { hello: "world" }); // -> { hello: "world" }
|
|
* util.update({}, { hello: "world" }, { hello: "le monde" }); // -> { hello: "le monde" }
|
|
*
|
|
* @returns Updated object
|
|
* @type Object
|
|
*
|
|
*/
|
|
exports.object.update = function () {
|
|
return variadicHelper(arguments, function(target, source) {
|
|
var key;
|
|
for (key in source) {
|
|
if (exports.object.has(source, key)) {
|
|
target[key] = source[key];
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
exports.object.complete = function () {
|
|
return variadicHelper(arguments, function(target, source) {
|
|
var key;
|
|
for (key in source) {
|
|
if (
|
|
exports.object.has(source, key) &&
|
|
!exports.object.has(target, key)
|
|
) {
|
|
target[key] = source[key];
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
exports.object.repr = function (object) {
|
|
return "{" +
|
|
exports.object.keys(object)
|
|
.map(function (key) {
|
|
return exports.enquote(key) + ": " +
|
|
exports.repr(object[key]);
|
|
}).join(", ") +
|
|
"}";
|
|
};
|
|
|
|
/**
|
|
* @param args Arguments list of the calling function
|
|
* First argument should be a callback that takes target and source parameters.
|
|
* Second argument should be target.
|
|
* Remaining arguments are treated a sources.
|
|
*
|
|
* @returns Target
|
|
* @type Object
|
|
*/
|
|
var variadicHelper = function (args, callback) {
|
|
var sources = Array.prototype.slice.call(args);
|
|
var target = sources.shift();
|
|
|
|
sources.forEach(function(source) {
|
|
callback(target, source);
|
|
});
|
|
|
|
return target;
|
|
};
|
|
|
|
// array
|
|
|
|
exports.array = function (array) {
|
|
if (exports.no(array))
|
|
return [];
|
|
if (!exports.isArrayLike(array)) {
|
|
if (
|
|
array.toArray &&
|
|
!Object.prototype.hasOwnProperty.call(array, 'toArray')
|
|
) {
|
|
return array.toArray();
|
|
} else if (
|
|
array.forEach &&
|
|
!Object.prototype.hasOwnProperty.call(array, 'forEach')
|
|
) {
|
|
var results = [];
|
|
array.forEach(function (value) {
|
|
results.push(value);
|
|
});
|
|
return results;
|
|
} else {
|
|
return exports.items(array);
|
|
}
|
|
}
|
|
return Array.prototype.slice.call(array);
|
|
};
|
|
|
|
exports.array.coerce = function (array) {
|
|
if (!Array.isArray(array))
|
|
return exports.array(array);
|
|
return array;
|
|
};
|
|
|
|
exports.isArrayLike = function(object) {
|
|
return Array.isArray(object) || exports.isArguments(object);
|
|
};
|
|
|
|
// from http://code.google.com/p/google-caja/wiki/NiceNeighbor
|
|
// by "kangax"
|
|
//
|
|
// Mark Miller posted a solution that will work in ES5 compliant
|
|
// implementations, that may provide future insight:
|
|
// (http://groups.google.com/group/narwhaljs/msg/116097568bae41c6)
|
|
exports.isArguments = function (object) {
|
|
// ES5 reliable positive
|
|
if (Object.prototype.toString.call(object) == "[object Arguments]")
|
|
return true;
|
|
// for ES5, we will still need a way to distinguish false negatives
|
|
// from the following code (in ES5, it is possible to create
|
|
// an object that satisfies all of these constraints but is
|
|
// not an Arguments object).
|
|
// callee should exist
|
|
if (
|
|
!typeof object == "object" ||
|
|
!Object.prototype.hasOwnProperty.call(object, 'callee') ||
|
|
!object.callee ||
|
|
// It should be a Function object ([[Class]] === 'Function')
|
|
Object.prototype.toString.call(object.callee) !== '[object Function]' ||
|
|
typeof object.length != 'number'
|
|
)
|
|
return false;
|
|
for (var name in object) {
|
|
// both "callee" and "length" should be { DontEnum }
|
|
if (name === 'callee' || name === 'length') return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
exports.array.copy = exports.array;
|
|
|
|
exports.array.deepCopy = function (array) {
|
|
return array.map(exports.deepCopy);
|
|
};
|
|
|
|
exports.array.len = function (array) {
|
|
return array.length;
|
|
};
|
|
|
|
exports.array.has = function (array, value) {
|
|
return Array.prototype.indexOf.call(array, value) >= 0;
|
|
};
|
|
|
|
exports.array.put = function (array, key, value) {
|
|
array.splice(key, 0, value);
|
|
return array;
|
|
};
|
|
|
|
exports.array.del = function (array, begin, end) {
|
|
array.splice(begin, end === undefined ? 1 : (end - begin));
|
|
return array;
|
|
};
|
|
|
|
exports.array.eq = function (a, b, stack) {
|
|
return exports.isArrayLike(b) &&
|
|
a.length == b.length &&
|
|
exports.zip(a, b).every(exports.apply(function (a, b) {
|
|
return exports.eq(a, b, stack);
|
|
}));
|
|
};
|
|
|
|
exports.array.lt = function (a, b) {
|
|
var length = Math.max(a.length, b.length);
|
|
for (var i = 0; i < length; i++)
|
|
if (!exports.eq(a[i], b[i]))
|
|
return exports.lt(a[i], b[i]);
|
|
return false;
|
|
};
|
|
|
|
exports.array.repr = function (array) {
|
|
return "[" + exports.map(array, exports.repr).join(', ') + "]";
|
|
};
|
|
|
|
exports.array.first = function (array) {
|
|
return array[0];
|
|
};
|
|
|
|
exports.array.last = function (array) {
|
|
return array[array.length - 1];
|
|
};
|
|
|
|
exports.apply = exports.operator('apply', 2, function (args, block) {
|
|
return block.apply(this, args);
|
|
});
|
|
|
|
exports.copy = exports.operator('copy', 1, function (object) {
|
|
if (exports.no(object))
|
|
return object;
|
|
if (exports.isArrayLike(object))
|
|
return exports.array.copy(object);
|
|
if (typeof object == 'object')
|
|
return exports.object.copy(object);
|
|
return object;
|
|
});
|
|
|
|
exports.deepCopy = exports.operator('deepCopy', 1, function (object) {
|
|
if (exports.no(object))
|
|
return object;
|
|
if (exports.isArrayLike(object))
|
|
return exports.array.deepCopy(object);
|
|
if (typeof object == 'object')
|
|
return exports.object.deepCopy(object);
|
|
return object;
|
|
});
|
|
|
|
exports.repr = exports.operator('repr', 1, function (object) {
|
|
if (exports.no(object))
|
|
return String(object);
|
|
if (exports.isArrayLike(object))
|
|
return exports.array.repr(object);
|
|
if (typeof object == 'object')
|
|
return exports.object.repr(object);
|
|
if (typeof object == 'string')
|
|
return exports.enquote(object);
|
|
return object.toString();
|
|
});
|
|
|
|
exports.keys = exports.operator('keys', 1, function (object) {
|
|
if (exports.isArrayLike(object))
|
|
return exports.range(object.length);
|
|
else if (typeof object == 'object')
|
|
return exports.object.keys(object);
|
|
return [];
|
|
});
|
|
|
|
exports.values = exports.operator('values', 1, function (object) {
|
|
if (exports.isArrayLike(object))
|
|
return exports.array(object);
|
|
else if (typeof object == 'object')
|
|
return exports.object.values(object);
|
|
return [];
|
|
});
|
|
|
|
exports.items = exports.operator('items', 1, function (object) {
|
|
if (exports.isArrayLike(object) || typeof object == "string")
|
|
return exports.enumerate(object);
|
|
else if (typeof object == 'object')
|
|
return exports.object.items(object);
|
|
return [];
|
|
});
|
|
|
|
exports.len = exports.operator('len', 1, function (object) {
|
|
if (exports.isArrayLike(object))
|
|
return exports.array.len(object);
|
|
else if (typeof object == 'object')
|
|
return exports.object.len(object);
|
|
});
|
|
|
|
exports.has = exports.operator('has', 2, function (object, value) {
|
|
if (exports.isArrayLike(object))
|
|
return exports.array.has(object, value);
|
|
else if (typeof object == 'object')
|
|
return exports.object.has(object, value);
|
|
return false;
|
|
});
|
|
|
|
exports.get = exports.operator('get', 2, function (object, key, value) {
|
|
if (typeof object == "string") {
|
|
if (!typeof key == "number")
|
|
throw new Error("TypeError: String keys must be numbers");
|
|
if (!exports.has(exports.range(object.length), key)) {
|
|
if (arguments.length == 3)
|
|
return value;
|
|
throw new Error("KeyError: " + exports.repr(key));
|
|
}
|
|
return object.charAt(key);
|
|
}
|
|
if (typeof object == "object") {
|
|
if (!exports.object.has(object, key)) {
|
|
if (arguments.length == 3)
|
|
return value;
|
|
throw new Error("KeyError: " + exports.repr(key));
|
|
}
|
|
return object[key];
|
|
}
|
|
throw new Error("Object does not have keys: " + exports.repr(object));
|
|
});
|
|
|
|
exports.set = exports.operator('set', 3, function (object, key, value) {
|
|
object[key] = value;
|
|
return object;
|
|
});
|
|
|
|
exports.getset = exports.operator('getset', 3, function (object, key, value) {
|
|
if (!exports.has(object, key))
|
|
exports.set(object, key, value);
|
|
return exports.get(object, key);
|
|
});
|
|
|
|
exports.del = exports.operator('del', 2, function (object, begin, end) {
|
|
if (exports.isArrayLike(object))
|
|
return exports.array.del(object, begin, end);
|
|
delete object[begin];
|
|
return object;
|
|
});
|
|
|
|
exports.cut = exports.operator('cut', 2, function (object, key) {
|
|
var result = exports.get(object, key);
|
|
exports.del(object, key);
|
|
return result;
|
|
});
|
|
|
|
exports.put = exports.operator('put', 2, function (object, key, value) {
|
|
if (exports.isArrayLike(object))
|
|
return exports.array.put(object, key, value);
|
|
return exports.set(object, key, value);
|
|
});
|
|
|
|
exports.first = exports.operator('first', 1, function (object) {
|
|
return object[0];
|
|
});
|
|
|
|
exports.last = exports.operator('last', 1, function (object) {
|
|
return object[object.length - 1];
|
|
});
|
|
|
|
exports.update = exports.operator('update', 2, function () {
|
|
var args = Array.prototype.slice.call(arguments);
|
|
return exports.object.update.apply(this, args);
|
|
});
|
|
|
|
exports.complete = exports.operator('complete', 2, function (target, source) {
|
|
var args = Array.prototype.slice.call(arguments);
|
|
return exports.object.complete.apply(this, args);
|
|
});
|
|
|
|
// TODO insert
|
|
// TODO remove
|
|
// TODO discard
|
|
|
|
exports.range = function () {
|
|
var start = 0, stop = 0, step = 1;
|
|
if (arguments.length == 1) {
|
|
stop = arguments[0];
|
|
} else {
|
|
start = arguments[0];
|
|
stop = arguments[1];
|
|
step = arguments[2] || 1;
|
|
}
|
|
var range = [];
|
|
for (var i = start; i < stop; i += step)
|
|
range.push(i);
|
|
return range;
|
|
};
|
|
|
|
exports.forEach = function (array, block) {
|
|
Array.prototype.forEach.call(
|
|
exports.array.coerce(array),
|
|
block
|
|
);
|
|
};
|
|
|
|
exports.forEachApply = function (array, block) {
|
|
Array.prototype.forEach.call(
|
|
exports.array.coerce(array),
|
|
exports.apply(block)
|
|
);
|
|
};
|
|
|
|
exports.map = function (array, block, context) {
|
|
return Array.prototype.map.call(
|
|
exports.array.coerce(array),
|
|
block,
|
|
context
|
|
);
|
|
};
|
|
|
|
exports.mapApply = function (array, block) {
|
|
return Array.prototype.map.call(
|
|
exports.array.coerce(array),
|
|
exports.apply(block)
|
|
);
|
|
};
|
|
|
|
exports.every = exports.operator('every', 2, function (array, block, context) {
|
|
return exports.all(exports.map(array, block, context));
|
|
});
|
|
|
|
exports.some = exports.operator('some', 2, function (array, block, context) {
|
|
return exports.any(exports.map(array, block, context));
|
|
});
|
|
|
|
exports.all = exports.operator('all', 1, function (array) {
|
|
array = exports.array.coerce(array);
|
|
for (var i = 0; i < array.length; i++)
|
|
if (!array[i])
|
|
return false;
|
|
return true;
|
|
});
|
|
|
|
exports.any = exports.operator('all', 1, function (array) {
|
|
array = exports.array.coerce(array);
|
|
for (var i = 0; i < array.length; i++)
|
|
if (array[i])
|
|
return true;
|
|
return false;
|
|
});
|
|
|
|
exports.reduce = exports.operator('reduce', 2, function (array, block, basis) {
|
|
array = exports.array.coerce(array);
|
|
return array.reduce.apply(array, arguments);
|
|
});
|
|
|
|
exports.reduceRight = exports.operator('reduceRight', 2, function (array, block, basis) {
|
|
array = exports.array.coerce(array);
|
|
return array.reduceRight.apply(array, arguments);
|
|
});
|
|
|
|
exports.zip = function () {
|
|
return exports.transpose(arguments);
|
|
};
|
|
|
|
exports.transpose = function (array) {
|
|
array = exports.array.coerce(array);
|
|
var transpose = [];
|
|
var length = Math.min.apply(this, exports.map(array, function (row) {
|
|
return row.length;
|
|
}));
|
|
for (var i = 0; i < array.length; i++) {
|
|
var row = array[i];
|
|
for (var j = 0; j < length; j++) {
|
|
var cell = row[j];
|
|
if (!transpose[j])
|
|
transpose[j] = [];
|
|
transpose[j][i] = cell;
|
|
}
|
|
}
|
|
return transpose;
|
|
};
|
|
|
|
exports.enumerate = function (array, start) {
|
|
array = exports.array.coerce(array);
|
|
if (exports.no(start))
|
|
start = 0;
|
|
return exports.zip(
|
|
exports.range(start, start + array.length),
|
|
array
|
|
);
|
|
};
|
|
|
|
// arithmetic, transitive, and logical operators
|
|
|
|
exports.is = function (a, b) {
|
|
return a === b;
|
|
};
|
|
|
|
exports.eq = exports.operator('eq', 2, function (a, b, stack) {
|
|
if (!stack)
|
|
stack = [];
|
|
if (a === b)
|
|
return true;
|
|
if (typeof a !== typeof b)
|
|
return false;
|
|
if (exports.no(a))
|
|
return exports.no(b);
|
|
if (typeof a == "date")
|
|
return a.valueOf() == b.valueOf();
|
|
if (typeof a == "regexp")
|
|
return a.source == b.source &&
|
|
a.global == b.global &&
|
|
a.ignoreCase == b.ignoreCase &&
|
|
a.multiline == b.multiline;
|
|
if (typeof a == "function") {
|
|
var caller = stack[stack.length - 1];
|
|
return caller !== Object &&
|
|
typeof caller != "undefined";
|
|
}
|
|
if (exports.isArrayLike(a))
|
|
return exports.array.eq(
|
|
a, b,
|
|
stack.concat([a.constructor])
|
|
);
|
|
if (typeof a == 'object')
|
|
return exports.object.eq(
|
|
a, b,
|
|
stack.concat([a.constructor])
|
|
);
|
|
return a == b;
|
|
});
|
|
|
|
exports.ne = exports.operator('ne', 2, function (a, b) {
|
|
return !exports.eq(a, b);
|
|
});
|
|
|
|
exports.lt = exports.operator('lt', 2, function (a, b) {
|
|
if (exports.no(a) != exports.no(b))
|
|
return exports.no(a) > exports.no(b);
|
|
if (exports.isArrayLike(a) && exports.isArrayLike(b))
|
|
return exports.array.lt(a, b);
|
|
return a < b;
|
|
});
|
|
|
|
exports.gt = exports.operator('gt', 2, function (a, b) {
|
|
return !(exports.lt(a, b) || exports.eq(a, b));
|
|
});
|
|
|
|
exports.le = exports.operator(2, 'le', function (a, b) {
|
|
return exports.lt(a, b) || exports.eq(a, b);
|
|
});
|
|
|
|
exports.ge = exports.operator(2, 'ge', function (a, b) {
|
|
return !exports.lt(a, b);
|
|
});
|
|
|
|
exports.mul = exports.operator(2, 'mul', function (a, b) {
|
|
if (typeof a == "string")
|
|
return exports.string.mul(a, b);
|
|
return a * b;
|
|
});
|
|
|
|
/*** by
|
|
returns a `comparator` that compares
|
|
values based on the values resultant from
|
|
a given `relation`.
|
|
accepts a `relation` and an optional comparator.
|
|
|
|
To sort a list of objects based on their
|
|
"a" key::
|
|
|
|
objects.sort(by(get("a")))
|
|
|
|
To get those in descending order::
|
|
|
|
objects.sort(by(get("a")), desc)
|
|
|
|
`by` returns a comparison function that also tracks
|
|
the arguments you used to construct it. This permits
|
|
`sort` and `sorted` to perform a Schwartzian transform
|
|
which can increase the performance of the sort
|
|
by a factor of 2.
|
|
*/
|
|
exports.by = function (relation) {
|
|
var compare = arguments[1];
|
|
if (exports.no(compare))
|
|
compare = exports.compare;
|
|
var comparator = function (a, b) {
|
|
a = relation(a);
|
|
b = relation(b);
|
|
return compare(a, b);
|
|
};
|
|
comparator.by = relation;
|
|
comparator.compare = compare;
|
|
return comparator;
|
|
};
|
|
|
|
exports.compare = exports.operator(2, 'compare', function (a, b) {
|
|
if (exports.no(a) != exports.no(b))
|
|
return exports.no(b) - exports.no(a);
|
|
if (typeof a == "number" && typeof b == "number")
|
|
return a - b;
|
|
return exports.eq(a, b) ? 0 : exports.lt(a, b) ? -1 : 1;
|
|
});
|
|
|
|
/*** sort
|
|
an in-place array sorter that uses a deep comparison
|
|
function by default (compare), and improves performance if
|
|
you provide a comparator returned by "by", using a
|
|
Schwartzian transform.
|
|
*/
|
|
exports.sort = function (array, compare) {
|
|
if (exports.no(compare))
|
|
compare = exports.compare;
|
|
if (compare.by) {
|
|
/* schwartzian transform */
|
|
array.splice.apply(
|
|
array,
|
|
[0, array.length].concat(
|
|
array.map(function (value) {
|
|
return [compare.by(value), value];
|
|
}).sort(function (a, b) {
|
|
return compare.compare(a[0], b[0]);
|
|
}).map(function (pair) {
|
|
return pair[1];
|
|
})
|
|
)
|
|
);
|
|
} else {
|
|
array.sort(compare);
|
|
}
|
|
return array;
|
|
};
|
|
|
|
/*** sorted
|
|
returns a sorted copy of an array using a deep
|
|
comparison function by default (compare), and
|
|
improves performance if you provide a comparator
|
|
returned by "by", using a Schwartzian transform.
|
|
*/
|
|
exports.sorted = function (array, compare) {
|
|
return exports.sort(exports.array.copy(array), compare);
|
|
};
|
|
|
|
exports.hash = exports.operator(1, 'hash', function (object) {
|
|
return '' + object;
|
|
});
|
|
|
|
exports.unique = exports.operator(1, 'unique', function (array, eq, hash) {
|
|
var visited = {};
|
|
if (!eq) eq = exports.eq;
|
|
if (!hash) hash = exports.hash;
|
|
return array.filter(function (value) {
|
|
var bucket = exports.getset(visited, hash(value), []);
|
|
var finds = bucket.filter(function (other) {
|
|
return eq(value, other);
|
|
});
|
|
if (!finds.length)
|
|
bucket.push(value);
|
|
return !finds.length;
|
|
});
|
|
});
|
|
|
|
// string
|
|
|
|
exports.string = exports.operator(1, 'toString', function (object) {
|
|
return '' + object;
|
|
});
|
|
|
|
exports.string.mul = function (string, n) {
|
|
return exports.range(n).map(function () {
|
|
return string;
|
|
}).join('');
|
|
};
|
|
|
|
/*** escape
|
|
escapes all characters of a string that are
|
|
special to JavaScript and many other languages.
|
|
Recognizes all of the relevant
|
|
control characters and formats all other
|
|
non-printable characters as Hex byte escape
|
|
sequences or Unicode escape sequences depending
|
|
on their size.
|
|
|
|
Pass ``true`` as an optional second argument and
|
|
``escape`` produces valid contents for escaped
|
|
JSON strings, wherein non-printable-characters are
|
|
all escaped with the Unicode ``\u`` notation.
|
|
*/
|
|
/* more Steve Levithan flagrence */
|
|
var escapeExpression = /[^ !#-[\]-~]/g;
|
|
/* from Doug Crockford's JSON library */
|
|
var escapePatterns = {
|
|
'\b': '\\b', '\t': '\\t',
|
|
'\n': '\\n', '\f': '\\f', '\r': '\\r',
|
|
'"' : '\\"', '\\': '\\\\'
|
|
};
|
|
exports.escape = function (value, strictJson) {
|
|
if (typeof value != "string")
|
|
throw new Error(
|
|
module.path +
|
|
"#escape: requires a string. got " +
|
|
exports.repr(value)
|
|
);
|
|
return value.replace(
|
|
escapeExpression,
|
|
function (match) {
|
|
if (escapePatterns[match])
|
|
return escapePatterns[match];
|
|
match = match.charCodeAt();
|
|
if (!strictJson && match < 256)
|
|
return "\\x" + exports.padBegin(match.toString(16), 2);
|
|
return '\\u' + exports.padBegin(match.toString(16), 4);
|
|
}
|
|
);
|
|
};
|
|
|
|
/*** enquote
|
|
transforms a string into a string literal, escaping
|
|
all characters of a string that are special to
|
|
JavaScript and and some other languages.
|
|
|
|
``enquote`` uses double quotes to be JSON compatible.
|
|
|
|
Pass ``true`` as an optional second argument to
|
|
be strictly JSON compliant, wherein all
|
|
non-printable-characters are represented with
|
|
Unicode escape sequences.
|
|
*/
|
|
exports.enquote = function (value, strictJson) {
|
|
return '"' + exports.escape(value, strictJson) + '"';
|
|
};
|
|
|
|
/*** expand
|
|
transforms tabs to an equivalent number of spaces.
|
|
*/
|
|
// TODO special case for \r if it ever matters
|
|
exports.expand = function (str, tabLength) {
|
|
str = String(str);
|
|
tabLength = tabLength || 4;
|
|
var output = [],
|
|
tabLf = /[\t\n]/g,
|
|
lastLastIndex = 0,
|
|
lastLfIndex = 0,
|
|
charsAddedThisLine = 0,
|
|
tabOffset, match;
|
|
while (match = tabLf.exec(str)) {
|
|
if (match[0] == "\t") {
|
|
tabOffset = (
|
|
tabLength - 1 -
|
|
(
|
|
(match.index - lastLfIndex) +
|
|
charsAddedThisLine
|
|
) % tabLength
|
|
);
|
|
charsAddedThisLine += tabOffset;
|
|
output.push(
|
|
str.slice(lastLastIndex, match.index) +
|
|
exports.mul(" ", tabOffset + 1)
|
|
);
|
|
} else if (match[0] === "\n") {
|
|
output.push(str.slice(lastLastIndex, tabLf.lastIndex));
|
|
lastLfIndex = tabLf.lastIndex;
|
|
charsAddedThisLine = 0;
|
|
}
|
|
lastLastIndex = tabLf.lastIndex;
|
|
}
|
|
return output.join("") + str.slice(lastLastIndex);
|
|
};
|
|
|
|
var trimBeginExpression = /^\s\s*/g;
|
|
exports.trimBegin = function (value) {
|
|
return String(value).replace(trimBeginExpression, "");
|
|
};
|
|
|
|
var trimEndExpression = /\s\s*$/g;
|
|
exports.trimEnd = function (value) {
|
|
return String(value).replace(trimEndExpression, "");
|
|
};
|
|
|
|
exports.trim = function (value) {
|
|
return String(value).replace(trimBeginExpression, "").replace(trimEndExpression, "");
|
|
};
|
|
|
|
/* generates padBegin and padEnd */
|
|
var augmentor = function (augment) {
|
|
return function (value, length, pad) {
|
|
if (exports.no(pad)) pad = '0';
|
|
if (exports.no(length)) length = 2;
|
|
value = String(value);
|
|
while (value.length < length) {
|
|
value = augment(value, pad);
|
|
}
|
|
return value;
|
|
};
|
|
};
|
|
|
|
/*** padBegin
|
|
|
|
accepts:
|
|
- a `String` or `Number` value
|
|
- a minimum length of the resultant `String`:
|
|
by default, 2
|
|
- a pad string: by default, ``'0'``.
|
|
|
|
returns a `String` of the value padded up to at least
|
|
the minimum length. adds the padding to the begining
|
|
side of the `String`.
|
|
|
|
*/
|
|
exports.padBegin = augmentor(function (value, pad) {
|
|
return pad + value;
|
|
});
|
|
|
|
/*** padEnd
|
|
|
|
accepts:
|
|
- a `String` or `Number` value
|
|
- a minimum length of the resultant `String`:
|
|
by default, 2
|
|
- a pad string: by default, ``'0'``.
|
|
|
|
returns a `String` of the value padded up to at least
|
|
the minimum length. adds the padding to the end
|
|
side of the `String`.
|
|
|
|
*/
|
|
exports.padEnd = augmentor(function (value, pad) {
|
|
return value + pad;
|
|
});
|
|
|
|
/*** splitName
|
|
splits a string into an array of words from an original
|
|
string.
|
|
*/
|
|
// thanks go to Steve Levithan for this regular expression
|
|
// that, in addition to splitting any normal-form identifier
|
|
// in any case convention, splits XMLHttpRequest into
|
|
// "XML", "Http", and "Request"
|
|
var splitNameExpression = /[a-z]+|[A-Z](?:[a-z]+|[A-Z]*(?![a-z]))|[.\d]+/g;
|
|
exports.splitName = function (value) {
|
|
return String(value).match(splitNameExpression);
|
|
};
|
|
|
|
/*** joinName
|
|
joins a list of words with a given delimiter
|
|
between alphanumeric words.
|
|
*/
|
|
exports.joinName = function (delimiter, parts) {
|
|
if (exports.no(delimiter)) delimiter = '_';
|
|
parts.unshift([]);
|
|
return parts.reduce(function (parts, part) {
|
|
if (
|
|
part.match(/\d/) &&
|
|
exports.len(parts) && parts[parts.length-1].match(/\d/)
|
|
) {
|
|
return parts.concat([delimiter + part]);
|
|
} else {
|
|
return parts.concat([part]);
|
|
}
|
|
}).join('');
|
|
};
|
|
|
|
/*** upper
|
|
converts a name to ``UPPER CASE`` using
|
|
a given delimiter between numeric words.
|
|
|
|
see:
|
|
- `lower`
|
|
- `camel`
|
|
- `title`
|
|
|
|
*/
|
|
exports.upper = function (value, delimiter) {
|
|
if (exports.no(delimiter))
|
|
return value.toUpperCase();
|
|
return exports.splitName(value).map(function (part) {
|
|
return part.toUpperCase();
|
|
}).join(delimiter);
|
|
};
|
|
|
|
/*** lower
|
|
converts a name to a ``lower case`` using
|
|
a given delimiter between numeric words.
|
|
|
|
see:
|
|
- `upper`
|
|
- `camel`
|
|
- `title`
|
|
|
|
*/
|
|
exports.lower = function (value, delimiter) {
|
|
if (exports.no(delimiter))
|
|
return String(value).toLowerCase();
|
|
return exports.splitName(value).map(function (part) {
|
|
return part.toLowerCase();
|
|
}).join(delimiter);
|
|
};
|
|
|
|
/*** camel
|
|
converts a name to ``camel Case`` using
|
|
a given delimiter between numeric words.
|
|
|
|
see:
|
|
- `lower`
|
|
- `upper`
|
|
- `title`
|
|
|
|
*/
|
|
exports.camel = function (value, delimiter) {
|
|
return exports.joinName(
|
|
delimiter,
|
|
exports.mapApply(
|
|
exports.enumerate(exports.splitName(value)),
|
|
function (n, part) {
|
|
if (n) {
|
|
return (
|
|
part.substring(0, 1).toUpperCase() +
|
|
part.substring(1).toLowerCase()
|
|
);
|
|
} else {
|
|
return part.toLowerCase();
|
|
}
|
|
}
|
|
)
|
|
);
|
|
};
|
|
|
|
/*** title
|
|
converts a name to ``Title Case`` using
|
|
a given delimiter between numeric words.
|
|
|
|
see:
|
|
- `lower`
|
|
- `upper`
|
|
- `camel`
|
|
|
|
*/
|
|
exports.title = function (value, delimiter) {
|
|
return exports.joinName(
|
|
delimiter,
|
|
exports.splitName(value).map(function (part) {
|
|
return (
|
|
part.substring(0, 1).toUpperCase() +
|
|
part.substring(1).toLowerCase()
|
|
);
|
|
})
|
|
);
|
|
};
|
|
|
|
|