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.

128 lines
3.1 KiB

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