From e5a299363783609f17de4fd169373336d30664bd Mon Sep 17 00:00:00 2001 From: George Date: Fri, 4 Mar 2022 12:22:27 -0500 Subject: [PATCH] export template function (#8) * Export template function This facilitates wrapping template behavior in other functions, and not needing to reference the tag function in many places. * Add tests and types Additionally add typescript as a dev dependency * Update README --- index.d.ts | 12 ++++++++ index.js | 2 +- index.test-d.ts | 4 ++- package.json | 3 +- readme.md | 10 +++++++ test/template.js | 78 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 test/template.js diff --git a/index.d.ts b/index.d.ts index 31f6910..10bfe71 100644 --- a/index.d.ts +++ b/index.d.ts @@ -21,3 +21,15 @@ log(chalk.red.bgBlack(chalkTemplate`2 + 3 = {bold ${2 + 3}}`)); ``` */ export default function chalkTemplate(text: TemplateStringsArray, ...placeholders: unknown[]): string; + +/** +Terminal string styling. It is preferred that you use the template tag (default export) but this function is useful if you'd like to wrap the color template function. + +@example +``` +import { template } from 'chalk-template'; + +log(template('Today is {red hot}') +``` +*/ +export function template(text: string): string; diff --git a/index.js b/index.js index acaf034..7686387 100644 --- a/index.js +++ b/index.js @@ -117,7 +117,7 @@ function buildStyle(styles) { return current; } -function template(string) { +export function template(string) { const styles = []; const chunks = []; let chunk = []; diff --git a/index.test-d.ts b/index.test-d.ts index 496454e..c9d46fc 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -1,6 +1,6 @@ import {expectType} from 'tsd'; import chalk from 'chalk'; -import chalkTemplate from './index.js'; +import chalkTemplate, {template} from './index.js'; // -- Template literal -- expectType(chalkTemplate``); @@ -8,6 +8,8 @@ const name = 'John'; expectType(chalkTemplate`Hello {bold.red ${name}}`); expectType(chalkTemplate`Works with numbers {bold.red ${1}}`); +expectType(template('Today is {bold.red hot}')); + // -- Complex template literal -- expectType(chalk.red.bgGreen.bold(chalkTemplate`Hello {italic.blue ${name}}`)); expectType(chalk.strikethrough.cyanBright.bgBlack(chalkTemplate`Works with {reset {bold numbers}} {bold.red ${1}}`)); diff --git a/package.json b/package.json index c9e40b4..8f72f2a 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "node": ">=12" }, "scripts": { - "test": "xo && ava test/index.js && cross-env FORCE_COLOR=0 ava test/no-color.js && cross-env FORCE_COLOR=3 TERM=dumb ava test/full-color.js && tsd" + "test": "xo && ava test/index.js && cross-env FORCE_COLOR=0 ava test/no-color.js && cross-env FORCE_COLOR=3 TERM=dumb ava test/full-color.js && cross-env FORCE_COLOR=3 TERM=dumb ava test/template.js && tsd" }, "files": [ "index.js", @@ -49,6 +49,7 @@ "ava": "^3.15.0", "cross-env": "^7.0.3", "tsd": "^0.18.0", + "typescript": "^4.6.2", "xo": "^0.45.0" } } diff --git a/readme.md b/readme.md index bac35ba..df59873 100644 --- a/readme.md +++ b/readme.md @@ -58,6 +58,16 @@ Note that function styles (`rgb()`, etc.) may not contain spaces between paramet All interpolated values (`` chalkTemplate`${foo}` ``) are converted to strings via the `.toString()` method. All curly braces (`{` and `}`) in interpolated value strings are escaped. +## Template function + +You may also use the template function as an alternative to the tagged template function. + +```js +import {template} from 'chalk-template'; + +console.log(template('Today is {red hot}')); +``` + ## Related - [chalk](https://github.com/chalk/chalk) - Terminal string styling done right diff --git a/test/template.js b/test/template.js new file mode 100644 index 0000000..bf13899 --- /dev/null +++ b/test/template.js @@ -0,0 +1,78 @@ +import test from 'ava'; +import {template} from '../index.js'; + +test('correctly parse and evaluate color-convert functions', t => { + t.is(template('{bold.rgb(144,10,178).inverse Hello, {~inverse there!}}'), + '\u001B[1m\u001B[38;2;144;10;178m\u001B[7mHello, ' + + '\u001B[27m\u001B[39m\u001B[22m\u001B[1m' + + '\u001B[38;2;144;10;178mthere!\u001B[39m\u001B[22m'); + + t.is(template('{bold.bgRgb(144,10,178).inverse Hello, {~inverse there!}}'), + '\u001B[1m\u001B[48;2;144;10;178m\u001B[7mHello, ' + + '\u001B[27m\u001B[49m\u001B[22m\u001B[1m' + + '\u001B[48;2;144;10;178mthere!\u001B[49m\u001B[22m'); +}); + +test('properly handle escapes', t => { + t.is(template('{bold hello \\{in brackets\\}}'), + '\u001B[1mhello {in brackets}\u001B[22m'); +}); + +test('throw if there is an unclosed block', t => { + t.throws(() => { + template('{bold this shouldn\'t work ever\\}'); + }, { + message: 'Chalk template literal is missing 1 closing bracket (`}`)', + }); + + t.throws(() => { + template('{bold this shouldn\'t {inverse appear {underline ever\\} :) \\}'); + }, { + message: 'Chalk template literal is missing 3 closing brackets (`}`)', + }); +}); + +test('throw if there is an invalid style', t => { + t.throws(() => { + template('{abadstylethatdoesntexist this shouldn\'t work ever}'); + }, { + message: 'Unknown Chalk style: abadstylethatdoesntexist', + }); +}); + +test('properly style multiline color blocks', t => { + t.is( + template(`{bold + Hello! This is a + ${'multiline'} block! + :) + } {underline + I hope you enjoy + }`), + '\u001B[1m\u001B[22m\n' + + '\u001B[1m\t\t\tHello! This is a\u001B[22m\n' + + '\u001B[1m\t\t\tmultiline block!\u001B[22m\n' + + '\u001B[1m\t\t\t:)\u001B[22m\n' + + '\u001B[1m\t\t\u001B[22m \u001B[4m\u001B[24m\n' + + '\u001B[4m\t\t\tI hope you enjoy\u001B[24m\n' + + '\u001B[4m\t\t\u001B[24m', + ); +}); + +test('should allow bracketed Unicode escapes', t => { + t.is(template('\u{AB}'), '\u{AB}'); + t.is(template('This is a {bold \u{AB681}} test'), 'This is a \u001B[1m\u{AB681}\u001B[22m test'); + t.is(template('This is a {bold \u{10FFFF}} test'), 'This is a \u001B[1m\u{10FFFF}\u001B[22m test'); +}); + +test('should handle special hex case', t => { + t.is(template('{#FF0000 hello}'), '\u001B[38;2;255;0;0mhello\u001B[39m'); + t.is(template('{#:FF0000 hello}'), '\u001B[48;2;255;0;0mhello\u001B[49m'); + t.is(template('{#00FF00:FF0000 hello}'), '\u001B[38;2;0;255;0m\u001B[48;2;255;0;0mhello\u001B[49m\u001B[39m'); + t.is(template('{bold.#FF0000 hello}'), '\u001B[1m\u001B[38;2;255;0;0mhello\u001B[39m\u001B[22m'); + t.is(template('{bold.#:FF0000 hello}'), '\u001B[1m\u001B[48;2;255;0;0mhello\u001B[49m\u001B[22m'); + t.is(template('{bold.#00FF00:FF0000 hello}'), '\u001B[1m\u001B[38;2;0;255;0m\u001B[48;2;255;0;0mhello\u001B[49m\u001B[39m\u001B[22m'); + t.is(template('{#FF0000.bold hello}'), '\u001B[38;2;255;0;0m\u001B[1mhello\u001B[22m\u001B[39m'); + t.is(template('{#:FF0000.bold hello}'), '\u001B[48;2;255;0;0m\u001B[1mhello\u001B[22m\u001B[49m'); + t.is(template('{#00FF00:FF0000.bold hello}'), '\u001B[38;2;0;255;0m\u001B[48;2;255;0;0m\u001B[1mhello\u001B[22m\u001B[49m\u001B[39m'); +});