585 lines
14 KiB
JavaScript
Executable file
585 lines
14 KiB
JavaScript
Executable file
// 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);
|
|
});
|
|
};
|
|
};
|