diff --git a/package-lock.json b/package-lock.json index 0ead6b8..3148478 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,7 @@ "dependencies": { "@matt.kantor/either": "^1.2.0", "@matt.kantor/option": "^1.0.0", - "@matt.kantor/parsing": "^2.0.0", - "kleur": "^4.1.5" + "@matt.kantor/parsing": "^2.0.0" }, "bin": { "please": "please" @@ -22,7 +21,7 @@ "typescript": "^5.9.2" }, "engines": { - "node": ">=22" + "node": ">=22.18" } }, "node_modules/@matt.kantor/either": { @@ -87,15 +86,6 @@ "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", diff --git a/package.json b/package.json index 06c3819..b4ee0fb 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,7 @@ "dependencies": { "@matt.kantor/either": "^1.2.0", "@matt.kantor/option": "^1.0.0", - "@matt.kantor/parsing": "^2.0.0", - "kleur": "^4.1.5" + "@matt.kantor/parsing": "^2.0.0" }, "engines": { "node": ">=22.18" diff --git a/src/language/cli/output.ts b/src/language/cli/output.ts index 15c8a97..2c585c0 100644 --- a/src/language/cli/output.ts +++ b/src/language/cli/output.ts @@ -1,5 +1,4 @@ import either, { type Either } from '@matt.kantor/either' -import kleur from 'kleur' import { parseArgs } from 'node:util' import { type SyntaxTree } from '../parsing/syntax-tree.js' import { @@ -27,7 +26,10 @@ export const handleOutput = async ( if (typeof noColorArg !== 'boolean') { throw new Error('Unsupported value for --no-color') } else if (noColorArg === true) { - kleur.enabled = false + // Warning: the global state mutation here means that we can't style text in static contexts! + // Functions like `node:util`'s `styleText` shouldn't be called from the top level of modules. + delete process.env['FORCE_COLOR'] + process.env['NO_COLOR'] = 'true' } const outputFormatArg = args.values['output-format'] diff --git a/src/language/unparsing/inline-plz.ts b/src/language/unparsing/inline-plz.ts index cc0eee0..1bd7441 100644 --- a/src/language/unparsing/inline-plz.ts +++ b/src/language/unparsing/inline-plz.ts @@ -1,5 +1,5 @@ import either from '@matt.kantor/either' -import kleur from 'kleur' +import { styleText } from 'node:util' import type { Atom, Molecule } from '../parsing.js' import { moleculeAsKeyValuePairStrings, @@ -9,7 +9,7 @@ import { import { punctuation, type Notation } from './unparsing-utilities.js' const unparseSugarFreeMolecule = (value: Molecule) => { - const { comma, closeBrace, openBrace } = punctuation(kleur) + const { comma, closeBrace, openBrace } = punctuation(styleText) if (Object.keys(value).length === 0) { return either.makeRight(openBrace + closeBrace) } else { diff --git a/src/language/unparsing/plz-utilities.ts b/src/language/unparsing/plz-utilities.ts index 6fa5649..cfa6300 100644 --- a/src/language/unparsing/plz-utilities.ts +++ b/src/language/unparsing/plz-utilities.ts @@ -1,6 +1,6 @@ import either, { type Either, type Right } from '@matt.kantor/either' import parsing from '@matt.kantor/parsing' -import kleur from 'kleur' +import { styleText } from 'node:util' import type { UnserializableValueError } from '../errors.js' import type { Atom, Molecule } from '../parsing.js' import { unquotedAtomParser } from '../parsing/atom.js' @@ -76,7 +76,7 @@ export const moleculeAsKeyValuePairStrings = ( unparseAtomOrMolecule: UnparseAtomOrMolecule, options: { readonly ordinalKeys: 'omit' | 'preserve' }, ): Either => { - const { colon } = punctuation(kleur) + const { colon } = punctuation(styleText) const entries = Object.entries(value) const keyValuePairsAsStrings: string[] = [] @@ -96,8 +96,7 @@ export const moleculeAsKeyValuePairStrings = ( ordinalPropertyKeyCounter += 1n } else { keyValuePairsAsStrings.push( - kleur - .cyan(quoteAtomIfNecessary(propertyKey).concat(colon)) + styleText('cyan', quoteAtomIfNecessary(propertyKey).concat(colon)) .concat(' ') .concat(valueAsStringResult.value), ) @@ -109,7 +108,7 @@ export const moleculeAsKeyValuePairStrings = ( export const unparseAtom = (atom: string): Right => either.makeRight( /^@[^@]/.test(atom) - ? kleur.bold(kleur.underline(quoteAtomIfNecessary(atom))) + ? styleText(['bold', 'underline'], quoteAtomIfNecessary(atom)) : quoteAtomIfNecessary(atom), ) @@ -117,7 +116,7 @@ const requiresQuotation = (atom: string): boolean => either.isLeft(parsing.parse(unquotedAtomParser, atom)) const quoteAtomIfNecessary = (value: string): string => { - const { quote } = punctuation(kleur) + const { quote } = punctuation(styleText) if (requiresQuotation(value)) { return quote.concat(escapeStringContents(value)).concat(quote) } else { @@ -126,7 +125,7 @@ const quoteAtomIfNecessary = (value: string): string => { } const quoteKeyPathComponentIfNecessary = (value: string): string => { - const { quote } = punctuation(kleur) + const { quote } = punctuation(styleText) const unquotedAtomResult = parsing.parse(unquotedAtomParser, value) if (either.isLeft(unquotedAtomResult) || value.includes('.')) { return quote.concat(escapeStringContents(value)).concat(quote) @@ -153,7 +152,7 @@ const unparseSugaredApply = ( expression: ApplyExpression, unparseAtomOrMolecule: UnparseAtomOrMolecule, ) => { - const { closeParenthesis, openParenthesis } = punctuation(kleur) + const { closeParenthesis, openParenthesis } = punctuation(styleText) return either.flatMap( either.map( either.flatMap( @@ -188,8 +187,8 @@ const unparseSugaredFunction = ( either.flatMap(serializeIfNeeded(expression[1].body), serializedBody => either.map(unparseAtomOrMolecule(serializedBody), bodyAsString => [ - kleur.cyan(expression[1].parameter), - punctuation(kleur).arrow, + styleText('cyan', expression[1].parameter), + punctuation(styleText).arrow, bodyAsString, ].join(' '), ), @@ -238,7 +237,7 @@ const unparseSugaredIndex = ( message: 'invalid key path', }) } else { - const { dot } = punctuation(kleur) + const { dot } = punctuation(styleText) return either.makeRight( unparsedObject .concat(dot) @@ -254,8 +253,9 @@ const unparseSugaredLookup = ( _unparseAtomOrMolecule: UnparseAtomOrMolecule, ) => either.makeRight( - kleur.cyan( - punctuation(kleur).colon.concat( + styleText( + 'cyan', + punctuation(styleText).colon.concat( quoteKeyPathComponentIfNecessary(expression[1].key), ), ), @@ -280,7 +280,7 @@ const unparseSugaredGeneralizedKeywordExpression = ( 'expression cannot be faithfully represented using generalized keyword expression sugar', }) } else { - const unparsedKeyword = kleur.bold(kleur.underline(expression['0'])) + const unparsedKeyword = styleText(['bold', 'underline'], expression['0']) if ('1' in expression) { return either.map( either.flatMap( diff --git a/src/language/unparsing/pretty-json.ts b/src/language/unparsing/pretty-json.ts index 59bb9a1..95d8b6c 100644 --- a/src/language/unparsing/pretty-json.ts +++ b/src/language/unparsing/pretty-json.ts @@ -1,6 +1,6 @@ import type { Right } from '@matt.kantor/either' import either from '@matt.kantor/either' -import kleur from 'kleur' +import { styleText } from 'node:util' import type { Atom, Molecule } from '../parsing.js' import { indent, punctuation, type Notation } from './unparsing-utilities.js' @@ -8,16 +8,18 @@ const escapeStringContents = (value: string) => value.replace('\\', '\\\\').replace('"', '\\"') const key = (value: Atom): string => { - const { quote } = punctuation(kleur) - return quote.concat(kleur.bold(escapeStringContents(value))).concat(quote) + const { quote } = punctuation(styleText) + return quote + .concat(styleText('bold', escapeStringContents(value))) + .concat(quote) } const unparseAtom = (value: Atom): Right => { - const { quote } = punctuation(kleur) + const { quote } = punctuation(styleText) return either.makeRight( quote.concat( escapeStringContents( - /^@[^@]/.test(value) ? kleur.bold(kleur.underline(value)) : value, + /^@[^@]/.test(value) ? styleText(['bold', 'underline'], value) : value, ), quote, ), @@ -25,7 +27,7 @@ const unparseAtom = (value: Atom): Right => { } const unparseMolecule = (value: Molecule): Right => { - const { closeBrace, colon, comma, openBrace } = punctuation(kleur) + const { closeBrace, colon, comma, openBrace } = punctuation(styleText) const entries = Object.entries(value) if (entries.length === 0) { return either.makeRight(openBrace.concat(closeBrace)) diff --git a/src/language/unparsing/pretty-plz.ts b/src/language/unparsing/pretty-plz.ts index 8f21228..af9c45f 100644 --- a/src/language/unparsing/pretty-plz.ts +++ b/src/language/unparsing/pretty-plz.ts @@ -1,5 +1,5 @@ import either from '@matt.kantor/either' -import kleur from 'kleur' +import { styleText } from 'node:util' import type { Atom, Molecule } from '../parsing.js' import { moleculeAsKeyValuePairStrings, @@ -9,7 +9,7 @@ import { import { indent, punctuation, type Notation } from './unparsing-utilities.js' const unparseSugarFreeMolecule = (value: Molecule) => { - const { closeBrace, openBrace } = punctuation(kleur) + const { closeBrace, openBrace } = punctuation(styleText) if (Object.keys(value).length === 0) { return either.makeRight(openBrace + closeBrace) } else { diff --git a/src/language/unparsing/sugar-free-pretty-plz.ts b/src/language/unparsing/sugar-free-pretty-plz.ts index 248935b..703e7a3 100644 --- a/src/language/unparsing/sugar-free-pretty-plz.ts +++ b/src/language/unparsing/sugar-free-pretty-plz.ts @@ -1,11 +1,11 @@ import either from '@matt.kantor/either' -import kleur from 'kleur' +import { styleText } from 'node:util' import type { Atom, Molecule } from '../parsing.js' import { moleculeAsKeyValuePairStrings, unparseAtom } from './plz-utilities.js' import { indent, punctuation, type Notation } from './unparsing-utilities.js' const unparseMolecule = (value: Molecule) => { - const { closeBrace, openBrace } = punctuation(kleur) + const { closeBrace, openBrace } = punctuation(styleText) if (Object.keys(value).length === 0) { return either.makeRight(openBrace + closeBrace) } else { diff --git a/src/language/unparsing/unparsing-utilities.ts b/src/language/unparsing/unparsing-utilities.ts index 041b3e0..9535e95 100644 --- a/src/language/unparsing/unparsing-utilities.ts +++ b/src/language/unparsing/unparsing-utilities.ts @@ -1,5 +1,5 @@ import { type Either } from '@matt.kantor/either' -import type { Kleur } from 'kleur' +import * as util from 'node:util' import type { UnserializableValueError } from '../errors.js' import type { Atom, Molecule } from '../parsing.js' @@ -18,14 +18,16 @@ export const indent = (spaces: number, textToIndent: string) => { .replace(/(\r?\n)/g, `$1${indentation}`) } -export const punctuation = (kleur: Kleur) => ({ - dot: kleur.dim('.'), - quote: kleur.dim('"'), - colon: kleur.dim(':'), - comma: kleur.dim(','), - openBrace: kleur.dim('{'), - closeBrace: kleur.dim('}'), - openParenthesis: kleur.dim('('), - closeParenthesis: kleur.dim(')'), - arrow: kleur.dim('=>'), +// Note that `node:util`'s `styleText` changes behavior based on the current global state of +// `process.env`, which may be mutated at runtime (e.g. to handle `--no-color`). +export const punctuation = (styleText: typeof util.styleText) => ({ + dot: styleText('dim', '.'), + quote: styleText('dim', '"'), + colon: styleText('dim', ':'), + comma: styleText('dim', ','), + openBrace: styleText('dim', '{'), + closeBrace: styleText('dim', '}'), + openParenthesis: styleText('dim', '('), + closeParenthesis: styleText('dim', ')'), + arrow: styleText('dim', '=>'), })