diff --git a/src/cli-ux/index.ts b/src/cli-ux/index.ts index 40c2b647e..ab626f9ce 100644 --- a/src/cli-ux/index.ts +++ b/src/cli-ux/index.ts @@ -9,7 +9,6 @@ import * as uxPrompt from './prompt' import * as styled from './styled' import uxWait from './wait' import write from './write' - const hyperlinker = require('hyperlinker') export class ux { @@ -195,4 +194,5 @@ export {ExitError} from './exit' export {IPromptOptions} from './prompt' export {Table} from './styled' +export {colorize} from './theme' export {default as write} from './write' diff --git a/src/cli-ux/theme.ts b/src/cli-ux/theme.ts new file mode 100644 index 000000000..443f9afd9 --- /dev/null +++ b/src/cli-ux/theme.ts @@ -0,0 +1,31 @@ +import chalk from 'chalk' +import * as Color from 'color' + +import {STANDARD_CHALK, StandardChalk, Theme} from '../interfaces/theme' + +function isStandardChalk(color: any): color is StandardChalk { + return STANDARD_CHALK.includes(color) +} + +export function colorize(color: Color | StandardChalk | undefined, text: string): string { + if (isStandardChalk(color)) return chalk[color](text) + + return color ? chalk.hex(color.hex())(text) : text +} + +export function parseTheme(untypedTheme: Record): Theme { + return Object.fromEntries( + Object.entries(untypedTheme) + .map(([key, value]) => [key, getColor(value)]) + .filter(([_, value]) => value), + ) +} + +export function getColor(color: string): Color +export function getColor(color: StandardChalk): StandardChalk +export function getColor(color: string | StandardChalk): Color | StandardChalk | undefined { + try { + // eslint-disable-next-line new-cap + return isStandardChalk(color) ? color : new Color.default(color) + } catch {} +} diff --git a/src/config/config.ts b/src/config/config.ts index dd426ebc1..92b23ab3e 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -5,6 +5,7 @@ import {join, resolve, sep} from 'node:path' import {URL, fileURLToPath} from 'node:url' import {ux} from '../cli-ux' +import {parseTheme} from '../cli-ux/theme' import {Command} from '../command' import {CLIError, error, exit, warn} from '../errors' import {getHelpFlagAdditions} from '../help/util' @@ -17,7 +18,6 @@ import {OCLIF_MARKER_OWNER, Performance} from '../performance' import {settings} from '../settings' import {requireJson, safeReadJson} from '../util/fs' import {getHomeDir, getPlatform} from '../util/os' -import {parseTheme} from '../util/theme' import {compact, isProd} from '../util/util' import Cache from './cache' import PluginLoader from './plugin-loader' diff --git a/src/help/command.ts b/src/help/command.ts index 8b9add9f3..4e2c5f1f6 100644 --- a/src/help/command.ts +++ b/src/help/command.ts @@ -1,11 +1,11 @@ import chalk from 'chalk' import stripAnsi from 'strip-ansi' +import {colorize} from '../cli-ux/theme' import {Command} from '../command' import * as Interfaces from '../interfaces' import {ensureArgObject} from '../util/ensure-arg-object' import {toStandardizedId} from '../util/ids' -import {colorize} from '../util/theme' import {castArray, compact, sortBy} from '../util/util' import {DocOpts} from './docopts' import {HelpFormatter, HelpSection, HelpSectionRenderer} from './formatter' diff --git a/src/help/formatter.ts b/src/help/formatter.ts index 954740359..94f6926c6 100644 --- a/src/help/formatter.ts +++ b/src/help/formatter.ts @@ -5,10 +5,10 @@ import stripAnsi from 'strip-ansi' import widestLine from 'widest-line' import wrap from 'wrap-ansi' +import {colorize} from '../cli-ux/theme' import {Command} from '../command' import * as Interfaces from '../interfaces' import {stdtermwidth} from '../screen' -import {colorize} from '../util/theme' import {template} from './util' export type HelpSectionKeyValueTable = {description: string; name: string}[] diff --git a/src/help/index.ts b/src/help/index.ts index fa9221278..e4c66167d 100644 --- a/src/help/index.ts +++ b/src/help/index.ts @@ -1,6 +1,7 @@ import {format} from 'node:util' import stripAnsi from 'strip-ansi' +import {colorize} from '../cli-ux/theme' import write from '../cli-ux/write' import {Command} from '../command' import {error} from '../errors' @@ -8,7 +9,6 @@ import * as Interfaces from '../interfaces' import {load} from '../module-loader' import {cacheDefaultValue} from '../util/cache-default-value' import {toConfiguredId} from '../util/ids' -import {colorize} from '../util/theme' import {compact, sortBy, uniqBy} from '../util/util' import {CommandHelp} from './command' import {HelpFormatter} from './formatter' diff --git a/src/help/root.ts b/src/help/root.ts index 87b03ea3a..37ef88ac2 100644 --- a/src/help/root.ts +++ b/src/help/root.ts @@ -1,7 +1,7 @@ import stripAnsi from 'strip-ansi' +import {colorize} from '../cli-ux/theme' import * as Interfaces from '../interfaces' -import {colorize} from '../util/theme' import {compact} from '../util/util' import {HelpFormatter} from './formatter' diff --git a/src/interfaces/theme.ts b/src/interfaces/theme.ts index 652f05373..b522796b9 100644 --- a/src/interfaces/theme.ts +++ b/src/interfaces/theme.ts @@ -40,4 +40,41 @@ export const STANDARD_CHALK = [ export type StandardChalk = (typeof STANDARD_CHALK)[number] -export type Theme = Record +export const THEME_KEYS = [ + 'alias', + 'bin', + 'command', + 'commandSummary', + 'dollarSign', + 'flag', + 'flagDefaultValue', + 'flagOptions', + 'flagRequired', + 'flagSeparator', + 'flagType', + 'sectionDescription', + 'sectionHeader', + 'topic', + 'version', +] as const + +export type ThemeKey = (typeof THEME_KEYS)[number] + +export type Theme = { + [key: string | ThemeKey]: Color | StandardChalk | undefined + alias?: Color | StandardChalk + bin?: Color | StandardChalk + command?: Color | StandardChalk + commandSummary?: Color | StandardChalk + dollarSign?: Color | StandardChalk + flag?: Color | StandardChalk + flagDefaultValue?: Color | StandardChalk + flagOptions?: Color | StandardChalk + flagRequired?: Color | StandardChalk + flagSeparator?: Color | StandardChalk + flagType?: Color | StandardChalk + sectionDescription?: Color | StandardChalk + sectionHeader?: Color | StandardChalk + topic?: Color | StandardChalk + version?: Color | StandardChalk +} diff --git a/src/util/theme.ts b/src/util/theme.ts deleted file mode 100644 index 219d36591..000000000 --- a/src/util/theme.ts +++ /dev/null @@ -1,170 +0,0 @@ -import chalk from 'chalk' -import * as Color from 'color' - -import {STANDARD_CHALK, StandardChalk, Theme} from '../interfaces/theme' - -// eslint-disable-next-line complexity -function standardChalk(color: StandardChalk, text: string): string { - switch (color) { - case 'red': { - return chalk.red(text) - } - - case 'blue': { - return chalk.blue(text) - } - - case 'yellow': { - return chalk.yellow(text) - } - - case 'green': { - return chalk.green(text) - } - - case 'cyan': { - return chalk.cyan(text) - } - - case 'magenta': { - return chalk.magenta(text) - } - - case 'white': { - return chalk.white(text) - } - - case 'black': { - return chalk.black(text) - } - - case 'gray': { - return chalk.gray(text) - } - - case 'blackBright': { - return chalk.blackBright(text) - } - - case 'redBright': { - return chalk.redBright(text) - } - - case 'greenBright': { - return chalk.greenBright(text) - } - - case 'yellowBright': { - return chalk.yellowBright(text) - } - - case 'blueBright': { - return chalk.blueBright(text) - } - - case 'magentaBright': { - return chalk.magentaBright(text) - } - - case 'cyanBright': { - return chalk.cyanBright(text) - } - - case 'whiteBright': { - return chalk.whiteBright(text) - } - - case 'bgBlack': { - return chalk.bgBlack(text) - } - - case 'bgRed': { - return chalk.bgRed(text) - } - - case 'bgGreen': { - return chalk.bgGreen(text) - } - - case 'bgYellow': { - return chalk.bgYellow(text) - } - - case 'bgBlue': { - return chalk.bgBlue(text) - } - - case 'bgMagenta': { - return chalk.bgMagenta(text) - } - - case 'bgCyan': { - return chalk.bgCyan(text) - } - - case 'bgWhite': { - return chalk.bgWhite(text) - } - - case 'bgGray': { - return chalk.bgGray(text) - } - - case 'bgBlackBright': { - return chalk.bgBlackBright(text) - } - - case 'bgRedBright': { - return chalk.bgRedBright(text) - } - - case 'bgGreenBright': { - return chalk.bgGreenBright(text) - } - - case 'bgYellowBright': { - return chalk.bgYellowBright(text) - } - - case 'bgBlueBright': { - return chalk.bgBlueBright(text) - } - - case 'bgMagentaBright': { - return chalk.bgMagentaBright(text) - } - - case 'bgCyanBright': { - return chalk.bgCyanBright(text) - } - - case 'bgWhiteBright': { - return chalk.bgWhiteBright(text) - } - - default: { - return chalk.gray(text) - } - } -} - -function isStandardChalk(color: any): color is StandardChalk { - return STANDARD_CHALK.includes(color) -} - -export function colorize(color: Color | StandardChalk | undefined, text: string): string { - if (isStandardChalk(color)) return standardChalk(color, text) - - return color ? chalk.hex(color.hex())(text) : text -} - -export function parseTheme(untypedTheme: Record): Theme { - return Object.fromEntries(Object.entries(untypedTheme).map(([key, value]) => [key, getColor(value)])) -} - -export function getColor(color: string): Color -export function getColor(color: StandardChalk): StandardChalk -export function getColor(color: string | StandardChalk): Color | StandardChalk { - // eslint-disable-next-line new-cap - return isStandardChalk(color) ? color : new Color.default(color) -} diff --git a/test/util/theme.test.ts b/test/cli-ux/theme.test.ts similarity index 59% rename from test/util/theme.test.ts rename to test/cli-ux/theme.test.ts index 3d8ad2923..c81f780f5 100644 --- a/test/util/theme.test.ts +++ b/test/cli-ux/theme.test.ts @@ -3,7 +3,8 @@ import chalk from 'chalk' config.truncateThreshold = 0 -import {colorize, getColor, parseTheme} from '../../src/util/theme' +import {colorize, getColor, parseTheme} from '../../src/cli-ux/theme' +import {THEME_KEYS} from '../../src/interfaces/theme' describe('colorize', () => { it('should return text with ansi characters when given hex code', () => { const color = getColor('#FF0000') @@ -54,11 +55,26 @@ describe('theme parsing', () => { const theme = parseTheme(untypedTheme) - for (const key of Object.keys(theme)) { - expect(typeof theme[key]).to.equal('object') - expect(theme[key]).to.have.property('model') - expect(theme[key]).to.have.property('color') - expect(theme[key]).to.have.property('valpha') + for (const value of Object.values(theme)) { + expect(typeof value).to.equal('object') + expect(value).to.have.property('model') + expect(value).to.have.property('color') + expect(value).to.have.property('valpha') + } + }) + + it('should parse untyped theme json to theme using rgb', () => { + const untypedTheme = { + alias: 'rgb(255, 255, 255)', + } + + const theme = parseTheme(untypedTheme) + + for (const value of Object.values(theme)) { + expect(typeof value).to.equal('object') + expect(value).to.have.property('model') + expect(value).to.have.property('color') + expect(value).to.have.property('valpha') } }) @@ -81,9 +97,39 @@ describe('theme parsing', () => { } const theme = parseTheme(untypedTheme) + for (const value of Object.values(theme)) { + expect(value).to.equal('cyan') + } + }) - for (const key of Object.keys(theme)) { - expect(theme[key]).to.equal('cyan') + it('should ignore unsupported values', () => { + const untypedTheme = { + alias: 'FOO', } + + const theme = parseTheme(untypedTheme) + expect(theme).to.deep.equal({}) + }) +}) + +describe('THEME_KEYS', () => { + it('should always have native theme keys', () => { + expect(THEME_KEYS).deep.equal([ + 'alias', + 'bin', + 'command', + 'commandSummary', + 'dollarSign', + 'flag', + 'flagDefaultValue', + 'flagOptions', + 'flagRequired', + 'flagSeparator', + 'flagType', + 'sectionDescription', + 'sectionHeader', + 'topic', + 'version', + ]) }) })