@ecmaos/coreutils 0.3.1 → 0.4.0

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 (44) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +42 -0
  3. package/dist/commands/cron.d.ts.map +1 -1
  4. package/dist/commands/cron.js +116 -23
  5. package/dist/commands/cron.js.map +1 -1
  6. package/dist/commands/env.d.ts +4 -0
  7. package/dist/commands/env.d.ts.map +1 -0
  8. package/dist/commands/env.js +129 -0
  9. package/dist/commands/env.js.map +1 -0
  10. package/dist/commands/head.d.ts.map +1 -1
  11. package/dist/commands/head.js +184 -77
  12. package/dist/commands/head.js.map +1 -1
  13. package/dist/commands/less.d.ts.map +1 -1
  14. package/dist/commands/less.js +1 -0
  15. package/dist/commands/less.js.map +1 -1
  16. package/dist/commands/man.d.ts.map +1 -1
  17. package/dist/commands/man.js +3 -1
  18. package/dist/commands/man.js.map +1 -1
  19. package/dist/commands/mount.d.ts +4 -0
  20. package/dist/commands/mount.d.ts.map +1 -0
  21. package/dist/commands/mount.js +1041 -0
  22. package/dist/commands/mount.js.map +1 -0
  23. package/dist/commands/umount.d.ts +4 -0
  24. package/dist/commands/umount.d.ts.map +1 -0
  25. package/dist/commands/umount.js +104 -0
  26. package/dist/commands/umount.js.map +1 -0
  27. package/dist/commands/view.d.ts +1 -0
  28. package/dist/commands/view.d.ts.map +1 -1
  29. package/dist/commands/view.js +408 -66
  30. package/dist/commands/view.js.map +1 -1
  31. package/dist/index.d.ts +3 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +9 -0
  34. package/dist/index.js.map +1 -1
  35. package/package.json +10 -2
  36. package/src/commands/cron.ts +115 -23
  37. package/src/commands/env.ts +143 -0
  38. package/src/commands/head.ts +176 -77
  39. package/src/commands/less.ts +1 -0
  40. package/src/commands/man.ts +4 -1
  41. package/src/commands/mount.ts +1193 -0
  42. package/src/commands/umount.ts +117 -0
  43. package/src/commands/view.ts +463 -73
  44. package/src/index.ts +9 -0
