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
14 changes: 2 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 4 additions & 2 deletions src/language/cli/output.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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'
Comment on lines +29 to +32
Copy link
Owner Author

@mkantor mkantor Aug 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels even worse than the global state mutation I had to do for kleur, but on the upside it's more general: now --no-color will also disable styling in stack traces, console.log output, etc.

}

const outputFormatArg = args.values['output-format']
Expand Down
4 changes: 2 additions & 2 deletions src/language/unparsing/inline-plz.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 {
Expand Down
28 changes: 14 additions & 14 deletions src/language/unparsing/plz-utilities.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -76,7 +76,7 @@ export const moleculeAsKeyValuePairStrings = (
unparseAtomOrMolecule: UnparseAtomOrMolecule,
options: { readonly ordinalKeys: 'omit' | 'preserve' },
): Either<UnserializableValueError, readonly string[]> => {
const { colon } = punctuation(kleur)
const { colon } = punctuation(styleText)
const entries = Object.entries(value)

const keyValuePairsAsStrings: string[] = []
Expand All @@ -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),
)
Expand All @@ -109,15 +108,15 @@ export const moleculeAsKeyValuePairStrings = (
export const unparseAtom = (atom: string): Right<string> =>
either.makeRight(
/^@[^@]/.test(atom)
? kleur.bold(kleur.underline(quoteAtomIfNecessary(atom)))
? styleText(['bold', 'underline'], quoteAtomIfNecessary(atom))
: quoteAtomIfNecessary(atom),
)

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 {
Expand All @@ -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)
Expand All @@ -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(
Expand Down Expand Up @@ -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(' '),
),
Expand Down Expand Up @@ -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)
Expand All @@ -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),
),
),
Expand All @@ -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(
Expand Down
14 changes: 8 additions & 6 deletions src/language/unparsing/pretty-json.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
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'

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<string> => {
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,
),
)
}

const unparseMolecule = (value: Molecule): Right<string> => {
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))
Expand Down
4 changes: 2 additions & 2 deletions src/language/unparsing/pretty-plz.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions src/language/unparsing/sugar-free-pretty-plz.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
24 changes: 13 additions & 11 deletions src/language/unparsing/unparsing-utilities.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -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', '=>'),
})