blob: 806260d25eb0eec5e0bdfaab30f426d25a578b7e [file] [log] [blame]
// Load modules
var Fs = require('fs');
var Escape = require('./escape');
// Declare internals
var internals = {};
// Clone object or array
exports.clone = function (obj, seen) {
if (typeof obj !== 'object' ||
obj === null) {
return obj;
}
seen = seen || { orig: [], copy: [] };
var lookup = seen.orig.indexOf(obj);
if (lookup !== -1) {
return seen.copy[lookup];
}
var newObj = (obj instanceof Array) ? [] : {};
seen.orig.push(obj);
seen.copy.push(newObj);
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
if (obj[i] instanceof Buffer) {
newObj[i] = new Buffer(obj[i]);
}
else if (obj[i] instanceof Date) {
newObj[i] = new Date(obj[i].getTime());
}
else if (obj[i] instanceof RegExp) {
var flags = '' + (obj[i].global ? 'g' : '') + (obj[i].ignoreCase ? 'i' : '') + (obj[i].multiline ? 'm' : '');
newObj[i] = new RegExp(obj[i].source, flags);
}
else {
newObj[i] = exports.clone(obj[i], seen);
}
}
}
return newObj;
};
// Merge all the properties of source into target, source wins in conflic, and by default null and undefined from source are applied
exports.merge = function (target, source, isNullOverride /* = true */, isMergeArrays /* = true */) {
exports.assert(target && typeof target == 'object', 'Invalid target value: must be an object');
exports.assert(source === null || source === undefined || typeof source === 'object', 'Invalid source value: must be null, undefined, or an object');
if (!source) {
return target;
}
if (source instanceof Array) {
exports.assert(target instanceof Array, 'Cannot merge array onto an object');
if (isMergeArrays === false) { // isMergeArrays defaults to true
target.length = 0; // Must not change target assignment
}
for (var i = 0, il = source.length; i < il; ++i) {
target.push(source[i]);
}
return target;
}
var keys = Object.keys(source);
for (var k = 0, kl = keys.length; k < kl; ++k) {
var key = keys[k];
var value = source[key];
if (value &&
typeof value === 'object') {
if (!target[key] ||
typeof target[key] !== 'object') {
target[key] = exports.clone(value);
}
else {
exports.merge(target[key], source[key], isNullOverride, isMergeArrays);
}
}
else {
if (value !== null && value !== undefined) { // Explicit to preserve empty strings
target[key] = value;
}
else if (isNullOverride !== false) { // Defaults to true
target[key] = value;
}
}
}
return target;
};
// Apply options to a copy of the defaults
exports.applyToDefaults = function (defaults, options) {
exports.assert(defaults && typeof defaults == 'object', 'Invalid defaults value: must be an object');
exports.assert(!options || options === true || typeof options === 'object', 'Invalid options value: must be true, falsy or an object');
if (!options) { // If no options, return null
return null;
}
var copy = exports.clone(defaults);
if (options === true) { // If options is set to true, use defaults
return copy;
}
return exports.merge(copy, options, false, false);
};
// Remove duplicate items from array
exports.unique = function (array, key) {
var index = {};
var result = [];
for (var i = 0, il = array.length; i < il; ++i) {
var id = (key ? array[i][key] : array[i]);
if (index[id] !== true) {
result.push(array[i]);
index[id] = true;
}
}
return result;
};
// Convert array into object
exports.mapToObject = function (array, key) {
if (!array) {
return null;
}
var obj = {};
for (var i = 0, il = array.length; i < il; ++i) {
if (key) {
if (array[i][key]) {
obj[array[i][key]] = true;
}
}
else {
obj[array[i]] = true;
}
}
return obj;
};
// Find the common unique items in two arrays
exports.intersect = function (array1, array2, justFirst) {
if (!array1 || !array2) {
return [];
}
var common = [];
var hash = (array1 instanceof Array ? exports.mapToObject(array1) : array1);
var found = {};
for (var i = 0, il = array2.length; i < il; ++i) {
if (hash[array2[i]] && !found[array2[i]]) {
if (justFirst) {
return array2[i];
}
common.push(array2[i]);
found[array2[i]] = true;
}
}
return (justFirst ? null : common);
};
// Find which keys are present
exports.matchKeys = function (obj, keys) {
var matched = [];
for (var i = 0, il = keys.length; i < il; ++i) {
if (obj.hasOwnProperty(keys[i])) {
matched.push(keys[i]);
}
}
return matched;
};
// Flatten array
exports.flatten = function (array, target) {
var result = target || [];
for (var i = 0, il = array.length; i < il; ++i) {
if (Array.isArray(array[i])) {
exports.flatten(array[i], result);
}
else {
result.push(array[i]);
}
}
return result;
};
// Remove keys
exports.removeKeys = function (object, keys) {
for (var i = 0, il = keys.length; i < il; i++) {
delete object[keys[i]];
}
};
// Convert an object key chain string ('a.b.c') to reference (object[a][b][c])
exports.reach = function (obj, chain) {
var path = chain.split('.');
var ref = obj;
for (var i = 0, il = path.length; i < il; ++i) {
if (ref) {
ref = ref[path[i]];
}
}
return ref;
};
// Inherits a selected set of methods from an object, wrapping functions in asynchronous syntax and catching errors
exports.inheritAsync = function (self, obj, keys) {
keys = keys || null;
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
if (keys instanceof Array &&
keys.indexOf(i) < 0) {
continue;
}
self.prototype[i] = (function (fn) {
return function (next) {
var result = null;
try {
result = fn();
}
catch (err) {
return next(err);
}
return next(null, result);
};
})(obj[i]);
}
}
};
exports.formatStack = function (stack) {
var trace = [];
for (var i = 0, il = stack.length; i < il; ++i) {
var item = stack[i];
trace.push([item.getFileName(), item.getLineNumber(), item.getColumnNumber(), item.getFunctionName(), item.isConstructor()]);
}
return trace;
};
exports.formatTrace = function (trace) {
var display = [];
for (var i = 0, il = trace.length; i < il; ++i) {
var row = trace[i];
display.push((row[4] ? 'new ' : '') + row[3] + ' (' + row[0] + ':' + row[1] + ':' + row[2] + ')');
}
return display;
};
exports.callStack = function (slice) {
// http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
var v8 = Error.prepareStackTrace;
Error.prepareStackTrace = function (err, stack) {
return stack;
};
var capture = {};
Error.captureStackTrace(capture, arguments.callee);
var stack = capture.stack;
Error.prepareStackTrace = v8;
var trace = exports.formatStack(stack);
if (slice) {
return trace.slice(slice);
}
return trace;
};
exports.displayStack = function (slice) {
var trace = exports.callStack(slice === undefined ? 1 : slice + 1);
return exports.formatTrace(trace);
};
exports.abortThrow = false;
exports.abort = function (message, hideStack) {
if (process.env.NODE_ENV === 'test' || exports.abortThrow === true) {
throw new Error(message || 'Unknown error');
}
var stack = '';
if (!hideStack) {
stack = exports.displayStack(1).join('\n\t');
}
console.log('ABORT: ' + message + '\n\t' + stack);
process.exit(1);
};
exports.assert = function (condition /*, msg1, msg2, msg3 */) {
if (condition) {
return;
}
var msgs = Array.prototype.slice.call(arguments, 1);
msgs = msgs.map(function (msg) {
return typeof msg === 'string' ? msg : msg instanceof Error ? msg.message : JSON.stringify(msg);
});
throw new Error(msgs.join(' ') || 'Unknown error');
};
exports.loadDirModules = function (path, excludeFiles, target) { // target(filename, name, capName)
var exclude = {};
for (var i = 0, il = excludeFiles.length; i < il; ++i) {
exclude[excludeFiles[i] + '.js'] = true;
}
var files = Fs.readdirSync(path);
for (i = 0, il = files.length; i < il; ++i) {
var filename = files[i];
if (/\.js$/.test(filename) &&
!exclude[filename]) {
var name = filename.substr(0, filename.lastIndexOf('.'));
var capName = name.charAt(0).toUpperCase() + name.substr(1).toLowerCase();
if (typeof target !== 'function') {
target[capName] = require(path + '/' + name);
}
else {
target(path + '/' + name, name, capName);
}
}
}
};
exports.rename = function (obj, from, to) {
obj[to] = obj[from];
delete obj[from];
};
exports.Timer = function () {
this.reset();
};
exports.Timer.prototype.reset = function () {
this.ts = Date.now();
};
exports.Timer.prototype.elapsed = function () {
return Date.now() - this.ts;
};
// Load and parse package.json process root or given directory
exports.loadPackage = function (dir) {
var result = {};
var filepath = (dir || process.env.PWD) + '/package.json';
if (Fs.existsSync(filepath)) {
try {
result = JSON.parse(Fs.readFileSync(filepath));
}
catch (e) { }
}
return result;
};
// Escape string for Regex construction
exports.escapeRegex = function (string) {
// Escape ^$.*+-?=!:|\/()[]{},
return string.replace(/[\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}\,]/g, '\\$&');
};
// Return an error as first argument of a callback
exports.toss = function (condition /*, [message], next */) {
var message = (arguments.length === 3 ? arguments[1] : '');
var next = (arguments.length === 3 ? arguments[2] : arguments[1]);
var err = (message instanceof Error ? message : (message ? new Error(message) : (condition instanceof Error ? condition : new Error())));
if (condition instanceof Error ||
!condition) {
return next(err);
}
};
// Base64url (RFC 4648) encode
exports.base64urlEncode = function (value) {
return (new Buffer(value, 'binary')).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
};
// Base64url (RFC 4648) decode
exports.base64urlDecode = function (encoded) {
if (encoded &&
!encoded.match(/^[\w\-]*$/)) {
return new Error('Invalid character');
}
try {
return (new Buffer(encoded.replace(/-/g, '+').replace(/:/g, '/'), 'base64')).toString('binary');
}
catch (err) {
return err;
}
};
// Escape attribute value for use in HTTP header
exports.escapeHeaderAttribute = function (attribute) {
// Allowed value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9, \, "
exports.assert(attribute.match(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~\"\\]*$/), 'Bad attribute value (' + attribute + ')');
return attribute.replace(/\\/g, '\\\\').replace(/\"/g, '\\"'); // Escape quotes and slash
};
exports.escapeHtml = function (string) {
return Escape.escapeHtml(string);
};
exports.escapeJavaScript = function (string) {
return Escape.escapeJavaScript(string);
};
/*
var event = {
timestamp: now.getTime(),
tags: ['tag'],
data: { some: 'data' }
};
*/
exports.consoleFunc = console.log;
exports.printEvent = function (event) {
var pad = function (value) {
return (value < 10 ? '0' : '') + value;
};
var now = new Date(event.timestamp);
var timestring = (now.getYear() - 100).toString() +
pad(now.getMonth() + 1) +
pad(now.getDate()) +
'/' +
pad(now.getHours()) +
pad(now.getMinutes()) +
pad(now.getSeconds()) +
'.' +
now.getMilliseconds();
var data = event.data;
if (typeof event.data !== 'string') {
try {
data = JSON.stringify(event.data);
}
catch (e) {
data = 'JSON Error: ' + e.message;
}
}
var output = timestring + ', ' + event.tags[0] + ', ' + data;
exports.consoleFunc(output);
};
exports.nextTick = function (callback) {
return function () {
var args = arguments;
process.nextTick(function () {
callback.apply(null, args);
});
};
};