From eedcc67c6280e5637419044434361ee25fa7d138 Mon Sep 17 00:00:00 2001 From: surunzi Date: Tue, 23 Aug 2016 22:40:56 +0800 Subject: [PATCH] Dev: Basic string substitution support --- doc/Util_Api.md | 26 +++++++++ src/Console/Console.es6 | 4 +- src/Console/Log.es6 | 107 ++++++++++++++++++++++--------------- src/Console/Log.hbs | 2 +- src/Console/Logger.es6 | 32 +++++++---- src/Elements/Elements.es6 | 2 +- src/Sources/JsonViewer.es6 | 7 +++ src/lib/stringify.es6 | 91 ++++++++++++++++++++++++------- src/lib/util.js | 28 ++++++++++ test/console.js | 23 ++++++-- 10 files changed, 244 insertions(+), 78 deletions(-) diff --git a/doc/Util_Api.md b/doc/Util_Api.md index 0f6bb12..ad13f32 100644 --- a/doc/Util_Api.md +++ b/doc/Util_Api.md @@ -846,6 +846,19 @@ Check if keys and values in src are contained in obj. isMatch({a: 1, b: 2}, {a: 1}); // -> true ``` +## isNull + +Check if value is an Null. + +|Name |Type |Desc | +|------|-------|-----------------------| +|value |* |Value to check | +|return|boolean|True if value is an Null| + +```javascript +isNull(null); // -> true +``` + ## isNum Checks if value is classified as a Number primitive or object. @@ -1222,6 +1235,19 @@ toArr(1); // -> [] toArr(null); // -> [] ``` +## toInt + +Convert value to an integer. + +|Name |Type |Desc | +|------|------|-----------------| +|val |* |Value to convert | +|return|number|Converted integer| + +```javascript +toInt(1.1); // -> 1 +``` + ## toNum Convert value to a number. diff --git a/src/Console/Console.es6 b/src/Console/Console.es6 index 6b077fa..d6bc5e6 100644 --- a/src/Console/Console.es6 +++ b/src/Console/Console.es6 @@ -195,7 +195,7 @@ export default class Console extends Tool if (cfg.get('catchGlobalErr')) this.catchGlobalErr(); if (cfg.get('overrideConsole')) this.overrideConsole(); - if (cfg.get('displayExtraInfo')) logger.displayExtraInfo(true); + if (cfg.get('displayExtraInfo')) logger.displayHeader(true); logger.maxNum(maxLogNum); cfg.on('change', (key, val) => @@ -205,7 +205,7 @@ export default class Console extends Tool case 'catchGlobalErr': return val ? this.catchGlobalErr() : this.ignoreGlobalErr(); case 'overrideConsole': return val ? this.overrideConsole() : this.restoreConsole(); case 'maxLogNum': return logger.maxNum(val === 'infinite' ? val : +val); - case 'displayExtraInfo': return logger.displayExtraInfo(val); + case 'displayExtraInfo': return logger.displayHeader(val); } }); diff --git a/src/Console/Log.es6 b/src/Console/Log.es6 index 3f6163b..504c4cd 100644 --- a/src/Console/Log.es6 +++ b/src/Console/Log.es6 @@ -5,15 +5,24 @@ import beautify from 'js-beautify' export default class Log { - constructor({type, args, idx, header}) + constructor({ + type = 'log', + args = [], + idx = 0, + displayHeader = false, + ignoreFilter = false}) { - this._type = type; - this._args = args; - this._idx = idx; - this._header = header; - this._ignoreFilter = false; + this.type = type; + this.args = args; + this.idx = idx; + this.displayHeader = displayHeader; + this.ignoreFilter = false; - this._preProcess(); + if (displayHeader) + { + this.time = getCurTime(); + this.from = getFrom(); + } } get formattedMsg() { @@ -21,38 +30,30 @@ export default class Log return this._formattedMsg; } - get ignoreFilter() + _needSrc() { - return this._ignoreFilter; - } - get type() - { - return this._type; - } - _preProcess() - { - switch (this._type) + let {type, args} = this; + + if (type === 'html') return false; + + if (args.length === 1) { - case 'input': - case 'output': - this._ignoreFilter = true; - break; + let arg = args[0]; + if (util.isStr(arg)) return false; + if (util.isBool(arg)) return false; + if (util.isNum(arg)) return false; } - if (this._header) - { - this._time = getCurTime(); - this._from = getFrom(); - } + return true; } _formatMsg() { - let type = this._type, - idx = this._idx, - hasHeader = this._header, - time = this._time, - from = this._from, - args = this._args; + let {type, idx, displayHeader, time, from, args} = this; + + if (this._needSrc()) + { + this.src = extractObj(args.length === 1 && util.isObj(args[0]) ? args[0] : args, false); + } let msg = '', icon; @@ -85,10 +86,9 @@ export default class Log break; } - msg = render({msg, type, icon, idx, hasHeader, time, from}); - this.src = stringify(this._args); + msg = render({msg, type, icon, idx, displayHeader, time, from}); - delete this._args; + delete this.args; this._formattedMsg = msg; } } @@ -142,7 +142,9 @@ function formatMsg(args) args[i] = 'null'; } else { - args[i] = util.escape(util.toStr(val)); + val = util.toStr(val); + if (i !== 0) val = util.escape(val); + args[i] = val; } } @@ -151,7 +153,7 @@ function formatMsg(args) function substituteStr(args) { - var str = args[0], + var str = util.escape(args[0]), newStr = ''; args.shift(); @@ -166,16 +168,30 @@ function substituteStr(args) let arg = args.shift(); switch (str[i]) { + case 'i': case 'd': + newStr += util.toInt(arg); + break; + case 'f': newStr += util.toNum(arg); break; case 's': newStr += util.toStr(arg); break; + case 'O': + if (util.isObj(arg)) + { + newStr += stringify(arg, {simple: true, keyQuotes: false, highlight: true}); + } + break; case 'o': - try { - newStr += JSON.stringify(arg); - } catch (e) {} + if (util.isEl(arg)) + { + newStr += formatEl(arg); + } else if (util.isObj(arg)) + { + newStr += stringify(arg, {simple: true, keyQuotes: false, highlight: true}); + } break; default: i--; @@ -195,7 +211,7 @@ function substituteStr(args) function formatObj(val) { - return `${util.upperFirst(typeof val)} ${JSON.stringify(extractObj(val, true))}`; + return `${getObjType(val)} ${stringify(val, {keyQuotes: false, simple: true, highlight: true})}`; } function formatFn(val) @@ -234,9 +250,16 @@ function getFrom() return ret; } +function getObjType(obj) +{ + if (obj.constructor) return obj.constructor.name; + + return util.upperFirst(({}).toString.call(obj).replace(/(\[object )|]/g, '')); +} + var padZero = (num) => util.lpad(util.toStr(num), 2, '0'); var tpl = require('./Log.hbs'); var render = data => tpl(data); -var extractObj = (obj, simple) => JSON.parse(stringify(obj, null, obj, simple)); +var extractObj = (obj, simple) => JSON.parse(stringify(obj, {simple})); diff --git a/src/Console/Log.hbs b/src/Console/Log.hbs index baac40b..f8fc653 100644 --- a/src/Console/Log.hbs +++ b/src/Console/Log.hbs @@ -1,5 +1,5 @@
  • - {{#if hasHeader}} + {{#if displayHeader}}
    {{time}} {{from}}
    diff --git a/src/Console/Logger.es6 b/src/Console/Logger.es6 index a5ef0ad..c927fe5 100644 --- a/src/Console/Logger.es6 +++ b/src/Console/Logger.es6 @@ -17,13 +17,13 @@ export default class Logger extends util.Emitter this._timer = {}; this._filter = 'all'; this._maxNum = 'infinite'; - this._displayExtraInfo = false; + this._displayHeader = false; this._bindEvent(); } - displayExtraInfo(flag) + displayHeader(flag) { - this._displayExtraInfo = flag; + this._displayHeader = flag; } maxNum(val) { @@ -106,20 +106,32 @@ export default class Logger extends util.Emitter return this.filter(new RegExp(util.escapeRegExp(jsCode.slice(1)))); } - this.insert('input', [jsCode]); + this.insert({ + type: 'input', + args: [jsCode], + ignoreFilter: true + }); try { this.output(evalJs(jsCode)); } catch (e) { - this.error(e); + this.insert({ + type: 'error', + ignoreFilter: true, + args: [e] + }); } return this; } output(val) { - return this.insert('output', [val]); + return this.insert({ + type: 'output', + args: [val], + ignoreFilter: true + }); } html(...args) { @@ -150,11 +162,13 @@ export default class Logger extends util.Emitter { let logs = this._logs; - let log = new Log({ - type, args, + let options = util.isStr(type) ? {type, args} : type; + util.extend(options, { idx: logs.length, - header: this._displayExtraInfo + displayHeader: this._displayHeader }); + + let log = new Log(options); logs.push(log); this.render(); diff --git a/src/Elements/Elements.es6 b/src/Elements/Elements.es6 index d3f4512..5f323d6 100644 --- a/src/Elements/Elements.es6 +++ b/src/Elements/Elements.es6 @@ -128,7 +128,7 @@ export default class Elements extends Tool } }).on('click', '.eruda-breadcrumb', () => { - let data = this._elData || JSON.parse(stringify(this._curEl, null, this._curEl, false)), + let data = this._elData || JSON.parse(stringify(this._curEl)), sources = parent.get('sources'); this._elData = data; diff --git a/src/Sources/JsonViewer.es6 b/src/Sources/JsonViewer.es6 index f5af4b3..721097a 100644 --- a/src/Sources/JsonViewer.es6 +++ b/src/Sources/JsonViewer.es6 @@ -99,6 +99,13 @@ function createEl(key, val, firstLevel) ${val.length > 250 ? encode(val) : highlight(val, 'js')}
  • ` } + if (val === '(...)' || val === '[circular]') + { + return `
  • + ${encode(key)}: + ${val} +
  • ` + } return `
  • ${encode(key)}: diff --git a/src/lib/stringify.es6 b/src/lib/stringify.es6 index 7261405..b975135 100644 --- a/src/lib/stringify.es6 +++ b/src/lib/stringify.es6 @@ -1,7 +1,14 @@ import util from './util' // Modified from: https://jsconsole.com/ -export default function stringify(obj, visited, topObj, simple) +export default function stringify(obj, { + visited = [], + topObj, + simple = false, + keyQuotes = true, + getterVal = false, + highlight = false +} = {}) { let json = '', type = '', @@ -10,8 +17,28 @@ export default function stringify(obj, visited, topObj, simple) proto, circular = false; - visited = visited || []; topObj = topObj || obj; + let dbQuotes = keyQuotes ? '"' : ''; + + let keyWrapper = '', + numWrapper = '', + strWrapper = '', + nullWrapper = '', + wrapperEnd = ''; + + if (highlight) + { + keyWrapper = ''; + numWrapper = ''; + nullWrapper = ''; + strWrapper = ''; + wrapperEnd = '' + } + + let wrapKey = key => keyWrapper + dbQuotes + key + dbQuotes + wrapperEnd, + wrapNum = num => numWrapper + num + wrapperEnd, + wrapStr = str => strWrapper + str + wrapperEnd, + wrapNull = str => nullWrapper + str + wrapperEnd; try { type = ({}).toString.call(obj); @@ -39,16 +66,16 @@ export default function stringify(obj, visited, topObj, simple) if (circular) { - json = '"[circular]"'; + json = wrapStr('"[circular]"'); } else if (isStr) { - json = `"${escapeJsonStr(obj)}"`; + json = wrapStr(`"${escapeJsonStr(obj)}"`); } else if (isArr) { visited.push(obj); json = '['; - util.each(obj, val => parts.push(`${stringify(val, visited, null, simple)}`)); + util.each(obj, val => parts.push(`${stringify(val, {visited, simple, getterVal, keyQuotes, highlight})}`)); json += parts.join(', ') + ']'; } else if (isObj || isFn) { @@ -57,16 +84,16 @@ export default function stringify(obj, visited, topObj, simple) names = Object.getOwnPropertyNames(obj); proto = Object.getPrototypeOf(obj); if (proto === Object.prototype || isFn || simple) proto = null; - if (proto) proto = `"erudaProto": ${stringify(proto, visited, topObj)}`; + if (proto) proto = `${wrapKey('erudaProto')}: ${stringify(proto, {visited, getterVal, topObj, keyQuotes, highlight})}`; names.sort(sortObjName); if (isFn) { - // We don't these properties to be display for functions. + // We don't need these properties to display for functions. names = names.filter(val => ['arguments', 'caller', 'name', 'length', 'prototype'].indexOf(val) < 0); } if (names.length === 0 && isFn) { - json = `"${escapeJsonStr(obj.toString())}"`; + json = wrapStr(`"${escapeJsonStr(obj.toString())}"`); } else { json = '{'; @@ -75,11 +102,21 @@ export default function stringify(obj, visited, topObj, simple) // Function length is restricted to 500 for performance reason. var fnStr = obj.toString(); if (fnStr.length > 500) fnStr = fnStr.slice(0, 500) + '...'; - parts.push(`"erudaObjAbstract": "${escapeJsonStr(fnStr)}"`); + parts.push(`${wrapKey('erudaObjAbstract')}: ${wrapStr('"' + escapeJsonStr(fnStr) + '"')}`); } util.each(names, name => { - parts.push(`"${escapeJsonStr(name)}": ${stringify(topObj[name], visited, null, simple)}`); + let key = wrapKey(escapeJsonStr(name)); + + if (!getterVal) + { + let descriptor = Object.getOwnPropertyDescriptor(obj, name); + if (descriptor.get) + { + return parts.push(`${key}: "(...)"`); + } + } + parts.push(`${key}: ${stringify(topObj[name], {visited, getterVal, simple, keyQuotes, highlight})}`); }); if (proto) parts.push(proto); json += parts.join(', ') + '}'; @@ -87,19 +124,25 @@ export default function stringify(obj, visited, topObj, simple) } else if (isNum) { json = obj + ''; - if (util.endWith(json, 'Infinity') || json === 'NaN') json = `"${json}"`; + if (util.endWith(json, 'Infinity') || json === 'NaN') + { + json = `"${json}"`; + } else + { + json = wrapNum(json); + } } else if (isBool) { json = obj ? 'true' : 'false'; } else if (obj === null) { - json = 'null'; + json = wrapNull('null'); } else if (isSymbol) { - json = '"Symbol"'; + json = wrapStr('"Symbol"'); } else if (obj === undefined) { - json = '"undefined"'; + json = wrapStr('"undefined"'); } else if (type === '[object HTMLAllCollection]') { // https://docs.webplatform.org/wiki/dom/HTMLAllCollection @@ -112,7 +155,7 @@ export default function stringify(obj, visited, topObj, simple) visited.push(obj); json = '{\n'; - if (!simple) parts.push(`"erudaObjAbstract": "${type.replace(/(\[object )|]/g, '')}"`); + if (!simple) parts.push(`${wrapKey('erudaObjAbstract')}: "${type.replace(/(\[object )|]/g, '')}"`); names = Object.getOwnPropertyNames(obj); proto = Object.getPrototypeOf(obj); if (proto === Object.prototype || simple) proto = null; @@ -120,21 +163,31 @@ export default function stringify(obj, visited, topObj, simple) { try { - proto = `"erudaProto": ${stringify(proto, visited, topObj)}`; + proto = `${wrapKey('erudaProto')}: ${stringify(proto, {visited, topObj, getterVal, keyQuotes, highlight})}`; } catch(e) { - proto = `"erudaProto": "${escapeJsonStr(e.message)}"`; + proto = `${wrapKey('erudaProto')}: ${wrapStr('"' + escapeJsonStr(e.message) + '"')}`; } } names.sort(sortObjName); util.each(names, name => { - parts.push(`"${escapeJsonStr(name)}": ${stringify(topObj[name], visited, null, simple)}`); + let key = wrapKey(escapeJsonStr(name)); + + if (!getterVal) + { + let descriptor = Object.getOwnPropertyDescriptor(obj, name); + if (descriptor.get) + { + return parts.push(`${key}: "(...)"`); + } + } + parts.push(`${key}: ${stringify(topObj[name], {visited, getterVal, simple, keyQuotes, highlight})}`); }); if (proto) parts.push(proto); json += parts.join(',\n') + '\n}'; } catch (e) { - json = `"${obj}"`; + json = wrapStr(`"${obj}"`); } } diff --git a/src/lib/util.js b/src/lib/util.js index 52987d9..b3fd94d 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -3345,6 +3345,34 @@ module.exports = (function () return exports; })(); + /* ------------------------------ toInt ------------------------------ */ + + var toInt = _.toInt = (function () + { + /* Convert value to an integer. + * + * |Name |Type |Desc | + * |------|------|-----------------| + * |val |* |Value to convert | + * |return|number|Converted integer| + * + * ```javascript + * toInt(1.1); // -> 1 + * ``` + */ + + function exports(val) + { + if (!val) return val === 0 ? val : 0; + + val = toNum(val); + + return val - val % 1; + } + + return exports; + })(); + /* ------------------------------ toStr ------------------------------ */ var toStr = _.toStr = (function () diff --git a/test/console.js b/test/console.js index 330462a..ab11d15 100644 --- a/test/console.js +++ b/test/console.js @@ -11,12 +11,18 @@ describe('log', function () expect($tool.find('.eruda-log')).toContainText(text); }); + it('clear', function () + { + tool.clear(); + expect($tool.find('.eruda-log')).toHaveLength(0); + }); + it('basic object', function () { var obj = {a: 1}; tool.clear().log(obj); - expect($tool.find('.eruda-log')).toContainText('Object {"a":1}'); + expect($tool.find('.eruda-log')).toContainText('Object {a: 1}'); }); it('html', function () @@ -37,8 +43,14 @@ describe('substitution', function () { it('number', function () { - tool.clear().log('Eruda is %d', 1, 'year old'); + tool.clear().log('Eruda is %d', 1.2, 'year old'); expect($tool.find('.eruda-log')).toContainText('Eruda is 1 year old'); + + tool.clear().log('%i', 1.2, 'year old'); + expect($tool.find('.eruda-log')).toContainText('1 year old'); + + tool.clear().log('%f', 1.2, 'year old'); + expect($tool.find('.eruda-log')).toContainText('1.2 year old'); }); it('string', function () @@ -49,8 +61,11 @@ describe('substitution', function () it('object', function () { - tool.clear().log('Object is %o', {a: 1}); - expect($tool.find('.eruda-log')).toContainText('Object is {"a":1}'); + tool.clear().log('Object is %O', {a: 1}); + expect($tool.find('.eruda-log')).toContainText('Object is {a: 1}'); + + tool.clear().log('Dom is %o', document.createElement('script')); + expect($tool.find('.eruda-log')).toContainText('Dom is '); }); });