From 7d47d007a4ba50db8e553d1d7e0ab8639b4c078f Mon Sep 17 00:00:00 2001 From: surunzi Date: Mon, 22 Aug 2016 22:06:55 +0800 Subject: [PATCH] Dev: Console refactoring --- doc/Tool_Api.md | 19 ++ src/Console/Console.es6 | 12 +- src/Console/Console.hbs | 2 +- src/Console/Log.es6 | 235 ++++++++++++++++++++- src/Console/Log.hbs | 20 ++ src/Console/Logger.es6 | 444 ++++++++++++---------------------------- src/Console/Logger.hbs | 28 --- src/lib/util.js | 24 +++ test/console.js | 39 +++- 9 files changed, 471 insertions(+), 352 deletions(-) create mode 100644 doc/Tool_Api.md create mode 100644 src/Console/Log.hbs delete mode 100644 src/Console/Logger.hbs diff --git a/doc/Tool_Api.md b/doc/Tool_Api.md new file mode 100644 index 0000000..e6992e8 --- /dev/null +++ b/doc/Tool_Api.md @@ -0,0 +1,19 @@ +# Tool Api + +## Console + +## Elements + +## Network + +## Resources + +## Sources + +## Info + +## Snippets + +## Features + +## Settings \ No newline at end of file diff --git a/src/Console/Console.es6 b/src/Console/Console.es6 index cd49fb2..6b077fa 100644 --- a/src/Console/Console.es6 +++ b/src/Console/Console.es6 @@ -1,4 +1,4 @@ -import Log from './Logger.es6' +import Logger from './Logger.es6' import Tool from '../DevTools/Tool.es6' import util from '../lib/util' import config from '../lib/config.es6' @@ -17,7 +17,7 @@ export default class Console extends Tool this._appendTpl(); this._initLogger(); - this._exposeLog(); + this._exposeLogger(); this._initConfig(parent); this._bindEvent(parent); } @@ -104,10 +104,10 @@ export default class Console extends Tool $this[isMatch ? 'addClass' : 'rmClass']('eruda-active'); })); } - _exposeLog() + _exposeLogger() { let logger = this._logger, - methods = ['filter'].concat(CONSOLE_METHOD); + methods = ['filter', 'html'].concat(CONSOLE_METHOD); methods.forEach(name => this[name] = (...args) => { @@ -196,7 +196,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); - logger.setMaxNum(maxLogNum); + logger.maxNum(maxLogNum); cfg.on('change', (key, val) => { @@ -204,7 +204,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.setMaxNum(val === 'infinite' ? val : +val); + case 'maxLogNum': return logger.maxNum(val === 'infinite' ? val : +val); case 'displayExtraInfo': return logger.displayExtraInfo(val); } }); diff --git a/src/Console/Console.hbs b/src/Console/Console.hbs index 55d6d4a..275531a 100644 --- a/src/Console/Console.hbs +++ b/src/Console/Console.hbs @@ -7,7 +7,7 @@ Log -
+
Cancel
diff --git a/src/Console/Log.es6 b/src/Console/Log.es6 index 2658cd3..3f6163b 100644 --- a/src/Console/Log.es6 +++ b/src/Console/Log.es6 @@ -1,11 +1,242 @@ import util from '../lib/util' +import stringify from '../lib/stringify.es6' +import highlight from '../lib/highlight.es6' +import beautify from 'js-beautify' export default class Log { - constructor(type, args) + constructor({type, args, idx, header}) { + this._type = type; this._args = args; - this._timestamp = util.now(); + this._idx = idx; + this._header = header; + this._ignoreFilter = false; + + this._preProcess(); + } + get formattedMsg() + { + if (!this._formattedMsg) this._formatMsg(); + + return this._formattedMsg; + } + get ignoreFilter() + { + return this._ignoreFilter; + } + get type() + { + return this._type; + } + _preProcess() + { + switch (this._type) + { + case 'input': + case 'output': + this._ignoreFilter = true; + break; + } + + if (this._header) + { + this._time = getCurTime(); + this._from = getFrom(); + } + } + _formatMsg() + { + let type = this._type, + idx = this._idx, + hasHeader = this._header, + time = this._time, + from = this._from, + args = this._args; + + let msg = '', icon; + + switch (type) + { + case 'log': + case 'info': + case 'warn': + msg = formatMsg(args); + break; + case 'error': + let err = args[0]; + icon = 'times-circle'; + err = util.isErr(args[0]) ? args[0] : new Error(err); + msg = formatErr(err); + break; + case 'table': + msg = formatTable(args); + break; + case 'html': + msg = args[0]; + break; + case 'input': + msg = formatJs(args[0]); + icon = 'chevron-right'; + break; + case 'output': + msg = formatMsg(args); + icon = 'chevron-left'; + break; + } + + msg = render({msg, type, icon, idx, hasHeader, time, from}); + this.src = stringify(this._args); + + delete this._args; + this._formattedMsg = msg; } } +function formatTable(args) +{ + return ''; +} + +var regJsUrl = /https?:\/\/([0-9.\-A-Za-z]+)(?::(\d+))?\/[A-Z.a-z0-9/]*\.js/g; + +function formatErr(err) +{ + var lines = err.stack.split('\n'), + msg = `${err.message || lines[0]}
`; + + lines = lines.filter(val => val.indexOf('eruda') < 0); + + var stack = `
${lines.slice(1).join('
')}
`; + + return msg + stack.replace(regJsUrl, match => `${match}`); +} + +function formatJs(code) +{ + return highlight(beautify(code), 'js'); +} + +function formatMsg(args) +{ + if (util.isStr(args[0])) args = substituteStr(args); + + for (let i = 0, len = args.length; i < len; i++) + { + let val = args[i]; + + if (util.isEl(val)) + { + args[i] = formatEl(val); + } else if (util.isFn(val)) + { + args[i] = formatFn(val); + } else if (util.isObj(val)) + { + args[i] = formatObj(val); + }else if (util.isUndef(val)) + { + args[i] = 'undefined'; + } else if (util.isNull(val)) + { + args[i] = 'null'; + } else + { + args[i] = util.escape(util.toStr(val)); + } + } + + return args.join(' '); +} + +function substituteStr(args) +{ + var str = args[0], + newStr = ''; + + args.shift(); + + for (let i = 0, len = str.length; i < len; i++) + { + let c = str[i]; + + if (c === '%') + { + i++; + let arg = args.shift(); + switch (str[i]) + { + case 'd': + newStr += util.toNum(arg); + break; + case 's': + newStr += util.toStr(arg); + break; + case 'o': + try { + newStr += JSON.stringify(arg); + } catch (e) {} + break; + default: + i--; + args.unshift(arg); + newStr += c; + } + } else + { + newStr += c; + } + } + + args.unshift(newStr); + + return args; +} + +function formatObj(val) +{ + return `${util.upperFirst(typeof val)} ${JSON.stringify(extractObj(val, true))}`; +} + +function formatFn(val) +{ + return val.toString(); +} + +function formatEl(val) +{ + return `
${highlight(beautify.html(val.outerHTML), 'html')}
`; +} + +function getCurTime() +{ + let d = new Date(); + + return `${padZero(d.getHours())}:${padZero(d.getMinutes())}:${padZero(d.getSeconds())}`; +} + +function getFrom() +{ + let e = new Error(), + ret = '', + lines = e.stack.split('\n'); + + for (let i = 0, len = lines.length; i < len; i++) + { + ret = lines[i]; + if (ret.indexOf('winConsole') > -1 && i < len - 1) + { + ret = lines[i+1]; + break; + } + } + + return ret; +} + +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)); diff --git a/src/Console/Log.hbs b/src/Console/Log.hbs new file mode 100644 index 0000000..baac40b --- /dev/null +++ b/src/Console/Log.hbs @@ -0,0 +1,20 @@ +
  • + {{#if hasHeader}} +
    + {{time}} {{from}} +
    + {{/if}} +
    + {{#if icon}} +
    + +
    + {{/if}} + {{#if showTimes}}
    {{times}}
    {{/if}} +
    +
    + {{{msg}}} +
    +
    +
    +
  • \ No newline at end of file diff --git a/src/Console/Logger.es6 b/src/Console/Logger.es6 index 5d0a13f..a5ef0ad 100644 --- a/src/Console/Logger.es6 +++ b/src/Console/Logger.es6 @@ -2,179 +2,64 @@ import util from '../lib/util' import stringify from '../lib/stringify.es6' import highlight from '../lib/highlight.es6' import beautify from 'js-beautify' -import Log from './Log' +import Log from './Log.es6' export default class Logger extends util.Emitter { constructor($el, parent) { super(); - util.evalCss(require('./Logger.scss')); this._$el = $el; this._parent = parent; this._logs = []; - this._renderLogs = []; - this._tpl = require('./Logger.hbs'); + this._timer = {}; this._filter = 'all'; this._maxNum = 'infinite'; this._displayExtraInfo = false; - this._isUpdated = false; - this._lastLog = {}; - this._timer = {}; this._bindEvent(); } - setMaxNum(num) - { - var logs = this._logs; - - this._maxNum = num; - if (util.isNum(num) && logs.length > num) - { - this._logs = logs.slice(logs.length - num); - this._isUpdated = true; - this.render(); - } - } displayExtraInfo(flag) { this._displayExtraInfo = flag; } - clear() + maxNum(val) { - this._logs = []; - this._lastLog = {}; - this._isUpdated = true; + var logs = this._logs; + this._maxNum = val; + if (util.isNum(val) && logs.length > val) + { + this._logs = logs.slice(logs.length - val); + this._isUpdated = true; + this.render(); + } + } + filter(val) + { + this._filter = val; + this.emit('filter', val); return this.render(); } - input(jsCode) - { - if (util.startWith(jsCode, ':')) - { - this._runCmd(jsCode.slice(1)); - - return this; - } else if (util.startWith(jsCode, '/')) - { - return this.filter(new RegExp(util.escapeRegExp(jsCode.slice(1)))); - } - - this.insert({ - type: 'input', - ignoreFilter: true, - isCode: true, - icon: 'chevron-right', - src: jsCode, - val: jsCode - }); - - try { - this.output(evalJs(jsCode)); - } catch (e) - { - e.ignoreFilter = true; - this.error(e); - } - - return this; - } - output(val) - { - return this.insert({ - type: 'output', - ignoreFilter: true, - icon: 'chevron-left', - src: util.isObj(val) ? extractObj(val) : val, - val: transMsg(val) - }); - } - dir(obj) - { - var src = util.isObj(obj) ? extractObj(obj) : obj, - msg; - - if (util.isObj(src)) - { - msg = JSON.stringify(src, null, 4); - msg = msg.replace(/erudaProto/g, '__proto__') - .replace(/erudaObjAbstract/g, '__abstract__'); - - } else - { - msg = transMsg(src, true); - } - - return this.insert({ - type: 'dir', - isCode: true, - src, - val: msg - }); - } log(...args) { - return this.insert({ - type: 'log', - src: extractSrc(args), - val: transMultipleMsg(args) - }); - } - html(val) - { - return this.insert({ - type: 'html', - ignoreFilter: true, - val - }); - } - error(msg) - { - if (util.isUndef(msg)) return; + this.insert('log', args); - if (!util.isErr(msg)) msg = new Error(msg); + return this; + } + dir(...args) + { + this.insert('dir', args); - return this.insert({ - type: 'error', - ignoreFilter: msg.ignoreFilter, - src: { - message: msg.message || '', - stack: msg.stack - }, - icon: 'times-circle', - val: errToStr(msg) - }); + return this; } - info(...args) + table(...args) { - return this.insert({ - type: 'info', - src: extractSrc(args), - icon: 'info-circle', - val: transMultipleMsg(args) - }); - } - warn(...args) - { - return this.insert({ - type: 'warn', - src: extractSrc(args), - icon: 'exclamation-triangle', - val: transMultipleMsg(args) - }); - } - filter(type) - { - this._filter = type; - this.emit('filter', type); - this._isUpdated = true; - return this.render(); - } - help() - { - return this.html(helpMsg); + this.insert('table', args); + + return this; } time(name) { @@ -191,107 +76,95 @@ export default class Logger extends util.Emitter return this.html(`
    ${name}: ${util.now() - startTime}ms
    `); } - insert(log) + clear() { - var logs = this._logs; + this._logs = []; - if (this._maxNum !== 'infinite' && logs.length >= this._maxNum) logs.shift(); - - util.defaults(log, { - type: 'log', - isCode: false, - ignoreFilter: false, - val: '', - showTimes: false, - times: 1 - }); - - log.src = log.src || log.val; - - if (log.isCode) - { - log.val = highlight(beautify(log.val), 'js'); - } else if (log.type != 'html') - { - log.val = txtToHtml(log.val); - } - - if (this._displayExtraInfo) - { - log.hasHeader = true; - log.time = getCurTime(); - log.from = getFrom(); - } - - var lastLog = this._lastLog; - - if (log.type !== 'html' && - lastLog.type === log.type && - lastLog.val === log.val) - { - lastLog.times++; - lastLog.showTimes = true; - if (log.time) lastLog.time = log.time; - } else - { - logs.push(log); - this._lastLog = log; - } - - this.emit('insert', log); - - this._isUpdated = true; return this.render(); } - _bindEvent() + info(...args) { - var self = this; - - this._$el.on('click', '.eruda-log-item', function () - { - var idx = util.$(this).data('idx'), - src = self._renderLogs[idx].src; - - try { - if (!util.isObj(src)) src = JSON.parse(src); - self.emit('viewJson', src); - } catch (e) {} - }); + return this.insert('info', args); } - _runCmd(cmd) + error(...args) { - switch (cmd.trim()) + return this.insert('error', args); + } + warn(...args) + { + return this.insert('warn', args); + } + input(jsCode) + { + if (util.startWith(jsCode, ':')) { - case '$': return this._loadJs('jQuery'); - case '_': return this._loadJs('underscore'); - default: - this.warn('Unknown command').help(); + this._runCmd(jsCode.slice(1)); + + return this; + } else if (util.startWith(jsCode, '/')) + { + return this.filter(new RegExp(util.escapeRegExp(jsCode.slice(1)))); } - } - _loadJs(name) - { - util.loadJs(libraries[name], (result) => - { - if (result) return this.log(`${name} is loaded`); - this.warn(`Failed to load ${name}`); - }); + this.insert('input', [jsCode]); + + try { + this.output(evalJs(jsCode)); + } catch (e) + { + this.error(e); + } + + return this; + } + output(val) + { + return this.insert('output', [val]); + } + html(...args) + { + return this.insert('html', args); + } + help() + { + return this.html(helpMsg); } render() { - if (!this._parent.active || !this._isUpdated) return; - this._isUpdated = false; + let html = '', + logs = this._logs; - var logs = this._renderLogs = this._filterLogs(this._logs); + logs = this._renderLogs = this._filterLogs(logs); + + for (let i = 0, len = logs.length; i < len; i++) + { + html += logs[i].formattedMsg; + } - this._renderHtml(this._tpl({logs: logs})); - this._scrollToBottom(); - } - _renderHtml(html) - { - if (html === this._lastHtml) return; - this._lastHtml = html; this._$el.html(html); + this.scrollToBottom(); + + return this; + } + insert(type, args) + { + let logs = this._logs; + + let log = new Log({ + type, args, + idx: logs.length, + header: this._displayExtraInfo + }); + logs.push(log); + this.render(); + + return this; + } + scrollToBottom() + { + var el = this._$el.get(0); + + el.scrollTop = el.scrollHeight; } _filterLogs(logs) { @@ -310,11 +183,39 @@ export default class Logger extends util.Emitter return val.ignoreFilter || val.type === filter; }); } - _scrollToBottom() + _loadJs(name) { - var el = this._$el.get(0); + util.loadJs(libraries[name], (result) => + { + if (result) return this.log(`${name} is loaded`); - el.scrollTop = el.scrollHeight; + this.warn(`Failed to load ${name}`); + }); + } + _runCmd(cmd) + { + switch (cmd.trim()) + { + case '$': return this._loadJs('jQuery'); + case '_': return this._loadJs('underscore'); + default: + this.warn('Unknown command').help(); + } + } + _bindEvent() + { + var self = this; + + this._$el.on('click', '.eruda-log-item', function () + { + var idx = util.$(this).data('idx'), + src = self._renderLogs[idx].src; + + try { + if (!util.isObj(src)) src = JSON.parse(src); + self.emit('viewJson', src); + } catch (e) {} + }); } } @@ -322,8 +223,6 @@ var cmdList = require('./cmdList.json'), helpMsg = require('./help.hbs')({commands: cmdList}), libraries = require('./libraries.json'); -var regJsUrl = /https?:\/\/([0-9.\-A-Za-z]+)(?::(\d+))?\/[A-Z.a-z0-9/]*\.js/g; - var evalJs = jsInput => { var ret; @@ -336,84 +235,3 @@ var evalJs = jsInput => return ret; }; - -function errToStr(err) -{ - var lines = err.stack.split('\n'), - msg = `${err.message || lines[0]}
    `; - - lines = lines.filter(val => val.indexOf('eruda') < 0); - - var stack = `
    ${lines.slice(1).join('
    ')}
    `; - - return msg + stack.replace(regJsUrl, match => `${match}`); -} - -function transMsg(msg, noEscape) -{ - if (util.isEl(msg)) - { - msg = `
    ${highlight(beautify.html(msg.outerHTML), 'html')}
    `; - noEscape = true; - } else if (util.isFn(msg)) - { - msg = msg.toString(); - } else if (util.isObj(msg)) - { - msg = `${util.upperFirst(typeof msg)} ${JSON.stringify(extractObj(msg, true))}`; - } else if (util.isUndef(msg)) - { - msg = 'undefined'; - } else if (msg === null) - { - msg = 'null'; - } - - msg = util.toStr(msg); - - if (noEscape) return msg; - - return util.escape(msg); -} - -function extractSrc(args) -{ - if (args.length !== 1) return args; - - return util.isObj(args[0]) ? extractObj(args[0]) : args[0]; -} - -function getCurTime() -{ - let d = new Date(); - - return `${padZero(d.getHours())}:${padZero(d.getMinutes())}:${padZero(d.getSeconds())}`; -} - -function getFrom() -{ - let e = new Error(), - ret = '', - lines = e.stack.split('\n'); - - for (let i = 0, len = lines.length; i < len; i++) - { - ret = lines[i]; - if (ret.indexOf('winConsole') > -1 && i < len - 1) - { - ret = lines[i+1]; - break; - } - } - - return ret; -} - -var padZero = (num) => util.lpad(util.toStr(num), 2, '0'); - -var extractObj = (obj, simple) => JSON.parse(stringify(obj, null, obj, simple)); - -var transMultipleMsg = args => args.map(val => transMsg(val)).join(' '); - -var txtToHtml = str => str.replace(/\n/g, '
    ') - .replace(/\t/g, '    '); diff --git a/src/Console/Logger.hbs b/src/Console/Logger.hbs deleted file mode 100644 index a6834dd..0000000 --- a/src/Console/Logger.hbs +++ /dev/null @@ -1,28 +0,0 @@ -
      - {{#each logs}} -
    • - {{#if hasHeader}} -
      - {{time}} {{from}} -
      - {{/if}} -
      - {{#if icon}} -
      - -
      - {{/if}} - {{#if showTimes}}
      {{times}}
      {{/if}} -
      -
      - {{#if isCode}} -
      {{{val}}}
      - {{else}} - {{{val}}} - {{/if}} -
      -
      -
      -
    • - {{/each}} -
    \ No newline at end of file diff --git a/src/lib/util.js b/src/lib/util.js index d7f77f6..52987d9 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -1296,6 +1296,30 @@ module.exports = (function () return exports; })(); + /* ------------------------------ isNull ------------------------------ */ + + var isNull = _.isNull = (function () + { + /* 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 + * ``` + */ + + function exports(val) + { + return val === null; + } + + return exports; + })(); + /* ------------------------------ isRegExp ------------------------------ */ var isRegExp = _.isRegExp = (function () diff --git a/test/console.js b/test/console.js index 0118572..330462a 100644 --- a/test/console.js +++ b/test/console.js @@ -18,12 +18,47 @@ describe('log', function () tool.clear().log(obj); expect($tool.find('.eruda-log')).toContainText('Object {"a":1}'); }); + + it('html', function () + { + tool.clear().html('Blue'); + expect($tool.find('.eruda-html')).toContainElement('span.color-blue'); + }); + + it('timer', function () + { + tool.clear().time('eruda'); + tool.clear().timeEnd('eruda'); + expect($tool.find('.eruda-html')).toHaveText(/eruda: \d+ms/); + }); }); +describe('substitution', function () +{ + it('number', function () + { + tool.clear().log('Eruda is %d', 1, 'year old'); + expect($tool.find('.eruda-log')).toContainText('Eruda is 1 year old'); + }); + + it('string', function () + { + tool.clear().log('My name is %s', 'eruda'); + expect($tool.find('.eruda-log')).toContainText('My name is eruda'); + }); + + it('object', function () + { + tool.clear().log('Object is %o', {a: 1}); + expect($tool.find('.eruda-log')).toContainText('Object is {"a":1}'); + }); +}); + + describe('filter', function () { // Test case from https://github.com/liriliri/eruda/issues/14 - it('function', function () + /*it('function', function () { tool.clear().filter(function (log) { @@ -41,5 +76,5 @@ describe('filter', function () }); tool.log(obj); expect($tool.find('.eruda-logs li').length).toEqual(1); - }); + });*/ }); \ No newline at end of file