feat: recreate tblf in ts

This commit is contained in:
2023-12-14 03:01:08 +02:00
parent cbbd44ec4e
commit facb40887c
4 changed files with 161 additions and 11 deletions

View File

@@ -5,10 +5,12 @@
"description": "",
"main": "index.js",
"bin": {
"tx": "tmux.js"
"tx": "tmux.js",
"tblf": "tblf.js"
},
"scripts": {
"tmux": "ts-node src/tmux.ts",
"tx": "ts-node src/tmux.ts",
"tblf": "ts-node src/tblf.ts",
"build": "tsc && cp package.json build/",
"ginst": "pnpm build && pnpm i -g utils@file:$(pwd)/build",
"dev": "tsc -w"

51
utils/src/common.ts Normal file
View File

@@ -0,0 +1,51 @@
import { spawn } from 'node:child_process'
export function log({ verbose, dry }: Opts, ...content: any[]) {
if (!verbose && !dry) return
console.log(...content)
}
export type Opts = {
key: string
verbose: boolean
dry: boolean
}
export async function runCommand(opts: Opts, command: string) {
const [cmd, ...args] = command.split(' ')
log(opts, '$ ' + command)
if (opts.dry) return 0
const proc = spawn(cmd, args, { stdio: 'inherit' })
return new Promise((resolve, reject) => {
proc.on('close', (code) => {
if (code === 0) {
resolve(code)
} else {
reject(code)
}
})
})
}
export async function getCommandOutput(
opts: Opts,
command: string,
): Promise<{ code: number; output: string }> {
const [cmd, ...args] = command.split(' ')
log(opts, '$ ' + command)
if (opts.dry) return { code: 0, output: '' }
const proc = spawn(cmd, args, { stdio: 'pipe' })
return new Promise<{ code: number; output: string }>((resolve, reject) => {
let output = ''
proc.stdout.on('data', (data) => {
output += data.toString()
})
proc.on('close', (code) => {
if (code === 0) {
resolve({ code, output })
} else {
reject(code)
}
})
})
}

97
utils/src/tblf.ts Normal file
View File

@@ -0,0 +1,97 @@
import { massarg } from 'massarg'
import { Opts as _Opts, log } from './common'
type Opts = _Opts & {
input?: string
header?: string
separator?: string
outputSeparator?: string
}
async function main(opts: Opts) {
let input = opts.input || ''
if (!opts.input) {
// read from stdin
log(opts, 'Reading from stdin...')
input = await new Promise((resolve) => {
let data = ''
process.stdin.on('data', (chunk) => {
data += chunk
})
process.stdin.on('end', () => {
resolve(data)
})
})
} else {
input = input.replaceAll('\\n', '\n')
}
if (!input) {
throw new Error('No input provided.')
}
log(opts, 'Input:', input + '\n')
const lines = input.split('\n')
if (opts.header) {
lines.unshift(opts.header)
}
const separator = opts.separator ? new RegExp(opts.separator) : /[\s\t]+/
const outputSeparator = opts.outputSeparator || ' '
const rows = lines.map((line) => line.split(separator))
const columnMaxes = new Array(rows[0].length).fill(0)
for (const row of rows) {
for (let i = 0; i < row.length; i++) {
columnMaxes[i] = Math.max(columnMaxes[i], row[i].length)
}
}
let output = ''
for (const row of rows) {
for (let i = 0; i < row.length; i++) {
const column = row[i]
output += column.padEnd(columnMaxes[i]) + outputSeparator
}
output += '\n'
}
console.log(output.trimEnd())
}
massarg<Opts>({
name: 'tblf',
description: 'Generate a table from a file or stdin.',
})
.main(main)
.option({
name: 'input',
description: 'The input file to read from.',
aliases: ['i'],
isDefault: true,
})
.option({
name: 'header',
description: 'The header to prepend to the input, which will be aligned.',
aliases: ['th'],
})
.option({
name: 'separator',
description: 'The separator to use when reading from stdin.',
aliases: ['s'],
})
.option({
name: 'output-separator',
description: 'The separator to use when writing to stdout.',
aliases: ['o'],
})
.flag({
name: 'verbose',
aliases: ['v'],
description: 'Print verbose output.',
})
.help({
bindOption: true,
bindCommand: true,
})
.parse()