| Leo Repp | 58b9f11 | 2021-11-22 11:57:47 +0100 | [diff] [blame^] | 1 | 'use strict'; |
| 2 | const ansiStyles = require('ansi-styles'); |
| 3 | const {stdout: stdoutColor, stderr: stderrColor} = require('supports-color'); |
| 4 | const { |
| 5 | stringReplaceAll, |
| 6 | stringEncaseCRLFWithFirstIndex |
| 7 | } = require('./util'); |
| 8 | |
| 9 | const {isArray} = Array; |
| 10 | |
| 11 | // `supportsColor.level` → `ansiStyles.color[name]` mapping |
| 12 | const levelMapping = [ |
| 13 | 'ansi', |
| 14 | 'ansi', |
| 15 | 'ansi256', |
| 16 | 'ansi16m' |
| 17 | ]; |
| 18 | |
| 19 | const styles = Object.create(null); |
| 20 | |
| 21 | const applyOptions = (object, options = {}) => { |
| 22 | if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) { |
| 23 | throw new Error('The `level` option should be an integer from 0 to 3'); |
| 24 | } |
| 25 | |
| 26 | // Detect level if not set manually |
| 27 | const colorLevel = stdoutColor ? stdoutColor.level : 0; |
| 28 | object.level = options.level === undefined ? colorLevel : options.level; |
| 29 | }; |
| 30 | |
| 31 | class ChalkClass { |
| 32 | constructor(options) { |
| 33 | // eslint-disable-next-line no-constructor-return |
| 34 | return chalkFactory(options); |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | const chalkFactory = options => { |
| 39 | const chalk = {}; |
| 40 | applyOptions(chalk, options); |
| 41 | |
| 42 | chalk.template = (...arguments_) => chalkTag(chalk.template, ...arguments_); |
| 43 | |
| 44 | Object.setPrototypeOf(chalk, Chalk.prototype); |
| 45 | Object.setPrototypeOf(chalk.template, chalk); |
| 46 | |
| 47 | chalk.template.constructor = () => { |
| 48 | throw new Error('`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.'); |
| 49 | }; |
| 50 | |
| 51 | chalk.template.Instance = ChalkClass; |
| 52 | |
| 53 | return chalk.template; |
| 54 | }; |
| 55 | |
| 56 | function Chalk(options) { |
| 57 | return chalkFactory(options); |
| 58 | } |
| 59 | |
| 60 | for (const [styleName, style] of Object.entries(ansiStyles)) { |
| 61 | styles[styleName] = { |
| 62 | get() { |
| 63 | const builder = createBuilder(this, createStyler(style.open, style.close, this._styler), this._isEmpty); |
| 64 | Object.defineProperty(this, styleName, {value: builder}); |
| 65 | return builder; |
| 66 | } |
| 67 | }; |
| 68 | } |
| 69 | |
| 70 | styles.visible = { |
| 71 | get() { |
| 72 | const builder = createBuilder(this, this._styler, true); |
| 73 | Object.defineProperty(this, 'visible', {value: builder}); |
| 74 | return builder; |
| 75 | } |
| 76 | }; |
| 77 | |
| 78 | const usedModels = ['rgb', 'hex', 'keyword', 'hsl', 'hsv', 'hwb', 'ansi', 'ansi256']; |
| 79 | |
| 80 | for (const model of usedModels) { |
| 81 | styles[model] = { |
| 82 | get() { |
| 83 | const {level} = this; |
| 84 | return function (...arguments_) { |
| 85 | const styler = createStyler(ansiStyles.color[levelMapping[level]][model](...arguments_), ansiStyles.color.close, this._styler); |
| 86 | return createBuilder(this, styler, this._isEmpty); |
| 87 | }; |
| 88 | } |
| 89 | }; |
| 90 | } |
| 91 | |
| 92 | for (const model of usedModels) { |
| 93 | const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); |
| 94 | styles[bgModel] = { |
| 95 | get() { |
| 96 | const {level} = this; |
| 97 | return function (...arguments_) { |
| 98 | const styler = createStyler(ansiStyles.bgColor[levelMapping[level]][model](...arguments_), ansiStyles.bgColor.close, this._styler); |
| 99 | return createBuilder(this, styler, this._isEmpty); |
| 100 | }; |
| 101 | } |
| 102 | }; |
| 103 | } |
| 104 | |
| 105 | const proto = Object.defineProperties(() => {}, { |
| 106 | ...styles, |
| 107 | level: { |
| 108 | enumerable: true, |
| 109 | get() { |
| 110 | return this._generator.level; |
| 111 | }, |
| 112 | set(level) { |
| 113 | this._generator.level = level; |
| 114 | } |
| 115 | } |
| 116 | }); |
| 117 | |
| 118 | const createStyler = (open, close, parent) => { |
| 119 | let openAll; |
| 120 | let closeAll; |
| 121 | if (parent === undefined) { |
| 122 | openAll = open; |
| 123 | closeAll = close; |
| 124 | } else { |
| 125 | openAll = parent.openAll + open; |
| 126 | closeAll = close + parent.closeAll; |
| 127 | } |
| 128 | |
| 129 | return { |
| 130 | open, |
| 131 | close, |
| 132 | openAll, |
| 133 | closeAll, |
| 134 | parent |
| 135 | }; |
| 136 | }; |
| 137 | |
| 138 | const createBuilder = (self, _styler, _isEmpty) => { |
| 139 | const builder = (...arguments_) => { |
| 140 | if (isArray(arguments_[0]) && isArray(arguments_[0].raw)) { |
| 141 | // Called as a template literal, for example: chalk.red`2 + 3 = {bold ${2+3}}` |
| 142 | return applyStyle(builder, chalkTag(builder, ...arguments_)); |
| 143 | } |
| 144 | |
| 145 | // Single argument is hot path, implicit coercion is faster than anything |
| 146 | // eslint-disable-next-line no-implicit-coercion |
| 147 | return applyStyle(builder, (arguments_.length === 1) ? ('' + arguments_[0]) : arguments_.join(' ')); |
| 148 | }; |
| 149 | |
| 150 | // We alter the prototype because we must return a function, but there is |
| 151 | // no way to create a function with a different prototype |
| 152 | Object.setPrototypeOf(builder, proto); |
| 153 | |
| 154 | builder._generator = self; |
| 155 | builder._styler = _styler; |
| 156 | builder._isEmpty = _isEmpty; |
| 157 | |
| 158 | return builder; |
| 159 | }; |
| 160 | |
| 161 | const applyStyle = (self, string) => { |
| 162 | if (self.level <= 0 || !string) { |
| 163 | return self._isEmpty ? '' : string; |
| 164 | } |
| 165 | |
| 166 | let styler = self._styler; |
| 167 | |
| 168 | if (styler === undefined) { |
| 169 | return string; |
| 170 | } |
| 171 | |
| 172 | const {openAll, closeAll} = styler; |
| 173 | if (string.indexOf('\u001B') !== -1) { |
| 174 | while (styler !== undefined) { |
| 175 | // Replace any instances already present with a re-opening code |
| 176 | // otherwise only the part of the string until said closing code |
| 177 | // will be colored, and the rest will simply be 'plain'. |
| 178 | string = stringReplaceAll(string, styler.close, styler.open); |
| 179 | |
| 180 | styler = styler.parent; |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | // We can move both next actions out of loop, because remaining actions in loop won't have |
| 185 | // any/visible effect on parts we add here. Close the styling before a linebreak and reopen |
| 186 | // after next line to fix a bleed issue on macOS: https://github.com/chalk/chalk/pull/92 |
| 187 | const lfIndex = string.indexOf('\n'); |
| 188 | if (lfIndex !== -1) { |
| 189 | string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex); |
| 190 | } |
| 191 | |
| 192 | return openAll + string + closeAll; |
| 193 | }; |
| 194 | |
| 195 | let template; |
| 196 | const chalkTag = (chalk, ...strings) => { |
| 197 | const [firstString] = strings; |
| 198 | |
| 199 | if (!isArray(firstString) || !isArray(firstString.raw)) { |
| 200 | // If chalk() was called by itself or with a string, |
| 201 | // return the string itself as a string. |
| 202 | return strings.join(' '); |
| 203 | } |
| 204 | |
| 205 | const arguments_ = strings.slice(1); |
| 206 | const parts = [firstString.raw[0]]; |
| 207 | |
| 208 | for (let i = 1; i < firstString.length; i++) { |
| 209 | parts.push( |
| 210 | String(arguments_[i - 1]).replace(/[{}\\]/g, '\\$&'), |
| 211 | String(firstString.raw[i]) |
| 212 | ); |
| 213 | } |
| 214 | |
| 215 | if (template === undefined) { |
| 216 | template = require('./templates'); |
| 217 | } |
| 218 | |
| 219 | return template(chalk, parts.join('')); |
| 220 | }; |
| 221 | |
| 222 | Object.defineProperties(Chalk.prototype, styles); |
| 223 | |
| 224 | const chalk = Chalk(); // eslint-disable-line new-cap |
| 225 | chalk.supportsColor = stdoutColor; |
| 226 | chalk.stderr = Chalk({level: stderrColor ? stderrColor.level : 0}); // eslint-disable-line new-cap |
| 227 | chalk.stderr.supportsColor = stderrColor; |
| 228 | |
| 229 | module.exports = chalk; |