Dev: Basic string substitution support

This commit is contained in:
surunzi
2016-08-23 22:40:56 +08:00
parent 7d47d007a4
commit eedcc67c62
10 changed files with 244 additions and 78 deletions

View File

@@ -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.

View File

@@ -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);
}
});

View File

@@ -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}));

View File

@@ -1,5 +1,5 @@
<li>
{{#if hasHeader}}
{{#if displayHeader}}
<div class="eruda-header">
{{time}} {{from}}
</div>

View File

@@ -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();

View File

@@ -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;

View File

@@ -99,6 +99,13 @@ function createEl(key, val, firstLevel)
<span class="eruda-function">${val.length > 250 ? encode(val) : highlight(val, 'js')}</span>
</li>`
}
if (val === '(...)' || val === '[circular]')
{
return `<li>
<span class="eruda-key">${encode(key)}: </span>
<span class="eruda-special">${val}</span>
</li>`
}
return `<li>
<span class="eruda-key">${encode(key)}: </span>

View File

@@ -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 = '<span style="color: #a71d5d;">';
numWrapper = '<span style="color: #0086b3;">';
nullWrapper = '<span style="color: #0086b3;">';
strWrapper = '<span style="color: #183691;">';
wrapperEnd = '</span>'
}
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}"`);
}
}

View File

@@ -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 ()

View File

@@ -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 <script></script>');
});
});