Skip to content

Commit

Permalink
Move from chalk (#3)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <[email protected]>
  • Loading branch information
Richienb and sindresorhus authored Nov 4, 2021
1 parent c0ddf60 commit ce0999f
Show file tree
Hide file tree
Showing 10 changed files with 282 additions and 215 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: CI
on:
- push
- pull_request
jobs:
test:
name: Node.js ${{ matrix.node-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version:
- 16
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm test
23 changes: 23 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
Terminal string styling with [tagged template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates)
@example
```
import chalkTemplate from 'chalk-template';
log(chalkTemplate`
CPU: {red ${cpu.totalPercent}%}
RAM: {green ${ram.used / ram.total * 100}%}
DISK: {rgb(255,131,0) ${disk.used / disk.total * 100}%}
`);
```
@example
```
import chalkTemplate from 'chalk-template';
import chalk from 'chalk';
log(chalk.red.bgBlack(chalkTemplate`2 + 3 = {bold ${2 + 3}}`));
```
*/
export default function chalkTemplate(text: TemplateStringsArray, ...placeholders: unknown[]): string;
43 changes: 31 additions & 12 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
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;
import chalk from 'chalk';

// eslint-disable-next-line unicorn/better-regex
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;
const STYLE_REGEX = /(?:^|\.)(\w+)(?:\(([^)]*)\))?/g;
const STRING_REGEX = /^(['"])((?:\\.|(?!\1)[^\\])*)\1$/;
const ESCAPE_REGEX = /\\(u(?:[a-f\d]{4}|{[a-f\d]{1,6}})|x[a-f\d]{2}|.)|([^\\])/gi;
Expand All @@ -13,7 +16,7 @@ const ESCAPES = new Map([
['0', '\0'],
['\\', '\\'],
['e', '\u001B'],
['a', '\u0007']
['a', '\u0007'],
]);

function unescape(c) {
Expand Down Expand Up @@ -41,7 +44,7 @@ function parseArguments(name, arguments_) {
if (!Number.isNaN(number)) {
results.push(number);
} else if ((matches = chunk.match(STRING_REGEX))) {
results.push(matches[2].replace(ESCAPE_REGEX, (m, escape, character) => escape ? unescape(escape) : character));
results.push(matches[2].replace(ESCAPE_REGEX, (_, escape, character) => escape ? unescape(escape) : character));
} else {
throw new Error(`Invalid Chalk template style argument: ${chunk} (in style '${name}')`);
}
Expand All @@ -60,8 +63,7 @@ function parseStyle(style) {
const name = matches[1];

if (matches[2]) {
const args = parseArguments(name, matches[2]);
results.push([name, ...args]);
results.push([name, ...parseArguments(name, matches[2])]);
} else {
results.push([name]);
}
Expand All @@ -70,7 +72,7 @@ function parseStyle(style) {
return results;
}

function buildStyle(chalk, styles) {
function buildStyle(styles) {
const enabled = {};

for (const layer of styles) {
Expand All @@ -95,26 +97,26 @@ function buildStyle(chalk, styles) {
return current;
}

export default function template(chalk, temporary) {
function template(string) {
const styles = [];
const chunks = [];
let chunk = [];

// eslint-disable-next-line max-params
temporary.replace(TEMPLATE_REGEX, (m, escapeCharacter, inverse, style, close, character) => {
string.replace(TEMPLATE_REGEX, (_, escapeCharacter, inverse, style, close, character) => {
if (escapeCharacter) {
chunk.push(unescape(escapeCharacter));
} else if (style) {
const string = chunk.join('');
chunk = [];
chunks.push(styles.length === 0 ? string : buildStyle(chalk, styles)(string));
chunks.push(styles.length === 0 ? string : buildStyle(styles)(string));
styles.push({inverse, styles: parseStyle(style)});
} else if (close) {
if (styles.length === 0) {
throw new Error('Found extraneous } in Chalk template literal');
}

chunks.push(buildStyle(chalk, styles)(chunk.join('')));
chunks.push(buildStyle(styles)(chunk.join('')));
chunk = [];
styles.pop();
} else {
Expand All @@ -125,9 +127,26 @@ export default function template(chalk, temporary) {
chunks.push(chunk.join(''));

if (styles.length > 0) {
const errorMessage = `Chalk template literal is missing ${styles.length} closing bracket${styles.length === 1 ? '' : 's'} (\`}\`)`;
throw new Error(errorMessage);
throw new Error(`Chalk template literal is missing ${styles.length} closing bracket${styles.length === 1 ? '' : 's'} (\`}\`)`);
}

return chunks.join('');
}

export default function chalkTemplate(firstString, ...arguments_) {
if (!Array.isArray(firstString) || !Array.isArray(firstString.raw)) {
// If chalkTemplate() was called by itself or with a string
throw new TypeError('A tagged template literal must be provided');
}

const parts = [firstString.raw[0]];

for (let index = 1; index < firstString.length; index++) {
parts.push(
String(arguments_[index - 1]).replace(/[{}\\]/g, '\\$&'),
String(firstString.raw[index]),
);
}

return template(parts.join(''));
}
13 changes: 13 additions & 0 deletions index.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {expectType} from 'tsd';
import chalk from 'chalk';
import chalkTemplate from './index.js';

// -- Template literal --
expectType<string>(chalkTemplate``);
const name = 'John';
expectType<string>(chalkTemplate`Hello {bold.red ${name}}`);
expectType<string>(chalkTemplate`Works with numbers {bold.red ${1}}`);

// -- Complex template literal --
expectType<string>(chalk.red.bgGreen.bold(chalkTemplate`Hello {italic.blue ${name}}`));
expectType<string>(chalk.strikethrough.cyanBright.bgBlack(chalkTemplate`Works with {reset {bold numbers}} {bold.red ${1}}`));
14 changes: 10 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "chalk-template",
"version": "0.0.0",
"description": "TODO",
"description": "Terminal string styling with tagged template literals",
"license": "MIT",
"repository": "chalk/chalk-template",
"funding": "https://github.com/chalk/chalk-template?sponsor=1",
Expand All @@ -11,10 +11,11 @@
"node": ">=12"
},
"scripts": {
"test": "xo && ava"
"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"
},
"files": [
"index.js"
"index.js",
"index.d.ts"
],
"keywords": [
"chalk",
Expand All @@ -41,8 +42,13 @@
"command-line",
"text"
],
"dependencies": {
"chalk": "^4.1.2"
},
"devDependencies": {
"ava": "^3.15.0",
"xo": "^0.38.2"
"cross-env": "^7.0.3",
"tsd": "^0.18.0",
"xo": "^0.45.0"
}
}
41 changes: 36 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,55 @@
# chalk-template

> TODO
> Terminal string styling with [tagged template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates)
## Install

```
$ npm install chalk-template
```sh
npm install chalk-template
```

## Usage

```js
import chalkTemplate from 'chalk-template';
import chalk from 'chalk';

const log = console.log;

log(chalkTemplate`
CPU: {red ${cpu.totalPercent}%}
RAM: {green ${ram.used / ram.total * 100}%}
DISK: {rgb(255,131,0) ${disk.used / disk.total * 100}%}
`);

log(chalk.red.bgBlack(chalkTemplate`2 + 3 = {bold ${2 + 3}}`));

//
const miles = 18;
const calculateFeet = miles => miles * 5280;

console.log(chalk`
There are {bold 5280 feet} in a mile.
In {bold ${miles} miles}, there are {green.bold ${calculateFeet(miles)} feet}.
`);
```

## API

### chalkTemplate()
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](https://github.com/chalk/chalk) styles. The following two statements are equivalent:

```js
import chalk from 'chalk';
import chalkTemplate from 'chalk-template';

console.log(chalk.bold.rgb(10, 100, 200)('Hello!'));
console.log(chalkTemplate`{bold.rgb(10,100,200) Hello!}`);
```

Note that function styles (`rgb()`, `hex()`, etc.) may not contain spaces between parameters.

All interpolated values (`` chalkTemplate`${foo}` ``) are converted to strings via the `.toString()` method. All curly braces (`{` and `}`) in interpolated value strings are escaped.

## Related

Expand Down
Loading

0 comments on commit ce0999f

Please sign in to comment.