// 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); }); }; };