@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,257 @@
1
+ import path from 'path'
2
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
3
+ import { TerminalEvents } from '@ecmaos/types'
4
+ import { TerminalCommand } from '../shared/terminal-command.js'
5
+ import { writelnStderr } from '../shared/helpers.js'
6
+
7
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
8
+ const usage = `Usage: fold [OPTION]... [FILE]...
9
+ Wrap each input line to fit in specified width.
10
+
11
+ -w, --width=WIDTH use WIDTH columns instead of 80
12
+ -s, --spaces break at spaces when possible
13
+ -b, --bytes count bytes instead of columns
14
+ --help display this help and exit`
15
+ writelnStderr(process, terminal, usage)
16
+ }
17
+
18
+ function wrapLine(line: string, width: number, breakAtSpaces: boolean, countBytes: boolean): string[] {
19
+ if (!line) return ['']
20
+
21
+ const result: string[] = []
22
+
23
+ if (countBytes) {
24
+ const encoder = new TextEncoder()
25
+ let current = ''
26
+ let currentBytes = 0
27
+
28
+ for (let i = 0; i < line.length; i++) {
29
+ const char = line[i]
30
+ const charBytes = encoder.encode(char).length
31
+
32
+ if (currentBytes + charBytes > width && current.length > 0) {
33
+ if (breakAtSpaces) {
34
+ const lastSpace = current.lastIndexOf(' ')
35
+ if (lastSpace > 0) {
36
+ result.push(current.slice(0, lastSpace))
37
+ current = current.slice(lastSpace + 1) + char
38
+ currentBytes = encoder.encode(current).length
39
+ continue
40
+ }
41
+ }
42
+ result.push(current)
43
+ current = char || ''
44
+ currentBytes = charBytes
45
+ } else {
46
+ current += char
47
+ currentBytes += charBytes
48
+ }
49
+ }
50
+
51
+ if (current.length > 0) {
52
+ result.push(current)
53
+ }
54
+ } else {
55
+ let current = ''
56
+
57
+ for (let i = 0; i < line.length; i++) {
58
+ const char = line[i]
59
+
60
+ if (current.length >= width && current.length > 0) {
61
+ if (breakAtSpaces) {
62
+ const lastSpace = current.lastIndexOf(' ')
63
+ if (lastSpace > 0) {
64
+ result.push(current.slice(0, lastSpace))
65
+ current = current.slice(lastSpace + 1) + char
66
+ continue
67
+ }
68
+ }
69
+ result.push(current)
70
+ current = char || ''
71
+ } else {
72
+ current += char
73
+ }
74
+ }
75
+
76
+ if (current.length > 0) {
77
+ result.push(current)
78
+ }
79
+ }
80
+
81
+ return result.length > 0 ? result : ['']
82
+ }
83
+
84
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
85
+ return new TerminalCommand({
86
+ command: 'fold',
87
+ description: 'Wrap each input line to fit in specified width',
88
+ kernel,
89
+ shell,
90
+ terminal,
91
+ run: async (pid: number, argv: string[]) => {
92
+ const process = kernel.processes.get(pid) as Process | undefined
93
+
94
+ if (!process) return 1
95
+
96
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
97
+ printUsage(process, terminal)
98
+ return 0
99
+ }
100
+
101
+ let width = 80
102
+ let breakAtSpaces = false
103
+ let countBytes = false
104
+ const files: string[] = []
105
+
106
+ for (let i = 0; i < argv.length; i++) {
107
+ const arg = argv[i]
108
+ if (!arg) continue
109
+
110
+ if (arg === '--help' || arg === '-h') {
111
+ printUsage(process, terminal)
112
+ return 0
113
+ } else if (arg === '-w' || arg === '--width') {
114
+ if (i + 1 < argv.length) {
115
+ const widthStr = argv[++i]
116
+ if (widthStr !== undefined) {
117
+ const parsed = parseInt(widthStr, 10)
118
+ if (!isNaN(parsed) && parsed > 0) {
119
+ width = parsed
120
+ } else {
121
+ await writelnStderr(process, terminal, `fold: invalid width: ${widthStr}`)
122
+ return 1
123
+ }
124
+ }
125
+ }
126
+ } else if (arg.startsWith('--width=')) {
127
+ const widthStr = arg.slice(8)
128
+ const parsed = parseInt(widthStr, 10)
129
+ if (!isNaN(parsed) && parsed > 0) {
130
+ width = parsed
131
+ } else {
132
+ await writelnStderr(process, terminal, `fold: invalid width: ${widthStr}`)
133
+ return 1
134
+ }
135
+ } else if (arg.startsWith('-w')) {
136
+ const widthStr = arg.slice(2)
137
+ if (widthStr) {
138
+ const parsed = parseInt(widthStr, 10)
139
+ if (!isNaN(parsed) && parsed > 0) {
140
+ width = parsed
141
+ } else {
142
+ await writelnStderr(process, terminal, `fold: invalid width: ${widthStr}`)
143
+ return 1
144
+ }
145
+ }
146
+ } else if (arg === '-s' || arg === '--spaces') {
147
+ breakAtSpaces = true
148
+ } else if (arg === '-b' || arg === '--bytes') {
149
+ countBytes = true
150
+ } else if (arg.startsWith('-')) {
151
+ const flags = arg.slice(1).split('')
152
+ if (flags.includes('s')) breakAtSpaces = true
153
+ if (flags.includes('b')) countBytes = true
154
+ const invalidFlags = flags.filter(f => !['s', 'b'].includes(f))
155
+ if (invalidFlags.length > 0) {
156
+ await writelnStderr(process, terminal, `fold: invalid option -- '${invalidFlags[0]}'`)
157
+ await writelnStderr(process, terminal, "Try 'fold --help' for more information.")
158
+ return 1
159
+ }
160
+ } else {
161
+ files.push(arg)
162
+ }
163
+ }
164
+
165
+ const writer = process.stdout.getWriter()
166
+
167
+ try {
168
+ let lines: string[] = []
169
+
170
+ if (files.length === 0) {
171
+ if (!process.stdin) {
172
+ return 0
173
+ }
174
+
175
+ const reader = process.stdin.getReader()
176
+ const decoder = new TextDecoder()
177
+ let buffer = ''
178
+
179
+ try {
180
+ while (true) {
181
+ const { done, value } = await reader.read()
182
+ if (done) break
183
+ if (value) {
184
+ buffer += decoder.decode(value, { stream: true })
185
+ const newLines = buffer.split('\n')
186
+ buffer = newLines.pop() || ''
187
+ lines.push(...newLines)
188
+ }
189
+ }
190
+ if (buffer) {
191
+ lines.push(buffer)
192
+ }
193
+ } finally {
194
+ reader.releaseLock()
195
+ }
196
+ } else {
197
+ for (const file of files) {
198
+ const fullPath = path.resolve(shell.cwd, file)
199
+
200
+ let interrupted = false
201
+ const interruptHandler = () => { interrupted = true }
202
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
203
+
204
+ try {
205
+ if (fullPath.startsWith('/dev')) {
206
+ await writelnStderr(process, terminal, `fold: ${file}: cannot process device files`)
207
+ continue
208
+ }
209
+
210
+ const handle = await shell.context.fs.promises.open(fullPath, 'r')
211
+ const stat = await shell.context.fs.promises.stat(fullPath)
212
+
213
+ const decoder = new TextDecoder()
214
+ let content = ''
215
+ let bytesRead = 0
216
+ const chunkSize = 1024
217
+
218
+ while (bytesRead < stat.size) {
219
+ if (interrupted) break
220
+ const data = new Uint8Array(chunkSize)
221
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
222
+ await handle.read(data, 0, readSize, bytesRead)
223
+ const chunk = data.subarray(0, readSize)
224
+ content += decoder.decode(chunk, { stream: true })
225
+ bytesRead += readSize
226
+ }
227
+
228
+ const fileLines = content.split('\n')
229
+ if (fileLines[fileLines.length - 1] === '') {
230
+ fileLines.pop()
231
+ }
232
+ lines.push(...fileLines)
233
+ } catch (error) {
234
+ await writelnStderr(process, terminal, `fold: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
235
+ } finally {
236
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
237
+ }
238
+ }
239
+ }
240
+
241
+ for (const line of lines) {
242
+ const wrapped = wrapLine(line, width, breakAtSpaces, countBytes)
243
+ for (const wrappedLine of wrapped) {
244
+ await writer.write(new TextEncoder().encode(wrappedLine + '\n'))
245
+ }
246
+ }
247
+
248
+ return 0
249
+ } catch (error) {
250
+ await writelnStderr(process, terminal, `fold: ${error instanceof Error ? error.message : 'Unknown error'}`)
251
+ return 1
252
+ } finally {
253
+ writer.releaseLock()
254
+ }
255
+ }
256
+ })
257
+ }
@@ -0,0 +1,72 @@
1
+ import type { Kernel, Process, Shell, Terminal, User } from '@ecmaos/types'
2
+ import { TerminalCommand } from '../shared/terminal-command.js'
3
+ import { writelnStderr, writelnStdout } from '../shared/helpers.js'
4
+
5
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
6
+ const usage = `Usage: groups [USERNAME]...
7
+ Print the groups a user belongs to.
8
+
9
+ --help display this help and exit`
10
+ writelnStderr(process, terminal, usage)
11
+ }
12
+
13
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
14
+ return new TerminalCommand({
15
+ command: 'groups',
16
+ description: 'Print the groups a user belongs to',
17
+ kernel,
18
+ shell,
19
+ terminal,
20
+ run: async (pid: number, argv: string[]) => {
21
+ const process = kernel.processes.get(pid) as Process | undefined
22
+
23
+ if (!process) return 1
24
+
25
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
26
+ printUsage(process, terminal)
27
+ return 0
28
+ }
29
+
30
+ const usernames: string[] = []
31
+
32
+ for (const arg of argv) {
33
+ if (!arg) continue
34
+
35
+ if (arg === '--help' || arg === '-h') {
36
+ printUsage(process, terminal)
37
+ return 0
38
+ } else if (!arg.startsWith('-')) {
39
+ usernames.push(arg)
40
+ } else {
41
+ await writelnStderr(process, terminal, `groups: invalid option -- '${arg.slice(1)}'`)
42
+ await writelnStderr(process, terminal, "Try 'groups --help' for more information.")
43
+ return 1
44
+ }
45
+ }
46
+
47
+ const targets = usernames.length > 0 ? usernames : [shell.username]
48
+
49
+ for (const username of targets) {
50
+ const user = Array.from(kernel.users.all.values()).find(
51
+ (u): u is User => (u as User).username === username
52
+ )
53
+
54
+ if (!user) {
55
+ await writelnStderr(process, terminal, `groups: '${username}': no such user`)
56
+ continue
57
+ }
58
+
59
+ const groups = shell.credentials.groups || []
60
+ const groupNames = groups.map(gid => {
61
+ const groupUser = kernel.users.get(gid)
62
+ return groupUser?.username || gid.toString()
63
+ })
64
+
65
+ const output = `${username} : ${groupNames.join(' ')}`
66
+ await writelnStdout(process, terminal, output)
67
+ }
68
+
69
+ return 0
70
+ }
71
+ })
72
+ }
@@ -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 {