Commit 236d4ae
Changed files (4)
test
src/index.ts
@@ -40,7 +40,7 @@ export {
YAML,
} from './goods.js'
-export { log, formatCmd, LogEntry } from './log.js'
+export { log, LogEntry } from './log.js'
export { Duration } from './util.js'
src/log.ts
@@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import chalk from 'chalk'
import { RequestInfo, RequestInit } from 'node-fetch'
import { inspect } from 'node:util'
import { $ } from './core.js'
-import { colorize } from './util.js'
+import { formatCmd } from './util.js'
export type LogEntry =
| {
@@ -55,25 +56,14 @@ export function log(entry: LogEntry) {
break
case 'cd':
if (!$.verbose) return
- process.stderr.write('$ ' + colorize(`cd ${entry.dir}`) + '\n')
+ process.stderr.write('$ ' + chalk.greenBright('cd') + ` ${entry.dir}\n`)
break
case 'fetch':
if (!$.verbose) return
const init = entry.init ? ' ' + inspect(entry.init) : ''
- process.stderr.write('$ ' + colorize(`fetch ${entry.url}`) + init + '\n')
+ process.stderr.write(
+ '$ ' + chalk.greenBright('fetch') + ` ${entry.url}${init}\n`
+ )
break
}
}
-
-export function formatCmd(cmd: string) {
- if (/\n/.test(cmd)) {
- return (
- cmd
- .split('\n')
- .map((line, i) => `${i == 0 ? '$' : '>'} ${colorize(line)}`)
- .join('\n') + '\n'
- )
- } else {
- return `$ ${colorize(cmd)}\n`
- }
-}
src/util.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import chalk from 'chalk'
+import chalk, { ChalkInstance } from 'chalk'
import { promisify } from 'node:util'
import psTreeModule from 'ps-tree'
import { ProcessOutput, ProcessPromise } from './core.js'
@@ -62,12 +62,6 @@ export function stringify(arg: ProcessOutput | any) {
return `${arg}`
}
-export function colorize(cmd: string) {
- return cmd.replace(/^[\w_.-]+(\s|$)/, (substr) => {
- return chalk.greenBright(substr)
- })
-}
-
export function exitCodeInfo(exitCode: number | null): string | undefined {
return {
2: 'Misuse of shell builtins',
@@ -134,3 +128,113 @@ export function parseDuration(d: Duration) {
throw new Error(`Unknown duration: "${d}".`)
}
}
+
+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 (reservedWords.includes(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 (/[$]/.test(ch)) return dollar
+ if (/["]/.test(ch)) return strDouble
+ if (/[']/.test(ch)) return strSingle
+ return word
+ }
+ function space() {
+ if (/\s/.test(ch)) return space
+ return root
+ }
+ function word() {
+ if (/[0-9a-z/_.]/i.test(ch)) return word
+ return root
+ }
+ function syntax() {
+ if (isSyntax(ch)) return syntax
+ return root
+ }
+ function dollar() {
+ if (/[']/.test(ch)) return str
+ return root
+ }
+ function str() {
+ if (/[']/.test(ch)) return strEnd
+ if (/[\\]/.test(ch)) return strBackslash
+ return str
+ }
+ function strBackslash() {
+ return strEscape
+ }
+ function strEscape() {
+ return str
+ }
+ function strDouble() {
+ if (/["]/.test(ch)) return strEnd
+ return strDouble
+ }
+ function strSingle() {
+ if (/[']/.test(ch)) return strEnd
+ return strSingle
+ }
+ function strEnd() {
+ return root
+ }
+ return out + '\n'
+}
+
+const reservedWords = [
+ 'if',
+ 'then',
+ 'else',
+ 'elif',
+ 'fi',
+ 'case',
+ 'esac',
+ 'for',
+ 'select',
+ 'while',
+ 'until',
+ 'do',
+ 'done',
+ 'in',
+]
test/util.test.js
@@ -16,11 +16,12 @@ import { suite } from 'uvu'
import * as assert from 'uvu/assert'
import {
exitCodeInfo,
- randomId,
- noop,
+ formatCmd,
isString,
- quote,
+ noop,
parseDuration,
+ quote,
+ randomId,
} from '../build/util.js'
const test = suite('util')
@@ -57,4 +58,23 @@ test('duration parsing works', () => {
assert.throws(() => parseDuration('100'))
})
+test('formatCwd works', () => {
+ assert.is(
+ formatCmd(`echo $'hi'`),
+ "$ \u001b[92mecho\u001b[39m \u001b[93m$\u001b[39m\u001b[93m'hi\u001b[39m\u001b[93m'\u001b[39m\n"
+ )
+ assert.is(
+ 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.is(
+ 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.is(
+ formatCmd(`$'\\''`),
+ "$ \u001b[93m$\u001b[39m\u001b[93m'\u001b[39m\u001b[93m\\\u001b[39m\u001b[93m'\u001b[39m\u001b[93m'\u001b[39m\n"
+ )
+})
+
test.run()