Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
264 additions
and
225 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,7 +14,8 @@ | |
}, | ||
"files": [ | ||
"source", | ||
"index.d.ts" | ||
"index.d.ts", | ||
"stderr.js" | ||
], | ||
"keywords": [ | ||
"color", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
'use strict'; | ||
const ansiStyles = require('ansi-styles'); | ||
const template = require('./templates'); | ||
const { | ||
stringReplaceAll, | ||
stringEncaseCRLFWithFirstIndex | ||
} = require('./util'); | ||
|
||
// `supportsColor.level` → `ansiStyles.color[name]` mapping | ||
const levelMapping = [ | ||
'ansi', | ||
'ansi', | ||
'ansi256', | ||
'ansi16m' | ||
]; | ||
|
||
// `color-convert` models to exclude from the Chalk API due to conflicts and such | ||
const skipModels = new Set(['gray']); | ||
|
||
const styles = Object.create(null); | ||
const chalkPrototype = {}; | ||
|
||
const applyOptions = (defaultColorSupport, object, options = {}) => { | ||
if (options.level > 3 || options.level < 0) { | ||
throw new Error('The `level` option should be an integer from 0 to 3'); | ||
} | ||
|
||
// Detect level if not set manually | ||
const colorLevel = defaultColorSupport ? defaultColorSupport.level : 0; | ||
object.level = options.level === undefined ? colorLevel : options.level; | ||
object.enabled = 'enabled' in options ? options.enabled : object.level > 0; | ||
}; | ||
|
||
const chalkFactory = (defaultColorSupport, instanceClass, options) => { | ||
const chalk = {}; | ||
applyOptions(defaultColorSupport, chalk, options); | ||
|
||
chalk.template = (...arguments_) => chalkTag(chalk.template, ...arguments_); | ||
|
||
Object.setPrototypeOf(chalk, chalkPrototype); | ||
Object.setPrototypeOf(chalk.template, chalk); | ||
|
||
chalk.template.constructor = () => { | ||
throw new Error('`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.'); | ||
}; | ||
|
||
chalk.template.Instance = instanceClass; | ||
|
||
return chalk.template; | ||
}; | ||
|
||
for (const [styleName, style] of Object.entries(ansiStyles)) { | ||
styles[styleName] = { | ||
get() { | ||
const builder = createBuilder(this, createStyler(style.open, style.close, this._styler), this._isEmpty); | ||
Object.defineProperty(this, styleName, {value: builder}); | ||
return builder; | ||
} | ||
}; | ||
} | ||
|
||
styles.visible = { | ||
get() { | ||
const builder = createBuilder(this, this._styler, true); | ||
Object.defineProperty(this, 'visible', {value: builder}); | ||
return builder; | ||
} | ||
}; | ||
|
||
for (const model of Object.keys(ansiStyles.color.ansi)) { | ||
if (skipModels.has(model)) { | ||
continue; | ||
} | ||
|
||
styles[model] = { | ||
get() { | ||
const {level} = this; | ||
return function (...arguments_) { | ||
const styler = createStyler(ansiStyles.color[levelMapping[level]][model](...arguments_), ansiStyles.color.close, this._styler); | ||
return createBuilder(this, styler, this._isEmpty); | ||
}; | ||
} | ||
}; | ||
} | ||
|
||
for (const model of Object.keys(ansiStyles.bgColor.ansi)) { | ||
if (skipModels.has(model)) { | ||
continue; | ||
} | ||
|
||
const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); | ||
styles[bgModel] = { | ||
get() { | ||
const {level} = this; | ||
return function (...arguments_) { | ||
const styler = createStyler(ansiStyles.bgColor[levelMapping[level]][model](...arguments_), ansiStyles.bgColor.close, this._styler); | ||
return createBuilder(this, styler, this._isEmpty); | ||
}; | ||
} | ||
}; | ||
} | ||
|
||
const proto = Object.defineProperties(() => {}, { | ||
...styles, | ||
level: { | ||
enumerable: true, | ||
get() { | ||
return this._generator.level; | ||
}, | ||
set(level) { | ||
this._generator.level = level; | ||
} | ||
}, | ||
enabled: { | ||
enumerable: true, | ||
get() { | ||
return this._generator.enabled; | ||
}, | ||
set(enabled) { | ||
this._generator.enabled = enabled; | ||
} | ||
} | ||
}); | ||
|
||
const createStyler = (open, close, parent) => { | ||
let openAll; | ||
let closeAll; | ||
if (parent === undefined) { | ||
openAll = open; | ||
closeAll = close; | ||
} else { | ||
openAll = parent.openAll + open; | ||
closeAll = close + parent.closeAll; | ||
} | ||
|
||
return { | ||
open, | ||
close, | ||
openAll, | ||
closeAll, | ||
parent | ||
}; | ||
}; | ||
|
||
const createBuilder = (self, _styler, _isEmpty) => { | ||
const builder = (...arguments_) => { | ||
// Single argument is hot path, implicit coercion is faster than anything | ||
// eslint-disable-next-line no-implicit-coercion | ||
return applyStyle(builder, (arguments_.length === 1) ? ('' + arguments_[0]) : arguments_.join(' ')); | ||
}; | ||
|
||
// `__proto__` is used because we must return a function, but there is | ||
// no way to create a function with a different prototype | ||
builder.__proto__ = proto; // eslint-disable-line no-proto | ||
|
||
builder._generator = self; | ||
builder._styler = _styler; | ||
builder._isEmpty = _isEmpty; | ||
|
||
return builder; | ||
}; | ||
|
||
const applyStyle = (self, string) => { | ||
if (!self.enabled || self.level <= 0 || !string) { | ||
return self._isEmpty ? '' : string; | ||
} | ||
|
||
let styler = self._styler; | ||
|
||
if (styler === undefined) { | ||
return string; | ||
} | ||
|
||
const {openAll, closeAll} = styler; | ||
if (string.indexOf('\u001B') !== -1) { | ||
while (styler !== undefined) { | ||
// Replace any instances already present with a re-opening code | ||
// otherwise only the part of the string until said closing code | ||
// will be colored, and the rest will simply be 'plain'. | ||
string = stringReplaceAll(string, styler.close, styler.open); | ||
|
||
styler = styler.parent; | ||
} | ||
} | ||
|
||
// We can move both next actions out of loop, because remaining actions in loop won't have | ||
// any/visible effect on parts we add here. Close the styling before a linebreak and reopen | ||
// after next line to fix a bleed issue on macOS: https://github.com/chalk/chalk/pull/92 | ||
const lfIndex = string.indexOf('\n'); | ||
if (lfIndex !== -1) { | ||
string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex); | ||
} | ||
|
||
return openAll + string + closeAll; | ||
}; | ||
|
||
const chalkTag = (chalk, ...strings) => { | ||
const [firstString] = strings; | ||
|
||
if (!Array.isArray(firstString)) { | ||
// If chalk() was called by itself or with a string, | ||
// return the string itself as a string. | ||
return strings.join(' '); | ||
} | ||
|
||
const arguments_ = strings.slice(1); | ||
const parts = [firstString.raw[0]]; | ||
|
||
for (let i = 1; i < firstString.length; i++) { | ||
parts.push( | ||
String(arguments_[i - 1]).replace(/[{}\\]/g, '\\$&'), | ||
String(firstString.raw[i]) | ||
); | ||
} | ||
|
||
return template(chalk, parts.join('')); | ||
}; | ||
|
||
Object.defineProperties(chalkPrototype, styles); | ||
|
||
module.exports = chalkFactory; |
Oops, something went wrong.