Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ jobs:
- name: Installing dependencies
run: pnpm install --frozen-lockfile

- name: Linting
run: pnpm lint

- name: Type checking
run: pnpm typecheck

- name: Linting
run: pnpm lint

- name: Testing
run: pnpm coverage

Expand Down
8 changes: 5 additions & 3 deletions example/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { randomInt } from 'node:crypto';
import { styleText } from 'node:util';

import { createConsole } from 'styled-json-console';
import { mergeStyleOptions } from 'styled-json-console/mergeStyleOptions';

const myConsole = createConsole({
inspectOptions: {
Expand Down Expand Up @@ -37,9 +38,9 @@ const myConsole = createConsole({
// stderr settings are merged with stdout settings so you can override only what you want
{
space: 2,
style: {
string: ['white', 'whiteBright'],
},
style: mergeStyleOptions(['whiteBright'], {
number: ['magentaBright'],
}),
},
],
});
Expand All @@ -50,6 +51,7 @@ const data = {
randomNumber: randomInt(0, 2 ** 32 - 1),
example: true,
json: JSON.stringify({ a: 1, b: [2], c: { d: 3, e: [4, 5] } }),
nested: { a: 1, b: [2], c: { d: 3, e: [4, 5] } },
};

myConsole.info(JSON.stringify(data));
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@
"homepage": "https://github.com/webdeveric/styled-json-console/#readme",
"packageManager": "pnpm@10.26.2+sha512.0e308ff2005fc7410366f154f625f6631ab2b16b1d2e70238444dd6ae9d630a8482d92a451144debc492416896ed16f7b114a86ec68b8404b2443869e68ffda6",
"scripts": {
"clean": "rimraf ./dist/",
"clean": "rimraf ./dist/ ./cache/",
"prebuild": "pnpm clean",
"build": "tsc --build tsconfig.src.json --force",
"typecheck": "tsc --build tsconfig.src.json tsconfig.test.json tsconfig.configs.json --verbose",
"lint": "eslint ./*.{js,cjs,mjs,ts,cts,mts} ./src/ --ext .ts",
"typecheck": "tsc --build tsconfig.json --verbose",
"lint": "eslint ./*.{js,cjs,mjs,ts,cts,mts} ./src/ ./example/ --ext .ts",
"test": "vitest --typecheck",
"coverage": "vitest run --coverage --typecheck",
"validate": "validate-package-exports --check --verify",
Expand Down
6 changes: 3 additions & 3 deletions src/AnsiJsonWritable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { Writable, type WritableOptions } from 'node:stream';

import { ansiHighlightJson } from './ansiHighlightJson.js';
import { isColorEnabled } from './isColorEnabled.js';
import { Style, type StyleOptions } from './Style.js';
import { Style } from './Style.js';

import type { JsonReplacerFn } from './types.js';
import type { JsonReplacerFn, StyleOptions, StyleTextFormatArray } from './types.js';

export type ModifyOutputFn = (output: string, style: Style) => string;

Expand All @@ -13,7 +13,7 @@ export type AnsiJsonWritableOptions = WritableOptions & {
space?: number | string;
eol?: string;
replacer?: JsonReplacerFn;
styleOptions?: Partial<StyleOptions>;
styleOptions?: Partial<StyleOptions> | StyleTextFormatArray;
modifyOutput?: ModifyOutputFn;
};

Expand Down
48 changes: 7 additions & 41 deletions src/Style.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,23 @@
import { styleText, type StyleTextOptions } from 'node:util';

import type { StyleTextFormat } from './types.js';

export type StyleKeys =
| 'string'
| 'number'
| 'boolean'
| 'bracket'
| 'comma'
| 'colon'
| 'quoteKey'
| 'quoteString'
| 'key'
| 'null';
import { defaultStyleOptions } from './defaults.js';
import { mergeStyleOptions } from './mergeStyleOptions.js';

import type { StyleKey, StyleOptions, StyleTextFormat, StyleTextFormatArray } from './types.js';

export type StyleFn = (value: string, depth: number) => string;

export type BaseStyle = {
[K in StyleKeys]: StyleFn;
};

export type StyleOptions = Record<
StyleKeys,
[item: StyleTextFormat, ...rest: StyleTextFormat[]] // At least one item
>;

export const defaultStyleOptions: StyleOptions = {
// content
string: ['green'],
number: ['yellowBright'],
boolean: ['blueBright'],
null: ['redBright'],

// structural
bracket: ['white', 'blue', 'yellow', 'cyan', 'green', 'red'],
comma: ['white'],
colon: ['white'],

// quotes
quoteKey: ['cyan'],
quoteString: ['green'],

// keys
key: ['cyan'],
[K in StyleKey]: StyleFn;
};

export class Style implements BaseStyle {
readonly #options: StyleOptions;

readonly #styleTextOptions: StyleTextOptions = { validateStream: false };

constructor(options?: Partial<StyleOptions>) {
this.#options = { ...defaultStyleOptions, ...options };
constructor(options?: Partial<StyleOptions> | StyleTextFormatArray) {
this.#options = mergeStyleOptions(defaultStyleOptions, options);
}

#getStyleTextFormat(type: keyof StyleOptions, depth: number): StyleTextFormat {
Expand Down
11 changes: 4 additions & 7 deletions src/createConsole.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Console, type ConsoleConstructorOptions } from 'node:console';

import { AnsiJsonWritable, type ModifyOutputFn } from './AnsiJsonWritable.js';
import { mergeStyleOptions } from './mergeStyleOptions.js';

import type { StyleOptions } from './Style.js';
import type { JsonReplacerFn } from './types.js';
import type { JsonReplacerFn, StyleOptions, StyleTextFormatArray } from './types.js';

export type JsonConsoleOptions = {
style?: Partial<StyleOptions>;
style?: Partial<StyleOptions> | StyleTextFormatArray;
space?: number | string;
replacer?: JsonReplacerFn;
};
Expand All @@ -28,10 +28,7 @@ export function createConsole(options: Partial<CreateConsoleOptions> = {}): Cons
? {
...stdOutJson,
...json[1],
style: {
...stdOutJson?.style,
...json[1]?.style,
},
style: mergeStyleOptions(stdOutJson?.style, json[1]?.style),
}
: json;

Expand Down
22 changes: 22 additions & 0 deletions src/createSimpleStyleOptions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { describe, it, expect } from 'vitest';

import { createSimpleStyleOptions } from './createSimpleStyleOptions.js';

import type { StyleOptions } from './types.js';

describe('createSimpleStyleOptions', () => {
it('creates style options with the given value for all style keys', () => {
expect(createSimpleStyleOptions(['red'])).toEqual({
boolean: ['red'],
bracket: ['red'],
colon: ['red'],
comma: ['red'],
key: ['red'],
null: ['red'],
number: ['red'],
quoteKey: ['red'],
quoteString: ['red'],
string: ['red'],
} satisfies StyleOptions);
});
});
16 changes: 16 additions & 0 deletions src/createSimpleStyleOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { defaultStyleOptions } from './defaults.js';
import { isStyleKey } from './isStyleKey.js';

import type { StyleOptions, StyleTextFormatArray } from './types.js';

export function createSimpleStyleOptions(value: StyleTextFormatArray): StyleOptions {
const options: StyleOptions = { ...defaultStyleOptions };

for (const key in options) {
if (isStyleKey(key)) {
options[key] = value;
}
}

return options;
}
21 changes: 21 additions & 0 deletions src/defaults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { StyleOptions } from './types.js';

export const defaultStyleOptions: Readonly<StyleOptions> = {
// content
string: ['green'],
number: ['yellowBright'],
boolean: ['blueBright'],
null: ['redBright'],

// structural
bracket: ['white', 'blue', 'yellow', 'cyan', 'green', 'red'],
comma: ['white'],
colon: ['white'],

// quotes
quoteKey: ['cyan'],
quoteString: ['green'],

// keys
key: ['cyan'],
};
16 changes: 16 additions & 0 deletions src/isStyleKey.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { describe, expect, it } from 'vitest';

import { isStyleKey } from './isStyleKey.js';

describe('isStyleKey()', () => {
it.each(['string', 'number', 'boolean', 'bracket', 'comma', 'colon', 'quoteKey', 'quoteString', 'key', 'null'])(
'returns true for %j',
(input) => {
expect(isStyleKey(input)).toBe(true);
},
);

it.each([null, undefined, 42, {}, [], 'not valid'])('returns false for %j', (input) => {
expect(isStyleKey(input)).toBe(false);
});
});
16 changes: 16 additions & 0 deletions src/isStyleKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { StyleKey } from './types.js';

export const isStyleKey = (value: unknown): value is StyleKey => {
return (
value === 'string' ||
value === 'number' ||
value === 'boolean' ||
value === 'bracket' ||
value === 'comma' ||
value === 'colon' ||
value === 'quoteKey' ||
value === 'quoteString' ||
value === 'key' ||
value === 'null'
);
};
61 changes: 61 additions & 0 deletions src/mergeStyleOptions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { describe, expect, it } from 'vitest';

import { defaultStyleOptions } from './defaults.js';
import { mergeStyleOptions } from './mergeStyleOptions.js';

import type { StyleOptions } from './types.js';

describe('mergeStyleOptions()', () => {
it('merges two StyleOptions objects', () => {
expect(
mergeStyleOptions(
{
string: ['red'],
},
{
string: ['blue'],
},
),
).toEqual({
...defaultStyleOptions,
string: ['blue'],
} satisfies StyleOptions);
});

it('Builds StyleOptions when overrides is an array', () => {
expect(
mergeStyleOptions(
{
string: ['red'],
},
['blue'],
),
).toEqual({
boolean: ['blue'],
bracket: ['blue'],
colon: ['blue'],
comma: ['blue'],
key: ['blue'],
null: ['blue'],
number: ['blue'],
quoteKey: ['blue'],
quoteString: ['blue'],
string: ['blue'],
} satisfies StyleOptions);
});

it('Builds StyleOptions from base if it is an array', () => {
expect(mergeStyleOptions(['red'], { string: ['blue'] })).toEqual({
boolean: ['red'],
bracket: ['red'],
colon: ['red'],
comma: ['red'],
key: ['red'],
null: ['red'],
number: ['red'],
quoteKey: ['red'],
quoteString: ['red'],
string: ['blue'],
} satisfies StyleOptions);
});
});
22 changes: 22 additions & 0 deletions src/mergeStyleOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createSimpleStyleOptions } from './createSimpleStyleOptions.js';
import { defaultStyleOptions } from './defaults.js';

import type { StyleOptions, StyleTextFormatArray } from './types.js';

export function mergeStyleOptions(
base?: Partial<StyleOptions> | StyleTextFormatArray,
override?: Partial<StyleOptions> | StyleTextFormatArray,
): StyleOptions {
if (Array.isArray(override)) {
return createSimpleStyleOptions(override);
}

if (Array.isArray(base)) {
return {
...createSimpleStyleOptions(base),
...override,
};
}

return { ...defaultStyleOptions, ...base, ...override };
}
19 changes: 19 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ export type JsonReplacerFn = (this: unknown, key: string, value: unknown) => unk

export type StyleTextFormat = Parameters<typeof styleText>[0];

/**
* An array of `StyleTextFormat` with at least one item.
*/
export type StyleTextFormatArray = [item: StyleTextFormat, ...rest: StyleTextFormat[]];

/**
* @internal
*/
Expand All @@ -14,3 +19,17 @@ export type StyleTextBgColor = Extract<TextFormatStrings, `bg${string}`>;
export type StyleTextColor = StyleTextBgColor extends `bg${infer Color}` ? Uncapitalize<Color> : never;

export type StyleTextModifier = Exclude<TextFormatStrings, StyleTextBgColor | StyleTextColor>;

export type StyleKey =
| 'string'
| 'number'
| 'boolean'
| 'bracket'
| 'comma'
| 'colon'
| 'quoteKey'
| 'quoteString'
| 'key'
| 'null';

export type StyleOptions = Record<StyleKey, StyleTextFormatArray>;
9 changes: 9 additions & 0 deletions tsconfig.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"noEmit": true,
"rootDir": "./example/",
"tsBuildInfoFile": "./cache/example.tsbuildinfo"
},
"include": ["example/**/*"]
}
Loading