@ecmaos/coreutils 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +6 -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/curl.d.ts +4 -0
  34. package/dist/commands/curl.d.ts.map +1 -0
  35. package/dist/commands/curl.js +238 -0
  36. package/dist/commands/curl.js.map +1 -0
  37. package/dist/commands/du.d.ts +4 -0
  38. package/dist/commands/du.d.ts.map +1 -0
  39. package/dist/commands/du.js +168 -0
  40. package/dist/commands/du.js.map +1 -0
  41. package/dist/commands/echo.d.ts.map +1 -1
  42. package/dist/commands/echo.js +125 -2
  43. package/dist/commands/echo.js.map +1 -1
  44. package/dist/commands/expand.d.ts +4 -0
  45. package/dist/commands/expand.d.ts.map +1 -0
  46. package/dist/commands/expand.js +197 -0
  47. package/dist/commands/expand.js.map +1 -0
  48. package/dist/commands/factor.d.ts +4 -0
  49. package/dist/commands/factor.d.ts.map +1 -0
  50. package/dist/commands/factor.js +141 -0
  51. package/dist/commands/factor.js.map +1 -0
  52. package/dist/commands/fmt.d.ts +4 -0
  53. package/dist/commands/fmt.d.ts.map +1 -0
  54. package/dist/commands/fmt.js +278 -0
  55. package/dist/commands/fmt.js.map +1 -0
  56. package/dist/commands/fold.d.ts +4 -0
  57. package/dist/commands/fold.d.ts.map +1 -0
  58. package/dist/commands/fold.js +253 -0
  59. package/dist/commands/fold.js.map +1 -0
  60. package/dist/commands/groups.d.ts +4 -0
  61. package/dist/commands/groups.d.ts.map +1 -0
  62. package/dist/commands/groups.js +61 -0
  63. package/dist/commands/groups.js.map +1 -0
  64. package/dist/commands/hostname.d.ts +4 -0
  65. package/dist/commands/hostname.d.ts.map +1 -0
  66. package/dist/commands/hostname.js +80 -0
  67. package/dist/commands/hostname.js.map +1 -0
  68. package/dist/commands/mount.d.ts.map +1 -1
  69. package/dist/commands/mount.js +192 -97
  70. package/dist/commands/mount.js.map +1 -1
  71. package/dist/commands/od.d.ts +4 -0
  72. package/dist/commands/od.d.ts.map +1 -0
  73. package/dist/commands/od.js +342 -0
  74. package/dist/commands/od.js.map +1 -0
  75. package/dist/commands/pr.d.ts +4 -0
  76. package/dist/commands/pr.d.ts.map +1 -0
  77. package/dist/commands/pr.js +298 -0
  78. package/dist/commands/pr.js.map +1 -0
  79. package/dist/commands/printf.d.ts +4 -0
  80. package/dist/commands/printf.d.ts.map +1 -0
  81. package/dist/commands/printf.js +271 -0
  82. package/dist/commands/printf.js.map +1 -0
  83. package/dist/commands/readlink.d.ts +4 -0
  84. package/dist/commands/readlink.d.ts.map +1 -0
  85. package/dist/commands/readlink.js +104 -0
  86. package/dist/commands/readlink.js.map +1 -0
  87. package/dist/commands/realpath.d.ts +4 -0
  88. package/dist/commands/realpath.d.ts.map +1 -0
  89. package/dist/commands/realpath.js +111 -0
  90. package/dist/commands/realpath.js.map +1 -0
  91. package/dist/commands/rev.d.ts +4 -0
  92. package/dist/commands/rev.d.ts.map +1 -0
  93. package/dist/commands/rev.js +134 -0
  94. package/dist/commands/rev.js.map +1 -0
  95. package/dist/commands/shuf.d.ts +4 -0
  96. package/dist/commands/shuf.d.ts.map +1 -0
  97. package/dist/commands/shuf.js +221 -0
  98. package/dist/commands/shuf.js.map +1 -0
  99. package/dist/commands/sleep.d.ts +4 -0
  100. package/dist/commands/sleep.d.ts.map +1 -0
  101. package/dist/commands/sleep.js +102 -0
  102. package/dist/commands/sleep.js.map +1 -0
  103. package/dist/commands/strings.d.ts +4 -0
  104. package/dist/commands/strings.d.ts.map +1 -0
  105. package/dist/commands/strings.js +170 -0
  106. package/dist/commands/strings.js.map +1 -0
  107. package/dist/commands/tac.d.ts +4 -0
  108. package/dist/commands/tac.d.ts.map +1 -0
  109. package/dist/commands/tac.js +130 -0
  110. package/dist/commands/tac.js.map +1 -0
  111. package/dist/commands/time.d.ts +4 -0
  112. package/dist/commands/time.d.ts.map +1 -0
  113. package/dist/commands/time.js +126 -0
  114. package/dist/commands/time.js.map +1 -0
  115. package/dist/commands/umount.d.ts.map +1 -1
  116. package/dist/commands/umount.js +2 -3
  117. package/dist/commands/umount.js.map +1 -1
  118. package/dist/commands/uname.d.ts +4 -0
  119. package/dist/commands/uname.d.ts.map +1 -0
  120. package/dist/commands/uname.js +149 -0
  121. package/dist/commands/uname.js.map +1 -0
  122. package/dist/commands/unexpand.d.ts +4 -0
  123. package/dist/commands/unexpand.d.ts.map +1 -0
  124. package/dist/commands/unexpand.js +286 -0
  125. package/dist/commands/unexpand.js.map +1 -0
  126. package/dist/commands/uptime.d.ts +4 -0
  127. package/dist/commands/uptime.d.ts.map +1 -0
  128. package/dist/commands/uptime.js +62 -0
  129. package/dist/commands/uptime.js.map +1 -0
  130. package/dist/commands/yes.d.ts +4 -0
  131. package/dist/commands/yes.d.ts.map +1 -0
  132. package/dist/commands/yes.js +58 -0
  133. package/dist/commands/yes.js.map +1 -0
  134. package/dist/index.d.ts +21 -0
  135. package/dist/index.d.ts.map +1 -1
  136. package/dist/index.js +73 -0
  137. package/dist/index.js.map +1 -1
  138. package/package.json +3 -2
  139. package/src/commands/awk.ts +340 -0
  140. package/src/commands/chmod.ts +141 -2
  141. package/src/commands/chown.ts +321 -0
  142. package/src/commands/cksum.ts +133 -0
  143. package/src/commands/cmp.ts +126 -0
  144. package/src/commands/column.ts +273 -0
  145. package/src/commands/cp.ts +93 -4
  146. package/src/commands/curl.ts +231 -0
  147. package/src/commands/echo.ts +122 -2
  148. package/src/commands/expand.ts +207 -0
  149. package/src/commands/factor.ts +151 -0
  150. package/src/commands/fmt.ts +293 -0
  151. package/src/commands/fold.ts +257 -0
  152. package/src/commands/groups.ts +72 -0
  153. package/src/commands/hostname.ts +81 -0
  154. package/src/commands/mount.ts +208 -99
  155. package/src/commands/od.ts +327 -0
  156. package/src/commands/pr.ts +291 -0
  157. package/src/commands/printf.ts +271 -0
  158. package/src/commands/readlink.ts +102 -0
  159. package/src/commands/realpath.ts +126 -0
  160. package/src/commands/rev.ts +143 -0
  161. package/src/commands/shuf.ts +218 -0
  162. package/src/commands/sleep.ts +109 -0
  163. package/src/commands/strings.ts +176 -0
  164. package/src/commands/tac.ts +138 -0
  165. package/src/commands/time.ts +144 -0
  166. package/src/commands/umount.ts +2 -3
  167. package/src/commands/uname.ts +130 -0
  168. package/src/commands/unexpand.ts +305 -0
  169. package/src/commands/uptime.ts +73 -0
  170. package/src/index.ts +73 -0
  171. package/tsconfig.json +4 -0
