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