Skip to content

Commit

Permalink
fix: enhance cmd highligher
Browse files Browse the repository at this point in the history
  • Loading branch information
antongolub committed Mar 8, 2025
1 parent 10a0eef commit 499a2c4
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 120 deletions.
2 changes: 1 addition & 1 deletion .size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
{
"name": "all",
"path": "build/*",
"limit": "851.1 kB",
"limit": "850.7 kB",
"brotli": false,
"gzip": false
}
Expand Down
175 changes: 74 additions & 101 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,107 +235,9 @@ export function log(entry: LogEntry) {
}
}

export function formatCmd(cmd?: string): string {
if (cmd == undefined) return chalk.grey('undefined')
const chars = [...cmd]
let out = '$ '
let buf = ''
let ch: string
type State = (() => State) | undefined
let state: State = root
let wordCount = 0
while (state) {
ch = chars.shift() || 'EOF'
if (ch == '\n') {
out += style(state, buf) + '\n> '
buf = ''
continue
}
const next: State = ch === 'EOF' ? undefined : state()
if (next !== state) {
out += style(state, buf)
buf = ''
}
state = next === root ? next() : next
buf += ch
}

function style(state: State, s: string): string {
if (s === '') return ''
if (RESERVED_WORDS.has(s)) {
return chalk.cyanBright(s)
}
if (state == word && wordCount == 0) {
wordCount++
return chalk.greenBright(s)
}
if (state == syntax) {
wordCount = 0
return chalk.cyanBright(s)
}
if (state == dollar) return chalk.yellowBright(s)
if (state?.name.startsWith('str')) return chalk.yellowBright(s)
return s
}

function isSyntax(ch: string) {
return '()[]{}<>;:+|&='.includes(ch)
}

function root() {
if (/\s/.test(ch)) return space
if (isSyntax(ch)) return syntax
if (ch === '$') return dollar
if (ch === '"') return strDouble
if (ch === "'") return strSingle
return word
}

function space() {
return /\s/.test(ch) ? space : root
}

function word() {
return /[\w/.]/i.test(ch) ? word : root
}

function syntax() {
return isSyntax(ch) ? syntax : root
}

function dollar() {
return ch === "'" ? str : root
}

function str() {
if (ch === "'") return strEnd
if (ch === '\\') return strBackslash
return str
}

function strBackslash() {
return strEscape
}

function strEscape() {
return str
}

function strDouble() {
return ch === '"' ? strEnd : strDouble
}

function strSingle() {
return ch === "'" ? strEnd : strSingle
}

function strEnd() {
return root
}

return out + '\n'
}

