index.js 5.7 KB

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