diff --git a/.npmignore b/.npmignore index d398edd..e677ad7 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,6 @@ .DS_Store node_modules .travis.yml +test +core.js package-lock.json diff --git a/.travis.yml b/.travis.yml index 1e49e0e..e11171d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,9 @@ node_js: - 0.12 - 4 - 6 + - 10 git: depth: 1 branches: only: - - master \ No newline at end of file + - master diff --git a/cjs/index.js b/cjs/index.js new file mode 100644 index 0000000..a34ace6 --- /dev/null +++ b/cjs/index.js @@ -0,0 +1,334 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + +/*! (c) 2013-2018 Andrea Giammarchi (ISC) */ +/** + * Fully inspired by the work of John Gruber + * + */ +for (var + isNodeJS = typeof process === 'object' && !process.browser, + parse = isNodeJS ? + // on NodeJS simply fallback to echomd + (function (echomd, map) { + function parse(value) { + return typeof value === 'string' ? + echomd(value) : value; + } + return function () { + return map.call(arguments, parse); + }; + }(require('echomd').raw, [].map)) : + // on browsers implement some %cmagic%c + // The current algorithm is based on two passes: + // 1. collect all info ordered by string index + // 2. transform surrounding with special %c chars + // Info are grouped together whenever is possible + // since the console does not support one style per %c + (function () { + return function (txt) { + var + code = (Object.create || Object)(null), + multiLineCode = transform.multiLineCode.re, + singleLineCode = transform.singleLineCode.re, + storeAndHide = function ($0, $1, $2, $3) { + $3 = $3.replace(/%c/g, '%%c'); + return $1 + $2 + (code[$3] = md5Base64($3)) + $2; + }, + restoreHidden = function ($0, $1, $2, $3) { + return $1 + '%c' + getSource($3, code) + '%c'; + }, + out = [], + args, i, j, length, css, key + ; + + // match and hide possible code (which should not be parsed) + match(txt, 'multiLineCode', out); + txt = txt.replace(multiLineCode, storeAndHide); + match(txt, 'singleLineCode', out); + txt = txt.replace(singleLineCode, storeAndHide); + + // find all special cases preserving the order + // in which are these found + match(txt, 'header2', out); + match(txt, 'header1', out); + match(txt, 'blink', out); + match(txt, 'bold', out); + match(txt, 'dim', out); + match(txt, 'hidden', out); + match(txt, 'reverse', out); + match(txt, 'strike', out); + match(txt, 'underline', out); + match(txt, 'color', out); + + // transform using all info + + // - - - or ___ or * * * with or without space in between + txt = txt.replace(/^[ ]{0,2}([ ]?[*_-][ ]?){3,}[ \t]*$/gm, line); + + // ## Header + txt = replace(txt, 'header2'); + + // # Header + txt = replace(txt, 'header1'); + + // :blink: *bold* -dim- ?hidden? !reverse! _underline_ ~strike~ + txt = replace(txt, 'blink'); + txt = replace(txt, 'bold'); + txt = replace(txt, 'dim'); + txt = replace(txt, 'hidden'); + txt = replace(txt, 'reverse'); + txt = replace(txt, 'strike'); + txt = replace(txt, 'underline'); + + // * list bullets + txt = txt.replace(/^([ \t]{1,})[*+-]([ \t]{1,})/gm, '$1•$2'); + + // > simple quotes + txt = txt.replace(/^[ \t]*>([ \t]?)/gm, function ($0, $1) { + return Array($1.length + 1).join('▌') + $1; + }); + + // #RGBA(color) and !#RGBA(background-color) + txt = replace(txt, 'color'); + + // cleanup duplicates + txt = txt.replace(/(%c)+/g, '%c'); + + // put back code + txt = txt.replace(singleLineCode, restoreHidden); + txt = txt.replace(multiLineCode, restoreHidden); + + // create list of arguments to style the console + args = [txt]; + length = out.length; + for (i = 0; i < length; i++) { + css = ''; + key = ''; + // group styles by type (start/end) + for (j = i; j < length; j++) { + i = j; // update the i to move fast-forward + if (j in out) { + // first match or same kind of operation (start/end) + if (!key || (key === out[j].k)) { + key = out[j].k; + css += out[j].v; + } else { + i--; // if key changed, next loop should update + break; + } + } + } + if (css) args.push(css); + } + return args; + }; + }()) + , + line = Array(33).join('─'), + // just using same name used in echomd, not actual md5 + md5Base64 = function (txt) { + for (var out = [], i = 0; i < txt.length; i++) { + out[i] = txt.charCodeAt(i).toString(32); + } + return out.join('').slice(0, txt.length); + }, + getSource = function (hash, code) { + for (var source in code) { + if (code[source] === hash) { + return source; + } + } + }, + commonReplacer = function ($0, $1, $2, $3) { + return '%c' + $2 + $3 + '%c'; + }, + match = function (txt, what, stack) { + var info = transform[what], i, match; + while (match = info.re.exec(txt)) { + i = match.index; + stack[i] = { + k: 'start', + v: typeof info.start === 'string' ? + info.start : info.start(match) + }; + i = i + match[0].length - 1; + stack[i] = { + k: 'end', + v: typeof info.end === 'string' ? + info.end : info.end(match) + }; + } + }, + replace = function (txt, what) { + var info = transform[what]; + return txt.replace(info.re, info.place); + }, + transform = { + blink: { + re: /(\:{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'padding:0 2px;border:1px solid darkslategray;text-shadow:0 0 2px darkslategray;', + end: 'padding:none;border:none;text-shadow:none;' + }, + bold: { + re: /(\*{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'font-weight:bold;', + end: 'font-weight:default;' + }, + color: { + re: /(!?)#([a-zA-Z0-9]{3,8})\((.+?)\)(?!\))/g, + place: function ($0, bg, rgb, txt) { + return '%c' + txt + '%c'; + }, + start: function (match) { + return (match[1] ? 'background-' : '') + 'color:' + + (/^[a-fA-F0-9]{3,8}$/.test(match[2]) ? '#' : '') + + match[2] + ';'; + }, + end: function (match) { + return (match[1] ? 'background-' : '') + 'color:initial;'; + } + }, + dim: { + re: /(-{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'color:dimgray;', + end: 'color:none;' + }, + header1: { + re: /^(\#[ \t]+)(.+?)[ \t]*\#*([\r\n]+|$)/gm, + place: commonReplacer, + start: 'font-weight:bold;font-size:1.6em;', + end: 'font-weight:default;font-size:default;' + }, + header2: { + re: /^(\#{2,6}[ \t]+)(.+?)[ \t]*\#*([\r\n]+|$)/gm, + place: commonReplacer, + start: 'font-weight:bold;font-size:1.3em;', + end: 'font-weight:default;font-size:default;' + }, + hidden: { + re: /(\?{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'color:rgba(0,0,0,0);', + end: 'color:none;' + }, + reverse: { + re: /(\!{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'padding:0 2px;background:darkslategray;color:lightgray;', + end: 'padding:none;background:none;color:none;' + }, + multiLineCode: { + re: /(^|[^\\])(`{2,})([\s\S]+?)\2(?!`)/g, + start: 'font-family:monospace;', + end: 'font-family:default;' + }, + singleLineCode: { + re: /(^|[^\\])(`)(.+?)\2/gm, + start: 'font-family:monospace;', + end: 'font-family:default;' + }, + strike: { + re: /(~{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'text-decoration:line-through;', + end: 'text-decoration:default;' + }, + underline: { + re: /(_{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'border-bottom:1px solid;', + end: 'border-bottom:default;' + } + }, + // 'error', 'info', 'log', 'warn' are overwritten + // it is possible to use original method at any time + // simply accessing console.methodName.raw( ... ) instead + overwrite = function (method) { + var original = console[method]; + if (original) (consolemd[method] = isNodeJS ? + function () { + return original.apply(console, parse.apply(null, arguments)); + } : + function () { + var singleStringArg = arguments.length === 1 && typeof arguments[0] === 'string'; + var args = singleStringArg ? parse(arguments[0]) : arguments; + + // Todo: We might expose more to the reporter (e.g., returning + // `what` and `match` from the `parse`->`match` function) so + // the user could, e.g., build spans with classes rather than + // inline styles + if (_reporter) { + var + lastIndex, resultInfo, + msg = args[0], + formattingArgs = args.slice(1), + formatRegex = /%c(.*?)(?=%c|$)/g, + tmpIndex = 0; + _reporter.init(); + while ((resultInfo = formatRegex.exec(msg)) !== null) { + var lastIndex = formatRegex.lastIndex; + var result = resultInfo[0]; + if (result.length > 2) { // Ignore any empty %c's + var beginningResultIdx = lastIndex - result.length; + if (beginningResultIdx > tmpIndex) { + var text = msg.slice(tmpIndex, beginningResultIdx); + _reporter.report(text); + } + _reporter.report(result.slice(2), formattingArgs.splice(0, 1)[0]); + } + tmpIndex = lastIndex; + } + if (tmpIndex < msg.length) { + var text = msg.slice(tmpIndex); + _reporter.report(text); + } + _reporter.done(args); + } + return original.apply(console, args); + }).raw = function () { + return original.apply(console, arguments); + }; + }, + consolemd = {}, + methods = ['error', 'info', 'log', 'warn'], + key, + i = 0; i < methods.length; i++ +) { + overwrite(methods[i]); +} +// if this is a CommonJS module +try { + overwrite = function (original) { + return function () { + return original.apply(console, arguments); + }; + }; + for (key in console) { + if (!consolemd.hasOwnProperty(key)) { + consolemd[key] = overwrite(console[key]); + } + } +} catch(e) { + // otherwise replace global console methods + for (i = 0; i < methods.length; i++) { + key = methods[i]; + if (!console[key].raw) { + console[key] = consolemd[key]; + } + } +} + +var _reporter = null; +var addReporter = function (reporter) { + _reporter = reporter; +}; + +exports.addReporter = addReporter; +exports.default = consolemd; + +module.exports = exports.default; diff --git a/core.js b/core.js new file mode 100644 index 0000000..f96997d --- /dev/null +++ b/core.js @@ -0,0 +1,328 @@ +/*! (c) 2013-2018 Andrea Giammarchi (ISC) */ +/** + * Fully inspired by the work of John Gruber + * + */ +for (var + isNodeJS = typeof process === 'object' && !process.browser, + parse = isNodeJS ? + // on NodeJS simply fallback to echomd + (function (echomd, map) { + function parse(value) { + return typeof value === 'string' ? + echomd(value) : value; + } + return function () { + return map.call(arguments, parse); + }; + }(require('echomd').raw, [].map)) : + // on browsers implement some %cmagic%c + // The current algorithm is based on two passes: + // 1. collect all info ordered by string index + // 2. transform surrounding with special %c chars + // Info are grouped together whenever is possible + // since the console does not support one style per %c + (function () { + return function (txt) { + var + code = (Object.create || Object)(null), + multiLineCode = transform.multiLineCode.re, + singleLineCode = transform.singleLineCode.re, + storeAndHide = function ($0, $1, $2, $3) { + $3 = $3.replace(/%c/g, '%%c'); + return $1 + $2 + (code[$3] = md5Base64($3)) + $2; + }, + restoreHidden = function ($0, $1, $2, $3) { + return $1 + '%c' + getSource($3, code) + '%c'; + }, + out = [], + args, i, j, length, css, key + ; + + // match and hide possible code (which should not be parsed) + match(txt, 'multiLineCode', out); + txt = txt.replace(multiLineCode, storeAndHide); + match(txt, 'singleLineCode', out); + txt = txt.replace(singleLineCode, storeAndHide); + + // find all special cases preserving the order + // in which are these found + match(txt, 'header2', out); + match(txt, 'header1', out); + match(txt, 'blink', out); + match(txt, 'bold', out); + match(txt, 'dim', out); + match(txt, 'hidden', out); + match(txt, 'reverse', out); + match(txt, 'strike', out); + match(txt, 'underline', out); + match(txt, 'color', out); + + // transform using all info + + // - - - or ___ or * * * with or without space in between + txt = txt.replace(/^[ ]{0,2}([ ]?[*_-][ ]?){3,}[ \t]*$/gm, line); + + // ## Header + txt = replace(txt, 'header2'); + + // # Header + txt = replace(txt, 'header1'); + + // :blink: *bold* -dim- ?hidden? !reverse! _underline_ ~strike~ + txt = replace(txt, 'blink'); + txt = replace(txt, 'bold'); + txt = replace(txt, 'dim'); + txt = replace(txt, 'hidden'); + txt = replace(txt, 'reverse'); + txt = replace(txt, 'strike'); + txt = replace(txt, 'underline'); + + // * list bullets + txt = txt.replace(/^([ \t]{1,})[*+-]([ \t]{1,})/gm, '$1•$2'); + + // > simple quotes + txt = txt.replace(/^[ \t]*>([ \t]?)/gm, function ($0, $1) { + return Array($1.length + 1).join('▌') + $1; + }); + + // #RGBA(color) and !#RGBA(background-color) + txt = replace(txt, 'color'); + + // cleanup duplicates + txt = txt.replace(/(%c)+/g, '%c'); + + // put back code + txt = txt.replace(singleLineCode, restoreHidden); + txt = txt.replace(multiLineCode, restoreHidden); + + // create list of arguments to style the console + args = [txt]; + length = out.length; + for (i = 0; i < length; i++) { + css = ''; + key = ''; + // group styles by type (start/end) + for (j = i; j < length; j++) { + i = j; // update the i to move fast-forward + if (j in out) { + // first match or same kind of operation (start/end) + if (!key || (key === out[j].k)) { + key = out[j].k; + css += out[j].v; + } else { + i--; // if key changed, next loop should update + break; + } + } + } + if (css) args.push(css); + } + return args; + }; + }()) + , + line = Array(33).join('─'), + // just using same name used in echomd, not actual md5 + md5Base64 = function (txt) { + for (var out = [], i = 0; i < txt.length; i++) { + out[i] = txt.charCodeAt(i).toString(32); + } + return out.join('').slice(0, txt.length); + }, + getSource = function (hash, code) { + for (var source in code) { + if (code[source] === hash) { + return source; + } + } + }, + commonReplacer = function ($0, $1, $2, $3) { + return '%c' + $2 + $3 + '%c'; + }, + match = function (txt, what, stack) { + var info = transform[what], i, match; + while (match = info.re.exec(txt)) { + i = match.index; + stack[i] = { + k: 'start', + v: typeof info.start === 'string' ? + info.start : info.start(match) + }; + i = i + match[0].length - 1; + stack[i] = { + k: 'end', + v: typeof info.end === 'string' ? + info.end : info.end(match) + }; + } + }, + replace = function (txt, what) { + var info = transform[what]; + return txt.replace(info.re, info.place); + }, + transform = { + blink: { + re: /(\:{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'padding:0 2px;border:1px solid darkslategray;text-shadow:0 0 2px darkslategray;', + end: 'padding:none;border:none;text-shadow:none;' + }, + bold: { + re: /(\*{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'font-weight:bold;', + end: 'font-weight:default;' + }, + color: { + re: /(!?)#([a-zA-Z0-9]{3,8})\((.+?)\)(?!\))/g, + place: function ($0, bg, rgb, txt) { + return '%c' + txt + '%c'; + }, + start: function (match) { + return (match[1] ? 'background-' : '') + 'color:' + + (/^[a-fA-F0-9]{3,8}$/.test(match[2]) ? '#' : '') + + match[2] + ';'; + }, + end: function (match) { + return (match[1] ? 'background-' : '') + 'color:initial;'; + } + }, + dim: { + re: /(-{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'color:dimgray;', + end: 'color:none;' + }, + header1: { + re: /^(\#[ \t]+)(.+?)[ \t]*\#*([\r\n]+|$)/gm, + place: commonReplacer, + start: 'font-weight:bold;font-size:1.6em;', + end: 'font-weight:default;font-size:default;' + }, + header2: { + re: /^(\#{2,6}[ \t]+)(.+?)[ \t]*\#*([\r\n]+|$)/gm, + place: commonReplacer, + start: 'font-weight:bold;font-size:1.3em;', + end: 'font-weight:default;font-size:default;' + }, + hidden: { + re: /(\?{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'color:rgba(0,0,0,0);', + end: 'color:none;' + }, + reverse: { + re: /(\!{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'padding:0 2px;background:darkslategray;color:lightgray;', + end: 'padding:none;background:none;color:none;' + }, + multiLineCode: { + re: /(^|[^\\])(`{2,})([\s\S]+?)\2(?!`)/g, + start: 'font-family:monospace;', + end: 'font-family:default;' + }, + singleLineCode: { + re: /(^|[^\\])(`)(.+?)\2/gm, + start: 'font-family:monospace;', + end: 'font-family:default;' + }, + strike: { + re: /(~{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'text-decoration:line-through;', + end: 'text-decoration:default;' + }, + underline: { + re: /(_{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'border-bottom:1px solid;', + end: 'border-bottom:default;' + } + }, + // 'error', 'info', 'log', 'warn' are overwritten + // it is possible to use original method at any time + // simply accessing console.methodName.raw( ... ) instead + overwrite = function (method) { + var original = console[method]; + if (original) (consolemd[method] = isNodeJS ? + function () { + return original.apply(console, parse.apply(null, arguments)); + } : + function () { + var singleStringArg = arguments.length === 1 && typeof arguments[0] === 'string'; + var args = singleStringArg ? parse(arguments[0]) : arguments; + + // Todo: We might expose more to the reporter (e.g., returning + // `what` and `match` from the `parse`->`match` function) so + // the user could, e.g., build spans with classes rather than + // inline styles + if (_reporter) { + var + lastIndex, resultInfo, + msg = args[0], + formattingArgs = args.slice(1), + formatRegex = /%c(.*?)(?=%c|$)/g, + tmpIndex = 0; + _reporter.init(); + while ((resultInfo = formatRegex.exec(msg)) !== null) { + var lastIndex = formatRegex.lastIndex; + var result = resultInfo[0]; + if (result.length > 2) { // Ignore any empty %c's + var beginningResultIdx = lastIndex - result.length; + if (beginningResultIdx > tmpIndex) { + var text = msg.slice(tmpIndex, beginningResultIdx); + _reporter.report(text); + } + _reporter.report(result.slice(2), formattingArgs.splice(0, 1)[0]); + } + tmpIndex = lastIndex; + } + if (tmpIndex < msg.length) { + var text = msg.slice(tmpIndex); + _reporter.report(text); + } + _reporter.done(args); + } + return original.apply(console, args); + }).raw = function () { + return original.apply(console, arguments); + }; + }, + consolemd = {}, + methods = ['error', 'info', 'log', 'warn'], + key, + i = 0; i < methods.length; i++ +) { + overwrite(methods[i]); +} +// if this is a CommonJS module +try { + overwrite = function (original) { + return function () { + return original.apply(console, arguments); + }; + }; + for (key in console) { + if (!consolemd.hasOwnProperty(key)) { + consolemd[key] = overwrite(console[key]); + } + } +} catch(e) { + // otherwise replace global console methods + for (i = 0; i < methods.length; i++) { + key = methods[i]; + if (!console[key].raw) { + console[key] = consolemd[key]; + } + } +} + +var _reporter = null; +var addReporter = function (reporter) { + _reporter = reporter; +}; + +export { addReporter }; +export default consolemd; diff --git a/esm/index.js b/esm/index.js new file mode 100644 index 0000000..38e0592 --- /dev/null +++ b/esm/index.js @@ -0,0 +1,328 @@ +/*! (c) 2013-2018 Andrea Giammarchi (ISC) */ +/** + * Fully inspired by the work of John Gruber + * + */ +for (var + isNodeJS = typeof process === 'object' && !process.browser, + parse = isNodeJS ? + // on NodeJS simply fallback to echomd + (function (echomd, map) { + function parse(value) { + return typeof value === 'string' ? + echomd(value) : value; + } + return function () { + return map.call(arguments, parse); + }; + }(require('echomd').raw, [].map)) : + // on browsers implement some %cmagic%c + // The current algorithm is based on two passes: + // 1. collect all info ordered by string index + // 2. transform surrounding with special %c chars + // Info are grouped together whenever is possible + // since the console does not support one style per %c + (function () { + return function (txt) { + var + code = (Object.create || Object)(null), + multiLineCode = transform.multiLineCode.re, + singleLineCode = transform.singleLineCode.re, + storeAndHide = function ($0, $1, $2, $3) { + $3 = $3.replace(/%c/g, '%%c'); + return $1 + $2 + (code[$3] = md5Base64($3)) + $2; + }, + restoreHidden = function ($0, $1, $2, $3) { + return $1 + '%c' + getSource($3, code) + '%c'; + }, + out = [], + args, i, j, length, css, key + ; + + // match and hide possible code (which should not be parsed) + match(txt, 'multiLineCode', out); + txt = txt.replace(multiLineCode, storeAndHide); + match(txt, 'singleLineCode', out); + txt = txt.replace(singleLineCode, storeAndHide); + + // find all special cases preserving the order + // in which are these found + match(txt, 'header2', out); + match(txt, 'header1', out); + match(txt, 'blink', out); + match(txt, 'bold', out); + match(txt, 'dim', out); + match(txt, 'hidden', out); + match(txt, 'reverse', out); + match(txt, 'strike', out); + match(txt, 'underline', out); + match(txt, 'color', out); + + // transform using all info + + // - - - or ___ or * * * with or without space in between + txt = txt.replace(/^[ ]{0,2}([ ]?[*_-][ ]?){3,}[ \t]*$/gm, line); + + // ## Header + txt = replace(txt, 'header2'); + + // # Header + txt = replace(txt, 'header1'); + + // :blink: *bold* -dim- ?hidden? !reverse! _underline_ ~strike~ + txt = replace(txt, 'blink'); + txt = replace(txt, 'bold'); + txt = replace(txt, 'dim'); + txt = replace(txt, 'hidden'); + txt = replace(txt, 'reverse'); + txt = replace(txt, 'strike'); + txt = replace(txt, 'underline'); + + // * list bullets + txt = txt.replace(/^([ \t]{1,})[*+-]([ \t]{1,})/gm, '$1•$2'); + + // > simple quotes + txt = txt.replace(/^[ \t]*>([ \t]?)/gm, function ($0, $1) { + return Array($1.length + 1).join('▌') + $1; + }); + + // #RGBA(color) and !#RGBA(background-color) + txt = replace(txt, 'color'); + + // cleanup duplicates + txt = txt.replace(/(%c)+/g, '%c'); + + // put back code + txt = txt.replace(singleLineCode, restoreHidden); + txt = txt.replace(multiLineCode, restoreHidden); + + // create list of arguments to style the console + args = [txt]; + length = out.length; + for (i = 0; i < length; i++) { + css = ''; + key = ''; + // group styles by type (start/end) + for (j = i; j < length; j++) { + i = j; // update the i to move fast-forward + if (j in out) { + // first match or same kind of operation (start/end) + if (!key || (key === out[j].k)) { + key = out[j].k; + css += out[j].v; + } else { + i--; // if key changed, next loop should update + break; + } + } + } + if (css) args.push(css); + } + return args; + }; + }()) + , + line = Array(33).join('─'), + // just using same name used in echomd, not actual md5 + md5Base64 = function (txt) { + for (var out = [], i = 0; i < txt.length; i++) { + out[i] = txt.charCodeAt(i).toString(32); + } + return out.join('').slice(0, txt.length); + }, + getSource = function (hash, code) { + for (var source in code) { + if (code[source] === hash) { + return source; + } + } + }, + commonReplacer = function ($0, $1, $2, $3) { + return '%c' + $2 + $3 + '%c'; + }, + match = function (txt, what, stack) { + var info = transform[what], i, match; + while (match = info.re.exec(txt)) { + i = match.index; + stack[i] = { + k: 'start', + v: typeof info.start === 'string' ? + info.start : info.start(match) + }; + i = i + match[0].length - 1; + stack[i] = { + k: 'end', + v: typeof info.end === 'string' ? + info.end : info.end(match) + }; + } + }, + replace = function (txt, what) { + var info = transform[what]; + return txt.replace(info.re, info.place); + }, + transform = { + blink: { + re: /(\:{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'padding:0 2px;border:1px solid darkslategray;text-shadow:0 0 2px darkslategray;', + end: 'padding:none;border:none;text-shadow:none;' + }, + bold: { + re: /(\*{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'font-weight:bold;', + end: 'font-weight:default;' + }, + color: { + re: /(!?)#([a-zA-Z0-9]{3,8})\((.+?)\)(?!\))/g, + place: function ($0, bg, rgb, txt) { + return '%c' + txt + '%c'; + }, + start: function (match) { + return (match[1] ? 'background-' : '') + 'color:' + + (/^[a-fA-F0-9]{3,8}$/.test(match[2]) ? '#' : '') + + match[2] + ';'; + }, + end: function (match) { + return (match[1] ? 'background-' : '') + 'color:initial;'; + } + }, + dim: { + re: /(-{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'color:dimgray;', + end: 'color:none;' + }, + header1: { + re: /^(\#[ \t]+)(.+?)[ \t]*\#*([\r\n]+|$)/gm, + place: commonReplacer, + start: 'font-weight:bold;font-size:1.6em;', + end: 'font-weight:default;font-size:default;' + }, + header2: { + re: /^(\#{2,6}[ \t]+)(.+?)[ \t]*\#*([\r\n]+|$)/gm, + place: commonReplacer, + start: 'font-weight:bold;font-size:1.3em;', + end: 'font-weight:default;font-size:default;' + }, + hidden: { + re: /(\?{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'color:rgba(0,0,0,0);', + end: 'color:none;' + }, + reverse: { + re: /(\!{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'padding:0 2px;background:darkslategray;color:lightgray;', + end: 'padding:none;background:none;color:none;' + }, + multiLineCode: { + re: /(^|[^\\])(`{2,})([\s\S]+?)\2(?!`)/g, + start: 'font-family:monospace;', + end: 'font-family:default;' + }, + singleLineCode: { + re: /(^|[^\\])(`)(.+?)\2/gm, + start: 'font-family:monospace;', + end: 'font-family:default;' + }, + strike: { + re: /(~{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'text-decoration:line-through;', + end: 'text-decoration:default;' + }, + underline: { + re: /(_{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'border-bottom:1px solid;', + end: 'border-bottom:default;' + } + }, + // 'error', 'info', 'log', 'warn' are overwritten + // it is possible to use original method at any time + // simply accessing console.methodName.raw( ... ) instead + overwrite = function (method) { + var original = console[method]; + if (original) (consolemd[method] = isNodeJS ? + function () { + return original.apply(console, parse.apply(null, arguments)); + } : + function () { + var singleStringArg = arguments.length === 1 && typeof arguments[0] === 'string'; + var args = singleStringArg ? parse(arguments[0]) : arguments; + + // Todo: We might expose more to the reporter (e.g., returning + // `what` and `match` from the `parse`->`match` function) so + // the user could, e.g., build spans with classes rather than + // inline styles + if (_reporter) { + var + lastIndex, resultInfo, + msg = args[0], + formattingArgs = args.slice(1), + formatRegex = /%c(.*?)(?=%c|$)/g, + tmpIndex = 0; + _reporter.init(); + while ((resultInfo = formatRegex.exec(msg)) !== null) { + var lastIndex = formatRegex.lastIndex; + var result = resultInfo[0]; + if (result.length > 2) { // Ignore any empty %c's + var beginningResultIdx = lastIndex - result.length; + if (beginningResultIdx > tmpIndex) { + var text = msg.slice(tmpIndex, beginningResultIdx); + _reporter.report(text); + } + _reporter.report(result.slice(2), formattingArgs.splice(0, 1)[0]); + } + tmpIndex = lastIndex; + } + if (tmpIndex < msg.length) { + var text = msg.slice(tmpIndex); + _reporter.report(text); + } + _reporter.done(args); + } + return original.apply(console, args); + }).raw = function () { + return original.apply(console, arguments); + }; + }, + consolemd = {}, + methods = ['error', 'info', 'log', 'warn'], + key, + i = 0; i < methods.length; i++ +) { + overwrite(methods[i]); +} +// if this is a CommonJS module +try { + overwrite = function (original) { + return function () { + return original.apply(console, arguments); + }; + }; + for (key in console) { + if (!consolemd.hasOwnProperty(key)) { + consolemd[key] = overwrite(console[key]); + } + } +} catch(e) { + // otherwise replace global console methods + for (i = 0; i < methods.length; i++) { + key = methods[i]; + if (!console[key].raw) { + console[key] = consolemd[key]; + } + } +} + +var _reporter = null; +var addReporter = function (reporter) { + _reporter = reporter; +}; + +export default consolemd; +export { addReporter }; diff --git a/esm/index.min.js b/esm/index.min.js new file mode 100644 index 0000000..f8ccec1 --- /dev/null +++ b/esm/index.min.js @@ -0,0 +1 @@ +for(var key,isNodeJS="object"==typeof process&&!process.browser,parse=isNodeJS?function(e,r){function o(r){return"string"==typeof r?e(r):r}return function(){return r.call(arguments,o)}}(require("echomd").raw,[].map):function(e){var r,o,t,n,a,c,l=(Object.create||Object)(null),i=transform.multiLineCode.re,d=transform.singleLineCode.re,p=function(e,r,o,t){return t=t.replace(/%c/g,"%%c"),r+o+(l[t]=md5Base64(t))+o},s=function(e,r,o,t){return r+"%c"+getSource(t,l)+"%c"},f=[];for(match(e,"multiLineCode",f),e=e.replace(i,p),match(e,"singleLineCode",f),e=e.replace(d,p),match(e,"header2",f),match(e,"header1",f),match(e,"blink",f),match(e,"bold",f),match(e,"dim",f),match(e,"hidden",f),match(e,"reverse",f),match(e,"strike",f),match(e,"underline",f),match(e,"color",f),e=e.replace(/^[ ]{0,2}([ ]?[*_-][ ]?){3,}[ \t]*$/gm,line),e=replace(e,"header2"),e=replace(e,"header1"),e=replace(e,"blink"),e=replace(e,"bold"),e=replace(e,"dim"),e=replace(e,"hidden"),e=replace(e,"reverse"),e=replace(e,"strike"),e=(e=(e=replace(e,"underline")).replace(/^([ \t]{1,})[*+-]([ \t]{1,})/gm,"$1•$2")).replace(/^[ \t]*>([ \t]?)/gm,function(e,r){return Array(r.length+1).join("▌")+r}),r=[e=(e=(e=(e=replace(e,"color")).replace(/(%c)+/g,"%c")).replace(d,s)).replace(i,s)],n=f.length,o=0;o2){var d=l-i.length;if(d>c){var p=t.slice(c,d);_reporter.report(p)}_reporter.report(i.slice(2),n.splice(0,1)[0])}c=l}if(c - */ -(function () {'use strict'; +var consolemd = (function (exports) { + 'use strict'; + + /*! (c) 2013-2018 Andrea Giammarchi (ISC) */ + /** + * Fully inspired by the work of John Gruber + * + */ for (var isNodeJS = typeof process === 'object' && !process.browser, parse = isNodeJS ? @@ -37,7 +39,7 @@ return $1 + '%c' + getSource($3, code) + '%c'; }, out = [], - args, i, j, length, css, key, re + args, i, j, length, css, key ; // match and hide possible code (which should not be parsed) @@ -252,9 +254,41 @@ return original.apply(console, parse.apply(null, arguments)); } : function () { - return arguments.length === 1 && typeof arguments[0] === 'string' ? - original.apply(console, parse(arguments[0])) : - original.apply(console, arguments); + var singleStringArg = arguments.length === 1 && typeof arguments[0] === 'string'; + var args = singleStringArg ? parse(arguments[0]) : arguments; + + // Todo: We might expose more to the reporter (e.g., returning + // `what` and `match` from the `parse`->`match` function) so + // the user could, e.g., build spans with classes rather than + // inline styles + if (_reporter) { + var + lastIndex, resultInfo, + msg = args[0], + formattingArgs = args.slice(1), + formatRegex = /%c(.*?)(?=%c|$)/g, + tmpIndex = 0; + _reporter.init(); + while ((resultInfo = formatRegex.exec(msg)) !== null) { + var lastIndex = formatRegex.lastIndex; + var result = resultInfo[0]; + if (result.length > 2) { // Ignore any empty %c's + var beginningResultIdx = lastIndex - result.length; + if (beginningResultIdx > tmpIndex) { + var text = msg.slice(tmpIndex, beginningResultIdx); + _reporter.report(text); + } + _reporter.report(result.slice(2), formattingArgs.splice(0, 1)[0]); + } + tmpIndex = lastIndex; + } + if (tmpIndex < msg.length) { + var text = msg.slice(tmpIndex); + _reporter.report(text); + } + _reporter.done(args); + } + return original.apply(console, args); }).raw = function () { return original.apply(console, arguments); }; @@ -268,8 +302,6 @@ } // if this is a CommonJS module try { - // export consolemd fake object - module.exports = consolemd; overwrite = function (original) { return function () { return original.apply(console, arguments); @@ -289,4 +321,15 @@ } } } -}()); + + var _reporter = null; + var addReporter = function (reporter) { + _reporter = reporter; + }; + + exports.addReporter = addReporter; + exports.default = consolemd; + + return exports; + +}({})); diff --git a/min.js b/min.js index 1d3d214..c77a730 100644 --- a/min.js +++ b/min.js @@ -1,2 +1 @@ -/*! (c) 2013-2018 Andrea Giammarchi (ISC) */ -!function(){"use strict";for(var n,r="object"==typeof process&&!process.browser,t=r?function(n,e){function r(e){return"string"==typeof e?n(e):e}return function(){return e.call(arguments,r)}}(require("echomd").raw,[].map):function(e){var n,r,t,o,a,l,c=(Object.create||Object)(null),i=y.multiLineCode.re,d=y.singleLineCode.re,f=function(e,n,r,t){return t=t.replace(/%c/g,"%%c"),n+r+(c[t]=p(t))+r},u=function(e,n,r,t){return n+"%c"+h(t,c)+"%c"},s=[];for(m(e,"multiLineCode",s),e=e.replace(i,f),m(e,"singleLineCode",s),e=e.replace(d,f),m(e,"header2",s),m(e,"header1",s),m(e,"blink",s),m(e,"bold",s),m(e,"dim",s),m(e,"hidden",s),m(e,"reverse",s),m(e,"strike",s),m(e,"underline",s),m(e,"color",s),e=e.replace(/^[ ]{0,2}([ ]?[*_-][ ]?){3,}[ \t]*$/gm,g),e=b(e,"header2"),e=b(e,"header1"),e=b(e,"blink"),e=b(e,"bold"),e=b(e,"dim"),e=b(e,"hidden"),e=b(e,"reverse"),e=b(e,"strike"),e=(e=(e=b(e,"underline")).replace(/^([ \t]{1,})[*+-]([ \t]{1,})/gm,"$1•$2")).replace(/^[ \t]*>([ \t]?)/gm,function(e,n){return Array(n.length+1).join("▌")+n}),n=[e=(e=(e=(e=b(e,"color")).replace(/(%c)+/g,"%c")).replace(d,u)).replace(i,u)],o=s.length,r=0;r([ \t]?)/gm,function(e,r){return Array(r.length+1).join("▌")+r}),r=[e=(e=(e=(e=d(e,"color")).replace(/(%c)+/g,"%c")).replace(h,b)).replace(p,b)],i=y.length,n=0;n2){var f=c-d.length;if(f>i){var u=o.slice(i,f);h.report(u)}h.report(d.slice(2),a.splice(0,1)[0])}i=c}if(i + + + + consolemd tests + + + + + + diff --git a/test/test-cjs.js b/test/test-cjs.js new file mode 100644 index 0000000..01d5fc2 --- /dev/null +++ b/test/test-cjs.js @@ -0,0 +1,341 @@ +'use strict'; + +/*! (c) 2013-2018 Andrea Giammarchi (ISC) */ +/** + * Fully inspired by the work of John Gruber + * + */ +for (var + isNodeJS = typeof process === 'object' && !process.browser, + parse = isNodeJS ? + // on NodeJS simply fallback to echomd + (function (echomd, map) { + function parse(value) { + return typeof value === 'string' ? + echomd(value) : value; + } + return function () { + return map.call(arguments, parse); + }; + }(require('echomd').raw, [].map)) : + // on browsers implement some %cmagic%c + // The current algorithm is based on two passes: + // 1. collect all info ordered by string index + // 2. transform surrounding with special %c chars + // Info are grouped together whenever is possible + // since the console does not support one style per %c + (function () { + return function (txt) { + var + code = (Object.create || Object)(null), + multiLineCode = transform.multiLineCode.re, + singleLineCode = transform.singleLineCode.re, + storeAndHide = function ($0, $1, $2, $3) { + $3 = $3.replace(/%c/g, '%%c'); + return $1 + $2 + (code[$3] = md5Base64($3)) + $2; + }, + restoreHidden = function ($0, $1, $2, $3) { + return $1 + '%c' + getSource($3, code) + '%c'; + }, + out = [], + args, i, j, length, css, key + ; + + // match and hide possible code (which should not be parsed) + match(txt, 'multiLineCode', out); + txt = txt.replace(multiLineCode, storeAndHide); + match(txt, 'singleLineCode', out); + txt = txt.replace(singleLineCode, storeAndHide); + + // find all special cases preserving the order + // in which are these found + match(txt, 'header2', out); + match(txt, 'header1', out); + match(txt, 'blink', out); + match(txt, 'bold', out); + match(txt, 'dim', out); + match(txt, 'hidden', out); + match(txt, 'reverse', out); + match(txt, 'strike', out); + match(txt, 'underline', out); + match(txt, 'color', out); + + // transform using all info + + // - - - or ___ or * * * with or without space in between + txt = txt.replace(/^[ ]{0,2}([ ]?[*_-][ ]?){3,}[ \t]*$/gm, line); + + // ## Header + txt = replace(txt, 'header2'); + + // # Header + txt = replace(txt, 'header1'); + + // :blink: *bold* -dim- ?hidden? !reverse! _underline_ ~strike~ + txt = replace(txt, 'blink'); + txt = replace(txt, 'bold'); + txt = replace(txt, 'dim'); + txt = replace(txt, 'hidden'); + txt = replace(txt, 'reverse'); + txt = replace(txt, 'strike'); + txt = replace(txt, 'underline'); + + // * list bullets + txt = txt.replace(/^([ \t]{1,})[*+-]([ \t]{1,})/gm, '$1•$2'); + + // > simple quotes + txt = txt.replace(/^[ \t]*>([ \t]?)/gm, function ($0, $1) { + return Array($1.length + 1).join('▌') + $1; + }); + + // #RGBA(color) and !#RGBA(background-color) + txt = replace(txt, 'color'); + + // cleanup duplicates + txt = txt.replace(/(%c)+/g, '%c'); + + // put back code + txt = txt.replace(singleLineCode, restoreHidden); + txt = txt.replace(multiLineCode, restoreHidden); + + // create list of arguments to style the console + args = [txt]; + length = out.length; + for (i = 0; i < length; i++) { + css = ''; + key = ''; + // group styles by type (start/end) + for (j = i; j < length; j++) { + i = j; // update the i to move fast-forward + if (j in out) { + // first match or same kind of operation (start/end) + if (!key || (key === out[j].k)) { + key = out[j].k; + css += out[j].v; + } else { + i--; // if key changed, next loop should update + break; + } + } + } + if (css) args.push(css); + } + return args; + }; + }()) + , + line = Array(33).join('─'), + // just using same name used in echomd, not actual md5 + md5Base64 = function (txt) { + for (var out = [], i = 0; i < txt.length; i++) { + out[i] = txt.charCodeAt(i).toString(32); + } + return out.join('').slice(0, txt.length); + }, + getSource = function (hash, code) { + for (var source in code) { + if (code[source] === hash) { + return source; + } + } + }, + commonReplacer = function ($0, $1, $2, $3) { + return '%c' + $2 + $3 + '%c'; + }, + match = function (txt, what, stack) { + var info = transform[what], i, match; + while (match = info.re.exec(txt)) { + i = match.index; + stack[i] = { + k: 'start', + v: typeof info.start === 'string' ? + info.start : info.start(match) + }; + i = i + match[0].length - 1; + stack[i] = { + k: 'end', + v: typeof info.end === 'string' ? + info.end : info.end(match) + }; + } + }, + replace = function (txt, what) { + var info = transform[what]; + return txt.replace(info.re, info.place); + }, + transform = { + blink: { + re: /(\:{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'padding:0 2px;border:1px solid darkslategray;text-shadow:0 0 2px darkslategray;', + end: 'padding:none;border:none;text-shadow:none;' + }, + bold: { + re: /(\*{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'font-weight:bold;', + end: 'font-weight:default;' + }, + color: { + re: /(!?)#([a-zA-Z0-9]{3,8})\((.+?)\)(?!\))/g, + place: function ($0, bg, rgb, txt) { + return '%c' + txt + '%c'; + }, + start: function (match) { + return (match[1] ? 'background-' : '') + 'color:' + + (/^[a-fA-F0-9]{3,8}$/.test(match[2]) ? '#' : '') + + match[2] + ';'; + }, + end: function (match) { + return (match[1] ? 'background-' : '') + 'color:initial;'; + } + }, + dim: { + re: /(-{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'color:dimgray;', + end: 'color:none;' + }, + header1: { + re: /^(\#[ \t]+)(.+?)[ \t]*\#*([\r\n]+|$)/gm, + place: commonReplacer, + start: 'font-weight:bold;font-size:1.6em;', + end: 'font-weight:default;font-size:default;' + }, + header2: { + re: /^(\#{2,6}[ \t]+)(.+?)[ \t]*\#*([\r\n]+|$)/gm, + place: commonReplacer, + start: 'font-weight:bold;font-size:1.3em;', + end: 'font-weight:default;font-size:default;' + }, + hidden: { + re: /(\?{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'color:rgba(0,0,0,0);', + end: 'color:none;' + }, + reverse: { + re: /(\!{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'padding:0 2px;background:darkslategray;color:lightgray;', + end: 'padding:none;background:none;color:none;' + }, + multiLineCode: { + re: /(^|[^\\])(`{2,})([\s\S]+?)\2(?!`)/g, + start: 'font-family:monospace;', + end: 'font-family:default;' + }, + singleLineCode: { + re: /(^|[^\\])(`)(.+?)\2/gm, + start: 'font-family:monospace;', + end: 'font-family:default;' + }, + strike: { + re: /(~{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'text-decoration:line-through;', + end: 'text-decoration:default;' + }, + underline: { + re: /(_{1,2})(?=\S)(.*?)(\S)\1/g, + place: commonReplacer, + start: 'border-bottom:1px solid;', + end: 'border-bottom:default;' + } + }, + // 'error', 'info', 'log', 'warn' are overwritten + // it is possible to use original method at any time + // simply accessing console.methodName.raw( ... ) instead + overwrite = function (method) { + var original = console[method]; + if (original) (consolemd[method] = isNodeJS ? + function () { + return original.apply(console, parse.apply(null, arguments)); + } : + function () { + var singleStringArg = arguments.length === 1 && typeof arguments[0] === 'string'; + var args = singleStringArg ? parse(arguments[0]) : arguments; + + // Todo: We might expose more to the reporter (e.g., returning + // `what` and `match` from the `parse`->`match` function) so + // the user could, e.g., build spans with classes rather than + // inline styles + if (_reporter) { + var + lastIndex, resultInfo, + msg = args[0], + formattingArgs = args.slice(1), + formatRegex = /%c(.*?)(?=%c|$)/g, + tmpIndex = 0; + _reporter.init(); + while ((resultInfo = formatRegex.exec(msg)) !== null) { + var lastIndex = formatRegex.lastIndex; + var result = resultInfo[0]; + if (result.length > 2) { // Ignore any empty %c's + var beginningResultIdx = lastIndex - result.length; + if (beginningResultIdx > tmpIndex) { + var text = msg.slice(tmpIndex, beginningResultIdx); + _reporter.report(text); + } + _reporter.report(result.slice(2), formattingArgs.splice(0, 1)[0]); + } + tmpIndex = lastIndex; + } + if (tmpIndex < msg.length) { + var text = msg.slice(tmpIndex); + _reporter.report(text); + } + _reporter.done(args); + } + return original.apply(console, args); + }).raw = function () { + return original.apply(console, arguments); + }; + }, + consolemd = {}, + methods = ['error', 'info', 'log', 'warn'], + key, + i = 0; i < methods.length; i++ +) { + overwrite(methods[i]); +} +// if this is a CommonJS module +try { + overwrite = function (original) { + return function () { + return original.apply(console, arguments); + }; + }; + for (key in console) { + if (!consolemd.hasOwnProperty(key)) { + consolemd[key] = overwrite(console[key]); + } + } +} catch(e) { + // otherwise replace global console methods + for (i = 0; i < methods.length; i++) { + key = methods[i]; + if (!console[key].raw) { + console[key] = consolemd[key]; + } + } +} + +var _reporter = null; +var addReporter = function (reporter) { + _reporter = reporter; +}; + +function test (reporter) { + if (reporter) { + addReporter(reporter); + } + consolemd.log(':#green(*✓*): *OK*'); + consolemd.log(':#red(*x*): *FAIL*'); + consolemd.log('no formatting front only#yellow(*✓*)'); + consolemd.log('no formatting front#yellow(*✓*)no formatting back'); + consolemd.log('#yellow(*✓*)no formatting back only'); + consolemd.log('no formatting at all'); +} + +module.exports = test; diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..a833d7b --- /dev/null +++ b/test/test.js @@ -0,0 +1,13 @@ +import {default as consolemd, addReporter} from '../esm/index.js'; + +export default function (reporter) { + if (reporter) { + addReporter(reporter); + } + consolemd.log(':#green(*✓*): *OK*'); + consolemd.log(':#red(*x*): *FAIL*'); + consolemd.log('no formatting front only#yellow(*✓*)'); + consolemd.log('no formatting front#yellow(*✓*)no formatting back'); + consolemd.log('#yellow(*✓*)no formatting back only'); + consolemd.log('no formatting at all'); +}