| // This is (almost) directly from Node.js utils |
| // https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/util.js |
| |
| var getName = require('get-func-name'); |
| var getProperties = require('./getProperties'); |
| var getEnumerableProperties = require('./getEnumerableProperties'); |
| var config = require('../config'); |
| |
| module.exports = inspect; |
| |
| /** |
| * ### .inspect(obj, [showHidden], [depth], [colors]) |
| * |
| * Echoes the value of a value. Tries to print the value out |
| * in the best way possible given the different types. |
| * |
| * @param {Object} obj The object to print out. |
| * @param {Boolean} showHidden Flag that shows hidden (not enumerable) |
| * properties of objects. Default is false. |
| * @param {Number} depth Depth in which to descend in object. Default is 2. |
| * @param {Boolean} colors Flag to turn on ANSI escape codes to color the |
| * output. Default is false (no coloring). |
| * @namespace Utils |
| * @name inspect |
| */ |
| function inspect(obj, showHidden, depth, colors) { |
| var ctx = { |
| showHidden: showHidden, |
| seen: [], |
| stylize: function (str) { return str; } |
| }; |
| return formatValue(ctx, obj, (typeof depth === 'undefined' ? 2 : depth)); |
| } |
| |
| // Returns true if object is a DOM element. |
| var isDOMElement = function (object) { |
| if (typeof HTMLElement === 'object') { |
| return object instanceof HTMLElement; |
| } else { |
| return object && |
| typeof object === 'object' && |
| 'nodeType' in object && |
| object.nodeType === 1 && |
| typeof object.nodeName === 'string'; |
| } |
| }; |
| |
| function formatValue(ctx, value, recurseTimes) { |
| // Provide a hook for user-specified inspect functions. |
| // Check that value is an object with an inspect function on it |
| if (value && typeof value.inspect === 'function' && |
| // Filter out the util module, it's inspect function is special |
| value.inspect !== exports.inspect && |
| // Also filter out any prototype objects using the circular check. |
| !(value.constructor && value.constructor.prototype === value)) { |
| var ret = value.inspect(recurseTimes, ctx); |
| if (typeof ret !== 'string') { |
| ret = formatValue(ctx, ret, recurseTimes); |
| } |
| return ret; |
| } |
| |
| // Primitive types cannot have properties |
| var primitive = formatPrimitive(ctx, value); |
| if (primitive) { |
| return primitive; |
| } |
| |
| // If this is a DOM element, try to get the outer HTML. |
| if (isDOMElement(value)) { |
| if ('outerHTML' in value) { |
| return value.outerHTML; |
| // This value does not have an outerHTML attribute, |
| // it could still be an XML element |
| } else { |
| // Attempt to serialize it |
| try { |
| if (document.xmlVersion) { |
| var xmlSerializer = new XMLSerializer(); |
| return xmlSerializer.serializeToString(value); |
| } else { |
| // Firefox 11- do not support outerHTML |
| // It does, however, support innerHTML |
| // Use the following to render the element |
| var ns = "http://www.w3.org/1999/xhtml"; |
| var container = document.createElementNS(ns, '_'); |
| |
| container.appendChild(value.cloneNode(false)); |
| var html = container.innerHTML |
| .replace('><', '>' + value.innerHTML + '<'); |
| container.innerHTML = ''; |
| return html; |
| } |
| } catch (err) { |
| // This could be a non-native DOM implementation, |
| // continue with the normal flow: |
| // printing the element as if it is an object. |
| } |
| } |
| } |
| |
| // Look up the keys of the object. |
| var visibleKeys = getEnumerableProperties(value); |
| var keys = ctx.showHidden ? getProperties(value) : visibleKeys; |
| |
| var name, nameSuffix; |
| |
| // Some type of object without properties can be shortcut. |
| // In IE, errors have a single `stack` property, or if they are vanilla `Error`, |
| // a `stack` plus `description` property; ignore those for consistency. |
| if (keys.length === 0 || (isError(value) && ( |
| (keys.length === 1 && keys[0] === 'stack') || |
| (keys.length === 2 && keys[0] === 'description' && keys[1] === 'stack') |
| ))) { |
| if (typeof value === 'function') { |
| name = getName(value); |
| nameSuffix = name ? ': ' + name : ''; |
| return ctx.stylize('[Function' + nameSuffix + ']', 'special'); |
| } |
| if (isRegExp(value)) { |
| return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); |
| } |
| if (isDate(value)) { |
| return ctx.stylize(Date.prototype.toUTCString.call(value), 'date'); |
| } |
| if (isError(value)) { |
| return formatError(value); |
| } |
| } |
| |
| var base = '' |
| , array = false |
| , typedArray = false |
| , braces = ['{', '}']; |
| |
| if (isTypedArray(value)) { |
| typedArray = true; |
| braces = ['[', ']']; |
| } |
| |
| // Make Array say that they are Array |
| if (isArray(value)) { |
| array = true; |
| braces = ['[', ']']; |
| } |
| |
| // Make functions say that they are functions |
| if (typeof value === 'function') { |
| name = getName(value); |
| nameSuffix = name ? ': ' + name : ''; |
| base = ' [Function' + nameSuffix + ']'; |
| } |
| |
| // Make RegExps say that they are RegExps |
| if (isRegExp(value)) { |
| base = ' ' + RegExp.prototype.toString.call(value); |
| } |
| |
| // Make dates with properties first say the date |
| if (isDate(value)) { |
| base = ' ' + Date.prototype.toUTCString.call(value); |
| } |
| |
| // Make error with message first say the error |
| if (isError(value)) { |
| return formatError(value); |
| } |
| |
| if (keys.length === 0 && (!array || value.length == 0)) { |
| return braces[0] + base + braces[1]; |
| } |
| |
| if (recurseTimes < 0) { |
| if (isRegExp(value)) { |
| return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); |
| } else { |
| return ctx.stylize('[Object]', 'special'); |
| } |
| } |
| |
| ctx.seen.push(value); |
| |
| var output; |
| if (array) { |
| output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); |
| } else if (typedArray) { |
| return formatTypedArray(value); |
| } else { |
| output = keys.map(function(key) { |
| return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); |
| }); |
| } |
| |
| ctx.seen.pop(); |
| |
| return reduceToSingleString(output, base, braces); |
| } |
| |
| function formatPrimitive(ctx, value) { |
| switch (typeof value) { |
| case 'undefined': |
| return ctx.stylize('undefined', 'undefined'); |
| |
| case 'string': |
| var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') |
| .replace(/'/g, "\\'") |
| .replace(/\\"/g, '"') + '\''; |
| return ctx.stylize(simple, 'string'); |
| |
| case 'number': |
| if (value === 0 && (1/value) === -Infinity) { |
| return ctx.stylize('-0', 'number'); |
| } |
| return ctx.stylize('' + value, 'number'); |
| |
| case 'boolean': |
| return ctx.stylize('' + value, 'boolean'); |
| |
| case 'symbol': |
| return ctx.stylize(value.toString(), 'symbol'); |
| } |
| // For some reason typeof null is "object", so special case here. |
| if (value === null) { |
| return ctx.stylize('null', 'null'); |
| } |
| } |
| |
| function formatError(value) { |
| return '[' + Error.prototype.toString.call(value) + ']'; |
| } |
| |
| function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { |
| var output = []; |
| for (var i = 0, l = value.length; i < l; ++i) { |
| if (Object.prototype.hasOwnProperty.call(value, String(i))) { |
| output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, |
| String(i), true)); |
| } else { |
| output.push(''); |
| } |
| } |
| |
| keys.forEach(function(key) { |
| if (!key.match(/^\d+$/)) { |
| output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, |
| key, true)); |
| } |
| }); |
| return output; |
| } |
| |
| function formatTypedArray(value) { |
| var str = '[ '; |
| |
| for (var i = 0; i < value.length; ++i) { |
| if (str.length >= config.truncateThreshold - 7) { |
| str += '...'; |
| break; |
| } |
| str += value[i] + ', '; |
| } |
| str += ' ]'; |
| |
| // Removing trailing `, ` if the array was not truncated |
| if (str.indexOf(', ]') !== -1) { |
| str = str.replace(', ]', ' ]'); |
| } |
| |
| return str; |
| } |
| |
| function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { |
| var name; |
| var propDescriptor = Object.getOwnPropertyDescriptor(value, key); |
| var str; |
| |
| if (propDescriptor) { |
| if (propDescriptor.get) { |
| if (propDescriptor.set) { |
| str = ctx.stylize('[Getter/Setter]', 'special'); |
| } else { |
| str = ctx.stylize('[Getter]', 'special'); |
| } |
| } else { |
| if (propDescriptor.set) { |
| str = ctx.stylize('[Setter]', 'special'); |
| } |
| } |
| } |
| if (visibleKeys.indexOf(key) < 0) { |
| name = '[' + key + ']'; |
| } |
| if (!str) { |
| if (ctx.seen.indexOf(value[key]) < 0) { |
| if (recurseTimes === null) { |
| str = formatValue(ctx, value[key], null); |
| } else { |
| str = formatValue(ctx, value[key], recurseTimes - 1); |
| } |
| if (str.indexOf('\n') > -1) { |
| if (array) { |
| str = str.split('\n').map(function(line) { |
| return ' ' + line; |
| }).join('\n').substr(2); |
| } else { |
| str = '\n' + str.split('\n').map(function(line) { |
| return ' ' + line; |
| }).join('\n'); |
| } |
| } |
| } else { |
| str = ctx.stylize('[Circular]', 'special'); |
| } |
| } |
| if (typeof name === 'undefined') { |
| if (array && key.match(/^\d+$/)) { |
| return str; |
| } |
| name = JSON.stringify('' + key); |
| if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { |
| name = name.substr(1, name.length - 2); |
| name = ctx.stylize(name, 'name'); |
| } else { |
| name = name.replace(/'/g, "\\'") |
| .replace(/\\"/g, '"') |
| .replace(/(^"|"$)/g, "'"); |
| name = ctx.stylize(name, 'string'); |
| } |
| } |
| |
| return name + ': ' + str; |
| } |
| |
| function reduceToSingleString(output, base, braces) { |
| var length = output.reduce(function(prev, cur) { |
| return prev + cur.length + 1; |
| }, 0); |
| |
| if (length > 60) { |
| return braces[0] + |
| (base === '' ? '' : base + '\n ') + |
| ' ' + |
| output.join(',\n ') + |
| ' ' + |
| braces[1]; |
| } |
| |
| return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; |
| } |
| |
| function isTypedArray(ar) { |
| // Unfortunately there's no way to check if an object is a TypedArray |
| // We have to check if it's one of these types |
| return (typeof ar === 'object' && /\w+Array]$/.test(objectToString(ar))); |
| } |
| |
| function isArray(ar) { |
| return Array.isArray(ar) || |
| (typeof ar === 'object' && objectToString(ar) === '[object Array]'); |
| } |
| |
| function isRegExp(re) { |
| return typeof re === 'object' && objectToString(re) === '[object RegExp]'; |
| } |
| |
| function isDate(d) { |
| return typeof d === 'object' && objectToString(d) === '[object Date]'; |
| } |
| |
| function isError(e) { |
| return typeof e === 'object' && objectToString(e) === '[object Error]'; |
| } |
| |
| function objectToString(o) { |
| return Object.prototype.toString.call(o); |
| } |