@@ -0,0 +1,327 @@
1
+ import path from 'path'
2
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
3
+ import { TerminalCommand } from '../shared/terminal-command.js'
4
+ import { writelnStderr } from '../shared/helpers.js'
5
+
6
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
7
+ const usage = `Usage: od [OPTION]... [FILE]...
8
+ Dump files in octal and other formats.
9
+
10
+ -A, --address-radix=RADIX address format: d (decimal), o (octal), x (hex), n (none)
11
+ -t, --format=TYPE output format: o (octal), x (hex), d (decimal), u (unsigned), c (char), a (named char)
12
+ -N, --read-bytes=BYTES limit number of bytes to read
13
+ -j, --skip-bytes=BYTES skip bytes before reading
14
+ --help display this help and exit`
15
+ writelnStderr(process, terminal, usage)
16
+ }
17
+
18
+ function formatAddress(offset: number, radix: string): string {
19
+ switch (radix) {
20
+ case 'd':
21
+ return offset.toString(10).padStart(7, '0')
22
+ case 'o':
23
+ return offset.toString(8).padStart(7, '0')
24
+ case 'x':
25
+ return offset.toString(16).padStart(7, '0')
26
+ case 'n':
27
+ return ''
28
+ default:
29
+ return offset.toString(8).padStart(7, '0')
30
+ }
31
+ }
32
+
33
+ function formatByte(byte: number, format: string): string {
34
+ switch (format) {
35
+ case 'o':
36
+ case 'o1':
37
+ return byte.toString(8).padStart(3, '0')
38
+ case 'x':
39
+ case 'x1':
40
+ return byte.toString(16).padStart(2, '0')
41
+ case 'd':
42
+ case 'd1':
43
+ return byte.toString(10).padStart(3, '0')
44
+ case 'u':
45
+ case 'u1':
46
+ return byte.toString(10).padStart(3, '0')
47
+ case 'c':
48
+ if (byte >= 32 && byte <= 126) {
49
+ return `'${String.fromCharCode(byte)}'`
50
+ } else if (byte === 0) {
51
+ return '\\0'
52
+ } else if (byte === 7) {
53
+ return '\\a'
54
+ } else if (byte === 8) {
55
+ return '\\b'
56
+ } else if (byte === 9) {
57
+ return '\\t'
58
+ } else if (byte === 10) {
59
+ return '\\n'
60
+ } else if (byte === 11) {
61
+ return '\\v'
62
+ } else if (byte === 12) {
63
+ return '\\f'
64
+ } else if (byte === 13) {
65
+ return '\\r'
66
+ } else {
67
+ return `\\${byte.toString(8).padStart(3, '0')}`
68
+ }
69
+ case 'a':
70
+ if (byte >= 32 && byte <= 126) {
71
+ return String.fromCharCode(byte)
72
+ } else {
73
+ return '.'
74
+ }
75
+ default:
76
+ return byte.toString(8).padStart(3, '0')
77
+ }
78
+ }
79
+
80
+ function formatLine(data: Uint8Array, offset: number, addressRadix: string, format: string): string {
81
+ const address = formatAddress(offset, addressRadix)
82
+ const bytes: string[] = []
83
+ const ascii: string[] = []
84
+
85
+ for (let i = 0; i < data.length; i++) {
86
+ const byte = data[i]
87
+ if (byte === undefined) continue
88
+
89
+ bytes.push(formatByte(byte, format))
90
+
91
+ if (byte >= 32 && byte <= 126) {
92
+ ascii.push(String.fromCharCode(byte))
93
+ } else {
94
+ ascii.push('.')
95
+ }
96
+ }
97
+
98
+ let result = address ? `${address}: ` : ''
99
+ result += bytes.join(' ')
100
+
101
+ if (format !== 'c' && format !== 'a') {
102
+ result += ' ' + ascii.join('')
103
+ }
104
+
105
+ return result
106
+ }
107
+
108
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
109
+ return new TerminalCommand({
110
+ command: 'od',
111
+ description: 'Dump files in octal and other formats',
112
+ kernel,
113
+ shell,
114
+ terminal,
115
+ run: async (pid: number, argv: string[]) => {
116
+ const process = kernel.processes.get(pid) as Process | undefined
117
+
118
+ if (!process) return 1
119
+
120
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
121
+ printUsage(process, terminal)
122
+ return 0
123
+ }
124
+
125
+ let addressRadix = 'o'
126
+ let format = 'o1'
127
+ let readBytes: number | null = null
128
+ let skipBytes = 0
129
+ const files: string[] = []
130
+
131
+ for (let i = 0; i < argv.length; i++) {
132
+ const arg = argv[i]
133
+ if (!arg) continue
134
+
135
+ if (arg === '--help' || arg === '-h') {
136
+ printUsage(process, terminal)
137
+ return 0
138
+ } else if (arg === '-A' || arg === '--address-radix') {
139
+ if (i + 1 < argv.length) {
140
+ const radix = argv[++i]
141
+ if (radix && ['d', 'o', 'x', 'n'].includes(radix)) {
142
+ addressRadix = radix
143
+ } else {
144
+ await writelnStderr(process, terminal, `od: invalid address radix: ${radix}`)
145
+ return 1
146
+ }
147
+ }
148
+ } else if (arg.startsWith('--address-radix=')) {
149
+ const radix = arg.slice(16)
150
+ if (['d', 'o', 'x', 'n'].includes(radix)) {
151
+ addressRadix = radix
152
+ } else {
153
+ await writelnStderr(process, terminal, `od: invalid address radix: ${radix}`)
154
+ return 1
155
+ }
156
+ } else if (arg.startsWith('-A')) {
157
+ const radix = arg.slice(2)
158
+ if (['d', 'o', 'x', 'n'].includes(radix)) {
159
+ addressRadix = radix
160
+ } else {
161
+ await writelnStderr(process, terminal, `od: invalid address radix: ${radix}`)
162
+ return 1
163
+ }
164
+ } else if (arg === '-t' || arg === '--format') {
165
+ if (i + 1 < argv.length) {
166
+ const fmt = argv[++i]
167
+ if (fmt) {
168
+ format = fmt
169
+ }
170
+ }
171
+ } else if (arg.startsWith('--format=')) {
172
+ format = arg.slice(9)
173
+ } else if (arg.startsWith('-t')) {
174
+ format = arg.slice(2) || 'o1'
175
+ } else if (arg === '-N' || arg === '--read-bytes') {
176
+ if (i + 1 < argv.length) {
177
+ const bytesStr = argv[++i]
178
+ if (bytesStr !== undefined) {
179
+ const parsed = parseInt(bytesStr, 10)
180
+ if (!isNaN(parsed) && parsed > 0) {
181
+ readBytes = parsed
182
+ } else {
183
+ await writelnStderr(process, terminal, `od: invalid byte count: ${bytesStr}`)
184
+ return 1
185
+ }
186
+ }
187
+ }
188
+ } else if (arg.startsWith('--read-bytes=')) {
189
+ const bytesStr = arg.slice(13)
190
+ const parsed = parseInt(bytesStr, 10)
191
+ if (!isNaN(parsed) && parsed > 0) {
192
+ readBytes = parsed
193
+ } else {
194
+ await writelnStderr(process, terminal, `od: invalid byte count: ${bytesStr}`)
195
+ return 1
196
+ }
197
+ } else if (arg.startsWith('-N')) {
198
+ const bytesStr = arg.slice(2)
199
+ const parsed = parseInt(bytesStr, 10)
200
+ if (!isNaN(parsed) && parsed > 0) {
201
+ readBytes = parsed
202
+ } else {
203
+ await writelnStderr(process, terminal, `od: invalid byte count: ${bytesStr}`)
204
+ return 1
205
+ }
206
+ } else if (arg === '-j' || arg === '--skip-bytes') {
207
+ if (i + 1 < argv.length) {
208
+ const bytesStr = argv[++i]
209
+ if (bytesStr !== undefined) {
210
+ const parsed = parseInt(bytesStr, 10)
211
+ if (!isNaN(parsed) && parsed >= 0) {
212
+ skipBytes = parsed
213
+ } else {
214
+ await writelnStderr(process, terminal, `od: invalid skip count: ${bytesStr}`)
215
+ return 1
216
+ }
217
+ }
218
+ }
219
+ } else if (arg.startsWith('--skip-bytes=')) {
220
+ const bytesStr = arg.slice(13)
221
+ const parsed = parseInt(bytesStr, 10)
222
+ if (!isNaN(parsed) && parsed >= 0) {
223
+ skipBytes = parsed
224
+ } else {
225
+ await writelnStderr(process, terminal, `od: invalid skip count: ${bytesStr}`)
226
+ return 1
227
+ }
228
+ } else if (arg.startsWith('-j')) {
229
+ const bytesStr = arg.slice(2)
230
+ const parsed = parseInt(bytesStr, 10)
231
+ if (!isNaN(parsed) && parsed >= 0) {
232
+ skipBytes = parsed
233
+ } else {
234
+ await writelnStderr(process, terminal, `od: invalid skip count: ${bytesStr}`)
235
+ return 1
236
+ }
237
+ } else if (!arg.startsWith('-')) {
238
+ files.push(arg)
239
+ } else {
240
+ await writelnStderr(process, terminal, `od: invalid option -- '${arg.slice(1)}'`)
241
+ await writelnStderr(process, terminal, "Try 'od --help' for more information.")
242
+ return 1
243
+ }
244
+ }
245
+
246
+ const writer = process.stdout.getWriter()
247
+
248
+ try {
249
+ let data: Uint8Array
250
+
251
+ if (files.length === 0) {
252
+ if (!process.stdin) {
253
+ return 0
254
+ }
255
+
256
+ const reader = process.stdin.getReader()
257
+ const chunks: Uint8Array[] = []
258
+
259
+ try {
260
+ while (true) {
261
+ const { done, value } = await reader.read()
262
+ if (done) break
263
+ if (value) {
264
+ chunks.push(value)
265
+ }
266
+ }
267
+ } finally {
268
+ reader.releaseLock()
269
+ }
270
+
271
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0)
272
+ data = new Uint8Array(totalLength)
273
+ let offset = 0
274
+ for (const chunk of chunks) {
275
+ data.set(chunk, offset)
276
+ offset += chunk.length
277
+ }
278
+ } else {
279
+ const file = files[0]
280
+ if (!file) return 1
281
+ const fullPath = path.resolve(shell.cwd, file)
282
+
283
+ try {
284
+ if (fullPath.startsWith('/dev')) {
285
+ await writelnStderr(process, terminal, `od: ${file}: cannot process device files`)
286
+ return 1
287
+ }
288
+
289
+ const fileData = await shell.context.fs.promises.readFile(fullPath)
290
+ data = new Uint8Array(fileData)
291
+ } catch (error) {
292
+ await writelnStderr(process, terminal, `od: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
293
+ return 1
294
+ }
295
+ }
296
+
297
+ if (skipBytes > 0) {
298
+ if (skipBytes >= data.length) {
299
+ return 0
300
+ }
301
+ data = data.slice(skipBytes)
302
+ }
303
+
304
+ if (readBytes !== null && readBytes < data.length) {
305
+ data = data.slice(0, readBytes)
306
+ }
307
+
308
+ const bytesPerLine = 16
309
+ let offset = 0
310
+
311
+ while (offset < data.length) {
312
+ const lineData = data.slice(offset, offset + bytesPerLine)
313
+ const line = formatLine(lineData, offset + skipBytes, addressRadix, format)
314
+ await writer.write(new TextEncoder().encode(line + '\n'))
315
+ offset += lineData.length
316
+ }
317
+
318
+ return 0
319
+ } catch (error) {
320
+ await writelnStderr(process, terminal, `od: ${error instanceof Error ? error.message : 'Unknown error'}`)
321
+ return 1
322
+ } finally {
323
+ writer.releaseLock()
324
+ }
325
+ }
326
+ })
327
+ }
@@ -0,0 +1,291 @@
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: pr [OPTION]... [FILE]...
9
+ Paginate or columnate files for printing.
10
+
11
+ -l, --length=NUMBER set page length (default: 66)
12
+ -w, --width=NUMBER set page width (default: 72)
13
+ -h, --header=HEADER set header string
14
+ -t, --omit-header omit page headers and footers
15
+ -n, --number-lines number lines
16
+ --help display this help and exit`
17
+ writelnStderr(process, terminal, usage)
18
+ }
19
+
20
+ function formatPage(lines: string[], pageLength: number, pageWidth: number, header: string | undefined, omitHeader: boolean, numberLines: boolean, pageNum: number, filename: string): string[] {
21
+ const result: string[] = []
22
+ const bodyLength = omitHeader ? pageLength : pageLength - 2
23
+
24
+ if (!omitHeader && header !== undefined) {
25
+ const headerLine = header.padEnd(pageWidth).slice(0, pageWidth)
26
+ result.push(headerLine)
27
+ result.push('')
28
+ } else if (!omitHeader) {
29
+ const date = new Date().toLocaleString()
30
+ const headerLine = `${filename} ${date}`.padEnd(pageWidth).slice(0, pageWidth)
31
+ result.push(headerLine)
32
+ result.push('')
33
+ }
34
+
35
+ const startLine = (pageNum - 1) * bodyLength
36
+ const endLine = Math.min(startLine + bodyLength, lines.length)
37
+
38
+ for (let i = startLine; i < endLine; i++) {
39
+ let line = lines[i] || ''
40
+ if (line.length > pageWidth) {
41
+ line = line.slice(0, pageWidth)
42
+ } else {
43
+ line = line.padEnd(pageWidth)
44
+ }
45
+
46
+ if (numberLines) {
47
+ const lineNum = (i + 1).toString().padStart(6)
48
+ line = `${lineNum} ${line.slice(0, pageWidth - 8)}`
49
+ }
50
+
51
+ result.push(line)
52
+ }
53
+
54
+ while (result.length < pageLength && !omitHeader) {
55
+ result.push('')
56
+ }
57
+
58
+ if (!omitHeader) {
59
+ const footer = `Page ${pageNum}`.padStart(pageWidth)
60
+ result.push(footer)
61
+ }
62
+
63
+ return result
64
+ }
65
+
66
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
67
+ return new TerminalCommand({
68
+ command: 'pr',
69
+ description: 'Paginate or columnate files for printing',
70
+ kernel,
71
+ shell,
72
+ terminal,
73
+ run: async (pid: number, argv: string[]) => {
74
+ const process = kernel.processes.get(pid) as Process | undefined
75
+
76
+ if (!process) return 1
77
+
78
+ let pageLength = 66
79
+ let pageWidth = 72
80
+ let header: string | undefined
81
+ let omitHeader = false
82
+ let numberLines = false
83
+ const files: string[] = []
84
+
85
+ for (let i = 0; i < argv.length; i++) {
86
+ const arg = argv[i]
87
+ if (!arg) continue
88
+
89
+ if (arg === '--help') {
90
+ printUsage(process, terminal)
91
+ return 0
92
+ } else if (arg.startsWith('--header=')) {
93
+ header = arg.slice(9)
94
+ } else if (arg === '--header') {
95
+ if (i + 1 < argv.length) {
96
+ header = argv[++i]
97
+ } else {
98
+ await writelnStderr(process, terminal, `pr: option '--header' requires an argument`)
99
+ return 1
100
+ }
101
+ } else if (arg === '-h') {
102
+ if (i + 1 < argv.length) {
103
+ header = argv[++i]
104
+ } else {
105
+ printUsage(process, terminal)
106
+ return 0
107
+ }
108
+ } else if (arg.startsWith('-h') && arg.length > 2) {
109
+ header = arg.slice(2)
110
+ } else if (arg === '-l' || arg === '--length') {
111
+ if (i + 1 < argv.length) {
112
+ const lengthStr = argv[++i]
113
+ if (lengthStr !== undefined) {
114
+ const parsed = parseInt(lengthStr, 10)
115
+ if (!isNaN(parsed) && parsed > 0) {
116
+ pageLength = parsed
117
+ } else {
118
+ await writelnStderr(process, terminal, `pr: invalid page length: ${lengthStr}`)
119
+ return 1
120
+ }
121
+ }
122
+ }
123
+ } else if (arg.startsWith('--length=')) {
124
+ const lengthStr = arg.slice(9)
125
+ const parsed = parseInt(lengthStr, 10)
126
+ if (!isNaN(parsed) && parsed > 0) {
127
+ pageLength = parsed
128
+ } else {
129
+ await writelnStderr(process, terminal, `pr: invalid page length: ${lengthStr}`)
130
+ return 1
131
+ }
132
+ } else if (arg.startsWith('-l')) {
133
+ const lengthStr = arg.slice(2)
134
+ if (lengthStr) {
135
+ const parsed = parseInt(lengthStr, 10)
136
+ if (!isNaN(parsed) && parsed > 0) {
137
+ pageLength = parsed
138
+ } else {
139
+ await writelnStderr(process, terminal, `pr: invalid page length: ${lengthStr}`)
140
+ return 1
141
+ }
142
+ }
143
+ } else if (arg === '-w' || arg === '--width') {
144
+ if (i + 1 < argv.length) {
145
+ const widthStr = argv[++i]
146
+ if (widthStr !== undefined) {
147
+ const parsed = parseInt(widthStr, 10)
148
+ if (!isNaN(parsed) && parsed > 0) {
149
+ pageWidth = parsed
150
+ } else {
151
+ await writelnStderr(process, terminal, `pr: invalid page width: ${widthStr}`)
152
+ return 1
153
+ }
154
+ }
155
+ }
156
+ } else if (arg.startsWith('--width=')) {
157
+ const widthStr = arg.slice(8)
158
+ const parsed = parseInt(widthStr, 10)
159
+ if (!isNaN(parsed) && parsed > 0) {
160
+ pageWidth = parsed
161
+ } else {
162
+ await writelnStderr(process, terminal, `pr: invalid page width: ${widthStr}`)
163
+ return 1
164
+ }
165
+ } else if (arg.startsWith('-w')) {
166
+ const widthStr = arg.slice(2)
167
+ if (widthStr) {
168
+ const parsed = parseInt(widthStr, 10)
169
+ if (!isNaN(parsed) && parsed > 0) {
170
+ pageWidth = parsed
171
+ } else {
172
+ await writelnStderr(process, terminal, `pr: invalid page width: ${widthStr}`)
173
+ return 1
174
+ }
175
+ }
176
+ } else if (arg === '-t' || arg === '--omit-header') {
177
+ omitHeader = true
178
+ } else if (arg === '-n' || arg === '--number-lines') {
179
+ numberLines = true
180
+ } else if (arg.startsWith('-')) {
181
+ const flags = arg.slice(1).split('')
182
+ if (flags.includes('t')) omitHeader = true
183
+ if (flags.includes('n')) numberLines = true
184
+ const invalidFlags = flags.filter(f => !['t', 'n'].includes(f))
185
+ if (invalidFlags.length > 0) {
186
+ await writelnStderr(process, terminal, `pr: invalid option -- '${invalidFlags[0]}'`)
187
+ await writelnStderr(process, terminal, "Try 'pr --help' for more information.")
188
+ return 1
189
+ }
190
+ } else {
191
+ files.push(arg)
192
+ }
193
+ }
194
+
195
+ const writer = process.stdout.getWriter()
196
+
197
+ try {
198
+ let lines: string[] = []
199
+
200
+ if (files.length === 0) {
201
+ if (!process.stdin) {
202
+ return 0
203
+ }
204
+
205
+ const reader = process.stdin.getReader()
206
+ const decoder = new TextDecoder()
207
+ let buffer = ''
208
+
209
+ try {
210
+ while (true) {
211
+ const { done, value } = await reader.read()
212
+ if (done) break
213
+ if (value) {
214
+ buffer += decoder.decode(value, { stream: true })
215
+ const newLines = buffer.split('\n')
216
+ buffer = newLines.pop() || ''
217
+ lines.push(...newLines)
218
+ }
219
+ }
220
+ if (buffer) {
221
+ lines.push(buffer)
222
+ }
223
+ } finally {
224
+ reader.releaseLock()
225
+ }
226
+ } else {
227
+ for (const file of files) {
228
+ const fullPath = path.resolve(shell.cwd, file)
229
+
230
+ let interrupted = false
231
+ const interruptHandler = () => { interrupted = true }
232
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
233
+
234
+ try {
235
+ if (fullPath.startsWith('/dev')) {
236
+ await writelnStderr(process, terminal, `pr: ${file}: cannot process device files`)
237
+ continue
238
+ }
239
+
240
+ const handle = await shell.context.fs.promises.open(fullPath, 'r')
241
+ const stat = await shell.context.fs.promises.stat(fullPath)
242
+
243
+ const decoder = new TextDecoder()
244
+ let content = ''
245
+ let bytesRead = 0
246
+ const chunkSize = 1024
247
+
248
+ while (bytesRead < stat.size) {
249
+ if (interrupted) break
250
+ const data = new Uint8Array(chunkSize)
251
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
252
+ await handle.read(data, 0, readSize, bytesRead)
253
+ const chunk = data.subarray(0, readSize)
254
+ content += decoder.decode(chunk, { stream: true })
255
+ bytesRead += readSize
256
+ }
257
+
258
+ const fileLines = content.split('\n')
259
+ if (fileLines[fileLines.length - 1] === '') {
260
+ fileLines.pop()
261
+ }
262
+ lines.push(...fileLines)
263
+ } catch (error) {
264
+ await writelnStderr(process, terminal, `pr: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
265
+ } finally {
266
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
267
+ }
268
+ }
269
+ }
270
+
271
+ const bodyLength = omitHeader ? pageLength : pageLength - 2
272
+ const totalPages = Math.ceil(lines.length / bodyLength)
273
+ const filename = files[0] ?? 'stdin'
274
+
275
+ for (let page = 1; page <= totalPages; page++) {
276
+ const pageLines = formatPage(lines, pageLength, pageWidth, header, omitHeader, numberLines, page, filename)
277
+ for (const line of pageLines) {
278
+ await writer.write(new TextEncoder().encode(line + '\n'))
279
+ }
280
+ }
281
+
282
+ return 0
283
+ } catch (error) {
284
+ await writelnStderr(process, terminal, `pr: ${error instanceof Error ? error.message : 'Unknown error'}`)
285
+ return 1
286
+ } finally {
287
+ writer.releaseLock()
288
+ }
289
+ }
290
+ })
291
+ }