const SYNTAX = '()[]{}<>;:+|&='
const CMD_BREAK = new Set(['|', '&', ';', '>', '<'])
const SPACE_RE = /\s/
const RESERVED_WORDS = new Set([
'if',
'then',
Expand All @@ -353,6 +255,77 @@ const RESERVED_WORDS = new Set([
'in',
])

export function formatCmd(cmd?: string): string {
if (cmd == undefined) return chalk.grey('undefined')
let q = ''
let out = '$ '
let buf = ''
let mode: 'syntax' | 'quote' | 'dollar' | '' = ''
let pos = 0
const cap = () => {
const word = buf.trim()
if (word) {
pos++
if (mode === 'syntax') {
if (CMD_BREAK.has(word)) {
pos = 0
}
out += chalk.red(buf)
} else if (mode === 'quote' || mode === 'dollar') {
out += chalk.yellowBright(buf)
} else if (RESERVED_WORDS.has(word)) {
out += chalk.cyanBright(buf)
} else if (pos === 1) {
out += chalk.greenBright(buf)
pos = Infinity
} else {
out += buf
}
} else {
out += buf
}
mode = ''
buf = ''
}

for (const c of [...cmd]) {
if (!q) {
if (c === '$') {
cap()
mode = 'dollar'
buf += c
cap()
} else if (c === "'" || c === '"') {
cap()
mode = 'quote'
q = c
buf += c
} else if (SPACE_RE.test(c)) {
cap()
buf += c
} else if (SYNTAX.includes(c)) {
const isEnv = c === '=' && pos === 0
isEnv && (pos = 1)
cap()
mode = 'syntax'
buf += c
cap()
isEnv && (pos = -1)
} else {
buf += c
}
} else {
buf += c
if (c === q) {
cap()
q = ''
}
}
}
cap()
return out.replaceAll('\n', chalk.reset('\n> ')) + '\n'
}

export const once = <T extends (...args: any[]) => any>(fn: T) => {
let called = false
let result: ReturnType<T>
Expand Down
59 changes: 41 additions & 18 deletions test/util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@

import assert from 'node:assert'
import fs from 'node:fs'
import { test, describe, after } from 'node:test'
import { fs as fsCore } from '../build/index.js'
import { test, describe } from 'node:test'
import {
formatCmd,
isString,
Expand Down Expand Up @@ -85,22 +84,46 @@ describe('util', () => {
})

test('formatCwd works', () => {
assert.equal(
formatCmd(`echo $'hi'`),
"$ \u001b[92mecho\u001b[39m \u001b[93m$\u001b[39m\u001b[93m'hi\u001b[39m\u001b[93m'\u001b[39m\n"
)
assert.equal(
formatCmd(`while true; do "$" done`),
'$ \u001b[96mwhile\u001b[39m \u001b[92mtrue\u001b[39m\u001b[96m;\u001b[39m \u001b[96mdo\u001b[39m \u001b[93m"$\u001b[39m\u001b[93m"\u001b[39m \u001b[96mdone\u001b[39m\n'
)
assert.equal(
formatCmd(`echo '\n str\n'`),
"$ \u001b[92mecho\u001b[39m \u001b[93m'\u001b[39m\n> \u001b[93m str\u001b[39m\n> \u001b[93m'\u001b[39m\n"
)
assert.equal(
formatCmd(`$'\\''`),
"$ \u001b[93m$\u001b[39m\u001b[93m'\u001b[39m\u001b[93m\\\u001b[39m\u001b[93m'\u001b[39m\u001b[93m'\u001b[39m\n"
)
const cases = [
[
`echo $'hi'`,
"$ \x1B[92mecho\x1B[39m \x1B[93m$\x1B[39m\x1B[93m'hi'\x1B[39m\n",
],
[`echo$foo`, '$ \x1B[92mecho\x1B[39m\x1B[93m$\x1B[39mfoo\n'],
[
`test --foo=bar p1 p2`,
'$ \x1B[92mtest\x1B[39m --foo\x1B[31m=\x1B[39mbar p1 p2\n',
],
[
`cmd1 --foo || cmd2`,
'$ \x1B[92mcmd1\x1B[39m --foo \x1B[31m|\x1B[39m\x1B[31m|\x1B[39m\x1B[92m cmd2\x1B[39m\n',
],
[
`A=B C='D' cmd`,
"$ A\x1B[31m=\x1B[39mB C\x1B[31m=\x1B[39m\x1B[93m'D'\x1B[39m\x1B[92m cmd\x1B[39m\n",
],
[
`foo-extra --baz = b-a-z --bar = 'b-a-r' -q -u x`,
"$ \x1B[92mfoo-extra\x1B[39m --baz \x1B[31m=\x1B[39m b-a-z --bar \x1B[31m=\x1B[39m \x1B[93m'b-a-r'\x1B[39m -q -u x\n",
],
[
`while true; do "$" done`,
'$ \x1B[96mwhile\x1B[39m true\x1B[31m;\x1B[39m\x1B[96m do\x1B[39m \x1B[93m"$"\x1B[39m\x1B[96m done\x1B[39m\n',
],
[
`echo '\n str\n'`,
"$ \x1B[92mecho\x1B[39m \x1B[93m'\x1B[39m\x1B[0m\x1B[0m\n\x1B[0m> \x1B[0m\x1B[93m str\x1B[39m\x1B[0m\x1B[0m\n\x1B[0m> \x1B[0m\x1B[93m'\x1B[39m\n",
],
[`$'\\''`, "$ \x1B[93m$\x1B[39m\x1B[93m'\\'\x1B[39m\x1B[93m'\x1B[39m\n"],
[
'sass-compiler --style=compressed src/static/bootstrap.scss > dist/static/bootstrap-v5.3.3.min.css',
'foo',
],
]

cases.forEach(([input, expected]) => {
assert.equal(formatCmd(input), expected, input)
})
})

// test('normalizeMultilinePieces()', () => {
Expand Down

0 comments on commit 499a2c4

Please sign in to comment.