UNPKG

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