@ecmaos/coreutils 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +6 -0
- package/dist/commands/awk.d.ts +4 -0
- package/dist/commands/awk.d.ts.map +1 -0
- package/dist/commands/awk.js +324 -0
- package/dist/commands/awk.js.map +1 -0
- package/dist/commands/chgrp.d.ts +4 -0
- package/dist/commands/chgrp.d.ts.map +1 -0
- package/dist/commands/chgrp.js +187 -0
- package/dist/commands/chgrp.js.map +1 -0
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +139 -2
- package/dist/commands/chmod.js.map +1 -1
- package/dist/commands/chown.d.ts +4 -0
- package/dist/commands/chown.d.ts.map +1 -0
- package/dist/commands/chown.js +257 -0
- package/dist/commands/chown.js.map +1 -0
- package/dist/commands/cksum.d.ts +4 -0
- package/dist/commands/cksum.d.ts.map +1 -0
- package/dist/commands/cksum.js +124 -0
- package/dist/commands/cksum.js.map +1 -0
- package/dist/commands/cmp.d.ts +4 -0
- package/dist/commands/cmp.d.ts.map +1 -0
- package/dist/commands/cmp.js +120 -0
- package/dist/commands/cmp.js.map +1 -0
- package/dist/commands/column.d.ts +4 -0
- package/dist/commands/column.d.ts.map +1 -0
- package/dist/commands/column.js +274 -0
- package/dist/commands/column.js.map +1 -0
- package/dist/commands/cp.d.ts.map +1 -1
- package/dist/commands/cp.js +81 -4
- package/dist/commands/cp.js.map +1 -1
- package/dist/commands/curl.d.ts +4 -0
- package/dist/commands/curl.d.ts.map +1 -0
- package/dist/commands/curl.js +238 -0
- package/dist/commands/curl.js.map +1 -0
- package/dist/commands/du.d.ts +4 -0
- package/dist/commands/du.d.ts.map +1 -0
- package/dist/commands/du.js +168 -0
- package/dist/commands/du.js.map +1 -0
- package/dist/commands/echo.d.ts.map +1 -1
- package/dist/commands/echo.js +125 -2
- package/dist/commands/echo.js.map +1 -1
- package/dist/commands/expand.d.ts +4 -0
- package/dist/commands/expand.d.ts.map +1 -0
- package/dist/commands/expand.js +197 -0
- package/dist/commands/expand.js.map +1 -0
- package/dist/commands/factor.d.ts +4 -0
- package/dist/commands/factor.d.ts.map +1 -0
- package/dist/commands/factor.js +141 -0
- package/dist/commands/factor.js.map +1 -0
- package/dist/commands/fmt.d.ts +4 -0
- package/dist/commands/fmt.d.ts.map +1 -0
- package/dist/commands/fmt.js +278 -0
- package/dist/commands/fmt.js.map +1 -0
- package/dist/commands/fold.d.ts +4 -0
- package/dist/commands/fold.d.ts.map +1 -0
- package/dist/commands/fold.js +253 -0
- package/dist/commands/fold.js.map +1 -0
- package/dist/commands/groups.d.ts +4 -0
- package/dist/commands/groups.d.ts.map +1 -0
- package/dist/commands/groups.js +61 -0
- package/dist/commands/groups.js.map +1 -0
- package/dist/commands/hostname.d.ts +4 -0
- package/dist/commands/hostname.d.ts.map +1 -0
- package/dist/commands/hostname.js +80 -0
- package/dist/commands/hostname.js.map +1 -0
- package/dist/commands/mount.d.ts.map +1 -1
- package/dist/commands/mount.js +192 -97
- package/dist/commands/mount.js.map +1 -1
- package/dist/commands/od.d.ts +4 -0
- package/dist/commands/od.d.ts.map +1 -0
- package/dist/commands/od.js +342 -0
- package/dist/commands/od.js.map +1 -0
- package/dist/commands/pr.d.ts +4 -0
- package/dist/commands/pr.d.ts.map +1 -0
- package/dist/commands/pr.js +298 -0
- package/dist/commands/pr.js.map +1 -0
- package/dist/commands/printf.d.ts +4 -0
- package/dist/commands/printf.d.ts.map +1 -0
- package/dist/commands/printf.js +271 -0
- package/dist/commands/printf.js.map +1 -0
- package/dist/commands/readlink.d.ts +4 -0
- package/dist/commands/readlink.d.ts.map +1 -0
- package/dist/commands/readlink.js +104 -0
- package/dist/commands/readlink.js.map +1 -0
- package/dist/commands/realpath.d.ts +4 -0
- package/dist/commands/realpath.d.ts.map +1 -0
- package/dist/commands/realpath.js +111 -0
- package/dist/commands/realpath.js.map +1 -0
- package/dist/commands/rev.d.ts +4 -0
- package/dist/commands/rev.d.ts.map +1 -0
- package/dist/commands/rev.js +134 -0
- package/dist/commands/rev.js.map +1 -0
- package/dist/commands/shuf.d.ts +4 -0
- package/dist/commands/shuf.d.ts.map +1 -0
- package/dist/commands/shuf.js +221 -0
- package/dist/commands/shuf.js.map +1 -0
- package/dist/commands/sleep.d.ts +4 -0
- package/dist/commands/sleep.d.ts.map +1 -0
- package/dist/commands/sleep.js +102 -0
- package/dist/commands/sleep.js.map +1 -0
- package/dist/commands/strings.d.ts +4 -0
- package/dist/commands/strings.d.ts.map +1 -0
- package/dist/commands/strings.js +170 -0
- package/dist/commands/strings.js.map +1 -0
- package/dist/commands/tac.d.ts +4 -0
- package/dist/commands/tac.d.ts.map +1 -0
- package/dist/commands/tac.js +130 -0
- package/dist/commands/tac.js.map +1 -0
- package/dist/commands/time.d.ts +4 -0
- package/dist/commands/time.d.ts.map +1 -0
- package/dist/commands/time.js +126 -0
- package/dist/commands/time.js.map +1 -0
- package/dist/commands/umount.d.ts.map +1 -1
- package/dist/commands/umount.js +2 -3
- package/dist/commands/umount.js.map +1 -1
- package/dist/commands/uname.d.ts +4 -0
- package/dist/commands/uname.d.ts.map +1 -0
- package/dist/commands/uname.js +149 -0
- package/dist/commands/uname.js.map +1 -0
- package/dist/commands/unexpand.d.ts +4 -0
- package/dist/commands/unexpand.d.ts.map +1 -0
- package/dist/commands/unexpand.js +286 -0
- package/dist/commands/unexpand.js.map +1 -0
- package/dist/commands/uptime.d.ts +4 -0
- package/dist/commands/uptime.d.ts.map +1 -0
- package/dist/commands/uptime.js +62 -0
- package/dist/commands/uptime.js.map +1 -0
- package/dist/commands/yes.d.ts +4 -0
- package/dist/commands/yes.d.ts.map +1 -0
- package/dist/commands/yes.js +58 -0
- package/dist/commands/yes.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/commands/awk.ts +340 -0
- package/src/commands/chmod.ts +141 -2
- package/src/commands/chown.ts +321 -0
- package/src/commands/cksum.ts +133 -0
- package/src/commands/cmp.ts +126 -0
- package/src/commands/column.ts +273 -0
- package/src/commands/cp.ts +93 -4
- package/src/commands/curl.ts +231 -0
- package/src/commands/echo.ts +122 -2
- package/src/commands/expand.ts +207 -0
- package/src/commands/factor.ts +151 -0
- package/src/commands/fmt.ts +293 -0
- package/src/commands/fold.ts +257 -0
- package/src/commands/groups.ts +72 -0
- package/src/commands/hostname.ts +81 -0
- package/src/commands/mount.ts +208 -99
- package/src/commands/od.ts +327 -0
- package/src/commands/pr.ts +291 -0
- package/src/commands/printf.ts +271 -0
- package/src/commands/readlink.ts +102 -0
- package/src/commands/realpath.ts +126 -0
- package/src/commands/rev.ts +143 -0
- package/src/commands/shuf.ts +218 -0
- package/src/commands/sleep.ts +109 -0
- package/src/commands/strings.ts +176 -0
- package/src/commands/tac.ts +138 -0
- package/src/commands/time.ts +144 -0
- package/src/commands/umount.ts +2 -3
- package/src/commands/uname.ts +130 -0
- package/src/commands/unexpand.ts +305 -0
- package/src/commands/uptime.ts +73 -0
- package/src/index.ts +73 -0
- package/tsconfig.json +4 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
3
|
+
import { TerminalEvents } from '@ecmaos/types'
|
|
4
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
5
|
+
import { writelnStderr } from '../shared/helpers.js'
|
|
6
|
+
|
|
7
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
8
|
+
const usage = `Usage: fmt [OPTION]... [FILE]...
|
|
9
|
+
Reformat paragraph text.
|
|
10
|
+
|
|
11
|
+
-w, --width=WIDTH maximum line width (default: 75)
|
|
12
|
+
-s, --split-only split long lines, but do not join short lines
|
|
13
|
+
-u, --uniform-spacing use uniform spacing (one space between words)
|
|
14
|
+
--help display this help and exit`
|
|
15
|
+
writelnStderr(process, terminal, usage)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeWhitespace(text: string): string {
|
|
19
|
+
return text.replace(/\s+/g, ' ').trim()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function formatText(lines: string[], width: number, splitOnly: boolean, uniformSpacing: boolean): string[] {
|
|
23
|
+
const result: string[] = []
|
|
24
|
+
let currentParagraph: string[] = []
|
|
25
|
+
|
|
26
|
+
for (const line of lines) {
|
|
27
|
+
const trimmed = line.trim()
|
|
28
|
+
|
|
29
|
+
if (trimmed === '') {
|
|
30
|
+
if (currentParagraph.length > 0) {
|
|
31
|
+
const formatted = formatParagraph(currentParagraph, width, splitOnly, uniformSpacing)
|
|
32
|
+
result.push(...formatted)
|
|
33
|
+
currentParagraph = []
|
|
34
|
+
}
|
|
35
|
+
result.push('')
|
|
36
|
+
} else {
|
|
37
|
+
currentParagraph.push(trimmed)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (currentParagraph.length > 0) {
|
|
42
|
+
const formatted = formatParagraph(currentParagraph, width, splitOnly, uniformSpacing)
|
|
43
|
+
result.push(...formatted)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return result
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function formatParagraph(paragraph: string[], width: number, splitOnly: boolean, uniformSpacing: boolean): string[] {
|
|
50
|
+
if (paragraph.length === 0) return []
|
|
51
|
+
|
|
52
|
+
let text = paragraph.join(' ')
|
|
53
|
+
if (uniformSpacing) {
|
|
54
|
+
text = normalizeWhitespace(text)
|
|
55
|
+
} else {
|
|
56
|
+
text = text.replace(/\s+/g, ' ')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (splitOnly) {
|
|
60
|
+
return splitLongLines(text, width)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const words = text.split(/\s+/).filter(w => w.length > 0)
|
|
64
|
+
if (words.length === 0) return []
|
|
65
|
+
|
|
66
|
+
const result: string[] = []
|
|
67
|
+
let currentLine = ''
|
|
68
|
+
|
|
69
|
+
for (const word of words) {
|
|
70
|
+
const testLine = currentLine ? `${currentLine} ${word}` : word
|
|
71
|
+
|
|
72
|
+
if (testLine.length <= width) {
|
|
73
|
+
currentLine = testLine
|
|
74
|
+
} else {
|
|
75
|
+
if (currentLine) {
|
|
76
|
+
result.push(currentLine)
|
|
77
|
+
}
|
|
78
|
+
currentLine = word
|
|
79
|
+
|
|
80
|
+
if (currentLine.length > width) {
|
|
81
|
+
const split = splitLongLines(currentLine, width)
|
|
82
|
+
if (split.length > 0) {
|
|
83
|
+
result.push(...split.slice(0, -1))
|
|
84
|
+
currentLine = split[split.length - 1] || word
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (currentLine) {
|
|
91
|
+
result.push(currentLine)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return result
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function splitLongLines(text: string, width: number): string[] {
|
|
98
|
+
if (text.length <= width) return [text]
|
|
99
|
+
|
|
100
|
+
const result: string[] = []
|
|
101
|
+
let remaining = text
|
|
102
|
+
|
|
103
|
+
while (remaining.length > width) {
|
|
104
|
+
let breakPoint = width
|
|
105
|
+
|
|
106
|
+
const spaceIndex = remaining.lastIndexOf(' ', width)
|
|
107
|
+
if (spaceIndex > 0) {
|
|
108
|
+
breakPoint = spaceIndex
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
result.push(remaining.slice(0, breakPoint).trim())
|
|
112
|
+
remaining = remaining.slice(breakPoint).trim()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (remaining.length > 0) {
|
|
116
|
+
result.push(remaining)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return result
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
123
|
+
return new TerminalCommand({
|
|
124
|
+
command: 'fmt',
|
|
125
|
+
description: 'Reformat paragraph text',
|
|
126
|
+
kernel,
|
|
127
|
+
shell,
|
|
128
|
+
terminal,
|
|
129
|
+
run: async (pid: number, argv: string[]) => {
|
|
130
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
131
|
+
|
|
132
|
+
if (!process) return 1
|
|
133
|
+
|
|
134
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
135
|
+
printUsage(process, terminal)
|
|
136
|
+
return 0
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let width = 75
|
|
140
|
+
let splitOnly = false
|
|
141
|
+
let uniformSpacing = false
|
|
142
|
+
const files: string[] = []
|
|
143
|
+
|
|
144
|
+
for (let i = 0; i < argv.length; i++) {
|
|
145
|
+
const arg = argv[i]
|
|
146
|
+
if (!arg) continue
|
|
147
|
+
|
|
148
|
+
if (arg === '--help' || arg === '-h') {
|
|
149
|
+
printUsage(process, terminal)
|
|
150
|
+
return 0
|
|
151
|
+
} else if (arg === '-w' || arg === '--width') {
|
|
152
|
+
if (i + 1 < argv.length) {
|
|
153
|
+
const widthStr = argv[++i]
|
|
154
|
+
if (widthStr !== undefined) {
|
|
155
|
+
const parsed = parseInt(widthStr, 10)
|
|
156
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
157
|
+
width = parsed
|
|
158
|
+
} else {
|
|
159
|
+
await writelnStderr(process, terminal, `fmt: invalid width: ${widthStr}`)
|
|
160
|
+
return 1
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} else if (arg.startsWith('--width=')) {
|
|
165
|
+
const widthStr = arg.slice(8)
|
|
166
|
+
const parsed = parseInt(widthStr, 10)
|
|
167
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
168
|
+
width = parsed
|
|
169
|
+
} else {
|
|
170
|
+
await writelnStderr(process, terminal, `fmt: invalid width: ${widthStr}`)
|
|
171
|
+
return 1
|
|
172
|
+
}
|
|
173
|
+
} else if (arg.startsWith('-w')) {
|
|
174
|
+
const widthStr = arg.slice(2)
|
|
175
|
+
if (widthStr) {
|
|
176
|
+
const parsed = parseInt(widthStr, 10)
|
|
177
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
178
|
+
width = parsed
|
|
179
|
+
} else {
|
|
180
|
+
await writelnStderr(process, terminal, `fmt: invalid width: ${widthStr}`)
|
|
181
|
+
return 1
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
} else if (arg === '-s' || arg === '--split-only') {
|
|
185
|
+
splitOnly = true
|
|
186
|
+
} else if (arg === '-u' || arg === '--uniform-spacing') {
|
|
187
|
+
uniformSpacing = true
|
|
188
|
+
} else if (arg.startsWith('-')) {
|
|
189
|
+
const flags = arg.slice(1).split('')
|
|
190
|
+
if (flags.includes('s')) splitOnly = true
|
|
191
|
+
if (flags.includes('u')) uniformSpacing = true
|
|
192
|
+
const invalidFlags = flags.filter(f => !['s', 'u'].includes(f))
|
|
193
|
+
if (invalidFlags.length > 0) {
|
|
194
|
+
await writelnStderr(process, terminal, `fmt: invalid option -- '${invalidFlags[0]}'`)
|
|
195
|
+
await writelnStderr(process, terminal, "Try 'fmt --help' for more information.")
|
|
196
|
+
return 1
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
files.push(arg)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const writer = process.stdout.getWriter()
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
let lines: string[] = []
|
|
207
|
+
|
|
208
|
+
if (files.length === 0) {
|
|
209
|
+
if (!process.stdin) {
|
|
210
|
+
return 0
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const reader = process.stdin.getReader()
|
|
214
|
+
const decoder = new TextDecoder()
|
|
215
|
+
let buffer = ''
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
while (true) {
|
|
219
|
+
const { done, value } = await reader.read()
|
|
220
|
+
if (done) break
|
|
221
|
+
if (value) {
|
|
222
|
+
buffer += decoder.decode(value, { stream: true })
|
|
223
|
+
const newLines = buffer.split('\n')
|
|
224
|
+
buffer = newLines.pop() || ''
|
|
225
|
+
lines.push(...newLines)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (buffer) {
|
|
229
|
+
lines.push(buffer)
|
|
230
|
+
}
|
|
231
|
+
} finally {
|
|
232
|
+
reader.releaseLock()
|
|
233
|
+
}
|
|
234
|
+
} else {
|
|
235
|
+
for (const file of files) {
|
|
236
|
+
const fullPath = path.resolve(shell.cwd, file)
|
|
237
|
+
|
|
238
|
+
let interrupted = false
|
|
239
|
+
const interruptHandler = () => { interrupted = true }
|
|
240
|
+
kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
if (fullPath.startsWith('/dev')) {
|
|
244
|
+
await writelnStderr(process, terminal, `fmt: ${file}: cannot process device files`)
|
|
245
|
+
continue
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const handle = await shell.context.fs.promises.open(fullPath, 'r')
|
|
249
|
+
const stat = await shell.context.fs.promises.stat(fullPath)
|
|
250
|
+
|
|
251
|
+
const decoder = new TextDecoder()
|
|
252
|
+
let content = ''
|
|
253
|
+
let bytesRead = 0
|
|
254
|
+
const chunkSize = 1024
|
|
255
|
+
|
|
256
|
+
while (bytesRead < stat.size) {
|
|
257
|
+
if (interrupted) break
|
|
258
|
+
const data = new Uint8Array(chunkSize)
|
|
259
|
+
const readSize = Math.min(chunkSize, stat.size - bytesRead)
|
|
260
|
+
await handle.read(data, 0, readSize, bytesRead)
|
|
261
|
+
const chunk = data.subarray(0, readSize)
|
|
262
|
+
content += decoder.decode(chunk, { stream: true })
|
|
263
|
+
bytesRead += readSize
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const fileLines = content.split('\n')
|
|
267
|
+
if (fileLines[fileLines.length - 1] === '') {
|
|
268
|
+
fileLines.pop()
|
|
269
|
+
}
|
|
270
|
+
lines.push(...fileLines)
|
|
271
|
+
} catch (error) {
|
|
272
|
+
await writelnStderr(process, terminal, `fmt: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
273
|
+
} finally {
|
|
274
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const formatted = formatText(lines, width, splitOnly, uniformSpacing)
|
|
280
|
+
for (const line of formatted) {
|
|
281
|
+
await writer.write(new TextEncoder().encode(line + '\n'))
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return 0
|
|
285
|
+
} catch (error) {
|
|
286
|
+
await writelnStderr(process, terminal, `fmt: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
287
|
+
return 1
|
|
288
|
+
} finally {
|
|
289
|
+
writer.releaseLock()
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
3
|
+
import { TerminalEvents } from '@ecmaos/types'
|
|
4
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
5
|
+
import { writelnStderr } from '../shared/helpers.js'
|
|
6
|
+
|
|
7
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
8
|
+
const usage = `Usage: fold [OPTION]... [FILE]...
|
|
9
|
+
Wrap each input line to fit in specified width.
|
|
10
|
+
|
|
11
|
+
-w, --width=WIDTH use WIDTH columns instead of 80
|
|
12
|
+
-s, --spaces break at spaces when possible
|
|
13
|
+
-b, --bytes count bytes instead of columns
|
|
14
|
+
--help display this help and exit`
|
|
15
|
+
writelnStderr(process, terminal, usage)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function wrapLine(line: string, width: number, breakAtSpaces: boolean, countBytes: boolean): string[] {
|
|
19
|
+
if (!line) return ['']
|
|
20
|
+
|
|
21
|
+
const result: string[] = []
|
|
22
|
+
|
|
23
|
+
if (countBytes) {
|
|
24
|
+
const encoder = new TextEncoder()
|
|
25
|
+
let current = ''
|
|
26
|
+
let currentBytes = 0
|
|
27
|
+
|
|
28
|
+
for (let i = 0; i < line.length; i++) {
|
|
29
|
+
const char = line[i]
|
|
30
|
+
const charBytes = encoder.encode(char).length
|
|
31
|
+
|
|
32
|
+
if (currentBytes + charBytes > width && current.length > 0) {
|
|
33
|
+
if (breakAtSpaces) {
|
|
34
|
+
const lastSpace = current.lastIndexOf(' ')
|
|
35
|
+
if (lastSpace > 0) {
|
|
36
|
+
result.push(current.slice(0, lastSpace))
|
|
37
|
+
current = current.slice(lastSpace + 1) + char
|
|
38
|
+
currentBytes = encoder.encode(current).length
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
result.push(current)
|
|
43
|
+
current = char || ''
|
|
44
|
+
currentBytes = charBytes
|
|
45
|
+
} else {
|
|
46
|
+
current += char
|
|
47
|
+
currentBytes += charBytes
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (current.length > 0) {
|
|
52
|
+
result.push(current)
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
let current = ''
|
|
56
|
+
|
|
57
|
+
for (let i = 0; i < line.length; i++) {
|
|
58
|
+
const char = line[i]
|
|
59
|
+
|
|
60
|
+
if (current.length >= width && current.length > 0) {
|
|
61
|
+
if (breakAtSpaces) {
|
|
62
|
+
const lastSpace = current.lastIndexOf(' ')
|
|
63
|
+
if (lastSpace > 0) {
|
|
64
|
+
result.push(current.slice(0, lastSpace))
|
|
65
|
+
current = current.slice(lastSpace + 1) + char
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
result.push(current)
|
|
70
|
+
current = char || ''
|
|
71
|
+
} else {
|
|
72
|
+
current += char
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (current.length > 0) {
|
|
77
|
+
result.push(current)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return result.length > 0 ? result : ['']
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
85
|
+
return new TerminalCommand({
|
|
86
|
+
command: 'fold',
|
|
87
|
+
description: 'Wrap each input line to fit in specified width',
|
|
88
|
+
kernel,
|
|
89
|
+
shell,
|
|
90
|
+
terminal,
|
|
91
|
+
run: async (pid: number, argv: string[]) => {
|
|
92
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
93
|
+
|
|
94
|
+
if (!process) return 1
|
|
95
|
+
|
|
96
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
97
|
+
printUsage(process, terminal)
|
|
98
|
+
return 0
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let width = 80
|
|
102
|
+
let breakAtSpaces = false
|
|
103
|
+
let countBytes = false
|
|
104
|
+
const files: string[] = []
|
|
105
|
+
|
|
106
|
+
for (let i = 0; i < argv.length; i++) {
|
|
107
|
+
const arg = argv[i]
|
|
108
|
+
if (!arg) continue
|
|
109
|
+
|
|
110
|
+
if (arg === '--help' || arg === '-h') {
|
|
111
|
+
printUsage(process, terminal)
|
|
112
|
+
return 0
|
|
113
|
+
} else if (arg === '-w' || arg === '--width') {
|
|
114
|
+
if (i + 1 < argv.length) {
|
|
115
|
+
const widthStr = argv[++i]
|
|
116
|
+
if (widthStr !== undefined) {
|
|
117
|
+
const parsed = parseInt(widthStr, 10)
|
|
118
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
119
|
+
width = parsed
|
|
120
|
+
} else {
|
|
121
|
+
await writelnStderr(process, terminal, `fold: invalid width: ${widthStr}`)
|
|
122
|
+
return 1
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} else if (arg.startsWith('--width=')) {
|
|
127
|
+
const widthStr = arg.slice(8)
|
|
128
|
+
const parsed = parseInt(widthStr, 10)
|
|
129
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
130
|
+
width = parsed
|
|
131
|
+
} else {
|
|
132
|
+
await writelnStderr(process, terminal, `fold: invalid width: ${widthStr}`)
|
|
133
|
+
return 1
|
|
134
|
+
}
|
|
135
|
+
} else if (arg.startsWith('-w')) {
|
|
136
|
+
const widthStr = arg.slice(2)
|
|
137
|
+
if (widthStr) {
|
|
138
|
+
const parsed = parseInt(widthStr, 10)
|
|
139
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
140
|
+
width = parsed
|
|
141
|
+
} else {
|
|
142
|
+
await writelnStderr(process, terminal, `fold: invalid width: ${widthStr}`)
|
|
143
|
+
return 1
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} else if (arg === '-s' || arg === '--spaces') {
|
|
147
|
+
breakAtSpaces = true
|
|
148
|
+
} else if (arg === '-b' || arg === '--bytes') {
|
|
149
|
+
countBytes = true
|
|
150
|
+
} else if (arg.startsWith('-')) {
|
|
151
|
+
const flags = arg.slice(1).split('')
|
|
152
|
+
if (flags.includes('s')) breakAtSpaces = true
|
|
153
|
+
if (flags.includes('b')) countBytes = true
|
|
154
|
+
const invalidFlags = flags.filter(f => !['s', 'b'].includes(f))
|
|
155
|
+
if (invalidFlags.length > 0) {
|
|
156
|
+
await writelnStderr(process, terminal, `fold: invalid option -- '${invalidFlags[0]}'`)
|
|
157
|
+
await writelnStderr(process, terminal, "Try 'fold --help' for more information.")
|
|
158
|
+
return 1
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
files.push(arg)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const writer = process.stdout.getWriter()
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
let lines: string[] = []
|
|
169
|
+
|
|
170
|
+
if (files.length === 0) {
|
|
171
|
+
if (!process.stdin) {
|
|
172
|
+
return 0
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const reader = process.stdin.getReader()
|
|
176
|
+
const decoder = new TextDecoder()
|
|
177
|
+
let buffer = ''
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
while (true) {
|
|
181
|
+
const { done, value } = await reader.read()
|
|
182
|
+
if (done) break
|
|
183
|
+
if (value) {
|
|
184
|
+
buffer += decoder.decode(value, { stream: true })
|
|
185
|
+
const newLines = buffer.split('\n')
|
|
186
|
+
buffer = newLines.pop() || ''
|
|
187
|
+
lines.push(...newLines)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (buffer) {
|
|
191
|
+
lines.push(buffer)
|
|
192
|
+
}
|
|
193
|
+
} finally {
|
|
194
|
+
reader.releaseLock()
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
for (const file of files) {
|
|
198
|
+
const fullPath = path.resolve(shell.cwd, file)
|
|
199
|
+
|
|
200
|
+
let interrupted = false
|
|
201
|
+
const interruptHandler = () => { interrupted = true }
|
|
202
|
+
kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
if (fullPath.startsWith('/dev')) {
|
|
206
|
+
await writelnStderr(process, terminal, `fold: ${file}: cannot process device files`)
|
|
207
|
+
continue
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const handle = await shell.context.fs.promises.open(fullPath, 'r')
|
|
211
|
+
const stat = await shell.context.fs.promises.stat(fullPath)
|
|
212
|
+
|
|
213
|
+
const decoder = new TextDecoder()
|
|
214
|
+
let content = ''
|
|
215
|
+
let bytesRead = 0
|
|
216
|
+
const chunkSize = 1024
|
|
217
|
+
|
|
218
|
+
while (bytesRead < stat.size) {
|
|
219
|
+
if (interrupted) break
|
|
220
|
+
const data = new Uint8Array(chunkSize)
|
|
221
|
+
const readSize = Math.min(chunkSize, stat.size - bytesRead)
|
|
222
|
+
await handle.read(data, 0, readSize, bytesRead)
|
|
223
|
+
const chunk = data.subarray(0, readSize)
|
|
224
|
+
content += decoder.decode(chunk, { stream: true })
|
|
225
|
+
bytesRead += readSize
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const fileLines = content.split('\n')
|
|
229
|
+
if (fileLines[fileLines.length - 1] === '') {
|
|
230
|
+
fileLines.pop()
|
|
231
|
+
}
|
|
232
|
+
lines.push(...fileLines)
|
|
233
|
+
} catch (error) {
|
|
234
|
+
await writelnStderr(process, terminal, `fold: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
235
|
+
} finally {
|
|
236
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
for (const line of lines) {
|
|
242
|
+
const wrapped = wrapLine(line, width, breakAtSpaces, countBytes)
|
|
243
|
+
for (const wrappedLine of wrapped) {
|
|
244
|
+
await writer.write(new TextEncoder().encode(wrappedLine + '\n'))
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return 0
|
|
249
|
+
} catch (error) {
|
|
250
|
+
await writelnStderr(process, terminal, `fold: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
251
|
+
return 1
|
|
252
|
+
} finally {
|
|
253
|
+
writer.releaseLock()
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Kernel, Process, Shell, Terminal, User } from '@ecmaos/types'
|
|
2
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
3
|
+
import { writelnStderr, writelnStdout } from '../shared/helpers.js'
|
|
4
|
+
|
|
5
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
6
|
+
const usage = `Usage: groups [USERNAME]...
|
|
7
|
+
Print the groups a user belongs to.
|
|
8
|
+
|
|
9
|
+
--help display this help and exit`
|
|
10
|
+
writelnStderr(process, terminal, usage)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
14
|
+
return new TerminalCommand({
|
|
15
|
+
command: 'groups',
|
|
16
|
+
description: 'Print the groups a user belongs to',
|
|
17
|
+
kernel,
|
|
18
|
+
shell,
|
|
19
|
+
terminal,
|
|
20
|
+
run: async (pid: number, argv: string[]) => {
|
|
21
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
22
|
+
|
|
23
|
+
if (!process) return 1
|
|
24
|
+
|
|
25
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
26
|
+
printUsage(process, terminal)
|
|
27
|
+
return 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const usernames: string[] = []
|
|
31
|
+
|
|
32
|
+
for (const arg of argv) {
|
|
33
|
+
if (!arg) continue
|
|
34
|
+
|
|
35
|
+
if (arg === '--help' || arg === '-h') {
|
|
36
|
+
printUsage(process, terminal)
|
|
37
|
+
return 0
|
|
38
|
+
} else if (!arg.startsWith('-')) {
|
|
39
|
+
usernames.push(arg)
|
|
40
|
+
} else {
|
|
41
|
+
await writelnStderr(process, terminal, `groups: invalid option -- '${arg.slice(1)}'`)
|
|
42
|
+
await writelnStderr(process, terminal, "Try 'groups --help' for more information.")
|
|
43
|
+
return 1
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const targets = usernames.length > 0 ? usernames : [shell.username]
|
|
48
|
+
|
|
49
|
+
for (const username of targets) {
|
|
50
|
+
const user = Array.from(kernel.users.all.values()).find(
|
|
51
|
+
(u): u is User => (u as User).username === username
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if (!user) {
|
|
55
|
+
await writelnStderr(process, terminal, `groups: '${username}': no such user`)
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const groups = shell.credentials.groups || []
|
|
60
|
+
const groupNames = groups.map(gid => {
|
|
61
|
+
const groupUser = kernel.users.get(gid)
|
|
62
|
+
return groupUser?.username || gid.toString()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const output = `${username} : ${groupNames.join(' ')}`
|
|
66
|
+
await writelnStdout(process, terminal, output)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return 0
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
2
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
3
|
+
import { writelnStderr, writelnStdout } from '../shared/helpers.js'
|
|
4
|
+
|
|
5
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
6
|
+
const usage = `Usage: hostname [OPTION]
|
|
7
|
+
Print the system hostname.
|
|
8
|
+
|
|
9
|
+
-f, --fqdn print the FQDN (Fully Qualified Domain Name)
|
|
10
|
+
-s, --short print the short hostname
|
|
11
|
+
--help display this help and exit`
|
|
12
|
+
writelnStderr(process, terminal, usage)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
16
|
+
return new TerminalCommand({
|
|
17
|
+
command: 'hostname',
|
|
18
|
+
description: 'Print the system hostname',
|
|
19
|
+
kernel,
|
|
20
|
+
shell,
|
|
21
|
+
terminal,
|
|
22
|
+
run: async (pid: number, argv: string[]) => {
|
|
23
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
24
|
+
|
|
25
|
+
if (!process) return 1
|
|
26
|
+
|
|
27
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
28
|
+
printUsage(process, terminal)
|
|
29
|
+
return 0
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let showFqdn = false
|
|
33
|
+
let showShort = false
|
|
34
|
+
const args: string[] = []
|
|
35
|
+
|
|
36
|
+
for (const arg of argv) {
|
|
37
|
+
if (!arg) continue
|
|
38
|
+
|
|
39
|
+
if (arg === '--help' || arg === '-h') {
|
|
40
|
+
printUsage(process, terminal)
|
|
41
|
+
return 0
|
|
42
|
+
} else if (arg === '-f' || arg === '--fqdn') {
|
|
43
|
+
showFqdn = true
|
|
44
|
+
} else if (arg === '-s' || arg === '--short') {
|
|
45
|
+
showShort = true
|
|
46
|
+
} else if (arg.startsWith('-')) {
|
|
47
|
+
const flags = arg.slice(1).split('')
|
|
48
|
+
if (flags.includes('f')) showFqdn = true
|
|
49
|
+
if (flags.includes('s')) showShort = true
|
|
50
|
+
const invalidFlags = flags.filter(f => !['f', 's'].includes(f))
|
|
51
|
+
if (invalidFlags.length > 0) {
|
|
52
|
+
await writelnStderr(process, terminal, `hostname: invalid option -- '${invalidFlags[0]}'`)
|
|
53
|
+
await writelnStderr(process, terminal, "Try 'hostname --help' for more information.")
|
|
54
|
+
return 1
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
args.push(arg)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (args.length > 0) {
|
|
62
|
+
await writelnStderr(process, terminal, 'hostname: invalid argument')
|
|
63
|
+
await writelnStderr(process, terminal, "Try 'hostname --help' for more information.")
|
|
64
|
+
return 1
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const hostname = typeof window !== 'undefined' ? window.location.hostname : 'localhost'
|
|
68
|
+
|
|
69
|
+
if (showFqdn) {
|
|
70
|
+
await writelnStdout(process, terminal, hostname)
|
|
71
|
+
} else if (showShort) {
|
|
72
|
+
const shortName = hostname.split('.')[0]
|
|
73
|
+
await writelnStdout(process, terminal, shortName ?? hostname)
|
|
74
|
+
} else {
|
|
75
|
+
await writelnStdout(process, terminal, hostname)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return 0
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
}
|