Skip to content

Commit

Permalink
Add stderr support (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
Qix- committed Dec 26, 2022
1 parent ba70fbb commit fc9ac0c
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 178 deletions.
41 changes: 41 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,31 @@ log(chalk.red.bgBlack(chalkTemplate`2 + 3 = {bold ${2 + 3}}`));
*/
export default function chalkTemplate(text: TemplateStringsArray, ...placeholders: unknown[]): string;

/**
Terminal string styling with [tagged template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates),
configured for standard error instead of standard output
@example
```
import {chalkTemplateStderr as 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 {chalkTemplateStderr as chalkTemplate} from 'chalk-template';
import {chalkStderr as chalk} from 'chalk';
log(chalk.red.bgBlack(chalkTemplate`2 + 3 = {bold ${2 + 3}}`));
```
*/
export function chalkTemplateStderr(text: TemplateStringsArray, ...placeholders: unknown[]): string;

/**
Terminal string styling.
Expand All @@ -37,3 +62,19 @@ console.log(template('Today is {red hot}'));
```
*/
export function template(text: string): string;

/**
Terminal string styling, configured for stderr.
This function can be useful if you need to wrap the template function. However, prefer the `chalkTemplateStderr` export whenever possible.
__Note:__ It's up to you to properly escape the input.
@example
```
import {templateStderr as template} from 'chalk-template';
console.log(template('Today is {red hot}'));
```
*/
export function templateStderr(text: string): string;
128 changes: 71 additions & 57 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,81 +92,95 @@ function parseStyle(style) {
return results;
}

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

for (const layer of styles) {
for (const style of layer.styles) {
enabled[style[0]] = layer.inverse ? null : style.slice(1);
for (const layer of styles) {
for (const style of layer.styles) {
enabled[style[0]] = layer.inverse ? null : style.slice(1);
}
}
}

let current = chalk;
for (const [styleName, styles] of Object.entries(enabled)) {
if (!Array.isArray(styles)) {
continue;
}
let current = chalk;
for (const [styleName, styles] of Object.entries(enabled)) {
if (!Array.isArray(styles)) {
continue;
}

if (!(styleName in current)) {
throw new Error(`Unknown Chalk style: ${styleName}`);
if (!(styleName in current)) {
throw new Error(`Unknown Chalk style: ${styleName}`);
}

current = styles.length > 0 ? current[styleName](...styles) : current[styleName];
}

current = styles.length > 0 ? current[styleName](...styles) : current[styleName];
return current;
}

return current;
}

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

// eslint-disable-next-line max-params
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(styles)(string));
styles.push({inverse, styles: parseStyle(style)});
} else if (close) {
if (styles.length === 0) {
throw new Error('Found extraneous } in Chalk template literal');
function template(string) {
const styles = [];
const chunks = [];
let chunk = [];

// eslint-disable-next-line max-params
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(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(styles)(chunk.join('')));
chunk = [];
styles.pop();
} else {
chunk.push(character);
}
});

chunks.push(buildStyle(styles)(chunk.join('')));
chunk = [];
styles.pop();
} else {
chunk.push(character);
}
});
chunks.push(chunk.join(''));

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

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

return chunks.join('');
return template;
}

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');
}
function makeChalkTemplate(template) {
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]];
const parts = [firstString.raw[0]];

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

return template(parts.join(''));
}

return template(parts.join(''));
return chalkTemplate;
}

export const template = makeTemplate(chalk);
export default makeChalkTemplate(template);

export const templateStderr = makeTemplate(chalk.stderr);
export const chalkTemplateStderr = makeChalkTemplate(templateStderr);
6 changes: 5 additions & 1 deletion index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {expectType} from 'tsd';
import chalk from 'chalk';
import chalkTemplate, {template} from './index.js';
import chalkTemplate, {template, chalkTemplateStderr, templateStderr} from './index.js';

// -- Template literal --
expectType<string>(chalkTemplate``);
Expand All @@ -13,3 +13,7 @@ expectType<string>(template('Today is {bold.red hot}'));
// -- 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}}`));

// -- Stderr Types --
expectType<typeof chalkTemplate>(chalkTemplateStderr);
expectType<typeof template>(templateStderr);
23 changes: 22 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ npm install chalk-template

## Usage

For printing to standard output (stdout):

```js
import chalkTemplate from 'chalk-template';
import chalk from 'chalk';
Expand All @@ -32,14 +34,27 @@ console.log(chalkTemplate`
In {bold ${miles} miles}, there are {green.bold ${calculateFeet(miles)} feet}.
`);


console.log(chalkTemplate`
There are also {#FF0000 shorthand hex styles} for
both the {#ABCDEF foreground}, {#:123456 background},
or {#ABCDEF:123456 both}.
`);
```

For printing to standard error (stderr):

```js
import {chalkTemplateStderr} from 'chalk-template';

const error = console.error;

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

## API

Blocks are delimited by an opening curly brace (`{`), a style, some content, and a closing curly brace (`}`).
Expand Down Expand Up @@ -70,6 +85,12 @@ import {template} from 'chalk-template';
console.log(template('Today is {red hot}'));
```

```js
import {templateStderr} from 'chalk-template';

console.error(templateStderr('Today is {red hot}'));
```

## Related

- [chalk](https://github.com/chalk/chalk) - Terminal string styling done right
Expand Down
6 changes: 5 additions & 1 deletion test/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import test from 'ava';
import chalkTemplate from '../index.js';
import chalkTemplate, {chalkTemplateStderr} from '../index.js';

test('return an empty string for an empty literal', t => {
t.is(chalkTemplate``, '');
});

test('return an empty string for an empty literal (stderr)', t => {
t.is(chalkTemplateStderr``, '');
});

0 comments on commit fc9ac0c

Please sign in to comment.