diff --git a/package.json b/package.json index 04a6967fdb9..2554927521d 100644 --- a/package.json +++ b/package.json @@ -169,7 +169,6 @@ "angular-native-dragdrop": "1.2.2", "angular-route": "1.6.6", "angular-sanitize": "1.6.6", - "ansicolor": "1.1.78", "baron": "^3.0.3", "brace": "^0.10.0", "classnames": "^2.2.6", diff --git a/public/app/features/explore/LogMessageAnsi.tsx b/public/app/features/explore/LogMessageAnsi.tsx index 53147656d6a..72e15bc38f8 100644 --- a/public/app/features/explore/LogMessageAnsi.tsx +++ b/public/app/features/explore/LogMessageAnsi.tsx @@ -1,5 +1,5 @@ import React, { PureComponent } from 'react'; -import ansicolor from 'ansicolor'; +import ansicolor from 'vendor/ansicolor/ansicolor'; interface Style { [key: string]: string; diff --git a/public/app/plugins/datasource/loki/result_transformer.ts b/public/app/plugins/datasource/loki/result_transformer.ts index 1fbabba1789..4f5896c782f 100644 --- a/public/app/plugins/datasource/loki/result_transformer.ts +++ b/public/app/plugins/datasource/loki/result_transformer.ts @@ -1,4 +1,4 @@ -import ansicolor from 'ansicolor'; +import ansicolor from 'vendor/ansicolor/ansicolor'; import _ from 'lodash'; import moment from 'moment'; diff --git a/public/vendor/ansicolor/ansicolor.ts b/public/vendor/ansicolor/ansicolor.ts new file mode 100644 index 00000000000..98524eb6827 --- /dev/null +++ b/public/vendor/ansicolor/ansicolor.ts @@ -0,0 +1,471 @@ +// Vendored and converted to TS, source: https://github.com/xpl/ansicolor/blob/b82360563ed29de444dc7618b9236191e0a77096/ansicolor.js +// License: Unlicense, author: https://github.com/xpl + +const O = Object; + +/* See https://misc.flogisoft.com/bash/tip_colors_and_formatting + ------------------------------------------------------------------------ */ + +const colorCodes = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'lightGray', '', 'default'], + colorCodesLight = [ + 'darkGray', + 'lightRed', + 'lightGreen', + 'lightYellow', + 'lightBlue', + 'lightMagenta', + 'lightCyan', + 'white', + '', + ], + styleCodes = ['', 'bright', 'dim', 'italic', 'underline', '', '', 'inverse'], + asBright = { + red: 'lightRed', + green: 'lightGreen', + yellow: 'lightYellow', + blue: 'lightBlue', + magenta: 'lightMagenta', + cyan: 'lightCyan', + black: 'darkGray', + lightGray: 'white', + }, + types = { + 0: 'style', + 2: 'unstyle', + 3: 'color', + 9: 'colorLight', + 4: 'bgColor', + 10: 'bgColorLight', + }, + subtypes = { + color: colorCodes, + colorLight: colorCodesLight, + bgColor: colorCodes, + bgColorLight: colorCodesLight, + style: styleCodes, + unstyle: styleCodes, + }; + +/* ------------------------------------------------------------------------ */ + +const clean = obj => { + for (const k in obj) { + if (!obj[k]) { + delete obj[k]; + } + } + return O.keys(obj).length === 0 ? undefined : obj; +}; + +/* ------------------------------------------------------------------------ */ + +class Color { + background: string; + name: string; + brightness: number; + + constructor(background?, name?, brightness?) { + this.background = background; + this.name = name; + this.brightness = brightness; + } + + get inverse() { + return new Color(!this.background, this.name || (this.background ? 'black' : 'white'), this.brightness); + } + + get clean() { + return clean({ + name: this.name === 'default' ? '' : this.name, + bright: this.brightness === Code.bright, + dim: this.brightness === Code.dim, + }); + } + + defaultBrightness(value) { + return new Color(this.background, this.name, this.brightness || value); + } + + css(inverted) { + const color = inverted ? this.inverse : this; + + const rgbName = (color.brightness === Code.bright && asBright[color.name]) || color.name; + + const prop = color.background ? 'background:' : 'color:', + rgb = Colors.rgb[rgbName], + alpha = this.brightness === Code.dim ? 0.5 : 1; + + return rgb + ? prop + 'rgba(' + [...rgb, alpha].join(',') + ');' + : !color.background && alpha < 1 ? 'color:rgba(0,0,0,0.5);' : ''; // Chrome does not support 'opacity' property... + } +} + +/* ------------------------------------------------------------------------ */ + +class Code { + static reset = 0; + static bright = 1; + static dim = 2; + static inverse = 7; + static noBrightness = 22; + static noItalic = 23; + static noUnderline = 24; + static noInverse = 27; + static noColor = 39; + static noBgColor = 49; + + value: number; + + constructor(n?) { + if (n !== undefined) { + this.value = Number(n); + } + } + + get type() { + return types[Math.floor(this.value / 10)]; + } + + get subtype() { + return subtypes[this.type][this.value % 10]; + } + + get str() { + return this.value ? '\u001b[' + this.value + 'm' : ''; + } + + static str(x) { + return new Code(x).str; + } + + get isBrightness() { + return this.value === Code.noBrightness || this.value === Code.bright || this.value === Code.dim; + } +} + +/* ------------------------------------------------------------------------ */ + +const replaceAll = (str, a, b) => str.split(a).join(b); + +/* ANSI brightness codes do not overlap, e.g. "{bright}{dim}foo" will be rendered bright (not dim). + So we fix it by adding brightness canceling before each brightness code, so the former example gets + converted to "{noBrightness}{bright}{noBrightness}{dim}foo" – this way it gets rendered as expected. + */ + +const denormalizeBrightness = s => s.replace(/(\u001b\[(1|2)m)/g, '\u001b[22m$1'); +const normalizeBrightness = s => s.replace(/\u001b\[22m(\u001b\[(1|2)m)/g, '$1'); + +const wrap = (x, openCode, closeCode) => { + const open = Code.str(openCode), + close = Code.str(closeCode); + + return String(x) + .split('\n') + .map(line => denormalizeBrightness(open + replaceAll(normalizeBrightness(line), close, open) + close)) + .join('\n'); +}; + +/* ------------------------------------------------------------------------ */ + +const camel = (a, b) => a + b.charAt(0).toUpperCase() + b.slice(1); + +const stringWrappingMethods = (() => + [ + ...colorCodes.map( + (k, i) => + !k + ? [] + : [ + // color methods + + [k, 30 + i, Code.noColor], + [camel('bg', k), 40 + i, Code.noBgColor], + ] + ), + + ...colorCodesLight.map( + (k, i) => + !k + ? [] + : [ + // light color methods + + [k, 90 + i, Code.noColor], + [camel('bg', k), 100 + i, Code.noBgColor], + ] + ), + + /* THIS ONE IS FOR BACKWARDS COMPATIBILITY WITH PREVIOUS VERSIONS (had 'bright' instead of 'light' for backgrounds) + */ + ...['', 'BrightRed', 'BrightGreen', 'BrightYellow', 'BrightBlue', 'BrightMagenta', 'BrightCyan'].map( + (k, i) => (!k ? [] : [['bg' + k, 100 + i, Code.noBgColor]]) + ), + + ...styleCodes.map( + (k, i) => + !k + ? [] + : [ + // style methods + + [k, i, k === 'bright' || k === 'dim' ? Code.noBrightness : 20 + i], + ] + ), + ].reduce((a, b) => a.concat(b)))(); + +/* ------------------------------------------------------------------------ */ + +const assignStringWrappingAPI = (target, wrapBefore = target) => + stringWrappingMethods.reduce( + (memo, [k, open, close]) => + O.defineProperty(memo, k, { + get: () => assignStringWrappingAPI(str => wrapBefore(wrap(str, open, close))), + }), + + target + ); + +/* ------------------------------------------------------------------------ */ + +const TEXT = 0, + BRACKET = 1, + CODE = 2; + +function rawParse(s) { + let state = TEXT, + buffer = '', + text = '', + code = '', + codes = []; + const spans = []; + + for (let i = 0, n = s.length; i < n; i++) { + const c = s[i]; + + buffer += c; + + switch (state) { + case TEXT: { + if (c === '\u001b') { + state = BRACKET; + buffer = c; + } else { + text += c; + } + break; + } + case BRACKET: + if (c === '[') { + state = CODE; + code = ''; + codes = []; + } else { + state = TEXT; + text += buffer; + } + break; + + case CODE: + if (c >= '0' && c <= '9') { + code += c; + } else if (c === ';') { + codes.push(new Code(code)); + code = ''; + } else if (c === 'm' && code.length) { + codes.push(new Code(code)); + for (const code of codes) { + spans.push({ text, code }); + text = ''; + } + state = TEXT; + } else { + state = TEXT; + text += buffer; + } + } + } + + if (state !== TEXT) { + text += buffer; + } + + if (text) { + spans.push({ text, code: new Code() }); + } + + return spans; +} + +/* ------------------------------------------------------------------------ */ + +/** + * Represents an ANSI-escaped string. + */ +export default class Colors { + spans: any[]; + static names = stringWrappingMethods.map(([k]) => k); + static rgb = { + black: [0, 0, 0], + darkGray: [100, 100, 100], + lightGray: [200, 200, 200], + white: [255, 255, 255], + + red: [204, 0, 0], + lightRed: [255, 51, 0], + + green: [0, 204, 0], + lightGreen: [51, 204, 51], + + yellow: [204, 102, 0], + lightYellow: [255, 153, 51], + + blue: [0, 0, 255], + lightBlue: [26, 140, 255], + + magenta: [204, 0, 204], + lightMagenta: [255, 0, 255], + + cyan: [0, 153, 255], + lightCyan: [0, 204, 255], + }; + + /** + * @param {string} s a string containing ANSI escape codes. + */ + constructor(s?) { + this.spans = s ? rawParse(s) : []; + } + + get str() { + return this.spans.reduce((str, p) => str + p.text + p.code.str, ''); + } + + get parsed() { + let color, bgColor, brightness, styles; + + function reset() { + (color = new Color()), + (bgColor = new Color(true /* background */)), + (brightness = undefined), + (styles = new Set()); + } + + reset(); + + return O.assign(new Colors(), { + spans: this.spans + .map(span => { + const c = span.code; + + const inverted = styles.has('inverse'), + underline = styles.has('underline') ? 'text-decoration: underline;' : '', + italic = styles.has('italic') ? 'font-style: italic;' : '', + bold = brightness === Code.bright ? 'font-weight: bold;' : ''; + + const foreColor = color.defaultBrightness(brightness); + + const styledSpan = O.assign( + { css: bold + italic + underline + foreColor.css(inverted) + bgColor.css(inverted) }, + clean({ bold: !!bold, color: foreColor.clean, bgColor: bgColor.clean }), + span + ); + + for (const k of styles) { + styledSpan[k] = true; + } + + if (c.isBrightness) { + brightness = c.value; + } else if (span.code.value !== undefined) { + if (span.code.value === Code.reset) { + reset(); + } else { + switch (span.code.type) { + case 'color': + case 'colorLight': + color = new Color(false, c.subtype); + break; + + case 'bgColor': + case 'bgColorLight': + bgColor = new Color(true, c.subtype); + break; + + case 'style': + styles.add(c.subtype); + break; + case 'unstyle': + styles.delete(c.subtype); + break; + } + } + } + + return styledSpan; + }) + .filter(s => s.text.length > 0), + }); + } + + /* Outputs with Chrome DevTools-compatible format */ + + get asChromeConsoleLogArguments() { + const spans = this.parsed.spans; + + return [spans.map(s => '%c' + s.text).join(''), ...spans.map(s => s.css)]; + } + + get browserConsoleArguments() /* LEGACY, DEPRECATED */ { + return this.asChromeConsoleLogArguments; + } + + /** + * @desc installs String prototype extensions + * @example + * require ('ansicolor').nice + * console.log ('foo'.bright.red) + */ + static get nice() { + Colors.names.forEach(k => { + if (!(k in String.prototype)) { + O.defineProperty(String.prototype, k, { + get: function() { + return Colors[k](this); + }, + }); + } + }); + + return Colors; + } + + /** + * @desc parses a string containing ANSI escape codes + * @return {Colors} parsed representation. + */ + static parse(s) { + return new Colors(s).parsed; + } + + /** + * @desc strips ANSI codes from a string + * @param {string} s a string containing ANSI escape codes. + * @return {string} clean string. + */ + static strip(s) { + return s.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><]/g, ''); // hope V8 caches the regexp + } + + /** + * @example + * const spans = [...ansi.parse ('\u001b[7m\u001b[7mfoo\u001b[7mbar\u001b[27m')] + */ + [Symbol.iterator]() { + return this.spans[Symbol.iterator](); + } +} + +/* ------------------------------------------------------------------------ */ + +assignStringWrappingAPI(Colors, str => str); diff --git a/tsconfig.json b/tsconfig.json index cd7ad983a51..1af0de2b3b9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -32,5 +32,5 @@ "sass": ["sass"] } }, - "include": ["public/app/**/*.ts", "public/app/**/*.tsx", "public/test/**/*.ts"] + "include": ["public/app/**/*.ts", "public/app/**/*.tsx", "public/test/**/*.ts", "public/vendor/**/*.ts"] } diff --git a/yarn.lock b/yarn.lock index cec36884075..e2e4b544195 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2690,11 +2690,6 @@ ansi-styles@~1.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.0.0.tgz#cb102df1c56f5123eab8b67cd7b98027a0279178" integrity sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg= -ansicolor@1.1.78: - version "1.1.78" - resolved "https://registry.yarnpkg.com/ansicolor/-/ansicolor-1.1.78.tgz#4c1f1dbef81ff3e1292e6f95b4bfb8ba51212db9" - integrity sha512-mdNo/iRwUyb4Z0L8AthEV4BZ3TlSWr6YakKtItA48ufGBzYYtTVp+gX6bkweKTfs7wGpUepOz+qHrTPqfBus2Q== - ansicolors@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979"