Download packages/experimental/webworker-runtime/src/shell/programs/text.ts from SaylorTwift/deepseek-harness: direct link, hf CLI and curl.
- Browser
- Download file 14.2 kB
-
https://huggingface.co/SaylorTwift/deepseek-harness/resolve/main/packages/experimental/webworker-runtime/src/shell/programs/text.ts
- Command line
-
hf download hf://SaylorTwift/deepseek-harness/packages/experimental/webworker-runtime/src/shell/programs/text.ts
-
curl -L -o text.ts https://huggingface.co/SaylorTwift/deepseek-harness/resolve/main/packages/experimental/webworker-runtime/src/shell/programs/text.ts
14.2 kB
| /** | |
| * Text utilities of the command table. Each one reads its operands as files | |
| * and falls back to standard input, the way its POSIX counterpart does. | |
| * @module @deepseek-ai/dsh-experimental-webworker-runtime/src/shell/programs/text | |
| */ | |
| import { describeFailure, resolveIn } from '../fs-access.ts' | |
| import type { ShellFileSystem, ShellIo, ShellProgram, ShellState } from '../types.ts' | |
| import { numberOption, parseOptions, toLines } from './options.ts' | |
| /** | |
| * Read every operand as a file, reporting the ones that fail. | |
| * @param program - name used in diagnostics. | |
| * @param operands - paths to read; empty means standard input. | |
| * @param io - source of standard input and sink for diagnostics. | |
| * @param state - shell state supplying the working directory. | |
| * @param fs - the filesystem to read from. | |
| * @returns one entry per readable source and the status the program should report. | |
| */ | |
| async function readInputs( | |
| program: string, | |
| operands: readonly string[], | |
| io: ShellIo, | |
| state: ShellState, | |
| fs: ShellFileSystem, | |
| ): Promise<{ sources: { name: string; text: string }[]; status: number }> { | |
| if (operands.length === 0) return { sources: [{ name: '-', text: io.stdin }], status: 0 } | |
| const sources: { name: string; text: string }[] = [] | |
| let status = 0 | |
| for (const operand of operands) { | |
| if (operand === '-') { | |
| sources.push({ name: '-', text: io.stdin }) | |
| continue | |
| } | |
| const path = resolveIn(state.cwd, operand) | |
| try { | |
| sources.push({ name: operand, text: await fs.readText(path) }) | |
| } catch (error) { | |
| io.err(`${describeFailure(program, operand, error)}\n`) | |
| status = 1 | |
| } | |
| } | |
| return { sources, status } | |
| } | |
| /** Append a trailing newline unless the text already ends with one. */ | |
| function terminated(text: string): string { | |
| return text === '' || text.endsWith('\n') ? text : `${text}\n` | |
| } | |
| const echo: ShellProgram = (argv, io) => { | |
| const suppressNewline = argv[1] === '-n' | |
| const words = argv.slice(suppressNewline ? 2 : 1) | |
| io.out(`${words.join(' ')}${suppressNewline ? '' : '\n'}`) | |
| return 0 | |
| } | |
| const printf: ShellProgram = (argv, io) => { | |
| const format = argv[1] ?? '' | |
| const operands = argv.slice(2) | |
| let cursor = 0 | |
| // The conversions a shell script realistically uses; anything else is left | |
| // verbatim so the output shows what was not understood. | |
| const rendered = format.replace(/%[sdi%]/g, (match) => { | |
| if (match === '%%') return '%' | |
| const value = operands[cursor] ?? '' | |
| cursor += 1 | |
| if (match === '%s') return value | |
| const parsed = Number.parseInt(value, 10) | |
| return String(Number.isFinite(parsed) ? parsed : 0) | |
| }) | |
| io.out(rendered.replace(/\\n/g, '\n').replace(/\\t/g, '\t')) | |
| return 0 | |
| } | |
| const cat: ShellProgram = async (argv, io, state, fs) => { | |
| const options = parseOptions(argv) | |
| const { sources, status } = await readInputs('cat', options.operands, io, state, fs) | |
| let line = 1 | |
| for (const source of sources) { | |
| if (!options.flags.has('n')) { | |
| io.out(source.text) | |
| continue | |
| } | |
| for (const content of toLines(source.text)) { | |
| io.out(`${String(line).padStart(6)}\t${content}\n`) | |
| line += 1 | |
| } | |
| } | |
| return status | |
| } | |
| const head: ShellProgram = async (argv, io, state, fs) => { | |
| const options = parseOptions(argv, new Set(['n'])) | |
| const count = numberOption(options, 'n', 10) | |
| const { sources, status } = await readInputs('head', options.operands, io, state, fs) | |
| for (const [index, source] of sources.entries()) { | |
| if (sources.length > 1) io.out(`${index > 0 ? '\n' : ''}==> ${source.name} <==\n`) | |
| io.out(terminated(toLines(source.text).slice(0, count).join('\n'))) | |
| } | |
| return status | |
| } | |
| const tail: ShellProgram = async (argv, io, state, fs) => { | |
| const options = parseOptions(argv, new Set(['n'])) | |
| const count = numberOption(options, 'n', 10) | |
| const { sources, status } = await readInputs('tail', options.operands, io, state, fs) | |
| for (const [index, source] of sources.entries()) { | |
| if (sources.length > 1) io.out(`${index > 0 ? '\n' : ''}==> ${source.name} <==\n`) | |
| io.out(terminated(toLines(source.text).slice(-count).join('\n'))) | |
| } | |
| return status | |
| } | |
| const wc: ShellProgram = async (argv, io, state, fs) => { | |
| const options = parseOptions(argv) | |
| const { sources, status } = await readInputs('wc', options.operands, io, state, fs) | |
| const selected = ['l', 'w', 'c'].filter(flag => options.flags.has(flag)) | |
| const columns = selected.length > 0 ? selected : ['l', 'w', 'c'] | |
| for (const source of sources) { | |
| const counts: Record<string, number> = { | |
| l: toLines(source.text).length, | |
| w: source.text.split(/\s+/).filter(word => word !== '').length, | |
| c: source.text.length, | |
| } | |
| const cells = columns.map(column => String(counts[column] ?? 0).padStart(columns.length > 1 ? 8 : 1)) | |
| io.out(`${cells.join(' ')}${source.name === '-' ? '' : ` ${source.name}`}\n`) | |
| } | |
| return status | |
| } | |
| /** Collect every file under one directory, for `grep -r`. */ | |
| async function walkFiles( | |
| path: string, | |
| display: string, | |
| into: { path: string; display: string }[], | |
| fs: ShellFileSystem, | |
| ): Promise<void> { | |
| for (const entry of await fs.list(path)) { | |
| const child = `${path.endsWith('/') ? path : `${path}/`}${entry.name}` | |
| const shown = `${display.endsWith('/') ? display : `${display}/`}${entry.name}` | |
| if (entry.directory) await walkFiles(child, shown, into, fs) | |
| else into.push({ path: child, display: shown }) | |
| } | |
| } | |
| const grep: ShellProgram = async (argv, io, state, fs) => { | |
| const options = parseOptions(argv, new Set(['e'])) | |
| const pattern = options.values.get('e') ?? options.operands[0] | |
| const targets = options.values.get('e') === undefined ? options.operands.slice(1) : options.operands | |
| if (pattern === undefined) { | |
| io.err('grep: no pattern given\n') | |
| return 2 | |
| } | |
| // Patterns are JavaScript regular expressions; `-F` matches them literally. | |
| const source = options.flags.has('F') ? pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') : pattern | |
| let matcher: RegExp | |
| try { | |
| matcher = new RegExp(source, options.flags.has('i') ? 'i' : '') | |
| } catch (error) { | |
| io.err(`grep: invalid pattern: ${error instanceof Error ? error.message : String(error)}\n`) | |
| return 2 | |
| } | |
| const sources: { name: string; text: string }[] = [] | |
| let status = 0 | |
| if (targets.length === 0) { | |
| sources.push({ name: '', text: io.stdin }) | |
| } else { | |
| for (const target of targets) { | |
| const path = resolveIn(state.cwd, target) | |
| const stats = await fs.stat(path) | |
| if (stats?.directory === true) { | |
| if (!options.flags.has('r') && !options.flags.has('R')) { | |
| io.err(`grep: ${target}: Is a directory\n`) | |
| status = Math.max(status, 2) | |
| continue | |
| } | |
| const files: { path: string; display: string }[] = [] | |
| await walkFiles(path, target, files, fs) | |
| for (const file of files) sources.push({ name: file.display, text: await fs.readText(file.path) }) | |
| continue | |
| } | |
| try { | |
| sources.push({ name: target, text: await fs.readText(path) }) | |
| } catch (error) { | |
| io.err(`${describeFailure('grep', target, error)}\n`) | |
| status = Math.max(status, 2) | |
| } | |
| } | |
| } | |
| const label = sources.length > 1 || options.flags.has('H') | |
| let matched = false | |
| for (const entry of sources) { | |
| const hits = toLines(entry.text) | |
| .map((text, index) => ({ text, number: index + 1 })) | |
| .filter(line => matcher.test(line.text) !== options.flags.has('v')) | |
| if (hits.length > 0) matched = true | |
| if (options.flags.has('l')) { | |
| if (hits.length > 0) io.out(`${entry.name}\n`) | |
| continue | |
| } | |
| if (options.flags.has('c')) { | |
| io.out(`${label && entry.name !== '' ? `${entry.name}:` : ''}${String(hits.length)}\n`) | |
| continue | |
| } | |
| for (const hit of hits) { | |
| const prefix = `${label && entry.name !== '' ? `${entry.name}:` : ''}${options.flags.has('n') ? `${String(hit.number)}:` : ''}` | |
| io.out(`${prefix}${hit.text}\n`) | |
| } | |
| } | |
| // `grep` reports "nothing matched" as status 1, distinct from an error. | |
| return status !== 0 ? status : matched ? 0 : 1 | |
| } | |
| const sort: ShellProgram = async (argv, io, state, fs) => { | |
| const options = parseOptions(argv) | |
| const { sources, status } = await readInputs('sort', options.operands, io, state, fs) | |
| let lines = sources.flatMap(source => toLines(source.text)) | |
| lines = options.flags.has('n') | |
| ? [...lines].sort((left, right) => (Number.parseFloat(left) || 0) - (Number.parseFloat(right) || 0)) | |
| : [...lines].sort((left, right) => left < right ? -1 : left > right ? 1 : 0) | |
| if (options.flags.has('r')) lines.reverse() | |
| if (options.flags.has('u')) lines = [...new Set(lines)] | |
| io.out(terminated(lines.join('\n'))) | |
| return status | |
| } | |
| const uniq: ShellProgram = async (argv, io, state, fs) => { | |
| const options = parseOptions(argv) | |
| const { sources, status } = await readInputs('uniq', options.operands, io, state, fs) | |
| const lines = sources.flatMap(source => toLines(source.text)) | |
| const groups: { text: string; count: number }[] = [] | |
| for (const line of lines) { | |
| const previous = groups[groups.length - 1] | |
| if (previous !== undefined && previous.text === line) previous.count += 1 | |
| else groups.push({ text: line, count: 1 }) | |
| } | |
| const selected = options.flags.has('d') | |
| ? groups.filter(group => group.count > 1) | |
| : options.flags.has('u') ? groups.filter(group => group.count === 1) : groups | |
| for (const group of selected) { | |
| io.out(`${options.flags.has('c') ? `${String(group.count).padStart(7)} ` : ''}${group.text}\n`) | |
| } | |
| return status | |
| } | |
| const cut: ShellProgram = async (argv, io, state, fs) => { | |
| const options = parseOptions(argv, new Set(['d', 'f', 'c'])) | |
| const delimiter = options.values.get('d') ?? '\t' | |
| const fields = (options.values.get('f') ?? '').split(',').map(field => Number.parseInt(field, 10)).filter(Number.isFinite) | |
| const characters = options.values.get('c') | |
| const { sources, status } = await readInputs('cut', options.operands, io, state, fs) | |
| if (fields.length === 0 && characters === undefined) { | |
| io.err('cut: expected -f or -c\n') | |
| return 2 | |
| } | |
| for (const source of sources) { | |
| for (const line of toLines(source.text)) { | |
| if (characters !== undefined) { | |
| const [from, to] = characters.split('-') | |
| const start = Number.parseInt(from ?? '1', 10) || 1 | |
| const end = to === undefined || to === '' ? start : Number.parseInt(to, 10) | |
| io.out(`${line.slice(start - 1, end)}\n`) | |
| continue | |
| } | |
| const parts = line.split(delimiter) | |
| io.out(`${fields.map(field => parts[field - 1] ?? '').join(delimiter)}\n`) | |
| } | |
| } | |
| return status | |
| } | |
| /** Expand one `tr` set: `a-z` becomes every character in that range. */ | |
| function characterSet(set: string): string[] { | |
| // oxlint-disable-next-line typescript/no-misused-spread -- a `tr` set names characters, and code points are that unit. | |
| const characters = [...set] | |
| const expanded: string[] = [] | |
| for (let index = 0; index < characters.length; index += 1) { | |
| const start = characters[index] as string | |
| const end = characters[index + 2] | |
| if (characters[index + 1] === '-' && end !== undefined) { | |
| for (let code = start.codePointAt(0) as number; code <= (end.codePointAt(0) as number); code += 1) { | |
| expanded.push(String.fromCodePoint(code)) | |
| } | |
| index += 2 | |
| continue | |
| } | |
| expanded.push(start) | |
| } | |
| return expanded | |
| } | |
| const tr: ShellProgram = (argv, io) => { | |
| const options = parseOptions(argv) | |
| const [fromSet, toSet] = options.operands | |
| const from = fromSet === undefined ? undefined : characterSet(fromSet).join('') | |
| const to = toSet === undefined ? undefined : characterSet(toSet).join('') | |
| if (from === undefined) { | |
| io.err('tr: expected a source set\n') | |
| return 2 | |
| } | |
| if (options.flags.has('d')) { | |
| // oxlint-disable-next-line typescript/no-misused-spread -- `tr` deletes per character, and code points are the unit it deletes. | |
| io.out([...io.stdin].filter(character => !from.includes(character)).join('')) | |
| return 0 | |
| } | |
| if (to === undefined) { | |
| io.err('tr: expected a replacement set\n') | |
| return 2 | |
| } | |
| // oxlint-disable-next-line typescript/no-misused-spread -- `tr` translates per character, and code points are the unit it maps. | |
| io.out([...io.stdin].map((character) => { | |
| const index = from.indexOf(character) | |
| return index < 0 ? character : to[Math.min(index, to.length - 1)] as string | |
| }).join('')) | |
| return 0 | |
| } | |
| /** `sed` accepts only the substitute command; anything else is reported, not guessed at. */ | |
| const sed: ShellProgram = async (argv, io, state, fs) => { | |
| const options = parseOptions(argv, new Set(['e'])) | |
| const script = options.values.get('e') ?? options.operands[0] | |
| const targets = options.values.get('e') === undefined ? options.operands.slice(1) : options.operands | |
| const parsed = /^s(.)(.*?[^\\])?\1(.*?)\1([gi]*)$/.exec(script ?? '') | |
| if (parsed === null) { | |
| io.err('sed: only substitution scripts (s/pattern/replacement/) run in the worker host\n') | |
| return 2 | |
| } | |
| const [, , pattern = '', replacement = '', modifiers = ''] = parsed | |
| let matcher: RegExp | |
| try { | |
| matcher = new RegExp(pattern, modifiers.includes('g') ? `g${modifiers.replace('g', '')}` : modifiers) | |
| } catch (error) { | |
| io.err(`sed: invalid pattern: ${error instanceof Error ? error.message : String(error)}\n`) | |
| return 2 | |
| } | |
| const { sources, status } = await readInputs('sed', targets, io, state, fs) | |
| for (const source of sources) { | |
| for (const line of toLines(source.text)) io.out(`${line.replace(matcher, replacement.replace(/\\(\d)/g, '$$$1'))}\n`) | |
| } | |
| return status | |
| } | |
| const tee: ShellProgram = async (argv, io, state, fs) => { | |
| const options = parseOptions(argv) | |
| io.out(io.stdin) | |
| for (const operand of options.operands) { | |
| try { | |
| await fs.writeText(resolveIn(state.cwd, operand), io.stdin, options.flags.has('a')) | |
| } catch (error) { | |
| io.err(`${describeFailure('tee', operand, error)}\n`) | |
| return 1 | |
| } | |
| } | |
| return 0 | |
| } | |
| /** The text utilities, keyed by the name a command line uses. */ | |
| export const TEXT_PROGRAMS: Readonly<Record<string, ShellProgram>> = { | |
| echo, printf, cat, head, tail, wc, grep, sort, uniq, cut, tr, sed, tee, | |
| } | |