import stringify from './stringify' import origGetAbstract from '../lib/getAbstract' import highlight from '../lib/highlight' import beautify from 'js-beautify' import JsonViewer from '../lib/JsonViewer' import { isObj, isStr, isErr, wrap, defaults, dateFormat, getObjType, isEl, toStr, toNum, toInt, escape, isNull, isUndef, isFn, toArr, isArr, unique, contain, isEmpty, clone, noop } from '../lib/util' export default class Log { constructor({ type = 'log', args = [], id, displayHeader = false, ignoreFilter = false }) { this.type = type this.args = args this.count = 1 this.id = id this.displayHeader = displayHeader this.ignoreFilter = ignoreFilter if (displayHeader) { this.time = getCurTime() this.from = getFrom() } this._formatMsg() } addCount() { this.count++ let count = this.count, msg = this.formattedMsg if (count === 2) msg = msg.replace('eruda-count eruda-hidden', 'eruda-count') msg = msg.replace(/data-mark="count">\d*/, 'data-mark="count">' + count) this.formattedMsg = msg return this } updateTime(time) { let msg = this.formattedMsg if (this.time) { msg = msg.replace(/data-mark="time">(.*?)${time}<`) this.time = time this.formattedMsg = msg } return this } _needSrc() { let { type, args } = this if (type === 'html') return false for (let i = 0, len = args.length; i < len; i++) { if (isObj(args[i])) return true } return false } extractObj(cb = noop) { let { args, type } = this let setSrc = result => { this.src = result cb() } if (type === 'table') { extractObj(args[0], {}, setSrc) } else { extractObj( args.length === 1 && isObj(args[0]) ? args[0] : args, {}, setSrc ) } } _formatMsg() { let { type, id, displayHeader, time, from, args } = this // Don't change original args for lazy evaluation. args = clone(args) if (this._needSrc() && !Log.lazyEvaluation) { this.extractObj() } let msg = '' let icon let err switch (type) { case 'log': msg = formatMsg(args) break case 'debug': msg = formatMsg(args) break case 'dir': msg = formatDir(args) break case 'info': icon = 'info' msg = formatMsg(args) break case 'warn': icon = 'warn' msg = formatMsg(args) break case 'error': if (isStr(args[0]) && args.length !== 1) args = substituteStr(args) err = args[0] icon = 'error' err = isErr(err) ? err : new Error(formatMsg(args)) this.src = err msg = formatErr(err) break case 'table': msg = formatTable(args) break case 'html': msg = args[0] break case 'input': msg = formatJs(args[0]) icon = 'arrow-right' break case 'output': msg = formatMsg(args) icon = 'arrow-left' break } if (type !== 'error') msg = recognizeUrl(msg) this.value = msg msg = render({ msg, type, icon, id, displayHeader, time, from }) if (!this._needSrc() || !Log.lazyEvaluation) { delete this.args } this.formattedMsg = msg } static click(type, log, $el, logger) { switch (type) { case 'log': case 'warn': case 'info': case 'debug': case 'output': case 'table': case 'dir': if (log.src) { if (Log.showSrcInSources) { return logger.emit('viewJson', log.src) } let $json = $el.find('.eruda-json') if ($json.hasClass('eruda-hidden')) { if ($json.data('init') !== 'true') { new JsonViewer(log.src, $json) $json.data('init', 'true') } $json.rmClass('eruda-hidden') } else { $json.addClass('eruda-hidden') } } else if (log.args) { log.extractObj(function() { Log.click(type, log, $el, logger) delete log.args }) } break case 'error': $el.find('.eruda-stack').toggleClass('eruda-hidden') break } return 'handled' } } // Looks like es6 doesn't support static properties yet. Log.showGetterVal = false Log.showUnenumerable = true Log.showSrcInSources = false Log.lazyEvaluation = true let getAbstract = wrap(origGetAbstract, function(fn, obj) { return fn(obj, { getterVal: Log.showGetterVal, unenumerable: false }) }) function formatTable(args) { let table = args[0], ret = '', filter = args[1], columns = [] if (isStr(filter)) filter = toArr(filter) if (!isArr(filter)) filter = null if (!isArr(table)) return formatMsg(args) table.forEach(val => { if (!isObj(val)) return columns = columns.concat(Object.getOwnPropertyNames(val)) }) columns = unique(columns) columns.sort() if (filter) columns = columns.filter(val => contain(filter, val)) if (isEmpty(columns)) return formatMsg(args) ret += '' columns.forEach(val => (ret += ``)) ret += '' table.forEach((obj, idx) => { if (!isObj(obj)) return ret += `` columns.forEach(column => { let val = obj[column] if (isUndef(val)) { val = '' } else if (isObj(val)) { val = getObjType(val) } ret += `` }) ret += '' }) ret += '
(index)${val}
${idx}${val}
' ret += '
' return ret } let regJsUrl = /https?:\/\/([0-9.\-A-Za-z]+)(?::(\d+))?\/[A-Z.a-z0-9/]*\.js/g let regErudaJs = /eruda(\.min)?\.js/ function formatErr(err) { let lines = err.stack ? err.stack.split('\n') : [] let msg = `${err.message || lines[0]}
` lines = lines.filter(val => !regErudaJs.test(val)).map(val => escape(val)) let stack = `
${lines .slice(1) .join('
')}
` return ( msg + stack.replace( regJsUrl, match => `${match}` ) ) } function formatJs(code) { return highlight(beautify(code), 'js') } function formatMsg(args, { htmlForEl = true } = {}) { let needStrSubstitution = isStr(args[0]) && args.length !== 1 if (needStrSubstitution) args = substituteStr(args) for (let i = 0, len = args.length; i < len; i++) { let val = args[i] if (isEl(val) && htmlForEl) { args[i] = formatEl(val) } else if (isFn(val)) { args[i] = formatFn(val) } else if (isObj(val)) { args[i] = formatObj(val) } else if (isUndef(val)) { args[i] = 'undefined' } else if (isNull(val)) { args[i] = 'null' } else { val = toStr(val) if (i !== 0 || !needStrSubstitution) val = escape(val) args[i] = val } } return args.join(' ') + '
' } let formatDir = args => formatMsg(args, { htmlForEl: false }) function substituteStr(args) { let str = escape(args[0]), isInCss = false, newStr = '' args.shift() for (let i = 0, len = str.length; i < len; i++) { let c = str[i] if (c === '%' && args.length !== 0) { i++ let arg = args.shift() switch (str[i]) { case 'i': case 'd': newStr += toInt(arg) break case 'f': newStr += toNum(arg) break case 's': newStr += toStr(arg) break case 'O': if (isObj(arg)) { newStr += getAbstract(arg) } break case 'o': if (isEl(arg)) { newStr += formatEl(arg) } else if (isObj(arg)) { newStr += getAbstract(arg) } break case 'c': if (isInCss) newStr += '' isInCss = true newStr += `` break default: i-- args.unshift(arg) newStr += c } } else { newStr += c } } if (isInCss) newStr += '' args.unshift(newStr) return args } function formatObj(val) { let type = getObjType(val) if (type === 'Array' && val.length > 1) type = `(${val.length})` return `${type} ${getAbstract(val)}` } function formatFn(val) { return `
${highlight(
    beautify.js(val.toString()),
    'js'
  )}
` } function formatEl(val) { return `
${highlight(
    beautify.html(val.outerHTML, { unformatted: [] }),
    'html'
  )}
` } let regUrl = /(^|[\s\n]|<[A-Za-z]*\/?>)((?:https?|ftp):\/\/[-A-Z0-9+\u0026\u2019@#/%?=()~_|!:,.;]*[-A-Z0-9+\u0026@#/%=~()_|])/gi let recognizeUrl = str => str.replace(regUrl, '$2') function getFrom() { let e = new Error(), ret = '', lines = e.stack ? 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 } let getCurTime = () => dateFormat('HH:MM:ss') let tpl = require('./Log.hbs') let render = data => tpl(data) function extractObj(obj, options = {}, cb) { defaults(options, { getterVal: Log.showGetterVal, unenumerable: Log.showUnenumerable }) stringify(obj, options, result => cb(JSON.parse(result))) }