@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.
Files changed (199) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +48 -0
  3. package/dist/commands/awk.d.ts +4 -0
  4. package/dist/commands/awk.d.ts.map +1 -0
  5. package/dist/commands/awk.js +324 -0
  6. package/dist/commands/awk.js.map +1 -0
  7. package/dist/commands/chgrp.d.ts +4 -0
  8. package/dist/commands/chgrp.d.ts.map +1 -0
  9. package/dist/commands/chgrp.js +187 -0
  10. package/dist/commands/chgrp.js.map +1 -0
  11. package/dist/commands/chmod.d.ts.map +1 -1
  12. package/dist/commands/chmod.js +139 -2
  13. package/dist/commands/chmod.js.map +1 -1
  14. package/dist/commands/chown.d.ts +4 -0
  15. package/dist/commands/chown.d.ts.map +1 -0
  16. package/dist/commands/chown.js +257 -0
  17. package/dist/commands/chown.js.map +1 -0
  18. package/dist/commands/cksum.d.ts +4 -0
  19. package/dist/commands/cksum.d.ts.map +1 -0
  20. package/dist/commands/cksum.js +124 -0
  21. package/dist/commands/cksum.js.map +1 -0
  22. package/dist/commands/cmp.d.ts +4 -0
  23. package/dist/commands/cmp.d.ts.map +1 -0
  24. package/dist/commands/cmp.js +120 -0
  25. package/dist/commands/cmp.js.map +1 -0
  26. package/dist/commands/column.d.ts +4 -0
  27. package/dist/commands/column.d.ts.map +1 -0
  28. package/dist/commands/column.js +274 -0
  29. package/dist/commands/column.js.map +1 -0
  30. package/dist/commands/cp.d.ts.map +1 -1
  31. package/dist/commands/cp.js +81 -4
  32. package/dist/commands/cp.js.map +1 -1
  33. package/dist/commands/cron.d.ts.map +1 -1
  34. package/dist/commands/cron.js +116 -23
  35. package/dist/commands/cron.js.map +1 -1
  36. package/dist/commands/curl.d.ts +4 -0
  37. package/dist/commands/curl.d.ts.map +1 -0
  38. package/dist/commands/curl.js +238 -0
  39. package/dist/commands/curl.js.map +1 -0
  40. package/dist/commands/du.d.ts +4 -0
  41. package/dist/commands/du.d.ts.map +1 -0
  42. package/dist/commands/du.js +168 -0
  43. package/dist/commands/du.js.map +1 -0
  44. package/dist/commands/echo.d.ts.map +1 -1
  45. package/dist/commands/echo.js +125 -2
  46. package/dist/commands/echo.js.map +1 -1
  47. package/dist/commands/env.d.ts +4 -0
  48. package/dist/commands/env.d.ts.map +1 -0
  49. package/dist/commands/env.js +129 -0
  50. package/dist/commands/env.js.map +1 -0
  51. package/dist/commands/expand.d.ts +4 -0
  52. package/dist/commands/expand.d.ts.map +1 -0
  53. package/dist/commands/expand.js +197 -0
  54. package/dist/commands/expand.js.map +1 -0
  55. package/dist/commands/factor.d.ts +4 -0
  56. package/dist/commands/factor.d.ts.map +1 -0
  57. package/dist/commands/factor.js +141 -0
  58. package/dist/commands/factor.js.map +1 -0
  59. package/dist/commands/fmt.d.ts +4 -0
  60. package/dist/commands/fmt.d.ts.map +1 -0
  61. package/dist/commands/fmt.js +278 -0
  62. package/dist/commands/fmt.js.map +1 -0
  63. package/dist/commands/fold.d.ts +4 -0
  64. package/dist/commands/fold.d.ts.map +1 -0
  65. package/dist/commands/fold.js +253 -0
  66. package/dist/commands/fold.js.map +1 -0
  67. package/dist/commands/groups.d.ts +4 -0
  68. package/dist/commands/groups.d.ts.map +1 -0
  69. package/dist/commands/groups.js +61 -0
  70. package/dist/commands/groups.js.map +1 -0
  71. package/dist/commands/head.d.ts.map +1 -1
  72. package/dist/commands/head.js +184 -77
  73. package/dist/commands/head.js.map +1 -1
  74. package/dist/commands/hostname.d.ts +4 -0
  75. package/dist/commands/hostname.d.ts.map +1 -0
  76. package/dist/commands/hostname.js +80 -0
  77. package/dist/commands/hostname.js.map +1 -0
  78. package/dist/commands/less.d.ts.map +1 -1
  79. package/dist/commands/less.js +1 -0
  80. package/dist/commands/less.js.map +1 -1
  81. package/dist/commands/man.d.ts.map +1 -1
  82. package/dist/commands/man.js +3 -1
  83. package/dist/commands/man.js.map +1 -1
  84. package/dist/commands/mount.d.ts +4 -0
  85. package/dist/commands/mount.d.ts.map +1 -0
  86. package/dist/commands/mount.js +1136 -0
  87. package/dist/commands/mount.js.map +1 -0
  88. package/dist/commands/od.d.ts +4 -0
  89. package/dist/commands/od.d.ts.map +1 -0
  90. package/dist/commands/od.js +342 -0
  91. package/dist/commands/od.js.map +1 -0
  92. package/dist/commands/pr.d.ts +4 -0
  93. package/dist/commands/pr.d.ts.map +1 -0
  94. package/dist/commands/pr.js +298 -0
  95. package/dist/commands/pr.js.map +1 -0
  96. package/dist/commands/printf.d.ts +4 -0
  97. package/dist/commands/printf.d.ts.map +1 -0
  98. package/dist/commands/printf.js +271 -0
  99. package/dist/commands/printf.js.map +1 -0
  100. package/dist/commands/readlink.d.ts +4 -0
  101. package/dist/commands/readlink.d.ts.map +1 -0
  102. package/dist/commands/readlink.js +104 -0
  103. package/dist/commands/readlink.js.map +1 -0
  104. package/dist/commands/realpath.d.ts +4 -0
  105. package/dist/commands/realpath.d.ts.map +1 -0
  106. package/dist/commands/realpath.js +111 -0
  107. package/dist/commands/realpath.js.map +1 -0
  108. package/dist/commands/rev.d.ts +4 -0
  109. package/dist/commands/rev.d.ts.map +1 -0
  110. package/dist/commands/rev.js +134 -0
  111. package/dist/commands/rev.js.map +1 -0
  112. package/dist/commands/shuf.d.ts +4 -0
  113. package/dist/commands/shuf.d.ts.map +1 -0
  114. package/dist/commands/shuf.js +221 -0
  115. package/dist/commands/shuf.js.map +1 -0
  116. package/dist/commands/sleep.d.ts +4 -0
  117. package/dist/commands/sleep.d.ts.map +1 -0
  118. package/dist/commands/sleep.js +102 -0
  119. package/dist/commands/sleep.js.map +1 -0
  120. package/dist/commands/strings.d.ts +4 -0
  121. package/dist/commands/strings.d.ts.map +1 -0
  122. package/dist/commands/strings.js +170 -0
  123. package/dist/commands/strings.js.map +1 -0
  124. package/dist/commands/tac.d.ts +4 -0
  125. package/dist/commands/tac.d.ts.map +1 -0
  126. package/dist/commands/tac.js +130 -0
  127. package/dist/commands/tac.js.map +1 -0
  128. package/dist/commands/time.d.ts +4 -0
  129. package/dist/commands/time.d.ts.map +1 -0
  130. package/dist/commands/time.js +126 -0
  131. package/dist/commands/time.js.map +1 -0
  132. package/dist/commands/umount.d.ts +4 -0
  133. package/dist/commands/umount.d.ts.map +1 -0
  134. package/dist/commands/umount.js +103 -0
  135. package/dist/commands/umount.js.map +1 -0
  136. package/dist/commands/uname.d.ts +4 -0
  137. package/dist/commands/uname.d.ts.map +1 -0
  138. package/dist/commands/uname.js +149 -0
  139. package/dist/commands/uname.js.map +1 -0
  140. package/dist/commands/unexpand.d.ts +4 -0
  141. package/dist/commands/unexpand.d.ts.map +1 -0
  142. package/dist/commands/unexpand.js +286 -0
  143. package/dist/commands/unexpand.js.map +1 -0
  144. package/dist/commands/uptime.d.ts +4 -0
  145. package/dist/commands/uptime.d.ts.map +1 -0
  146. package/dist/commands/uptime.js +62 -0
  147. package/dist/commands/uptime.js.map +1 -0
  148. package/dist/commands/view.d.ts +1 -0
  149. package/dist/commands/view.d.ts.map +1 -1
  150. package/dist/commands/view.js +408 -66
  151. package/dist/commands/view.js.map +1 -1
  152. package/dist/commands/yes.d.ts +4 -0
  153. package/dist/commands/yes.d.ts.map +1 -0
  154. package/dist/commands/yes.js +58 -0
  155. package/dist/commands/yes.js.map +1 -0
  156. package/dist/index.d.ts +24 -0
  157. package/dist/index.d.ts.map +1 -1
  158. package/dist/index.js +82 -0
  159. package/dist/index.js.map +1 -1
  160. package/package.json +12 -3
  161. package/src/commands/awk.ts +340 -0
  162. package/src/commands/chmod.ts +141 -2
  163. package/src/commands/chown.ts +321 -0
  164. package/src/commands/cksum.ts +133 -0
  165. package/src/commands/cmp.ts +126 -0
  166. package/src/commands/column.ts +273 -0
  167. package/src/commands/cp.ts +93 -4
  168. package/src/commands/cron.ts +115 -23
  169. package/src/commands/curl.ts +231 -0
  170. package/src/commands/echo.ts +122 -2
  171. package/src/commands/env.ts +143 -0
  172. package/src/commands/expand.ts +207 -0
  173. package/src/commands/factor.ts +151 -0
  174. package/src/commands/fmt.ts +293 -0
  175. package/src/commands/fold.ts +257 -0
  176. package/src/commands/groups.ts +72 -0
  177. package/src/commands/head.ts +176 -77
  178. package/src/commands/hostname.ts +81 -0
  179. package/src/commands/less.ts +1 -0
  180. package/src/commands/man.ts +4 -1
  181. package/src/commands/mount.ts +1302 -0
  182. package/src/commands/od.ts +327 -0
  183. package/src/commands/pr.ts +291 -0
  184. package/src/commands/printf.ts +271 -0
  185. package/src/commands/readlink.ts +102 -0
  186. package/src/commands/realpath.ts +126 -0
  187. package/src/commands/rev.ts +143 -0
  188. package/src/commands/shuf.ts +218 -0
  189. package/src/commands/sleep.ts +109 -0
  190. package/src/commands/strings.ts +176 -0
  191. package/src/commands/tac.ts +138 -0
  192. package/src/commands/time.ts +144 -0
  193. package/src/commands/umount.ts +116 -0
  194. package/src/commands/uname.ts +130 -0
  195. package/src/commands/unexpand.ts +305 -0
  196. package/src/commands/uptime.ts +73 -0
  197. package/src/commands/view.ts +463 -73
  198. package/src/index.ts +82 -0
  199. 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
+ }
@@ -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
- const invalidFlag = flags.find(f => f !== 'n')
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
- const text = textParts.join(' ')
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
+ }