diff --git a/README.md b/README.md index 7a92158..c4f6f76 100644 --- a/README.md +++ b/README.md @@ -43,18 +43,19 @@ customization ------------- These are the default styles and settings used by _eyes_. + styles: { // Styles applied to stdout all: 'cyan', // Overall style applied to everything label: 'underline', // Inspection labels, like 'array' in `array: [1, 2, 3]` other: 'inverted', // Objects which don't have a literal representation, such as functions key: 'bold', // The keys in object literals, like 'a' in `{a: 1}` - special: 'grey', // null, undefined... string: 'green', number: 'magenta', bool: 'blue', // true false regexp: 'green', // /\d+/ }, + pretty: true, // Indent object literals hideFunctions: false, // Don't output functions at all stream: process.stdout, // Stream to write to, or null diff --git a/lib/eyes.js b/lib/eyes.js index 23caafe..43bde8c 100644 --- a/lib/eyes.js +++ b/lib/eyes.js @@ -27,17 +27,30 @@ eyes.defaults = { regexp: 'green', // /\d+/ }, pretty: true, // Indent object literals + indent: 4, hideFunctions: false, - stream: process.stdout, - maxLength: 2048 // Truncate output if longer + showHidden: false, + sortKeys: false, + stream: process && process.stdout, + maxLength: 2048, // Truncate output if longer + colors: true, + html: false, + json: false, + escape: true, + functions: false }; // Return a curried inspect() function, with the `options` argument filled in. eyes.inspector = function (options) { var that = this; return function (obj, label, opts) { - return that.inspect.call(that, obj, label, - merge(options || {}, opts || {})); + var myopts=merge(options || {}, opts || {}); + var result=that.inspect.call(that, obj, label, myopts); + if (myopts.html && !myopts.stream) { + return '
'+result+''; + } else { + return result; + } }; }; @@ -45,11 +58,11 @@ eyes.inspector = function (options) { // if not, we just return the stringified object. eyes.inspect = function (obj, label, options) { options = merge(this.defaults, options || {}); - + stack = []; if (options.stream) { return this.print(stringify(obj, options), label, options); } else { - return stringify(obj, options) + (options.styles ? '\033[39m' : ''); + return stringify(obj, options) + (options.styles ? (options.html?'':(options.colors?'\033[39m':'')) : ''); } }; @@ -61,41 +74,63 @@ eyes.inspect = function (obj, label, options) { // versus a printable character (`c`). So we resort to counting the // length manually. eyes.print = function (str, label, options) { - for (var c = 0, i = 0; i < str.length; i++) { - if (str.charAt(i) === '\033') { i += 4 } // `4` because '\033[25m'.length + 1 == 5 - else if (c === options.maxLength) { - str = str.slice(0, i - 1) + '…'; - break; - } else { c++ } + if (!this.html) { + for (var c = 0, i = 0; i < str.length; i++) { + if (str.charAt(i) === '\033') { i += 4 } // `4` because '\033[25m'.length + 1 == 5 + else if (c === options.maxLength) { + str = str.slice(0, i - 1) + '…'; + break; + } else { c++ } + } } return options.stream.write.call(options.stream, (label ? - this.stylize(label, options.styles.label, options.styles) + ': ' : '') + - this.stylize(str, options.styles.all, options.styles) + '\033[0m' + "\n"); + this.stylize(label, options.styles.label, options) + ': ' : '') + + this.stylize(str, options.styles.all, options) + (this.html?'':(options.colors?'\033[0m':'')) + "\n"); }; // Apply a style to a string, eventually, // I'd like this to support passing multiple // styles. -eyes.stylize = function (str, style, styles) { - var codes = { - 'bold' : [1, 22], - 'underline' : [4, 24], - 'inverse' : [7, 27], - 'cyan' : [36, 39], - 'magenta' : [35, 39], - 'blue' : [34, 39], - 'yellow' : [33, 39], - 'green' : [32, 39], - 'red' : [31, 39], - 'grey' : [90, 39] - }, endCode; - - if (style && codes[style]) { - endCode = (codes[style][1] === 39 && styles.all) ? codes[styles.all][0] - : codes[style][1]; - return '\033[' + codes[style][0] + 'm' + str + - '\033[' + endCode + 'm'; - } else { return str } +eyes.stylize = function (str, style, options) { + if (options.colors) { + if (options.html) { + var codes = { + 'bold' : 'font-weight:bold', + 'underline' : 'text-decoration: underline', + }; + if (style) { + if (codes[style]) { + return ''+str+''; + } else { + return ''+str+''; + } + } else { + return str; + } + } else { + var codes = { + 'bold' : [1, 22], + 'underline' : [4, 24], + 'inverse' : [7, 27], + 'cyan' : [36, 39], + 'magenta' : [35, 39], + 'blue' : [34, 39], + 'yellow' : [33, 39], + 'green' : [32, 39], + 'red' : [31, 39], + 'grey' : [90, 39] + }, endCode; + + if (style && codes[style]) { + endCode = (codes[style][1] === 39 && options.styles.all) ? codes[options.styles.all][0] + : codes[style][1]; + return '\033[' + codes[style][0] + 'm' + str + + '\033[' + endCode + 'm'; + } else { return str } + } + } else { + return str; + } }; // Convert any object to a string, ready for output. @@ -104,7 +139,7 @@ eyes.stylize = function (str, style, styles) { // stringify(). function stringify(obj, options) { var that = this, stylize = function (str, style) { - return eyes.stylize(str, options.styles[style], options.styles) + return eyes.stylize(str, options.styles[style], options) }, index, result; if ((index = stack.indexOf(obj)) !== -1) { @@ -114,16 +149,16 @@ function stringify(obj, options) { result = (function (obj) { switch (typeOf(obj)) { - case "string" : obj = stringifyString(obj.indexOf("'") === -1 ? "'" + obj + "'" - : '"' + obj + '"'); + + case "string" : obj = stringifyString((obj.indexOf("'") === -1 && !options.json)? ("'" + obj.replace(/'/g,"\\'") + "'") : ('"' + obj.replace(/"/g,'\\"') + '"'),options); return stylize(obj, 'string'); case "regexp" : return stylize('/' + obj.source + '/', 'regexp'); case "number" : return stylize(obj + '', 'number'); - case "function" : return options.stream ? stylize("Function", 'other') : '[Function]'; + case "function" : return options.stream ? stylize(options.functions?obj.toString():"Function", 'other') : '[Function]'; case "null" : return stylize("null", 'special'); case "undefined": return stylize("undefined", 'special'); case "boolean" : return stylize(obj + '', 'bool'); - case "date" : return stylize(obj.toUTCString()); + case "date" : return stylize(obj.toUTCString(),'date'); case "array" : return stringifyArray(obj, options, stack.length); case "object" : return stringifyObject(obj, options, stack.length); } @@ -133,13 +168,73 @@ function stringify(obj, options) { return result; }; +function htmlspecialchars (string, quote_style, charset, double_encode) { + var optTemp = 0, + i = 0, + noquotes = false; + if (typeof quote_style === 'undefined' || quote_style === null) { + quote_style = 2; + } + string = string.toString(); + if (double_encode !== false) { + string = string.replace(/&/g, '&'); + } + string = string.replace(//g, '>'); +// console.log(string); + var OPTS = { + 'ENT_NOQUOTES': 0, + 'ENT_HTML_QUOTE_SINGLE': 1, + 'ENT_HTML_QUOTE_DOUBLE': 2, + 'ENT_COMPAT': 2, + 'ENT_QUOTES': 3, + 'ENT_IGNORE': 4 + }; + if (quote_style === 0) { + noquotes = true; + } + if (typeof quote_style !== 'number') { + quote_style = [].concat(quote_style); + for (var i = 0; i < quote_style.length; i++) { + if (OPTS[quote_style[i]] === 0) { + noquotes = true; + } + else if (OPTS[quote_style[i]]) { + optTemp = optTemp | OPTS[quote_style[i]]; + } + } + quote_style = optTemp; + } + if (quote_style & OPTS.ENT_HTML_QUOTE_SINGLE) { + string = string.replace(/'/g, '''); + } + if (!noquotes) { + string = string.replace(/"/g, '"'); + } + return string; +} + // Escape invisible characters in a string function stringifyString (str, options) { - return str.replace(/\\/g, '\\\\') - .replace(/\n/g, '\\n') - .replace(/[\u0001-\u001F]/g, function (match) { - return '\\0' + match[0].charCodeAt(0).toString(8); - }); + var result; + if (options.escape) { + result=str.replace(/\n/g, '\\n').replace(/\t/g, '\\t').replace(/[\u0001-\u001F]/g, function (match) { + return '\\0' + match[0].charCodeAt(0).toString(8); + }); + if (options.html) { + result=htmlspecialchars(result); + } + } else { + result=str; + } + + // Truncate the string if a maximum length is configured + var truncate = options.hasOwnProperty('maxStringLength') && options.maxStringLength >= 0; + if(truncate && result.length > options.maxStringLength) { + var length = Math.min(result.length, options.maxStringLength - 3); + result = result.substr(0, length) + "..."; + } + + return result; } // Convert an array to a string, such as [1, 2, 3]. @@ -148,47 +243,84 @@ function stringifyString (str, options) { function stringifyArray(ary, options, level) { var out = []; var pretty = options.pretty && (ary.length > 4 || ary.some(function (o) { - return (typeof(o) === 'object' && Object.keys(o).length > 0) || + return (o !== null && typeof(o) === 'object' && Object.keys(o).length > 0) || (Array.isArray(o) && o.length > 0); })); - var ws = pretty ? '\n' + new(Array)(level * 4 + 1).join(' ') : ' '; + var ws = pretty ? '\n' + new(Array)(level * options.indent + 1).join(' ') : ' '; - for (var i = 0; i < ary.length; i++) { + var truncate = options.hasOwnProperty('maxArrayLength') && options.maxArrayLength >= 0; + + var length = truncate ? Math.min(ary.length, options.maxArrayLength) : ary.length; + for (var i = 0; i < length; i++) { out.push(stringify(ary[i], options)); } + // Add a special String if the array was truncated + if(length < ary.length) { + out.push('<