From 7d238e2ebb769996de7d7f273cc3d71afa4476ff Mon Sep 17 00:00:00 2001 From: Toon Baeyens Date: Mon, 30 Mar 2020 12:33:28 +0200 Subject: [PATCH 1/5] template literals with a style tag, fixes #341 --- source/index.js | 5 +++++ test/template-literal.js | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/source/index.js b/source/index.js index 53fcc16..73135fe 100644 --- a/source/index.js +++ b/source/index.js @@ -134,6 +134,11 @@ const createStyler = (open, close, parent) => { const createBuilder = (self, _styler, _isEmpty) => { const builder = (...arguments_) => { + if (Array.isArray(arguments_[0])) { + // Called as a template litteral, e.g. chalk.red`2 + 3 = {bold ${2+3}}` + return applyStyle(builder, chalkTag(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(' ')); diff --git a/test/template-literal.js b/test/template-literal.js index ece1428..dc4b67b 100644 --- a/test/template-literal.js +++ b/test/template-literal.js @@ -30,6 +30,14 @@ test('correctly perform template substitutions', t => { instance.bold('Hello,', instance.cyan.inverse(name + '!'), 'This is a') + ' test. ' + instance.green(exclamation + '!')); }); +test('correctly perform nested template substitutions', t => { + const instance = new chalk.Instance({level: 0}); + const name = 'Sindre'; + const exclamation = 'Neat'; + t.is(instance.bold`Hello, {cyan.inverse ${name}!} This is a` + ' test. ' + instance.green`${exclamation}!`, + instance.bold('Hello,', instance.cyan.inverse(name + '!'), 'This is a') + ' test. ' + instance.green(exclamation + '!')); +}); + test('correctly parse and evaluate color-convert functions', t => { const instance = new chalk.Instance({level: 3}); t.is(instance`{bold.rgb(144,10,178).inverse Hello, {~inverse there!}}`, From e5ea8df1e52a6265caea6f6cab909892ec88765d Mon Sep 17 00:00:00 2001 From: Toon Baeyens Date: Wed, 6 May 2020 15:24:58 +0200 Subject: [PATCH 2/5] extra tests, specific benchmark, example in readme --- benchmark.js | 12 ++++++++++++ index.test-d.ts | 5 +++++ readme.md | 3 ++- source/index.js | 2 +- test/template-literal.js | 6 ++++++ 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/benchmark.js b/benchmark.js index dc24696..25b3f5d 100644 --- a/benchmark.js +++ b/benchmark.js @@ -47,4 +47,16 @@ suite('chalk', () => { bench('cached: 1 style nested non-intersecting', () => { chalkBgRed(blueStyledString); }); + + set('iterations', 10000); + + bench('cached: 1 style template literal', () => { + // eslint-disable-next-line no-unused-expressions + chalkRed`the fox jumps over the lazy dog`; + }); + + bench('cached: nested styles template literal', () => { + // eslint-disable-next-line no-unused-expressions + chalkRed`the fox {bold jumps} over the {underline lazy} dog`; + }); }); diff --git a/index.test-d.ts b/index.test-d.ts index 177d6de..a227dc4 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -154,6 +154,11 @@ expectType(chalk.bgWhiteBright`foo`); expectType(chalk.red.bgGreen.underline('foo')); expectType(chalk.underline.red.bgGreen('foo')); +// -- Complex template literal -- +expectType(chalk.underline``); +expectType(chalk.red.bgGreen.bold`Hello {italic.blue ${name}}`); +expectType(chalk.strikethrough.cyanBright.bgBlack`Works with {reset {bold numbers}} {bold.red ${1}}`); + // -- Color types == expectType('red'); expectError('hotpink'); diff --git a/readme.md b/readme.md index a0ca245..faca767 100644 --- a/readme.md +++ b/readme.md @@ -215,10 +215,11 @@ console.log(chalk` Blocks are delimited by an opening curly brace (`{`), a style, some content, and a closing curly brace (`}`). -Template styles are chained exactly like normal Chalk styles. The following two statements are equivalent: +Template styles are chained exactly like normal Chalk styles. The following three statements are equivalent: ```js console.log(chalk.bold.rgb(10, 100, 200)('Hello!')); +console.log(chalk.bold.rgb(10, 100, 200)`Hello!`); console.log(chalk`{bold.rgb(10,100,200) Hello!}`); ``` diff --git a/source/index.js b/source/index.js index 73135fe..1486dc3 100644 --- a/source/index.js +++ b/source/index.js @@ -135,7 +135,7 @@ const createStyler = (open, close, parent) => { const createBuilder = (self, _styler, _isEmpty) => { const builder = (...arguments_) => { if (Array.isArray(arguments_[0])) { - // Called as a template litteral, e.g. chalk.red`2 + 3 = {bold ${2+3}}` + // Called as a template literal, e.g. chalk.red`2 + 3 = {bold ${2+3}}` return applyStyle(builder, chalkTag(builder, ...arguments_)); } diff --git a/test/template-literal.js b/test/template-literal.js index dc4b67b..da0ed24 100644 --- a/test/template-literal.js +++ b/test/template-literal.js @@ -36,6 +36,12 @@ test('correctly perform nested template substitutions', t => { const exclamation = 'Neat'; t.is(instance.bold`Hello, {cyan.inverse ${name}!} This is a` + ' test. ' + instance.green`${exclamation}!`, instance.bold('Hello,', instance.cyan.inverse(name + '!'), 'This is a') + ' test. ' + instance.green(exclamation + '!')); + + t.is(instance.red.bgGreen.bold`Hello {italic.blue ${name}}`, + instance.red.bgGreen.bold('Hello ' + instance.italic.blue(name))); + + t.is(instance.strikethrough.cyanBright.bgBlack`Works with {reset {bold numbers}} {bold.red ${1}}`, + instance.strikethrough.cyanBright.bgBlack('Works with ' + instance.reset.bold('numbers') + ' ' + instance.bold.red(1))); }); test('correctly parse and evaluate color-convert functions', t => { From 60db67b5211eb70ba26164b422d6e54921a8e9ae Mon Sep 17 00:00:00 2001 From: Toon Baeyens Date: Thu, 7 May 2020 09:47:18 +0200 Subject: [PATCH 3/5] small example in index.d.ts --- index.d.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/index.d.ts b/index.d.ts index 7e22c45..d70f77e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -147,6 +147,13 @@ declare namespace chalk { DISK: {rgb(255,131,0) ${disk.used / disk.total * 100}%} `); ``` + + @example + ``` + import chalk = require('chalk'); + + log(chalk.red.bgBlack`2 + 3 = {bold ${2 + 3}}`) + ``` */ (text: TemplateStringsArray, ...placeholders: unknown[]): string; From 2451677995ab2025732ef7a3f3f29422f68545e1 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 6 Jun 2020 15:33:24 +0800 Subject: [PATCH 4/5] Update index.js --- source/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/index.js b/source/index.js index 1486dc3..93a0b64 100644 --- a/source/index.js +++ b/source/index.js @@ -135,7 +135,7 @@ const createStyler = (open, close, parent) => { const createBuilder = (self, _styler, _isEmpty) => { const builder = (...arguments_) => { if (Array.isArray(arguments_[0])) { - // Called as a template literal, e.g. chalk.red`2 + 3 = {bold ${2+3}}` + // Called as a template literal, for example: chalk.red`2 + 3 = {bold ${2+3}}` return applyStyle(builder, chalkTag(builder, ...arguments_)); } From 26c73d2b80f06f5673a7bb44433f69316f229629 Mon Sep 17 00:00:00 2001 From: Toon Baeyens Date: Mon, 8 Jun 2020 15:14:29 +0200 Subject: [PATCH 5/5] cache isArray, test for automatic casting --- source/index.js | 6 ++++-- test/chalk.js | 8 ++++++++ test/template-literal.js | 5 +++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/source/index.js b/source/index.js index 93a0b64..23ee00f 100644 --- a/source/index.js +++ b/source/index.js @@ -6,6 +6,8 @@ const { stringEncaseCRLFWithFirstIndex } = require('./util'); +const {isArray} = Array; + // `supportsColor.level` → `ansiStyles.color[name]` mapping const levelMapping = [ 'ansi', @@ -134,7 +136,7 @@ const createStyler = (open, close, parent) => { const createBuilder = (self, _styler, _isEmpty) => { const builder = (...arguments_) => { - if (Array.isArray(arguments_[0])) { + if (isArray(arguments_[0]) && isArray(arguments_[0].raw)) { // Called as a template literal, for example: chalk.red`2 + 3 = {bold ${2+3}}` return applyStyle(builder, chalkTag(builder, ...arguments_)); } @@ -193,7 +195,7 @@ let template; const chalkTag = (chalk, ...strings) => { const [firstString] = strings; - if (!Array.isArray(firstString)) { + if (!isArray(firstString) || !isArray(firstString.raw)) { // If chalk() was called by itself or with a string, // return the string itself as a string. return strings.join(' '); diff --git a/test/chalk.js b/test/chalk.js index 21f0346..4e78565 100644 --- a/test/chalk.js +++ b/test/chalk.js @@ -16,6 +16,14 @@ test('support multiple arguments in base function', t => { t.is(chalk('hello', 'there'), 'hello there'); }); +test('support automatic casting to string', t => { + t.is(chalk(['hello', 'there']), 'hello,there'); + t.is(chalk(123), '123'); + + t.is(chalk.bold(['foo', 'bar']), '\u001B[1mfoo,bar\u001B[22m'); + t.is(chalk.green(98765), '\u001B[32m98765\u001B[39m'); +}); + test('style string', t => { t.is(chalk.underline('foo'), '\u001B[4mfoo\u001B[24m'); t.is(chalk.red('foo'), '\u001B[31mfoo\u001B[39m'); diff --git a/test/template-literal.js b/test/template-literal.js index da0ed24..b74398e 100644 --- a/test/template-literal.js +++ b/test/template-literal.js @@ -42,6 +42,11 @@ test('correctly perform nested template substitutions', t => { t.is(instance.strikethrough.cyanBright.bgBlack`Works with {reset {bold numbers}} {bold.red ${1}}`, instance.strikethrough.cyanBright.bgBlack('Works with ' + instance.reset.bold('numbers') + ' ' + instance.bold.red(1))); + + t.is(chalk.bold`Also works on the shared {bgBlue chalk} object`, + '\u001B[1mAlso works on the shared \u001B[1m' + + '\u001B[44mchalk\u001B[49m\u001B[22m' + + '\u001B[1m object\u001B[22m'); }); test('correctly parse and evaluate color-convert functions', t => {