@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
@@ -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,207 @@
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: expand [OPTION]... [FILE]...
9
+ Convert tabs to spaces in each FILE.
10
+
11
+ -t, --tabs=NUMBER have tabs NUMBER characters apart, not 8
12
+ -t, --tabs=LIST use comma separated list of tab positions
13
+ --help display this help and exit`
14
+ writelnStderr(process, terminal, usage)
15
+ }
16
+
17
+ function parseTabStops(tabStr: string): number[] {
18
+ if (tabStr.includes(',')) {
19
+ const stops = tabStr.split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n) && n > 0)
20
+ return stops.length > 0 ? stops : [8]
21
+ }
22
+ const single = parseInt(tabStr, 10)
23
+ return !isNaN(single) && single > 0 ? [single] : [8]
24
+ }
25
+
26
+ function expandTabs(line: string, tabStops: number[]): string {
27
+ let result = ''
28
+ let column = 0
29
+
30
+ for (let i = 0; i < line.length; i++) {
31
+ const char = line[i]
32
+
33
+ if (char === '\t') {
34
+ let nextStop = tabStops[0] ?? 8
35
+ for (const stop of tabStops) {
36
+ if (stop > column) {
37
+ nextStop = stop
38
+ break
39
+ }
40
+ }
41
+
42
+ if (nextStop <= column) {
43
+ const lastStop = tabStops[tabStops.length - 1] ?? 8
44
+ nextStop = lastStop
45
+ while (nextStop <= column) {
46
+ nextStop += lastStop
47
+ }
48
+ }
49
+
50
+ const spaces = nextStop - column
51
+ result += ' '.repeat(spaces)
52
+ column = nextStop
53
+ } else {
54
+ result += char
55
+ if (char === '\n' || char === '\r') {
56
+ column = 0
57
+ } else {
58
+ column++
59
+ }
60
+ }
61
+ }
62
+
63
+ return result
64
+ }
65
+
66
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
67
+ return new TerminalCommand({
68
+ command: 'expand',
69
+ description: 'Convert tabs to spaces',
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
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
79
+ printUsage(process, terminal)
80
+ return 0
81
+ }
82
+
83
+ let tabStops: number[] = [8]
84
+ const files: string[] = []
85
+
86
+ for (let i = 0; i < argv.length; i++) {
87
+ const arg = argv[i]
88
+ if (!arg) continue
89
+
90
+ if (arg === '--help' || arg === '-h') {
91
+ printUsage(process, terminal)
92
+ return 0
93
+ } else if (arg === '-t' || arg === '--tabs') {
94
+ if (i + 1 < argv.length) {
95
+ const tabStr = argv[++i]
96
+ if (tabStr !== undefined) {
97
+ tabStops = parseTabStops(tabStr)
98
+ }
99
+ }
100
+ } else if (arg.startsWith('--tabs=')) {
101
+ const tabStr = arg.slice(7)
102
+ tabStops = parseTabStops(tabStr)
103
+ } else if (arg.startsWith('-t')) {
104
+ const tabStr = arg.slice(2)
105
+ if (tabStr) {
106
+ tabStops = parseTabStops(tabStr)
107
+ }
108
+ } else if (!arg.startsWith('-')) {
109
+ files.push(arg)
110
+ } else {
111
+ await writelnStderr(process, terminal, `expand: invalid option -- '${arg.slice(1)}'`)
112
+ await writelnStderr(process, terminal, "Try 'expand --help' for more information.")
113
+ return 1
114
+ }
115
+ }
116
+
117
+ const writer = process.stdout.getWriter()
118
+
119
+ try {
120
+ let lines: string[] = []
121
+
122
+ if (files.length === 0) {
123
+ if (!process.stdin) {
124
+ return 0
125
+ }
126
+
127
+ const reader = process.stdin.getReader()
128
+ const decoder = new TextDecoder()
129
+ let buffer = ''
130
+
131
+ try {
132
+ while (true) {
133
+ const { done, value } = await reader.read()
134
+ if (done) break
135
+ if (value) {
136
+ buffer += decoder.decode(value, { stream: true })
137
+ const newLines = buffer.split('\n')
138
+ buffer = newLines.pop() || ''
139
+ lines.push(...newLines)
140
+ }
141
+ }
142
+ if (buffer) {
143
+ lines.push(buffer)
144
+ }
145
+ } finally {
146
+ reader.releaseLock()
147
+ }
148
+ } else {
149
+ for (const file of files) {
150
+ const fullPath = path.resolve(shell.cwd, file)
151
+
152
+ let interrupted = false
153
+ const interruptHandler = () => { interrupted = true }
154
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
155
+
156
+ try {
157
+ if (fullPath.startsWith('/dev')) {
158
+ await writelnStderr(process, terminal, `expand: ${file}: cannot process device files`)
159
+ continue
160
+ }
161
+
162
+ const handle = await shell.context.fs.promises.open(fullPath, 'r')
163
+ const stat = await shell.context.fs.promises.stat(fullPath)
164
+
165
+ const decoder = new TextDecoder()
166
+ let content = ''
167
+ let bytesRead = 0
168
+ const chunkSize = 1024
169
+
170
+ while (bytesRead < stat.size) {
171
+ if (interrupted) break
172
+ const data = new Uint8Array(chunkSize)
173
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
174
+ await handle.read(data, 0, readSize, bytesRead)
175
+ const chunk = data.subarray(0, readSize)
176
+ content += decoder.decode(chunk, { stream: true })
177
+ bytesRead += readSize
178
+ }
179
+
180
+ const fileLines = content.split('\n')
181
+ if (fileLines[fileLines.length - 1] === '') {
182
+ fileLines.pop()
183
+ }
184
+ lines.push(...fileLines)
185
+ } catch (error) {
186
+ await writelnStderr(process, terminal, `expand: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
187
+ } finally {
188
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
189
+ }
190
+ }
191
+ }
192
+
193
+ for (const line of lines) {
194
+ const expanded = expandTabs(line, tabStops)
195
+ await writer.write(new TextEncoder().encode(expanded + '\n'))
196
+ }
197
+
198
+ return 0
199
+ } catch (error) {
200
+ await writelnStderr(process, terminal, `expand: ${error instanceof Error ? error.message : 'Unknown error'}`)
201
+ return 1
202
+ } finally {
203
+ writer.releaseLock()
204
+ }
205
+ }
206
+ })
207
+ }
@@ -0,0 +1,151 @@
1
+ import type { Kernel, Process, Shell, Terminal } 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: factor [NUMBER]...
7
+ Print prime factors of each NUMBER.
8
+
9
+ --help display this help and exit`
10
+ writelnStderr(process, terminal, usage)
11
+ }
12
+
13
+ function factorize(n: number): number[] {
14
+ if (n < 2) {
15
+ return [n]
16
+ }
17
+
18
+ const factors: number[] = []
19
+ let num = n
20
+
21
+ while (num % 2 === 0) {
22
+ factors.push(2)
23
+ num /= 2
24
+ }
25
+
26
+ for (let i = 3; i * i <= num; i += 2) {
27
+ while (num % i === 0) {
28
+ factors.push(i)
29
+ num /= i
30
+ }
31
+ }
32
+
33
+ if (num > 2) {
34
+ factors.push(num)
35
+ }
36
+
37
+ return factors
38
+ }
39
+
40
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
41
+ return new TerminalCommand({
42
+ command: 'factor',
43
+ description: 'Print prime factors of numbers',
44
+ kernel,
45
+ shell,
46
+ terminal,
47
+ run: async (pid: number, argv: string[]) => {
48
+ const process = kernel.processes.get(pid) as Process | undefined
49
+
50
+ if (!process) return 1
51
+
52
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
53
+ printUsage(process, terminal)
54
+ return 0
55
+ }
56
+
57
+ const numbers: string[] = []
58
+
59
+ for (const arg of argv) {
60
+ if (!arg) continue
61
+
62
+ if (arg === '--help' || arg === '-h') {
63
+ printUsage(process, terminal)
64
+ return 0
65
+ } else if (!arg.startsWith('-')) {
66
+ numbers.push(arg)
67
+ } else {
68
+ await writelnStderr(process, terminal, `factor: invalid option -- '${arg.slice(1)}'`)
69
+ await writelnStderr(process, terminal, "Try 'factor --help' for more information.")
70
+ return 1
71
+ }
72
+ }
73
+
74
+ if (numbers.length === 0) {
75
+ if (!process.stdin) {
76
+ await writelnStderr(process, terminal, 'factor: missing operand')
77
+ await writelnStderr(process, terminal, "Try 'factor --help' for more information.")
78
+ return 1
79
+ }
80
+
81
+ if (process.stdinIsTTY) {
82
+ try {
83
+ while (true) {
84
+ const line = await terminal.readline('', false, true)
85
+ if (!line) break
86
+ const trimmed = line.trim()
87
+ if (trimmed) {
88
+ numbers.push(...trimmed.split(/\s+/))
89
+ } else {
90
+ break
91
+ }
92
+ }
93
+ } catch {
94
+ } finally {
95
+ terminal.listen()
96
+ }
97
+ } else {
98
+ const reader = process.stdin.getReader()
99
+ const decoder = new TextDecoder()
100
+ let buffer = ''
101
+
102
+ try {
103
+ while (true) {
104
+ const { done, value } = await reader.read()
105
+ if (done) break
106
+ if (value) {
107
+ buffer += decoder.decode(value, { stream: true })
108
+ const lines = buffer.split('\n')
109
+ buffer = lines.pop() || ''
110
+ for (const line of lines) {
111
+ const trimmed = line.trim()
112
+ if (trimmed) {
113
+ numbers.push(...trimmed.split(/\s+/))
114
+ }
115
+ }
116
+ }
117
+ }
118
+ if (buffer.trim()) {
119
+ numbers.push(...buffer.trim().split(/\s+/))
120
+ }
121
+ } finally {
122
+ reader.releaseLock()
123
+ }
124
+ }
125
+ }
126
+
127
+ if (numbers.length === 0) {
128
+ await writelnStderr(process, terminal, 'factor: missing operand')
129
+ await writelnStderr(process, terminal, "Try 'factor --help' for more information.")
130
+ return 1
131
+ }
132
+
133
+ let hasError = false
134
+
135
+ for (const numStr of numbers) {
136
+ const num = parseInt(numStr, 10)
137
+ if (isNaN(num) || num < 0) {
138
+ await writelnStderr(process, terminal, `factor: '${numStr}' is not a valid positive integer`)
139
+ hasError = true
140
+ continue
141
+ }
142
+
143
+ const factors = factorize(num)
144
+ const output = `${num}: ${factors.join(' ')}`
145
+ await writelnStdout(process, terminal, output)
146
+ }
147
+
148
+ return hasError ? 1 : 0
149
+ }
150
+ })
151
+ }