@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,218 @@
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: shuf [OPTION]... [FILE]
9
+ Write a random permutation of the input lines to standard output.
10
+
11
+ -n, --head-count=COUNT output at most COUNT lines
12
+ -e, --echo treat each ARG as an input line
13
+ -i, --input-range=LO-HI treat each number LO through HI as an input line
14
+ --help display this help and exit`
15
+ writelnStderr(process, terminal, usage)
16
+ }
17
+
18
+ function shuffleArray<T>(array: T[]): T[] {
19
+ const shuffled = [...array]
20
+ for (let i = shuffled.length - 1; i > 0; i--) {
21
+ const j = Math.floor(Math.random() * (i + 1));
22
+ [shuffled[i]!, shuffled[j]!] = [shuffled[j]!, shuffled[i]!]
23
+ }
24
+ return shuffled
25
+ }
26
+
27
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
28
+ return new TerminalCommand({
29
+ command: 'shuf',
30
+ description: 'Write a random permutation of the input lines',
31
+ kernel,
32
+ shell,
33
+ terminal,
34
+ run: async (pid: number, argv: string[]) => {
35
+ const process = kernel.processes.get(pid) as Process | undefined
36
+
37
+ if (!process) return 1
38
+
39
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
40
+ printUsage(process, terminal)
41
+ return 0
42
+ }
43
+
44
+ let headCount: number | null = null
45
+ let echo = false
46
+ let inputRange: string | undefined
47
+ const files: string[] = []
48
+ const echoArgs: string[] = []
49
+
50
+ for (let i = 0; i < argv.length; i++) {
51
+ const arg = argv[i]
52
+ if (!arg) continue
53
+
54
+ if (arg === '--help' || arg === '-h') {
55
+ printUsage(process, terminal)
56
+ return 0
57
+ } else if (arg === '-n' || arg === '--head-count') {
58
+ if (i + 1 < argv.length) {
59
+ const countStr = argv[++i]
60
+ if (countStr !== undefined) {
61
+ const parsed = parseInt(countStr, 10)
62
+ if (!isNaN(parsed) && parsed > 0) {
63
+ headCount = parsed
64
+ } else {
65
+ await writelnStderr(process, terminal, `shuf: invalid line count: ${countStr}`)
66
+ return 1
67
+ }
68
+ }
69
+ }
70
+ } else if (arg.startsWith('--head-count=')) {
71
+ const countStr = arg.slice(13)
72
+ const parsed = parseInt(countStr, 10)
73
+ if (!isNaN(parsed) && parsed > 0) {
74
+ headCount = parsed
75
+ } else {
76
+ await writelnStderr(process, terminal, `shuf: invalid line count: ${countStr}`)
77
+ return 1
78
+ }
79
+ } else if (arg.startsWith('-n')) {
80
+ const countStr = arg.slice(2)
81
+ if (countStr) {
82
+ const parsed = parseInt(countStr, 10)
83
+ if (!isNaN(parsed) && parsed > 0) {
84
+ headCount = parsed
85
+ } else {
86
+ await writelnStderr(process, terminal, `shuf: invalid line count: ${countStr}`)
87
+ return 1
88
+ }
89
+ }
90
+ } else if (arg === '-e' || arg === '--echo') {
91
+ echo = true
92
+ } else if (arg === '-i' || arg === '--input-range') {
93
+ if (i + 1 < argv.length) {
94
+ inputRange = argv[++i]
95
+ }
96
+ } else if (arg.startsWith('--input-range=')) {
97
+ inputRange = arg.slice(15)
98
+ } else if (arg.startsWith('-i')) {
99
+ inputRange = arg.slice(2)
100
+ } else if (!arg.startsWith('-')) {
101
+ if (echo) {
102
+ echoArgs.push(arg)
103
+ } else {
104
+ files.push(arg)
105
+ }
106
+ } else {
107
+ await writelnStderr(process, terminal, `shuf: invalid option -- '${arg.slice(1)}'`)
108
+ await writelnStderr(process, terminal, "Try 'shuf --help' for more information.")
109
+ return 1
110
+ }
111
+ }
112
+
113
+ const writer = process.stdout.getWriter()
114
+
115
+ try {
116
+ let lines: string[] = []
117
+
118
+ if (inputRange) {
119
+ const [loStr, hiStr] = inputRange.split('-')
120
+ const lo = parseInt(loStr ?? '0', 10)
121
+ const hi = parseInt(hiStr ?? '0', 10)
122
+ if (isNaN(lo) || isNaN(hi) || lo > hi) {
123
+ await writelnStderr(process, terminal, `shuf: invalid input range: ${inputRange}`)
124
+ return 1
125
+ }
126
+ for (let i = lo; i <= hi; i++) {
127
+ lines.push(i.toString())
128
+ }
129
+ } else if (echo && echoArgs.length > 0) {
130
+ lines = echoArgs
131
+ } else if (files.length === 0) {
132
+ if (!process.stdin) {
133
+ return 0
134
+ }
135
+
136
+ const reader = process.stdin.getReader()
137
+ const decoder = new TextDecoder()
138
+ let buffer = ''
139
+
140
+ try {
141
+ while (true) {
142
+ const { done, value } = await reader.read()
143
+ if (done) break
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
+ }
150
+ }
151
+ if (buffer) {
152
+ lines.push(buffer)
153
+ }
154
+ } finally {
155
+ reader.releaseLock()
156
+ }
157
+ } else {
158
+ for (const file of files) {
159
+ const fullPath = path.resolve(shell.cwd, file)
160
+
161
+ let interrupted = false
162
+ const interruptHandler = () => { interrupted = true }
163
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
164
+
165
+ try {
166
+ if (fullPath.startsWith('/dev')) {
167
+ await writelnStderr(process, terminal, `shuf: ${file}: cannot process device files`)
168
+ continue
169
+ }
170
+
171
+ const handle = await shell.context.fs.promises.open(fullPath, 'r')
172
+ const stat = await shell.context.fs.promises.stat(fullPath)
173
+
174
+ const decoder = new TextDecoder()
175
+ let content = ''
176
+ let bytesRead = 0
177
+ const chunkSize = 1024
178
+
179
+ while (bytesRead < stat.size) {
180
+ if (interrupted) break
181
+ const data = new Uint8Array(chunkSize)
182
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
183
+ await handle.read(data, 0, readSize, bytesRead)
184
+ const chunk = data.subarray(0, readSize)
185
+ content += decoder.decode(chunk, { stream: true })
186
+ bytesRead += readSize
187
+ }
188
+
189
+ const fileLines = content.split('\n')
190
+ if (fileLines[fileLines.length - 1] === '') {
191
+ fileLines.pop()
192
+ }
193
+ lines.push(...fileLines)
194
+ } catch (error) {
195
+ await writelnStderr(process, terminal, `shuf: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
196
+ } finally {
197
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
198
+ }
199
+ }
200
+ }
201
+
202
+ const shuffled = shuffleArray(lines)
203
+ const output = headCount !== null ? shuffled.slice(0, headCount) : shuffled
204
+
205
+ for (const line of output) {
206
+ await writer.write(new TextEncoder().encode(line + '\n'))
207
+ }
208
+
209
+ return 0
210
+ } catch (error) {
211
+ await writelnStderr(process, terminal, `shuf: ${error instanceof Error ? error.message : 'Unknown error'}`)
212
+ return 1
213
+ } finally {
214
+ writer.releaseLock()
215
+ }
216
+ }
217
+ })
218
+ }
@@ -0,0 +1,109 @@
1
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
2
+ import { TerminalEvents } 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: sleep NUMBER[SUFFIX]...
8
+ Pause for NUMBER seconds. SUFFIX may be 's' for seconds (the default),
9
+ 'm' for minutes, 'h' for hours or 'd' for days.
10
+
11
+ --help display this help and exit`
12
+ writelnStderr(process, terminal, usage)
13
+ }
14
+
15
+ function parseDuration(value: string): number {
16
+ const match = value.match(/^([0-9]+(?:\.[0-9]+)?)([smhd]?)$/)
17
+ if (!match?.[1]) return NaN
18
+
19
+ const num = parseFloat(match[1])
20
+ if (isNaN(num)) return NaN
21
+
22
+ const suffix = match[2] || 's'
23
+ switch (suffix) {
24
+ case 's':
25
+ return num * 1000
26
+ case 'm':
27
+ return num * 60 * 1000
28
+ case 'h':
29
+ return num * 60 * 60 * 1000
30
+ case 'd':
31
+ return num * 24 * 60 * 60 * 1000
32
+ default:
33
+ return NaN
34
+ }
35
+ }
36
+
37
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
38
+ return new TerminalCommand({
39
+ command: 'sleep',
40
+ description: 'Delay for a specified amount of time',
41
+ kernel,
42
+ shell,
43
+ terminal,
44
+ run: async (pid: number, argv: string[]) => {
45
+ const process = kernel.processes.get(pid) as Process | undefined
46
+
47
+ if (!process) return 1
48
+
49
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
50
+ printUsage(process, terminal)
51
+ return 0
52
+ }
53
+
54
+ if (argv.length === 0) {
55
+ await writelnStderr(process, terminal, 'sleep: missing operand')
56
+ await writelnStderr(process, terminal, "Try 'sleep --help' for more information.")
57
+ return 1
58
+ }
59
+
60
+ let totalDuration = 0
61
+
62
+ for (const arg of argv) {
63
+ if (!arg) continue
64
+
65
+ if (arg === '--help' || arg === '-h') {
66
+ printUsage(process, terminal)
67
+ return 0
68
+ } else if (arg.startsWith('-')) {
69
+ await writelnStderr(process, terminal, `sleep: invalid option -- '${arg.slice(1)}'`)
70
+ await writelnStderr(process, terminal, "Try 'sleep --help' for more information.")
71
+ return 1
72
+ } else {
73
+ const duration = parseDuration(arg)
74
+ if (isNaN(duration)) {
75
+ await writelnStderr(process, terminal, `sleep: invalid time interval '${arg}'`)
76
+ return 1
77
+ }
78
+ totalDuration += duration
79
+ }
80
+ }
81
+
82
+ if (totalDuration <= 0) return 0
83
+
84
+ let interrupted = false
85
+ const interruptHandler = () => { interrupted = true }
86
+ terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
87
+
88
+ try {
89
+ await new Promise<void>((resolve) => {
90
+ const timeoutId = setTimeout(() => resolve(), totalDuration)
91
+
92
+ if (interrupted) {
93
+ clearTimeout(timeoutId)
94
+ resolve()
95
+ }
96
+ })
97
+ } catch (error) {
98
+ if (!interrupted) {
99
+ await writelnStderr(process, terminal, `sleep: ${error instanceof Error ? error.message : 'Unknown error'}`)
100
+ return 1
101
+ }
102
+ } finally {
103
+ terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
104
+ }
105
+
106
+ return interrupted ? 130 : 0
107
+ }
108
+ })
109
+ }
@@ -0,0 +1,176 @@
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: strings [OPTION]... [FILE]...
8
+ Print the sequences of printable characters in files.
9
+
10
+ -n, --bytes=MIN_LEN print sequences of at least MIN_LEN characters (default: 4)
11
+ --help display this help and exit`
12
+ writelnStderr(process, terminal, usage)
13
+ }
14
+
15
+ function extractStrings(data: Uint8Array, minLen: number): string[] {
16
+ const strings: string[] = []
17
+ let currentString = ''
18
+
19
+ for (let i = 0; i < data.length; i++) {
20
+ const byte = data[i]
21
+ if (byte === undefined) continue
22
+
23
+ if (byte >= 32 && byte <= 126) {
24
+ currentString += String.fromCharCode(byte)
25
+ } else {
26
+ if (currentString.length >= minLen) {
27
+ strings.push(currentString)
28
+ }
29
+ currentString = ''
30
+ }
31
+ }
32
+
33
+ if (currentString.length >= minLen) {
34
+ strings.push(currentString)
35
+ }
36
+
37
+ return strings
38
+ }
39
+
40
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
41
+ return new TerminalCommand({
42
+ command: 'strings',
43
+ description: 'Print the sequences of printable characters in files',
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
+ let minLen = 4
58
+ const files: string[] = []
59
+
60
+ for (let i = 0; i < argv.length; i++) {
61
+ const arg = argv[i]
62
+ if (!arg) continue
63
+
64
+ if (arg === '--help' || arg === '-h') {
65
+ printUsage(process, terminal)
66
+ return 0
67
+ } else if (arg === '-n' || arg === '--bytes') {
68
+ if (i + 1 < argv.length) {
69
+ const lenStr = argv[++i]
70
+ if (lenStr !== undefined) {
71
+ const parsed = parseInt(lenStr, 10)
72
+ if (!isNaN(parsed) && parsed > 0) {
73
+ minLen = parsed
74
+ } else {
75
+ await writelnStderr(process, terminal, `strings: invalid minimum length: ${lenStr}`)
76
+ return 1
77
+ }
78
+ }
79
+ }
80
+ } else if (arg.startsWith('--bytes=')) {
81
+ const lenStr = arg.slice(8)
82
+ const parsed = parseInt(lenStr, 10)
83
+ if (!isNaN(parsed) && parsed > 0) {
84
+ minLen = parsed
85
+ } else {
86
+ await writelnStderr(process, terminal, `strings: invalid minimum length: ${lenStr}`)
87
+ return 1
88
+ }
89
+ } else if (arg.startsWith('-n')) {
90
+ const lenStr = arg.slice(2)
91
+ if (lenStr) {
92
+ const parsed = parseInt(lenStr, 10)
93
+ if (!isNaN(parsed) && parsed > 0) {
94
+ minLen = parsed
95
+ } else {
96
+ await writelnStderr(process, terminal, `strings: invalid minimum length: ${lenStr}`)
97
+ return 1
98
+ }
99
+ }
100
+ } else if (!arg.startsWith('-')) {
101
+ files.push(arg)
102
+ } else {
103
+ await writelnStderr(process, terminal, `strings: invalid option -- '${arg.slice(1)}'`)
104
+ await writelnStderr(process, terminal, "Try 'strings --help' for more information.")
105
+ return 1
106
+ }
107
+ }
108
+
109
+ const writer = process.stdout.getWriter()
110
+
111
+ try {
112
+ if (files.length === 0) {
113
+ if (!process.stdin) {
114
+ return 0
115
+ }
116
+
117
+ const reader = process.stdin.getReader()
118
+ const chunks: Uint8Array[] = []
119
+
120
+ try {
121
+ while (true) {
122
+ const { done, value } = await reader.read()
123
+ if (done) break
124
+ if (value) {
125
+ chunks.push(value)
126
+ }
127
+ }
128
+ } finally {
129
+ reader.releaseLock()
130
+ }
131
+
132
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0)
133
+ const data = new Uint8Array(totalLength)
134
+ let offset = 0
135
+ for (const chunk of chunks) {
136
+ data.set(chunk, offset)
137
+ offset += chunk.length
138
+ }
139
+
140
+ const extractedStrings = extractStrings(data, minLen)
141
+ for (const str of extractedStrings) {
142
+ await writer.write(new TextEncoder().encode(str + '\n'))
143
+ }
144
+
145
+ return 0
146
+ }
147
+
148
+ for (const file of files) {
149
+ const fullPath = path.resolve(shell.cwd, file)
150
+
151
+ try {
152
+ if (fullPath.startsWith('/dev')) {
153
+ await writelnStderr(process, terminal, `strings: ${file}: cannot process device files`)
154
+ continue
155
+ }
156
+
157
+ const data = await shell.context.fs.promises.readFile(fullPath)
158
+ const extractedStrings = extractStrings(new Uint8Array(data), minLen)
159
+ for (const str of extractedStrings) {
160
+ await writer.write(new TextEncoder().encode(str + '\n'))
161
+ }
162
+ } catch (error) {
163
+ await writelnStderr(process, terminal, `strings: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
164
+ }
165
+ }
166
+
167
+ return 0
168
+ } catch (error) {
169
+ await writelnStderr(process, terminal, `strings: ${error instanceof Error ? error.message : 'Unknown error'}`)
170
+ return 1
171
+ } finally {
172
+ writer.releaseLock()
173
+ }
174
+ }
175
+ })
176
+ }
@@ -0,0 +1,138 @@
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: tac [OPTION]... [FILE]...
9
+ Write each FILE to standard output, last line first.
10
+
11
+ --help display this help and exit`
12
+ writelnStderr(process, terminal, usage)
13
+ }
14
+
15
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
16
+ return new TerminalCommand({
17
+ command: 'tac',
18
+ description: 'Write each FILE to standard output, last line first',
19
+ kernel,
20
+ shell,
21
+ terminal,
22
+ run: async (pid: number, argv: string[]) => {
23
+ const process = kernel.processes.get(pid) as Process | undefined
24
+
25
+ if (!process) return 1
26
+
27
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
28
+ printUsage(process, terminal)
29
+ return 0
30
+ }
31
+
32
+ const files: string[] = []
33
+
34
+ for (const arg of argv) {
35
+ if (!arg) continue
36
+
37
+ if (arg === '--help' || arg === '-h') {
38
+ printUsage(process, terminal)
39
+ return 0
40
+ } else if (!arg.startsWith('-')) {
41
+ files.push(arg)
42
+ } else {
43
+ await writelnStderr(process, terminal, `tac: invalid option -- '${arg.slice(1)}'`)
44
+ await writelnStderr(process, terminal, "Try 'tac --help' for more information.")
45
+ return 1
46
+ }
47
+ }
48
+
49
+ const writer = process.stdout.getWriter()
50
+
51
+ try {
52
+ let lines: string[] = []
53
+
54
+ if (files.length === 0) {
55
+ if (!process.stdin) {
56
+ return 0
57
+ }
58
+
59
+ const reader = process.stdin.getReader()
60
+ const decoder = new TextDecoder()
61
+ let buffer = ''
62
+
63
+ try {
64
+ while (true) {
65
+ const { done, value } = await reader.read()
66
+ if (done) break
67
+ if (value) {
68
+ buffer += decoder.decode(value, { stream: true })
69
+ const newLines = buffer.split('\n')
70
+ buffer = newLines.pop() || ''
71
+ lines.push(...newLines)
72
+ }
73
+ }
74
+ if (buffer) {
75
+ lines.push(buffer)
76
+ }
77
+ } finally {
78
+ reader.releaseLock()
79
+ }
80
+ } else {
81
+ for (const file of files) {
82
+ const fullPath = path.resolve(shell.cwd, file)
83
+
84
+ let interrupted = false
85
+ const interruptHandler = () => { interrupted = true }
86
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
87
+
88
+ try {
89
+ if (fullPath.startsWith('/dev')) {
90
+ await writelnStderr(process, terminal, `tac: ${file}: cannot process device files`)
91
+ continue
92
+ }
93
+
94
+ const handle = await shell.context.fs.promises.open(fullPath, 'r')
95
+ const stat = await shell.context.fs.promises.stat(fullPath)
96
+
97
+ const decoder = new TextDecoder()
98
+ let content = ''
99
+ let bytesRead = 0
100
+ const chunkSize = 1024
101
+
102
+ while (bytesRead < stat.size) {
103
+ if (interrupted) break
104
+ const data = new Uint8Array(chunkSize)
105
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
106
+ await handle.read(data, 0, readSize, bytesRead)
107
+ const chunk = data.subarray(0, readSize)
108
+ content += decoder.decode(chunk, { stream: true })
109
+ bytesRead += readSize
110
+ }
111
+
112
+ const fileLines = content.split('\n')
113
+ if (fileLines[fileLines.length - 1] === '') {
114
+ fileLines.pop()
115
+ }
116
+ lines.push(...fileLines)
117
+ } catch (error) {
118
+ await writelnStderr(process, terminal, `tac: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
119
+ } finally {
120
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
121
+ }
122
+ }
123
+ }
124
+
125
+ for (let i = lines.length - 1; i >= 0; i--) {
126
+ await writer.write(new TextEncoder().encode(lines[i] + '\n'))
127
+ }
128
+
129
+ return 0
130
+ } catch (error) {
131
+ await writelnStderr(process, terminal, `tac: ${error instanceof Error ? error.message : 'Unknown error'}`)
132
+ return 1
133
+ } finally {
134
+ writer.releaseLock()
135
+ }
136
+ }
137
+ })
138
+ }