@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,273 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import columnify from 'columnify'
|
|
3
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
4
|
+
import { TerminalEvents } from '@ecmaos/types'
|
|
5
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
6
|
+
import { writelnStderr } from '../shared/helpers.js'
|
|
7
|
+
|
|
8
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
9
|
+
const usage = `Usage: column [OPTION]... [FILE]...
|
|
10
|
+
Format input into columns.
|
|
11
|
+
|
|
12
|
+
-t, --table create a table
|
|
13
|
+
-s, --separator=SEP specify column separator (default: whitespace)
|
|
14
|
+
-c, --columns=COLS specify number of columns
|
|
15
|
+
--help display this help and exit`
|
|
16
|
+
writelnStderr(process, terminal, usage)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
20
|
+
return new TerminalCommand({
|
|
21
|
+
command: 'column',
|
|
22
|
+
description: 'Format input into columns',
|
|
23
|
+
kernel,
|
|
24
|
+
shell,
|
|
25
|
+
terminal,
|
|
26
|
+
run: async (pid: number, argv: string[]) => {
|
|
27
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
28
|
+
|
|
29
|
+
if (!process) return 1
|
|
30
|
+
|
|
31
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
32
|
+
printUsage(process, terminal)
|
|
33
|
+
return 0
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let table = false
|
|
37
|
+
let separator: string | undefined
|
|
38
|
+
let columns: number | undefined
|
|
39
|
+
const files: string[] = []
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < argv.length; i++) {
|
|
42
|
+
const arg = argv[i]
|
|
43
|
+
if (!arg) continue
|
|
44
|
+
|
|
45
|
+
if (arg === '--help' || arg === '-h') {
|
|
46
|
+
printUsage(process, terminal)
|
|
47
|
+
return 0
|
|
48
|
+
} else if (arg === '-t' || arg === '--table') {
|
|
49
|
+
table = true
|
|
50
|
+
} else if (arg === '-s' || arg === '--separator') {
|
|
51
|
+
if (i + 1 < argv.length) {
|
|
52
|
+
separator = argv[++i]
|
|
53
|
+
}
|
|
54
|
+
} else if (arg.startsWith('--separator=')) {
|
|
55
|
+
separator = arg.slice(12)
|
|
56
|
+
} else if (arg.startsWith('-s')) {
|
|
57
|
+
separator = arg.slice(2) || undefined
|
|
58
|
+
} else if (arg === '-c' || arg === '--columns') {
|
|
59
|
+
if (i + 1 < argv.length) {
|
|
60
|
+
const colsStr = argv[++i]
|
|
61
|
+
if (colsStr !== undefined) {
|
|
62
|
+
const parsed = parseInt(colsStr, 10)
|
|
63
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
64
|
+
columns = parsed
|
|
65
|
+
} else {
|
|
66
|
+
await writelnStderr(process, terminal, `column: invalid column count: ${colsStr}`)
|
|
67
|
+
return 1
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} else if (arg.startsWith('--columns=')) {
|
|
72
|
+
const colsStr = arg.slice(11)
|
|
73
|
+
const parsed = parseInt(colsStr, 10)
|
|
74
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
75
|
+
columns = parsed
|
|
76
|
+
} else {
|
|
77
|
+
await writelnStderr(process, terminal, `column: invalid column count: ${colsStr}`)
|
|
78
|
+
return 1
|
|
79
|
+
}
|
|
80
|
+
} else if (arg.startsWith('-c')) {
|
|
81
|
+
const colsStr = arg.slice(2)
|
|
82
|
+
if (colsStr) {
|
|
83
|
+
const parsed = parseInt(colsStr, 10)
|
|
84
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
85
|
+
columns = parsed
|
|
86
|
+
} else {
|
|
87
|
+
await writelnStderr(process, terminal, `column: invalid column count: ${colsStr}`)
|
|
88
|
+
return 1
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} else if (!arg.startsWith('-')) {
|
|
92
|
+
files.push(arg)
|
|
93
|
+
} else {
|
|
94
|
+
await writelnStderr(process, terminal, `column: invalid option -- '${arg.slice(1)}'`)
|
|
95
|
+
await writelnStderr(process, terminal, "Try 'column --help' for more information.")
|
|
96
|
+
return 1
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const writer = process.stdout.getWriter()
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
let lines: string[] = []
|
|
104
|
+
|
|
105
|
+
if (files.length === 0) {
|
|
106
|
+
if (!process.stdin) {
|
|
107
|
+
return 0
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const reader = process.stdin.getReader()
|
|
111
|
+
const decoder = new TextDecoder()
|
|
112
|
+
let buffer = ''
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
while (true) {
|
|
116
|
+
const { done, value } = await reader.read()
|
|
117
|
+
if (done) break
|
|
118
|
+
if (value) {
|
|
119
|
+
buffer += decoder.decode(value, { stream: true })
|
|
120
|
+
const newLines = buffer.split('\n')
|
|
121
|
+
buffer = newLines.pop() || ''
|
|
122
|
+
lines.push(...newLines)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (buffer) {
|
|
126
|
+
lines.push(buffer)
|
|
127
|
+
}
|
|
128
|
+
} finally {
|
|
129
|
+
reader.releaseLock()
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
for (const file of files) {
|
|
133
|
+
const fullPath = path.resolve(shell.cwd, file)
|
|
134
|
+
|
|
135
|
+
let interrupted = false
|
|
136
|
+
const interruptHandler = () => { interrupted = true }
|
|
137
|
+
kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
if (fullPath.startsWith('/dev')) {
|
|
141
|
+
await writelnStderr(process, terminal, `column: ${file}: cannot process device files`)
|
|
142
|
+
continue
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const handle = await shell.context.fs.promises.open(fullPath, 'r')
|
|
146
|
+
const stat = await shell.context.fs.promises.stat(fullPath)
|
|
147
|
+
|
|
148
|
+
const decoder = new TextDecoder()
|
|
149
|
+
let content = ''
|
|
150
|
+
let bytesRead = 0
|
|
151
|
+
const chunkSize = 1024
|
|
152
|
+
|
|
153
|
+
while (bytesRead < stat.size) {
|
|
154
|
+
if (interrupted) break
|
|
155
|
+
const data = new Uint8Array(chunkSize)
|
|
156
|
+
const readSize = Math.min(chunkSize, stat.size - bytesRead)
|
|
157
|
+
await handle.read(data, 0, readSize, bytesRead)
|
|
158
|
+
const chunk = data.subarray(0, readSize)
|
|
159
|
+
content += decoder.decode(chunk, { stream: true })
|
|
160
|
+
bytesRead += readSize
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const fileLines = content.split('\n')
|
|
164
|
+
if (fileLines[fileLines.length - 1] === '') {
|
|
165
|
+
fileLines.pop()
|
|
166
|
+
}
|
|
167
|
+
lines.push(...fileLines)
|
|
168
|
+
} catch (error) {
|
|
169
|
+
await writelnStderr(process, terminal, `column: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
170
|
+
} finally {
|
|
171
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (table && separator) {
|
|
177
|
+
const data: Array<Record<string, string>> = []
|
|
178
|
+
const headers = new Set<string>()
|
|
179
|
+
|
|
180
|
+
for (const line of lines) {
|
|
181
|
+
if (!line.trim()) continue
|
|
182
|
+
const parts = line.split(separator)
|
|
183
|
+
const row: Record<string, string> = {}
|
|
184
|
+
parts.forEach((part, idx) => {
|
|
185
|
+
const header = `col${idx + 1}`
|
|
186
|
+
headers.add(header)
|
|
187
|
+
row[header] = part.trim()
|
|
188
|
+
})
|
|
189
|
+
data.push(row)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (data.length > 0) {
|
|
193
|
+
const tableOutput = columnify(data, {
|
|
194
|
+
columns: Array.from(headers),
|
|
195
|
+
columnSplitter: ' ',
|
|
196
|
+
showHeaders: true
|
|
197
|
+
})
|
|
198
|
+
await writer.write(new TextEncoder().encode(tableOutput))
|
|
199
|
+
}
|
|
200
|
+
} else if (table) {
|
|
201
|
+
const data: Array<Record<string, string>> = []
|
|
202
|
+
const headers = new Set<string>()
|
|
203
|
+
|
|
204
|
+
for (const line of lines) {
|
|
205
|
+
if (!line.trim()) continue
|
|
206
|
+
const parts = line.trim().split(/\s+/)
|
|
207
|
+
const row: Record<string, string> = {}
|
|
208
|
+
parts.forEach((part, idx) => {
|
|
209
|
+
const header = `col${idx + 1}`
|
|
210
|
+
headers.add(header)
|
|
211
|
+
row[header] = part
|
|
212
|
+
})
|
|
213
|
+
data.push(row)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (data.length > 0) {
|
|
217
|
+
const tableOutput = columnify(data, {
|
|
218
|
+
columns: Array.from(headers),
|
|
219
|
+
columnSplitter: ' ',
|
|
220
|
+
showHeaders: true
|
|
221
|
+
})
|
|
222
|
+
await writer.write(new TextEncoder().encode(tableOutput))
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
const words: string[] = []
|
|
226
|
+
for (const line of lines) {
|
|
227
|
+
if (separator) {
|
|
228
|
+
words.push(...line.split(separator).map(w => w.trim()).filter(w => w))
|
|
229
|
+
} else {
|
|
230
|
+
words.push(...line.trim().split(/\s+/).filter(w => w))
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (columns && columns > 0) {
|
|
235
|
+
const rows: string[][] = []
|
|
236
|
+
for (let i = 0; i < words.length; i += columns) {
|
|
237
|
+
rows.push(words.slice(i, i + columns))
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const data: Array<Record<string, string>> = []
|
|
241
|
+
for (const row of rows) {
|
|
242
|
+
const rowObj: Record<string, string> = {}
|
|
243
|
+
for (let i = 0; i < columns; i++) {
|
|
244
|
+
rowObj[`col${i + 1}`] = row[i] || ''
|
|
245
|
+
}
|
|
246
|
+
data.push(rowObj)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (data.length > 0) {
|
|
250
|
+
const tableOutput = columnify(data, {
|
|
251
|
+
columns: Array.from({ length: columns }, (_, i) => `col${i + 1}`),
|
|
252
|
+
columnSplitter: ' ',
|
|
253
|
+
showHeaders: false
|
|
254
|
+
})
|
|
255
|
+
await writer.write(new TextEncoder().encode(tableOutput))
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
for (const word of words) {
|
|
259
|
+
await writer.write(new TextEncoder().encode(word + '\n'))
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return 0
|
|
265
|
+
} catch (error) {
|
|
266
|
+
await writelnStderr(process, terminal, `column: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
267
|
+
return 1
|
|
268
|
+
} finally {
|
|
269
|
+
writer.releaseLock()
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
}
|
package/src/commands/cp.ts
CHANGED
|
@@ -1,16 +1,52 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
3
3
|
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
4
|
-
import { writelnStderr } from '../shared/helpers.js'
|
|
4
|
+
import { writelnStderr, writelnStdout } from '../shared/helpers.js'
|
|
5
5
|
|
|
6
6
|
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
7
7
|
const usage = `Usage: cp [OPTION]... SOURCE... DEST
|
|
8
8
|
Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
-r, -R, --recursive copy directories recursively
|
|
11
|
+
-v, --verbose explain what is being done
|
|
12
|
+
--help display this help and exit`
|
|
11
13
|
writelnStderr(process, terminal, usage)
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
async function copyRecursive(
|
|
17
|
+
fs: typeof import('@zenfs/core').fs.promises,
|
|
18
|
+
sourcePath: string,
|
|
19
|
+
destPath: string,
|
|
20
|
+
verbose: boolean,
|
|
21
|
+
process: Process | undefined,
|
|
22
|
+
terminal: Terminal,
|
|
23
|
+
relativeSource: string,
|
|
24
|
+
relativeDest: string
|
|
25
|
+
): Promise<void> {
|
|
26
|
+
const stats = await fs.stat(sourcePath)
|
|
27
|
+
|
|
28
|
+
if (stats.isDirectory()) {
|
|
29
|
+
try {
|
|
30
|
+
await fs.mkdir(destPath)
|
|
31
|
+
if (verbose) await writelnStdout(process, terminal, `'${relativeSource}' -> '${relativeDest}'`)
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const entries = await fs.readdir(sourcePath)
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
const srcEntry = path.join(sourcePath, entry)
|
|
39
|
+
const destEntry = path.join(destPath, entry)
|
|
40
|
+
const srcRelative = path.join(relativeSource, entry)
|
|
41
|
+
const destRelative = path.join(relativeDest, entry)
|
|
42
|
+
await copyRecursive(fs, srcEntry, destEntry, verbose, process, terminal, srcRelative, destRelative)
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
await fs.copyFile(sourcePath, destPath)
|
|
46
|
+
if (verbose) await writelnStdout(process, terminal, `'${relativeSource}' -> '${relativeDest}'`)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
14
50
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
15
51
|
return new TerminalCommand({
|
|
16
52
|
command: 'cp',
|
|
@@ -26,9 +62,31 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
26
62
|
return 0
|
|
27
63
|
}
|
|
28
64
|
|
|
65
|
+
let recursive = false
|
|
66
|
+
let verbose = false
|
|
29
67
|
const args: string[] = []
|
|
68
|
+
|
|
30
69
|
for (const arg of argv) {
|
|
31
|
-
if (arg
|
|
70
|
+
if (arg.startsWith('-') && arg !== '--') {
|
|
71
|
+
if (arg === '--recursive') {
|
|
72
|
+
recursive = true
|
|
73
|
+
} else if (arg === '--verbose') {
|
|
74
|
+
verbose = true
|
|
75
|
+
} else if (arg.length > 1) {
|
|
76
|
+
for (let i = 1; i < arg.length; i++) {
|
|
77
|
+
const flag = arg[i]
|
|
78
|
+
if (flag === 'r' || flag === 'R') {
|
|
79
|
+
recursive = true
|
|
80
|
+
} else if (flag === 'v') {
|
|
81
|
+
verbose = true
|
|
82
|
+
} else {
|
|
83
|
+
await writelnStderr(process, terminal, `cp: invalid option -- '${flag}'`)
|
|
84
|
+
await writelnStderr(process, terminal, "Try 'cp --help' for more information.")
|
|
85
|
+
return 1
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} else if (arg && !arg.startsWith('-')) {
|
|
32
90
|
args.push(arg)
|
|
33
91
|
}
|
|
34
92
|
}
|
|
@@ -67,7 +125,38 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
67
125
|
: path.resolve(shell.cwd, destination)
|
|
68
126
|
|
|
69
127
|
try {
|
|
70
|
-
await shell.context.fs.promises.
|
|
128
|
+
const sourceStats = await shell.context.fs.promises.stat(sourcePath)
|
|
129
|
+
|
|
130
|
+
if (sourceStats.isDirectory()) {
|
|
131
|
+
if (!recursive) {
|
|
132
|
+
await writelnStderr(process, terminal, `cp: -r not specified; omitting directory '${source}'`)
|
|
133
|
+
hasError = true
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
const relativeSource = source
|
|
137
|
+
const relativeDest = isDestinationDir
|
|
138
|
+
? path.join(destination, path.basename(source))
|
|
139
|
+
: destination
|
|
140
|
+
await copyRecursive(
|
|
141
|
+
shell.context.fs.promises,
|
|
142
|
+
sourcePath,
|
|
143
|
+
finalDestination,
|
|
144
|
+
verbose,
|
|
145
|
+
process,
|
|
146
|
+
terminal,
|
|
147
|
+
relativeSource,
|
|
148
|
+
relativeDest
|
|
149
|
+
)
|
|
150
|
+
} else {
|
|
151
|
+
await shell.context.fs.promises.copyFile(sourcePath, finalDestination)
|
|
152
|
+
if (verbose) {
|
|
153
|
+
const relativeSource = source
|
|
154
|
+
const relativeDest = isDestinationDir
|
|
155
|
+
? path.join(destination, path.basename(source))
|
|
156
|
+
: destination
|
|
157
|
+
await writelnStdout(process, terminal, `'${relativeSource}' -> '${relativeDest}'`)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
71
160
|
} catch (error) {
|
|
72
161
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
73
162
|
await writelnStderr(process, terminal, `cp: ${source}: ${errorMessage}`)
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import type { Kernel, Process, Shell, Terminal } 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: curl [OPTION]... URL
|
|
9
|
+
Transfer data from or to a server.
|
|
10
|
+
|
|
11
|
+
-o, --output=FILE write output to FILE instead of stdout
|
|
12
|
+
-O, --remote-name write output to a file named like the remote file
|
|
13
|
+
-X, --request=METHOD HTTP method to use (default: GET)
|
|
14
|
+
-d, --data=DATA send data in POST request
|
|
15
|
+
-H, --header=HEADER add custom HTTP header (format: "Name: Value")
|
|
16
|
+
-s, --silent silent mode (don't show progress)
|
|
17
|
+
-v, --verbose verbose mode (show request/response headers)
|
|
18
|
+
--help display this help and exit`
|
|
19
|
+
writelnStderr(process, terminal, usage)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
23
|
+
return new TerminalCommand({
|
|
24
|
+
command: 'curl',
|
|
25
|
+
description: 'Transfer data from or to a server',
|
|
26
|
+
kernel,
|
|
27
|
+
shell,
|
|
28
|
+
terminal,
|
|
29
|
+
run: async (pid: number, argv: string[]) => {
|
|
30
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
31
|
+
|
|
32
|
+
if (!process) return 1
|
|
33
|
+
|
|
34
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
35
|
+
printUsage(process, terminal)
|
|
36
|
+
return 0
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let url: string | undefined
|
|
40
|
+
let outputFile: string | undefined
|
|
41
|
+
let remoteName = false
|
|
42
|
+
let method = 'GET'
|
|
43
|
+
let body: string | undefined
|
|
44
|
+
const headers: Record<string, string> = {}
|
|
45
|
+
let silent = false
|
|
46
|
+
let verbose = false
|
|
47
|
+
|
|
48
|
+
for (let i = 0; i < argv.length; i++) {
|
|
49
|
+
const arg = argv[i]
|
|
50
|
+
if (!arg) continue
|
|
51
|
+
|
|
52
|
+
if (arg === '--help' || arg === '-h') {
|
|
53
|
+
printUsage(process, terminal)
|
|
54
|
+
return 0
|
|
55
|
+
} else if (arg === '-o' || arg === '--output') {
|
|
56
|
+
if (i + 1 < argv.length) {
|
|
57
|
+
outputFile = argv[++i]
|
|
58
|
+
}
|
|
59
|
+
} else if (arg.startsWith('--output=')) {
|
|
60
|
+
outputFile = arg.slice(9)
|
|
61
|
+
} else if (arg.startsWith('-o')) {
|
|
62
|
+
outputFile = arg.slice(2) || undefined
|
|
63
|
+
} else if (arg === '-O' || arg === '--remote-name') {
|
|
64
|
+
remoteName = true
|
|
65
|
+
} else if (arg === '-X' || arg === '--request') {
|
|
66
|
+
if (i + 1 < argv.length) {
|
|
67
|
+
method = (argv[++i] || 'GET').toUpperCase()
|
|
68
|
+
}
|
|
69
|
+
} else if (arg.startsWith('--request=')) {
|
|
70
|
+
method = arg.slice(10).toUpperCase()
|
|
71
|
+
} else if (arg.startsWith('-X')) {
|
|
72
|
+
method = (arg.slice(2) || 'GET').toUpperCase()
|
|
73
|
+
} else if (arg === '-d' || arg === '--data') {
|
|
74
|
+
if (i + 1 < argv.length) {
|
|
75
|
+
body = argv[++i]
|
|
76
|
+
}
|
|
77
|
+
} else if (arg.startsWith('--data=')) {
|
|
78
|
+
body = arg.slice(7)
|
|
79
|
+
} else if (arg.startsWith('-d')) {
|
|
80
|
+
body = arg.slice(2) || undefined
|
|
81
|
+
} else if (arg === '-H' || arg === '--header') {
|
|
82
|
+
if (i + 1 < argv.length) {
|
|
83
|
+
const headerArg = argv[++i]
|
|
84
|
+
if (headerArg) {
|
|
85
|
+
const [name, ...valueParts] = headerArg.split(':')
|
|
86
|
+
if (name && valueParts.length > 0) {
|
|
87
|
+
headers[name.trim()] = valueParts.join(':').trim()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} else if (arg.startsWith('--header=')) {
|
|
92
|
+
const headerValue = arg.slice(9)
|
|
93
|
+
const [name, ...valueParts] = headerValue.split(':')
|
|
94
|
+
if (name && valueParts.length > 0) {
|
|
95
|
+
headers[name.trim()] = valueParts.join(':').trim()
|
|
96
|
+
}
|
|
97
|
+
} else if (arg.startsWith('-H')) {
|
|
98
|
+
const headerValue = arg.slice(2)
|
|
99
|
+
if (headerValue) {
|
|
100
|
+
const [name, ...valueParts] = headerValue.split(':')
|
|
101
|
+
if (name && valueParts.length > 0) {
|
|
102
|
+
headers[name.trim()] = valueParts.join(':').trim()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} else if (arg === '-s' || arg === '--silent') {
|
|
106
|
+
silent = true
|
|
107
|
+
} else if (arg === '-v' || arg === '--verbose') {
|
|
108
|
+
verbose = true
|
|
109
|
+
} else if (arg.startsWith('-')) {
|
|
110
|
+
const flags = arg.slice(1).split('')
|
|
111
|
+
if (flags.includes('s')) silent = true
|
|
112
|
+
if (flags.includes('v')) verbose = true
|
|
113
|
+
if (flags.includes('O')) remoteName = true
|
|
114
|
+
const invalidFlags = flags.filter(f => !['s', 'v', 'O'].includes(f))
|
|
115
|
+
if (invalidFlags.length > 0) {
|
|
116
|
+
await writelnStderr(process, terminal, `curl: invalid option -- '${invalidFlags[0]}'`)
|
|
117
|
+
await writelnStderr(process, terminal, "Try 'curl --help' for more information.")
|
|
118
|
+
return 1
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
if (!url) {
|
|
122
|
+
url = arg
|
|
123
|
+
} else {
|
|
124
|
+
await writelnStderr(process, terminal, `curl: unexpected argument: ${arg}`)
|
|
125
|
+
return 1
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!url) {
|
|
131
|
+
await writelnStderr(process, terminal, 'curl: URL is required')
|
|
132
|
+
await writelnStderr(process, terminal, "Try 'curl --help' for more information.")
|
|
133
|
+
return 1
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (remoteName && !outputFile) {
|
|
137
|
+
const urlObj = new URL(url)
|
|
138
|
+
const pathname = urlObj.pathname
|
|
139
|
+
outputFile = path.basename(pathname) || 'index.html'
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
if (verbose && !silent) {
|
|
144
|
+
await writelnStderr(process, terminal, `* Connecting to ${url}`)
|
|
145
|
+
await writelnStderr(process, terminal, `> ${method} ${url} HTTP/1.1`)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const fetchOptions: RequestInit = { method }
|
|
149
|
+
if (body) {
|
|
150
|
+
fetchOptions.body = body
|
|
151
|
+
if (!headers['Content-Type']) {
|
|
152
|
+
headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (Object.keys(headers).length > 0) {
|
|
156
|
+
fetchOptions.headers = headers
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (verbose && !silent) {
|
|
160
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
161
|
+
await writelnStderr(process, terminal, `> ${name}: ${value}`)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const response = await globalThis.fetch(url, fetchOptions)
|
|
166
|
+
|
|
167
|
+
if (verbose && !silent) {
|
|
168
|
+
await writelnStderr(process, terminal, `< HTTP/${response.status} ${response.status} ${response.statusText}`)
|
|
169
|
+
for (const [name, value] of response.headers.entries()) {
|
|
170
|
+
await writelnStderr(process, terminal, `< ${name}: ${value}`)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!response.ok && !silent) {
|
|
175
|
+
await writelnStderr(process, terminal, chalk.red(`curl: HTTP error! status: ${response.status}`))
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const reader = response.body?.getReader()
|
|
179
|
+
if (!reader) {
|
|
180
|
+
if (!silent) {
|
|
181
|
+
await writelnStderr(process, terminal, chalk.red('curl: No response body'))
|
|
182
|
+
}
|
|
183
|
+
return response.ok ? 0 : 1
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
let writer: WritableStreamDefaultWriter<Uint8Array> | { write: (chunk: Uint8Array) => Promise<void>, releaseLock: () => Promise<void> } | undefined
|
|
187
|
+
|
|
188
|
+
if (outputFile) {
|
|
189
|
+
const fullPath = path.resolve(shell.cwd, outputFile)
|
|
190
|
+
const fileHandle = await shell.context.fs.promises.open(fullPath, 'w')
|
|
191
|
+
writer = {
|
|
192
|
+
write: async (chunk: Uint8Array) => {
|
|
193
|
+
await fileHandle.write(chunk)
|
|
194
|
+
},
|
|
195
|
+
releaseLock: async () => {
|
|
196
|
+
await fileHandle.close()
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
if (!process.stdout) {
|
|
201
|
+
await writelnStderr(process, terminal, chalk.red('curl: No stdout available'))
|
|
202
|
+
return 1
|
|
203
|
+
}
|
|
204
|
+
writer = process.stdout.getWriter()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
while (true) {
|
|
209
|
+
const { done, value } = await reader.read()
|
|
210
|
+
if (done) break
|
|
211
|
+
if (value && value.length > 0) {
|
|
212
|
+
await writer.write(value)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} finally {
|
|
216
|
+
reader.releaseLock()
|
|
217
|
+
if (writer && 'releaseLock' in writer) {
|
|
218
|
+
await writer.releaseLock()
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return response.ok ? 0 : 1
|
|
223
|
+
} catch (error) {
|
|
224
|
+
if (!silent) {
|
|
225
|
+
await writelnStderr(process, terminal, chalk.red(`curl: ${error instanceof Error ? error.message : 'Unknown error'}`))
|
|
226
|
+
}
|
|
227
|
+
return 1
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
}
|