@@ -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
+ }
@@ -9,6 +9,7 @@ function printUsage(process: Process | undefined, terminal: Terminal): void {
9
9
  Print the first 10 lines of each FILE to standard output.
10
10
 
11
11
  -n, -nNUMBER print the first NUMBER lines instead of 10
12
+ -c, -cNUMBER print the first NUMBER bytes instead of lines
12
13
  --help display this help and exit`
13
14
  writelnStderr(process, terminal, usage)
14
15
  }
@@ -31,6 +32,7 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
31
32
  }
32
33
 
33
34
  let numLines = 10
35
+ let numBytes: number | null = null
34
36
  const files: string[] = []
35
37
 
36
38
  for (let i = 0; i < argv.length; i++) {
@@ -52,6 +54,18 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
52
54
  const num = parseInt(arg.slice(2), 10)
53
55
  if (!isNaN(num)) numLines = num
54
56
  }
57
+ } else if (arg === '-c' || arg.startsWith('-c')) {
58
+ if (arg === '-c' && i + 1 < argv.length) {
59
+ i++
60
+ const nextArg = argv[i]
61
+ if (nextArg !== undefined) {
62
+ const num = parseInt(nextArg, 10)
63
+ if (!isNaN(num)) numBytes = num
64
+ }
65
+ } else if (arg.startsWith('-c') && arg.length > 2) {
66
+ const num = parseInt(arg.slice(2), 10)
67
+ if (!isNaN(num)) numBytes = num
68
+ }
55
69
  } else if (!arg.startsWith('-')) {
56
70
  files.push(arg)
57
71
  }
@@ -66,37 +80,83 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
66
80
  }
67
81
 
68
82
  const reader = process.stdin.getReader()
69
- const decoder = new TextDecoder()
70
- const lines: string[] = []
71
- let buffer = ''
72
83
 
73
84
  try {
74
- while (true) {
75
- let readResult
76
- try {
77
- readResult = await reader.read()
78
- } catch (error) {
79
- if (error instanceof Error) {
80
- throw error
85
+ if (numBytes !== null) {
86
+ let bytesRead = 0
87
+ const chunks: Uint8Array[] = []
88
+
89
+ while (bytesRead < numBytes) {
90
+ let readResult
91
+ try {
92
+ readResult = await reader.read()
93
+ } catch (error) {
94
+ if (error instanceof Error) {
95
+ throw error
96
+ }
97
+ break
81
98
  }
82
- break
99
+
100
+ const { done, value } = readResult
101
+ if (done) break
102
+ if (value) {
103
+ const remaining = numBytes - bytesRead
104
+ if (value.length <= remaining) {
105
+ chunks.push(value)
106
+ bytesRead += value.length
107
+ } else {
108
+ chunks.push(value.subarray(0, remaining))
109
+ bytesRead = numBytes
110
+ }
111
+ if (bytesRead >= numBytes) break
112
+ }
113
+ }
114
+
115
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0)
116
+ const result = new Uint8Array(totalLength)
117
+ let offset = 0
118
+ for (const chunk of chunks) {
119
+ result.set(chunk, offset)
120
+ offset += chunk.length
83
121
  }
122
+ await writer.write(result)
123
+ } else {
124
+ const decoder = new TextDecoder()
125
+ const lines: string[] = []
126
+ let buffer = ''
84
127
 
85
- const { done, value } = readResult
86
- if (done) {
87
- buffer += decoder.decode()
88
- break
128
+ while (true) {
129
+ let readResult
130
+ try {
131
+ readResult = await reader.read()
132
+ } catch (error) {
133
+ if (error instanceof Error) {
134
+ throw error
135
+ }
136
+ break
137
+ }
138
+
139
+ const { done, value } = readResult
140
+ if (done) {
141
+ buffer += decoder.decode()
142
+ break
143
+ }
144
+ if (value) {
145
+ buffer += decoder.decode(value, { stream: true })
146
+ const newLines = buffer.split('\n')
147
+ buffer = newLines.pop() || ''
148
+ lines.push(...newLines)
149
+ if (lines.length >= numLines) break
150
+ }
89
151
  }
90
- if (value) {
91
- buffer += decoder.decode(value, { stream: true })
92
- const newLines = buffer.split('\n')
93
- buffer = newLines.pop() || ''
94
- lines.push(...newLines)
95
- if (lines.length >= numLines) break
152
+ if (buffer && lines.length < numLines) {
153
+ lines.push(buffer)
154
+ }
155
+
156
+ const output = lines.slice(0, numLines).join('\n')
157
+ if (output) {
158
+ await writer.write(new TextEncoder().encode(output + '\n'))
96
159
  }
97
- }
98
- if (buffer && lines.length < numLines) {
99
- lines.push(buffer)
100
160
  }
101
161
  } finally {
102
162
  try {
@@ -105,11 +165,6 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
105
165
  }
106
166
  }
107
167
 
108
- const output = lines.slice(0, numLines).join('\n')
109
- if (output) {
110
- await writer.write(new TextEncoder().encode(output + '\n'))
111
- }
112
-
113
168
  return 0
114
169
  }
115
170
 
@@ -130,66 +185,110 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
130
185
  kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
131
186
 
132
187
  try {
133
- if (!fullPath.startsWith('/dev')) {
134
- const handle = await shell.context.fs.promises.open(fullPath, 'r')
135
- const stat = await shell.context.fs.promises.stat(fullPath)
136
-
137
- const decoder = new TextDecoder()
138
- const lines: string[] = []
139
- let buffer = ''
140
- let bytesRead = 0
141
- const chunkSize = 1024
142
-
143
- while (bytesRead < stat.size && lines.length < numLines) {
144
- if (interrupted) break
188
+ if (numBytes !== null) {
189
+ if (!fullPath.startsWith('/dev')) {
190
+ const handle = await shell.context.fs.promises.open(fullPath, 'r')
191
+ const stat = await shell.context.fs.promises.stat(fullPath)
192
+ const readSize = Math.min(numBytes, stat.size)
193
+ const data = new Uint8Array(readSize)
194
+ await handle.read(data, 0, readSize, 0)
195
+ await writer.write(data)
196
+ } else {
197
+ const device = await shell.context.fs.promises.open(fullPath)
198
+ const chunkSize = 1024
145
199
  const data = new Uint8Array(chunkSize)
146
- const readSize = Math.min(chunkSize, stat.size - bytesRead)
147
- await handle.read(data, 0, readSize, bytesRead)
148
- const chunk = data.subarray(0, readSize)
149
- buffer += decoder.decode(chunk, { stream: true })
150
- const newLines = buffer.split('\n')
151
- buffer = newLines.pop() || ''
152
- lines.push(...newLines)
153
- bytesRead += readSize
154
- if (lines.length >= numLines) break
155
- }
156
- if (buffer && lines.length < numLines) {
157
- lines.push(buffer)
158
- }
200
+ let totalBytesRead = 0
201
+ let bytesRead = 0
202
+ const chunks: Uint8Array[] = []
159
203
 
160
- const output = lines.slice(0, numLines).join('\n')
161
- if (output) {
162
- await writer.write(new TextEncoder().encode(output + '\n'))
204
+ do {
205
+ if (interrupted) break
206
+ const result = await device.read(data)
207
+ bytesRead = result.bytesRead
208
+ if (bytesRead > 0) {
209
+ const remaining = numBytes - totalBytesRead
210
+ if (bytesRead <= remaining) {
211
+ chunks.push(data.subarray(0, bytesRead))
212
+ totalBytesRead += bytesRead
213
+ } else {
214
+ chunks.push(data.subarray(0, remaining))
215
+ totalBytesRead = numBytes
216
+ }
217
+ if (totalBytesRead >= numBytes) break
218
+ }
219
+ } while (totalBytesRead < numBytes && bytesRead > 0)
220
+
221
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0)
222
+ const result = new Uint8Array(totalLength)
223
+ let offset = 0
224
+ for (const chunk of chunks) {
225
+ result.set(chunk, offset)
226
+ offset += chunk.length
227
+ }
228
+ await writer.write(result)
163
229
  }
164
230
  } else {
165
- const device = await shell.context.fs.promises.open(fullPath)
166
- const decoder = new TextDecoder()
167
- const lines: string[] = []
168
- let buffer = ''
169
- const chunkSize = 1024
170
- const data = new Uint8Array(chunkSize)
171
- let bytesRead = 0
231
+ if (!fullPath.startsWith('/dev')) {
232
+ const handle = await shell.context.fs.promises.open(fullPath, 'r')
233
+ const stat = await shell.context.fs.promises.stat(fullPath)
172
234
 
173
- do {
174
- if (interrupted) break
175
- const result = await device.read(data)
176
- bytesRead = result.bytesRead
177
- if (bytesRead > 0) {
178
- buffer += decoder.decode(data.subarray(0, bytesRead), { stream: true })
235
+ const decoder = new TextDecoder()
236
+ const lines: string[] = []
237
+ let buffer = ''
238
+ let bytesRead = 0
239
+ const chunkSize = 1024
240
+
241
+ while (bytesRead < stat.size && lines.length < numLines) {
242
+ if (interrupted) break
243
+ const data = new Uint8Array(chunkSize)
244
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
245
+ await handle.read(data, 0, readSize, bytesRead)
246
+ const chunk = data.subarray(0, readSize)
247
+ buffer += decoder.decode(chunk, { stream: true })
179
248
  const newLines = buffer.split('\n')
180
249
  buffer = newLines.pop() || ''
181
250
  lines.push(...newLines)
251
+ bytesRead += readSize
182
252
  if (lines.length >= numLines) break
183
253
  }
184
- } while (bytesRead > 0 && lines.length < numLines)
254
+ if (buffer && lines.length < numLines) {
255
+ lines.push(buffer)
256
+ }
185
257
 
186
- if (buffer && lines.length < numLines) {
187
- lines.push(buffer)
188
- }
258
+ const output = lines.slice(0, numLines).join('\n')
259
+ if (output) {
260
+ await writer.write(new TextEncoder().encode(output + '\n'))
261
+ }
262
+ } else {
263
+ const device = await shell.context.fs.promises.open(fullPath)
264
+ const decoder = new TextDecoder()
265
+ const lines: string[] = []
266
+ let buffer = ''
267
+ const chunkSize = 1024
268
+ const data = new Uint8Array(chunkSize)
269
+ let bytesRead = 0
189
270
 
190
- const output = lines.slice(0, numLines).join('\n')
191
- if (output) {
192
- await writer.write(new TextEncoder().encode(output + '\n'))
271
+ do {
272
+ if (interrupted) break
273
+ const result = await device.read(data)
274
+ bytesRead = result.bytesRead
275
+ if (bytesRead > 0) {
276
+ buffer += decoder.decode(data.subarray(0, bytesRead), { stream: true })
277
+ const newLines = buffer.split('\n')
278
+ buffer = newLines.pop() || ''
279
+ lines.push(...newLines)
280
+ if (lines.length >= numLines) break
281
+ }
282
+ } while (bytesRead > 0 && lines.length < numLines)
283
+
284
+ if (buffer && lines.length < numLines) {
285
+ lines.push(buffer)
286
+ }
287
+
288
+ const output = lines.slice(0, numLines).join('\n')
289
+ if (output) {
290
+ await writer.write(new TextEncoder().encode(output + '\n'))
291
+ }
193
292
  }
194
293
  }
195
294
  } finally {
@@ -144,6 +144,7 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
144
144
 
145
145
  if (linesRendered > 0) {
146
146
  terminal.write(ansi.cursor.up(linesRendered))
147
+ terminal.write('\r')
147
148
  }
148
149
 
149
150
  const endLine = Math.min(currentLine + displayRows, lines.length)
@@ -409,7 +409,10 @@ async function displayManPage(
409
409
  if (horizontalOffset > maxHorizontalOffset) horizontalOffset = maxHorizontalOffset
410
410
  if (horizontalOffset < 0) horizontalOffset = 0
411
411
 
412
- if (linesRendered > 0) terminal.write(ansi.cursor.up(linesRendered))
412
+ if (linesRendered > 0) {
413
+ terminal.write(ansi.cursor.up(linesRendered))
414
+ terminal.write('\r')
415
+ }
413
416
 
414
417
  const endLine = Math.min(currentLine + displayRows, lines.length)
415
418
  linesRendered = 0