thebespokepixel/trucolor-cli

View on GitHub
src/index.js

Summary

Maintainability
A
0 mins
Test Coverage
/* ─────────╮
 │ trucolor │ CLI
 ╰──────────┴─────────────────────────────────────────────────────────────────── */
/* eslint unicorn/no-process-exit:0, no-process-exit:0, node/prefer-global/process: [error] */

import {dirname} from 'node:path'
import {fileURLToPath} from 'node:url'
import yargs from 'yargs'
import {hideBin} from 'yargs/helpers' // eslint-disable-line node/file-extension-in-import
import updateNotifier from 'update-notifier'
import {createConsole} from 'verbosity'
import meta from '@thebespokepixel/meta'
import {box} from '@thebespokepixel/string'
import {stripIndent} from 'common-tags'
import {readPackageSync} from 'read-pkg'
import {parse, render} from 'trucolor'
import {colorReplacer} from './lib/colour.js'
import help from './lib/help.js'

const console = createConsole({outStream: process.stderr})
export const metadata = meta(dirname(fileURLToPath(import.meta.url)))

const pkg = readPackageSync()

const yargsInstance = yargs(hideBin(process.argv))
    .strictOptions()
    .help(false)
    .version(false)
    .options({
        h: {
            alias: 'help',
            describe: 'Display this help.',
        },
        v: {
            alias: 'version',
            count: true,
            describe: 'Return the current version on stdout. -vv Return name & version.',
        },
        V: {
            alias: 'verbose',
            count: true,
            describe: 'Be verbose. -VV Be loquacious.',
        },
        m: {
            alias: 'message',
            nargs: 1,
            describe: 'Format message with SGR codes',
        },
        i: {
            alias: 'in',
            boolean: true,
            describe: 'Output SGR color escape code.',
        },
        o: {
            alias: 'out',
            boolean: true,
            describe: 'Output cancelling SGR color escape code.',
        },
        t: {
            alias: 'type',
            choices: ['none', 'direct', 'fish'],
            describe: 'CLI color styling flags output.',
            default: 'direct',
            requiresArg: true,
        },
        r: {
            alias: 'rgb',
            boolean: true,
            describe: 'Output color as rgb(r, g, b).',
        },
        s: {
            alias: 'swatch',
            boolean: true,
            describe: 'Output an isolated color swatch.',
        },
        color: {
            describe: 'Force color depth --color=256|16m. Disable with --no-color',
        },
    }).showHelpOnFail(false, 'Use \'trucolor --help\' for help.')

const {argv} = yargsInstance

const colorFlags = (type => {
    switch (type) {
        case undefined:
            return {}
        case 'default':
            return pkg.config.cli[pkg.config.cli.selected]
        default:
            return pkg.config.cli[type]
    }
})(argv.type)

if (argv.version) {
    process.stdout.write(`${metadata.version(argv.version)}\n`)
    process.exit(0)
}

if (argv.verbose) {
    const settings = {
        borderColor: 'green',
        margin: {
            bottom: 1,
            top: 1,
        },
        padding: {
            bottom: 0,
            top: 0,
            left: 2,
            right: 2,
        },
    }

    const titling = mode => stripIndent(colorReplacer)`
        ${`title|${metadata.name}`}${`dim| │ v${metadata.version()}`}
        Mode: ${mode}
    `
    switch (argv.verbose) {
        case 1:
            console.verbosity(4)
            console.log(box(titling('Verbose'), settings))
            break
        case 2:
            console.verbosity(5)
            console.log(box(titling('Some might say loquacious'), settings))
            console.yargs(argv)
            console.debug('')
            break
        default:
            console.verbosity(3)
    }
}

if (!(process.env.USER === 'root' && process.env.SUDO_USER !== process.env.USER)) {
    updateNotifier({
        pkg,
    }).notify()
}

if (argv.help) {
    (async () => {
        await help(yargsInstance, argv.help)
    })()
} else {
    if (argv._.length === 0) {
        console.error('At least one color must be specified.')
        process.exit(1)
    }

    const buffer = parse(argv._.join(' '))
        .map(color => render(color, {
            format: 'cli',
            colorFlags,
        }))

    const isList = buffer.length > 1

    for (const color of buffer) {
        if (console.canWrite(4)) {
            console.log('')
            console.pretty(color, {
                depth: 2,
            })
        }

        const output = isList ? `${color.name}: ` : ''
        const lineBreak = isList ? '\n' : ''

        switch (true) {
            case argv.message !== undefined:
                process.stdout.write(`${output}${color.in}${argv.message}${color.out}${lineBreak}`)
                break
            case argv.in:
                if (isList) {
                    throw new Error('SGR output only makes sense for a single color.')
                }

                process.stdout.write(`${color.in}`)
                break
            case argv.out:
                if (isList) {
                    throw new Error('SGR output only makes sense for a single color.')
                }

                process.stdout.write(`${color.out}`)
                break
            case argv.rgb:
                process.stdout.write(`${output}${color.rgb}${lineBreak}`)
                break
            case argv.swatch:
                process.stdout.write(`${output}${color.toSwatch()}${lineBreak}`)
                break
            default:
                process.stdout.write(`${output}${color}${lineBreak}`)
        }
    }
}