You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

134 lines
3.3 KiB

4 years ago
  1. 'use strict';
  2. const TEMPLATE_REGEX = /(?:\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi;
  3. const STYLE_REGEX = /(?:^|\.)(\w+)(?:\(([^)]*)\))?/g;
  4. const STRING_REGEX = /^(['"])((?:\\.|(?!\1)[^\\])*)\1$/;
  5. const ESCAPE_REGEX = /\\(u(?:[a-f\d]{4}|{[a-f\d]{1,6}})|x[a-f\d]{2}|.)|([^\\])/gi;
  6. const ESCAPES = new Map([
  7. ['n', '\n'],
  8. ['r', '\r'],
  9. ['t', '\t'],
  10. ['b', '\b'],
  11. ['f', '\f'],
  12. ['v', '\v'],
  13. ['0', '\0'],
  14. ['\\', '\\'],
  15. ['e', '\u001B'],
  16. ['a', '\u0007']
  17. ]);
  18. function unescape(c) {
  19. const u = c[0] === 'u';
  20. const bracket = c[1] === '{';
  21. if ((u && !bracket && c.length === 5) || (c[0] === 'x' && c.length === 3)) {
  22. return String.fromCharCode(parseInt(c.slice(1), 16));
  23. }
  24. if (u && bracket) {
  25. return String.fromCodePoint(parseInt(c.slice(2, -1), 16));
  26. }
  27. return ESCAPES.get(c) || c;
  28. }
  29. function parseArguments(name, arguments_) {
  30. const results = [];
  31. const chunks = arguments_.trim().split(/\s*,\s*/g);
  32. let matches;
  33. for (const chunk of chunks) {
  34. const number = Number(chunk);
  35. if (!Number.isNaN(number)) {
  36. results.push(number);
  37. } else if ((matches = chunk.match(STRING_REGEX))) {
  38. results.push(matches[2].replace(ESCAPE_REGEX, (m, escape, character) => escape ? unescape(escape) : character));
  39. } else {
  40. throw new Error(`Invalid Chalk template style argument: ${chunk} (in style '${name}')`);
  41. }
  42. }
  43. return results;
  44. }
  45. function parseStyle(style) {
  46. STYLE_REGEX.lastIndex = 0;
  47. const results = [];
  48. let matches;
  49. while ((matches = STYLE_REGEX.exec(style)) !== null) {
  50. const name = matches[1];
  51. if (matches[2]) {
  52. const args = parseArguments(name, matches[2]);
  53. results.push([name].concat(args));
  54. } else {
  55. results.push([name]);
  56. }
  57. }
  58. return results;
  59. }
  60. function buildStyle(chalk, styles) {
  61. const enabled = {};
  62. for (const layer of styles) {
  63. for (const style of layer.styles) {
  64. enabled[style[0]] = layer.inverse ? null : style.slice(1);
  65. }
  66. }
  67. let current = chalk;
  68. for (const [styleName, styles] of Object.entries(enabled)) {
  69. if (!Array.isArray(styles)) {
  70. continue;
  71. }
  72. if (!(styleName in current)) {
  73. throw new Error(`Unknown Chalk style: ${styleName}`);
  74. }
  75. current = styles.length > 0 ? current[styleName](...styles) : current[styleName];
  76. }
  77. return current;
  78. }
  79. module.exports = (chalk, temporary) => {
  80. const styles = [];
  81. const chunks = [];
  82. let chunk = [];
  83. // eslint-disable-next-line max-params
  84. temporary.replace(TEMPLATE_REGEX, (m, escapeCharacter, inverse, style, close, character) => {
  85. if (escapeCharacter) {
  86. chunk.push(unescape(escapeCharacter));
  87. } else if (style) {
  88. const string = chunk.join('');
  89. chunk = [];
  90. chunks.push(styles.length === 0 ? string : buildStyle(chalk, styles)(string));
  91. styles.push({inverse, styles: parseStyle(style)});
  92. } else if (close) {
  93. if (styles.length === 0) {
  94. throw new Error('Found extraneous } in Chalk template literal');
  95. }
  96. chunks.push(buildStyle(chalk, styles)(chunk.join('')));
  97. chunk = [];
  98. styles.pop();
  99. } else {
  100. chunk.push(character);
  101. }
  102. });
  103. chunks.push(chunk.join(''));
  104. if (styles.length > 0) {
  105. const errMessage = `Chalk template literal is missing ${styles.length} closing bracket${styles.length === 1 ? '' : 's'} (\`}\`)`;
  106. throw new Error(errMessage);
  107. }
  108. return chunks.join('');
  109. };