@ecmaos/coreutils 0.3.1 → 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 +48 -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/cron.d.ts.map +1 -1
- package/dist/commands/cron.js +116 -23
- package/dist/commands/cron.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/env.d.ts +4 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +129 -0
- package/dist/commands/env.js.map +1 -0
- 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/head.d.ts.map +1 -1
- package/dist/commands/head.js +184 -77
- package/dist/commands/head.js.map +1 -1
- 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/less.d.ts.map +1 -1
- package/dist/commands/less.js +1 -0
- package/dist/commands/less.js.map +1 -1
- package/dist/commands/man.d.ts.map +1 -1
- package/dist/commands/man.js +3 -1
- package/dist/commands/man.js.map +1 -1
- package/dist/commands/mount.d.ts +4 -0
- package/dist/commands/mount.d.ts.map +1 -0
- package/dist/commands/mount.js +1136 -0
- package/dist/commands/mount.js.map +1 -0
- 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 +4 -0
- package/dist/commands/umount.d.ts.map +1 -0
- package/dist/commands/umount.js +103 -0
- package/dist/commands/umount.js.map +1 -0
- 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/view.d.ts +1 -0
- package/dist/commands/view.d.ts.map +1 -1
- package/dist/commands/view.js +408 -66
- package/dist/commands/view.js.map +1 -1
- 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 +24 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -1
- package/package.json +12 -3
- 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/cron.ts +115 -23
- package/src/commands/curl.ts +231 -0
- package/src/commands/echo.ts +122 -2
- package/src/commands/env.ts +143 -0
- 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/head.ts +176 -77
- package/src/commands/hostname.ts +81 -0
- package/src/commands/less.ts +1 -0
- package/src/commands/man.ts +4 -1
- package/src/commands/mount.ts +1302 -0
- 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 +116 -0
- package/src/commands/uname.ts +130 -0
- package/src/commands/unexpand.ts +305 -0
- package/src/commands/uptime.ts +73 -0
- package/src/commands/view.ts +463 -73
- package/src/index.ts +82 -0
- package/tsconfig.json +4 -0
|
@@ -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
|
+
}
|
package/src/commands/echo.ts
CHANGED
|
@@ -6,11 +6,122 @@ function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
|
6
6
|
const usage = `Usage: echo [OPTION]... [STRING]...
|
|
7
7
|
Echo the STRING(s) to standard output.
|
|
8
8
|
|
|
9
|
+
-e enable interpretation of backslash escapes
|
|
9
10
|
-n do not output the trailing newline
|
|
10
11
|
--help display this help and exit`
|
|
11
12
|
writelnStderr(process, terminal, usage)
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
function interpretEscapes(text: string): string {
|
|
16
|
+
let result = ''
|
|
17
|
+
let i = 0
|
|
18
|
+
|
|
19
|
+
while (i < text.length) {
|
|
20
|
+
if (text[i] === '\\' && i + 1 < text.length) {
|
|
21
|
+
const next = text[i + 1]
|
|
22
|
+
switch (next) {
|
|
23
|
+
case '\\':
|
|
24
|
+
result += '\\'
|
|
25
|
+
i += 2
|
|
26
|
+
break
|
|
27
|
+
case 'a':
|
|
28
|
+
result += '\x07'
|
|
29
|
+
i += 2
|
|
30
|
+
break
|
|
31
|
+
case 'b':
|
|
32
|
+
result += '\b'
|
|
33
|
+
i += 2
|
|
34
|
+
break
|
|
35
|
+
case 'c':
|
|
36
|
+
return result
|
|
37
|
+
case 'e':
|
|
38
|
+
case 'E':
|
|
39
|
+
result += '\x1b'
|
|
40
|
+
i += 2
|
|
41
|
+
break
|
|
42
|
+
case 'f':
|
|
43
|
+
result += '\f'
|
|
44
|
+
i += 2
|
|
45
|
+
break
|
|
46
|
+
case 'n':
|
|
47
|
+
result += '\n'
|
|
48
|
+
i += 2
|
|
49
|
+
break
|
|
50
|
+
case 'r':
|
|
51
|
+
result += '\r'
|
|
52
|
+
i += 2
|
|
53
|
+
break
|
|
54
|
+
case 't':
|
|
55
|
+
result += '\t'
|
|
56
|
+
i += 2
|
|
57
|
+
break
|
|
58
|
+
case 'v':
|
|
59
|
+
result += '\v'
|
|
60
|
+
i += 2
|
|
61
|
+
break
|
|
62
|
+
case '0':
|
|
63
|
+
case '1':
|
|
64
|
+
case '2':
|
|
65
|
+
case '3':
|
|
66
|
+
case '4':
|
|
67
|
+
case '5':
|
|
68
|
+
case '6':
|
|
69
|
+
case '7': {
|
|
70
|
+
let octal = ''
|
|
71
|
+
let j = i + 1
|
|
72
|
+
while (j < text.length && j < i + 4) {
|
|
73
|
+
const char = text[j]
|
|
74
|
+
if (char && /[0-7]/.test(char)) {
|
|
75
|
+
octal += char
|
|
76
|
+
j++
|
|
77
|
+
} else {
|
|
78
|
+
break
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (octal) {
|
|
82
|
+
result += String.fromCharCode(parseInt(octal, 8))
|
|
83
|
+
i = j
|
|
84
|
+
} else {
|
|
85
|
+
result += text[i]
|
|
86
|
+
i++
|
|
87
|
+
}
|
|
88
|
+
break
|
|
89
|
+
}
|
|
90
|
+
case 'x': {
|
|
91
|
+
let hex = ''
|
|
92
|
+
let j = i + 2
|
|
93
|
+
while (j < text.length && j < i + 4) {
|
|
94
|
+
const char = text[j]
|
|
95
|
+
if (char && /[0-9a-fA-F]/.test(char)) {
|
|
96
|
+
hex += char
|
|
97
|
+
j++
|
|
98
|
+
} else {
|
|
99
|
+
break
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (hex) {
|
|
103
|
+
result += String.fromCharCode(parseInt(hex, 16))
|
|
104
|
+
i = j
|
|
105
|
+
} else {
|
|
106
|
+
result += text[i]
|
|
107
|
+
i++
|
|
108
|
+
}
|
|
109
|
+
break
|
|
110
|
+
}
|
|
111
|
+
default:
|
|
112
|
+
result += text[i]
|
|
113
|
+
i++
|
|
114
|
+
break
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
result += text[i]
|
|
118
|
+
i++
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return result
|
|
123
|
+
}
|
|
124
|
+
|
|
14
125
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
15
126
|
return new TerminalCommand({
|
|
16
127
|
command: 'echo',
|
|
@@ -27,6 +138,7 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
27
138
|
}
|
|
28
139
|
|
|
29
140
|
let noNewline = false
|
|
141
|
+
let enableEscapes = false
|
|
30
142
|
const textParts: string[] = []
|
|
31
143
|
let i = 0
|
|
32
144
|
|
|
@@ -41,12 +153,17 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
41
153
|
return 0
|
|
42
154
|
} else if (arg === '-n') {
|
|
43
155
|
noNewline = true
|
|
156
|
+
} else if (arg === '-e') {
|
|
157
|
+
enableEscapes = true
|
|
44
158
|
} else if (arg.startsWith('-') && arg.length > 1 && arg !== '--') {
|
|
45
159
|
const flags = arg.slice(1).split('')
|
|
46
160
|
if (flags.includes('n')) {
|
|
47
161
|
noNewline = true
|
|
48
162
|
}
|
|
49
|
-
|
|
163
|
+
if (flags.includes('e')) {
|
|
164
|
+
enableEscapes = true
|
|
165
|
+
}
|
|
166
|
+
const invalidFlag = flags.find(f => f !== 'n' && f !== 'e')
|
|
50
167
|
if (invalidFlag) {
|
|
51
168
|
await writelnStdout(process, terminal, `echo: invalid option -- '${invalidFlag}'`)
|
|
52
169
|
return 1
|
|
@@ -57,7 +174,10 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
57
174
|
i++
|
|
58
175
|
}
|
|
59
176
|
|
|
60
|
-
|
|
177
|
+
let text = textParts.join(' ')
|
|
178
|
+
if (enableEscapes) {
|
|
179
|
+
text = interpretEscapes(text)
|
|
180
|
+
}
|
|
61
181
|
const output = noNewline ? text : text + '\n'
|
|
62
182
|
|
|
63
183
|
if (process) {
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
3
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
4
|
+
import { writelnStderr, writeStdout } from '../shared/helpers.js'
|
|
5
|
+
|
|
6
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
7
|
+
const usage = `Usage: env [OPTION]... [NAME=VALUE]... [COMMAND [ARG]...]
|
|
8
|
+
Set each NAME to VALUE in the environment and run COMMAND.
|
|
9
|
+
|
|
10
|
+
-i, --ignore-environment start with an empty environment
|
|
11
|
+
-u, --unset=NAME remove variable from the environment
|
|
12
|
+
-0, --null end each output line with NUL, not newline
|
|
13
|
+
--help display this help and exit`
|
|
14
|
+
writelnStderr(process, terminal, usage)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
18
|
+
return new TerminalCommand({
|
|
19
|
+
command: 'env',
|
|
20
|
+
description: 'Run a program in a modified environment',
|
|
21
|
+
kernel,
|
|
22
|
+
shell,
|
|
23
|
+
terminal,
|
|
24
|
+
run: async (pid: number, argv: string[]) => {
|
|
25
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
26
|
+
|
|
27
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
28
|
+
printUsage(process, terminal)
|
|
29
|
+
return 0
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let ignoreEnvironment = false
|
|
33
|
+
const unsetVars: string[] = []
|
|
34
|
+
let nullTerminated = false
|
|
35
|
+
const envVars: Record<string, string> = {}
|
|
36
|
+
let commandStartIndex = -1
|
|
37
|
+
|
|
38
|
+
let i = 0
|
|
39
|
+
while (i < argv.length) {
|
|
40
|
+
const arg = argv[i]
|
|
41
|
+
if (!arg) {
|
|
42
|
+
i++
|
|
43
|
+
continue
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (arg === '-i' || arg === '--ignore-environment') {
|
|
47
|
+
ignoreEnvironment = true
|
|
48
|
+
} else if (arg === '-0' || arg === '--null') {
|
|
49
|
+
nullTerminated = true
|
|
50
|
+
} else if (arg === '-u' || arg.startsWith('--unset=')) {
|
|
51
|
+
let varName: string
|
|
52
|
+
if (arg.startsWith('--unset=')) {
|
|
53
|
+
varName = arg.slice(8)
|
|
54
|
+
} else {
|
|
55
|
+
i++
|
|
56
|
+
varName = argv[i] || ''
|
|
57
|
+
if (!varName) {
|
|
58
|
+
await writelnStderr(process, terminal, chalk.red('env: option requires an argument -- \'u\''))
|
|
59
|
+
return 1
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (varName) {
|
|
63
|
+
unsetVars.push(varName)
|
|
64
|
+
}
|
|
65
|
+
} else if (arg.includes('=')) {
|
|
66
|
+
const [name, ...valueParts] = arg.split('=')
|
|
67
|
+
if (name && valueParts.length > 0) {
|
|
68
|
+
envVars[name] = valueParts.join('=')
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
commandStartIndex = i
|
|
72
|
+
break
|
|
73
|
+
}
|
|
74
|
+
i++
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const baseEnv = ignoreEnvironment ? {} : Object.fromEntries(shell.env.entries())
|
|
78
|
+
|
|
79
|
+
for (const varName of unsetVars) {
|
|
80
|
+
delete baseEnv[varName]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const modifiedEnv = { ...baseEnv, ...envVars }
|
|
84
|
+
|
|
85
|
+
if (commandStartIndex === -1) {
|
|
86
|
+
const entries = Object.entries(modifiedEnv).sort(([a], [b]) => a.localeCompare(b))
|
|
87
|
+
const separator = nullTerminated ? '\0' : '\n'
|
|
88
|
+
|
|
89
|
+
for (const [key, value] of entries) {
|
|
90
|
+
const output = `${key}=${value}${separator}`
|
|
91
|
+
await writeStdout(process, terminal, output)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!nullTerminated && entries.length > 0) {
|
|
95
|
+
await writeStdout(process, terminal, '\n')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return 0
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const commandArgs = argv.slice(commandStartIndex)
|
|
102
|
+
const command = commandArgs[0]
|
|
103
|
+
if (!command) {
|
|
104
|
+
await writelnStderr(process, terminal, chalk.red('env: missing command'))
|
|
105
|
+
return 1
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const originalEnv = new Map(shell.env)
|
|
109
|
+
const originalProcessEnv = { ...globalThis.process.env }
|
|
110
|
+
|
|
111
|
+
for (const [key, value] of Object.entries(modifiedEnv)) {
|
|
112
|
+
shell.env.set(key, value)
|
|
113
|
+
globalThis.process.env[key] = value
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (const varName of unsetVars) {
|
|
117
|
+
shell.env.delete(varName)
|
|
118
|
+
delete globalThis.process.env[varName]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const commandLine = commandArgs.join(' ')
|
|
123
|
+
const exitCode = await shell.execute(commandLine)
|
|
124
|
+
return exitCode ?? 1
|
|
125
|
+
} finally {
|
|
126
|
+
shell.env.clear()
|
|
127
|
+
for (const [key, value] of originalEnv.entries()) {
|
|
128
|
+
shell.env.set(key, value)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
for (const key in globalThis.process.env) {
|
|
132
|
+
if (!(key in originalProcessEnv)) {
|
|
133
|
+
delete globalThis.process.env[key]
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
for (const [key, value] of Object.entries(originalProcessEnv)) {
|
|
138
|
+
globalThis.process.env[key] = value
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
}
|