@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,271 @@
1
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
2
+ import { TerminalCommand } from '../shared/terminal-command.js'
3
+ import { writelnStderr } from '../shared/helpers.js'
4
+
5
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
6
+ const usage = `Usage: printf FORMAT [ARGUMENT]...
7
+ Format and print ARGUMENT(s) according to FORMAT.
8
+
9
+ FORMAT controls the output as in C printf. Interpreted sequences:
10
+
11
+ \\\\ backslash
12
+ \\a alert (BEL)
13
+ \\b backspace
14
+ \\c produce no further output
15
+ \\e escape
16
+ \\f form feed
17
+ \\n new line
18
+ \\r carriage return
19
+ \\t horizontal tab
20
+ \\v vertical tab
21
+ \\0NNN byte with octal value NNN (1 to 3 digits)
22
+ \\xHH byte with hexadecimal value HH (1 to 2 digits)
23
+
24
+ Conversion specifiers:
25
+
26
+ %b ARGUMENT as a string with '\\' escapes interpreted
27
+ %c ARGUMENT as a single character
28
+ %d ARGUMENT as a signed decimal integer
29
+ %i ARGUMENT as a signed decimal integer
30
+ %o ARGUMENT as an unsigned octal number
31
+ %s ARGUMENT as a string
32
+ %u ARGUMENT as an unsigned decimal integer
33
+ %x ARGUMENT as an unsigned hexadecimal number (lowercase)
34
+ %X ARGUMENT as an unsigned hexadecimal number (uppercase)
35
+ %% a single %
36
+
37
+ --help display this help and exit`
38
+ writelnStderr(process, terminal, usage)
39
+ }
40
+
41
+ function interpretEscapes(text: string): string {
42
+ let result = ''
43
+ let i = 0
44
+
45
+ while (i < text.length) {
46
+ if (text[i] === '\\' && i + 1 < text.length) {
47
+ const next = text[i + 1]
48
+ switch (next) {
49
+ case '\\':
50
+ result += '\\'
51
+ i += 2
52
+ break
53
+ case 'a':
54
+ result += '\x07'
55
+ i += 2
56
+ break
57
+ case 'b':
58
+ result += '\b'
59
+ i += 2
60
+ break
61
+ case 'c':
62
+ return result
63
+ case 'e':
64
+ case 'E':
65
+ result += '\x1b'
66
+ i += 2
67
+ break
68
+ case 'f':
69
+ result += '\f'
70
+ i += 2
71
+ break
72
+ case 'n':
73
+ result += '\n'
74
+ i += 2
75
+ break
76
+ case 'r':
77
+ result += '\r'
78
+ i += 2
79
+ break
80
+ case 't':
81
+ result += '\t'
82
+ i += 2
83
+ break
84
+ case 'v':
85
+ result += '\v'
86
+ i += 2
87
+ break
88
+ case '0':
89
+ case '1':
90
+ case '2':
91
+ case '3':
92
+ case '4':
93
+ case '5':
94
+ case '6':
95
+ case '7': {
96
+ let octal = ''
97
+ let j = i + 1
98
+ while (j < text.length && j < i + 4) {
99
+ const char = text[j]
100
+ if (char && /[0-7]/.test(char)) {
101
+ octal += char
102
+ j++
103
+ } else {
104
+ break
105
+ }
106
+ }
107
+ if (octal) {
108
+ result += String.fromCharCode(parseInt(octal, 8))
109
+ i = j
110
+ } else {
111
+ result += text[i]
112
+ i++
113
+ }
114
+ break
115
+ }
116
+ case 'x': {
117
+ let hex = ''
118
+ let j = i + 2
119
+ while (j < text.length && j < i + 4) {
120
+ const char = text[j]
121
+ if (char && /[0-9a-fA-F]/.test(char)) {
122
+ hex += char
123
+ j++
124
+ } else {
125
+ break
126
+ }
127
+ }
128
+ if (hex) {
129
+ result += String.fromCharCode(parseInt(hex, 16))
130
+ i = j
131
+ } else {
132
+ result += text[i]
133
+ i++
134
+ }
135
+ break
136
+ }
137
+ default:
138
+ result += text[i]
139
+ i++
140
+ break
141
+ }
142
+ } else {
143
+ result += text[i]
144
+ i++
145
+ }
146
+ }
147
+
148
+ return result
149
+ }
150
+
151
+ function formatValue(format: string, value: string): string {
152
+ switch (format) {
153
+ case '%b':
154
+ return interpretEscapes(value)
155
+ case '%c': {
156
+ const num = parseInt(value, 10)
157
+ if (!isNaN(num)) {
158
+ return String.fromCharCode(num)
159
+ }
160
+ return value[0] || ''
161
+ }
162
+ case '%d':
163
+ case '%i': {
164
+ const num = parseInt(value, 10)
165
+ return isNaN(num) ? '0' : num.toString()
166
+ }
167
+ case '%o': {
168
+ const num = parseInt(value, 10)
169
+ return isNaN(num) ? '0' : num.toString(8)
170
+ }
171
+ case '%s':
172
+ return value
173
+ case '%u': {
174
+ const num = parseInt(value, 10)
175
+ if (isNaN(num)) return '0'
176
+ const unsigned = num >>> 0
177
+ return unsigned.toString()
178
+ }
179
+ case '%x': {
180
+ const num = parseInt(value, 10)
181
+ return isNaN(num) ? '0' : (num >>> 0).toString(16)
182
+ }
183
+ case '%X': {
184
+ const num = parseInt(value, 10)
185
+ return isNaN(num) ? '0' : (num >>> 0).toString(16).toUpperCase()
186
+ }
187
+ case '%%':
188
+ return '%'
189
+ default:
190
+ return format
191
+ }
192
+ }
193
+
194
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
195
+ return new TerminalCommand({
196
+ command: 'printf',
197
+ description: 'Format and print data',
198
+ kernel,
199
+ shell,
200
+ terminal,
201
+ run: async (pid: number, argv: string[]) => {
202
+ const process = kernel.processes.get(pid) as Process | undefined
203
+
204
+ if (!process) return 1
205
+
206
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
207
+ printUsage(process, terminal)
208
+ return 0
209
+ }
210
+
211
+ if (argv.length === 0) {
212
+ await writelnStderr(process, terminal, 'printf: missing format string')
213
+ await writelnStderr(process, terminal, "Try 'printf --help' for more information.")
214
+ return 1
215
+ }
216
+
217
+ const format = argv[0] || ''
218
+ const args = argv.slice(1)
219
+
220
+ let result = ''
221
+ let argIndex = 0
222
+ let i = 0
223
+
224
+ while (i < format.length) {
225
+ if (format[i] === '%' && i + 1 < format.length) {
226
+ const next = format[i + 1]
227
+ if (next === '%') {
228
+ result += '%'
229
+ i += 2
230
+ } else {
231
+ const specifier = `%${next}`
232
+ if (argIndex < args.length) {
233
+ result += formatValue(specifier, args[argIndex] || '')
234
+ argIndex++
235
+ } else {
236
+ result += specifier
237
+ }
238
+ i += 2
239
+ }
240
+ } else if (format[i] === '\\' && i + 1 < format.length) {
241
+ const escapeSeq = format.slice(i)
242
+ const match = escapeSeq.match(/^\\([\\abceEfnrtv]|x[0-9a-fA-F]{1,2}|0[0-7]{1,3})/)
243
+ if (match) {
244
+ const escaped = interpretEscapes(match[0])
245
+ result += escaped
246
+ i += match[0].length
247
+ } else {
248
+ result += format[i]
249
+ i++
250
+ }
251
+ } else {
252
+ result += format[i]
253
+ i++
254
+ }
255
+ }
256
+
257
+ if (process.stdout) {
258
+ const writer = process.stdout.getWriter()
259
+ try {
260
+ await writer.write(new TextEncoder().encode(result))
261
+ } finally {
262
+ writer.releaseLock()
263
+ }
264
+ } else {
265
+ terminal.write(result)
266
+ }
267
+
268
+ return 0
269
+ }
270
+ })
271
+ }
@@ -0,0 +1,102 @@
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, writelnStdout } from '../shared/helpers.js'
5
+
6
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
7
+ const usage = `Usage: readlink [OPTION]... FILE...
8
+ Print value of a symbolic link or canonical file name.
9
+
10
+ -f, --canonicalize canonicalize by following every symlink in every component
11
+ -e, --canonicalize-existing canonicalize by following every symlink in every component that exists
12
+ -m, --canonicalize-missing canonicalize by following every symlink in every component, without requirements on components existence
13
+ --help display this help and exit`
14
+ writelnStderr(process, terminal, usage)
15
+ }
16
+
17
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
18
+ return new TerminalCommand({
19
+ command: 'readlink',
20
+ description: 'Print value of a symbolic link or canonical file name',
21
+ kernel,
22
+ shell,
23
+ terminal,
24
+ run: async (pid: number, argv: string[]) => {
25
+ const process = kernel.processes.get(pid) as Process | undefined
26
+
27
+ if (!process) return 1
28
+
29
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
30
+ printUsage(process, terminal)
31
+ return 0
32
+ }
33
+
34
+ let canonicalize = false
35
+ let canonicalizeExisting = false
36
+ let canonicalizeMissing = false
37
+ const files: string[] = []
38
+
39
+ for (const arg of argv) {
40
+ if (!arg) continue
41
+
42
+ if (arg === '--help' || arg === '-h') {
43
+ printUsage(process, terminal)
44
+ return 0
45
+ } else if (arg === '-f' || arg === '--canonicalize') {
46
+ canonicalize = true
47
+ } else if (arg === '-e' || arg === '--canonicalize-existing') {
48
+ canonicalizeExisting = true
49
+ } else if (arg === '-m' || arg === '--canonicalize-missing') {
50
+ canonicalizeMissing = true
51
+ } else if (arg.startsWith('-')) {
52
+ const flags = arg.slice(1).split('')
53
+ if (flags.includes('f')) canonicalize = true
54
+ if (flags.includes('e')) canonicalizeExisting = true
55
+ if (flags.includes('m')) canonicalizeMissing = true
56
+ const invalidFlags = flags.filter(f => !['f', 'e', 'm'].includes(f))
57
+ if (invalidFlags.length > 0) {
58
+ await writelnStderr(process, terminal, `readlink: invalid option -- '${invalidFlags[0]}'`)
59
+ await writelnStderr(process, terminal, "Try 'readlink --help' for more information.")
60
+ return 1
61
+ }
62
+ } else {
63
+ files.push(arg)
64
+ }
65
+ }
66
+
67
+ if (files.length === 0) {
68
+ await writelnStderr(process, terminal, 'readlink: missing operand')
69
+ await writelnStderr(process, terminal, "Try 'readlink --help' for more information.")
70
+ return 1
71
+ }
72
+
73
+ let hasError = false
74
+
75
+ for (const file of files) {
76
+ const fullPath = path.resolve(shell.cwd, file)
77
+
78
+ try {
79
+ if (canonicalize || canonicalizeExisting || canonicalizeMissing) {
80
+ const resolved = path.resolve(fullPath)
81
+ await writelnStdout(process, terminal, resolved)
82
+ } else {
83
+ const linkTarget = await shell.context.fs.promises.readlink(fullPath)
84
+ await writelnStdout(process, terminal, linkTarget)
85
+ }
86
+ } catch (error) {
87
+ const errorMessage = error instanceof Error ? error.message : String(error)
88
+ if (errorMessage.includes('not a symlink') || errorMessage.includes('EINVAL')) {
89
+ await writelnStderr(process, terminal, `readlink: ${file}: invalid symlink`)
90
+ } else if (errorMessage.includes('ENOENT') || errorMessage.includes('not found')) {
91
+ await writelnStderr(process, terminal, `readlink: ${file}: No such file or directory`)
92
+ } else {
93
+ await writelnStderr(process, terminal, `readlink: ${file}: ${errorMessage}`)
94
+ }
95
+ hasError = true
96
+ }
97
+ }
98
+
99
+ return hasError ? 1 : 0
100
+ }
101
+ })
102
+ }
@@ -0,0 +1,126 @@
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, writelnStdout } from '../shared/helpers.js'
5
+
6
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
7
+ const usage = `Usage: realpath [OPTION]... FILE...
8
+ Print the resolved absolute file name.
9
+
10
+ -e, --canonicalize-existing all components of the path must exist
11
+ -q, --quiet suppress most error messages
12
+ --help display this help and exit`
13
+ writelnStderr(process, terminal, usage)
14
+ }
15
+
16
+ async function resolveRealPath(
17
+ fs: typeof import('@zenfs/core').fs.promises,
18
+ filePath: string,
19
+ canonicalizeExisting: boolean,
20
+ quiet: boolean
21
+ ): Promise<string | null> {
22
+ try {
23
+ const resolved = path.resolve(filePath)
24
+
25
+ if (canonicalizeExisting) {
26
+ const exists = await fs.exists(resolved)
27
+ if (!exists) {
28
+ if (!quiet) {
29
+ throw new Error('No such file or directory')
30
+ }
31
+ return null
32
+ }
33
+ }
34
+
35
+ return resolved
36
+ } catch (error) {
37
+ if (!quiet) {
38
+ throw error
39
+ }
40
+ return null
41
+ }
42
+ }
43
+
44
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
45
+ return new TerminalCommand({
46
+ command: 'realpath',
47
+ description: 'Print the resolved absolute file name',
48
+ kernel,
49
+ shell,
50
+ terminal,
51
+ run: async (pid: number, argv: string[]) => {
52
+ const process = kernel.processes.get(pid) as Process | undefined
53
+
54
+ if (!process) return 1
55
+
56
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
57
+ printUsage(process, terminal)
58
+ return 0
59
+ }
60
+
61
+ let canonicalizeExisting = false
62
+ let quiet = false
63
+ const files: string[] = []
64
+
65
+ for (const arg of argv) {
66
+ if (!arg) continue
67
+
68
+ if (arg === '--help' || arg === '-h') {
69
+ printUsage(process, terminal)
70
+ return 0
71
+ } else if (arg === '-e' || arg === '--canonicalize-existing') {
72
+ canonicalizeExisting = true
73
+ } else if (arg === '-q' || arg === '--quiet') {
74
+ quiet = true
75
+ } else if (arg.startsWith('-')) {
76
+ const flags = arg.slice(1).split('')
77
+ if (flags.includes('e')) canonicalizeExisting = true
78
+ if (flags.includes('q')) quiet = true
79
+ const invalidFlags = flags.filter(f => !['e', 'q'].includes(f))
80
+ if (invalidFlags.length > 0) {
81
+ await writelnStderr(process, terminal, `realpath: invalid option -- '${invalidFlags[0]}'`)
82
+ await writelnStderr(process, terminal, "Try 'realpath --help' for more information.")
83
+ return 1
84
+ }
85
+ } else {
86
+ files.push(arg)
87
+ }
88
+ }
89
+
90
+ if (files.length === 0) {
91
+ await writelnStderr(process, terminal, 'realpath: missing operand')
92
+ await writelnStderr(process, terminal, "Try 'realpath --help' for more information.")
93
+ return 1
94
+ }
95
+
96
+ let hasError = false
97
+
98
+ for (const file of files) {
99
+ const fullPath = path.resolve(shell.cwd, file)
100
+
101
+ try {
102
+ const resolved = await resolveRealPath(
103
+ shell.context.fs.promises,
104
+ fullPath,
105
+ canonicalizeExisting,
106
+ quiet
107
+ )
108
+
109
+ if (resolved !== null) {
110
+ await writelnStdout(process, terminal, resolved)
111
+ } else {
112
+ hasError = true
113
+ }
114
+ } catch (error) {
115
+ if (!quiet) {
116
+ const errorMessage = error instanceof Error ? error.message : String(error)
117
+ await writelnStderr(process, terminal, `realpath: ${file}: ${errorMessage}`)
118
+ }
119
+ hasError = true
120
+ }
121
+ }
122
+
123
+ return hasError ? 1 : 0
124
+ }
125
+ })
126
+ }
@@ -0,0 +1,143 @@
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: rev [FILE]...
9
+ Reverse the characters of each line.
10
+
11
+ --help display this help and exit`
12
+ writelnStderr(process, terminal, usage)
13
+ }
14
+
15
+ function reverseLine(line: string): string {
16
+ return line.split('').reverse().join('')
17
+ }
18
+
19
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
20
+ return new TerminalCommand({
21
+ command: 'rev',
22
+ description: 'Reverse the characters of each line',
23
+ kernel,
24
+ shell,
25
+ terminal,
26
+ run: async (pid: number, argv: string[]) => {
27
+ const process = kernel.processes.get(pid) as Process | undefined
28
+
29
+ if (!process) return 1
30
+
31
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
32
+ printUsage(process, terminal)
33
+ return 0
34
+ }
35
+
36
+ const files: string[] = []
37
+
38
+ for (const arg of argv) {
39
+ if (!arg) continue
40
+
41
+ if (arg === '--help' || arg === '-h') {
42
+ printUsage(process, terminal)
43
+ return 0
44
+ } else if (!arg.startsWith('-')) {
45
+ files.push(arg)
46
+ } else {
47
+ await writelnStderr(process, terminal, `rev: invalid option -- '${arg.slice(1)}'`)
48
+ await writelnStderr(process, terminal, "Try 'rev --help' for more information.")
49
+ return 1
50
+ }
51
+ }
52
+
53
+ const writer = process.stdout.getWriter()
54
+
55
+ try {
56
+ let lines: string[] = []
57
+
58
+ if (files.length === 0) {
59
+ if (!process.stdin) {
60
+ return 0
61
+ }
62
+
63
+ const reader = process.stdin.getReader()
64
+ const decoder = new TextDecoder()
65
+ let buffer = ''
66
+
67
+ try {
68
+ while (true) {
69
+ const { done, value } = await reader.read()
70
+ if (done) break
71
+ if (value) {
72
+ buffer += decoder.decode(value, { stream: true })
73
+ const newLines = buffer.split('\n')
74
+ buffer = newLines.pop() || ''
75
+ lines.push(...newLines)
76
+ }
77
+ }
78
+ if (buffer) {
79
+ lines.push(buffer)
80
+ }
81
+ } finally {
82
+ reader.releaseLock()
83
+ }
84
+ } else {
85
+ for (const file of files) {
86
+ const fullPath = path.resolve(shell.cwd, file)
87
+
88
+ let interrupted = false
89
+ const interruptHandler = () => { interrupted = true }
90
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
91
+
92
+ try {
93
+ if (fullPath.startsWith('/dev')) {
94
+ await writelnStderr(process, terminal, `rev: ${file}: cannot process device files`)
95
+ continue
96
+ }
97
+
98
+ const handle = await shell.context.fs.promises.open(fullPath, 'r')
99
+ const stat = await shell.context.fs.promises.stat(fullPath)
100
+
101
+ const decoder = new TextDecoder()
102
+ let content = ''
103
+ let bytesRead = 0
104
+ const chunkSize = 1024
105
+
106
+ while (bytesRead < stat.size) {
107
+ if (interrupted) break
108
+ const data = new Uint8Array(chunkSize)
109
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
110
+ await handle.read(data, 0, readSize, bytesRead)
111
+ const chunk = data.subarray(0, readSize)
112
+ content += decoder.decode(chunk, { stream: true })
113
+ bytesRead += readSize
114
+ }
115
+
116
+ const fileLines = content.split('\n')
117
+ if (fileLines[fileLines.length - 1] === '') {
118
+ fileLines.pop()
119
+ }
120
+ lines.push(...fileLines)
121
+ } catch (error) {
122
+ await writelnStderr(process, terminal, `rev: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
123
+ } finally {
124
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
125
+ }
126
+ }
127
+ }
128
+
129
+ for (const line of lines) {
130
+ const reversed = reverseLine(line)
131
+ await writer.write(new TextEncoder().encode(reversed + '\n'))
132
+ }
133
+
134
+ return 0
135
+ } catch (error) {
136
+ await writelnStderr(process, terminal, `rev: ${error instanceof Error ? error.message : 'Unknown error'}`)
137
+ return 1
138
+ } finally {
139
+ writer.releaseLock()
140
+ }
141
+ }
142
+ })
143
+ }