@ecmaos/coreutils 0.1.4 → 0.2.0

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 (229) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +55 -0
  3. package/dist/commands/basename.d.ts +4 -0
  4. package/dist/commands/basename.d.ts.map +1 -0
  5. package/dist/commands/basename.js +78 -0
  6. package/dist/commands/basename.js.map +1 -0
  7. package/dist/commands/cal.d.ts +4 -0
  8. package/dist/commands/cal.d.ts.map +1 -0
  9. package/dist/commands/cal.js +105 -0
  10. package/dist/commands/cal.js.map +1 -0
  11. package/dist/commands/cat.d.ts.map +1 -1
  12. package/dist/commands/cat.js +49 -22
  13. package/dist/commands/cat.js.map +1 -1
  14. package/dist/commands/cd.d.ts.map +1 -1
  15. package/dist/commands/cd.js +38 -10
  16. package/dist/commands/cd.js.map +1 -1
  17. package/dist/commands/chmod.d.ts.map +1 -1
  18. package/dist/commands/chmod.js +31 -9
  19. package/dist/commands/chmod.js.map +1 -1
  20. package/dist/commands/comm.d.ts +4 -0
  21. package/dist/commands/comm.d.ts.map +1 -0
  22. package/dist/commands/comm.js +162 -0
  23. package/dist/commands/comm.js.map +1 -0
  24. package/dist/commands/cp.d.ts.map +1 -1
  25. package/dist/commands/cp.js +61 -12
  26. package/dist/commands/cp.js.map +1 -1
  27. package/dist/commands/cut.d.ts +4 -0
  28. package/dist/commands/cut.d.ts.map +1 -0
  29. package/dist/commands/cut.js +208 -0
  30. package/dist/commands/cut.js.map +1 -0
  31. package/dist/commands/date.d.ts +4 -0
  32. package/dist/commands/date.d.ts.map +1 -0
  33. package/dist/commands/date.js +100 -0
  34. package/dist/commands/date.js.map +1 -0
  35. package/dist/commands/diff.d.ts +4 -0
  36. package/dist/commands/diff.d.ts.map +1 -0
  37. package/dist/commands/diff.js +194 -0
  38. package/dist/commands/diff.js.map +1 -0
  39. package/dist/commands/dirname.d.ts +4 -0
  40. package/dist/commands/dirname.d.ts.map +1 -0
  41. package/dist/commands/dirname.js +50 -0
  42. package/dist/commands/dirname.js.map +1 -0
  43. package/dist/commands/echo.d.ts.map +1 -1
  44. package/dist/commands/echo.js +51 -9
  45. package/dist/commands/echo.js.map +1 -1
  46. package/dist/commands/false.d.ts +4 -0
  47. package/dist/commands/false.d.ts.map +1 -0
  48. package/dist/commands/false.js +27 -0
  49. package/dist/commands/false.js.map +1 -0
  50. package/dist/commands/find.d.ts +4 -0
  51. package/dist/commands/find.d.ts.map +1 -0
  52. package/dist/commands/find.js +181 -0
  53. package/dist/commands/find.js.map +1 -0
  54. package/dist/commands/grep.d.ts +4 -0
  55. package/dist/commands/grep.d.ts.map +1 -0
  56. package/dist/commands/grep.js +175 -0
  57. package/dist/commands/grep.js.map +1 -0
  58. package/dist/commands/head.d.ts +4 -0
  59. package/dist/commands/head.d.ts.map +1 -0
  60. package/dist/commands/head.js +199 -0
  61. package/dist/commands/head.js.map +1 -0
  62. package/dist/commands/hex.d.ts.map +1 -1
  63. package/dist/commands/hex.js +82 -20
  64. package/dist/commands/hex.js.map +1 -1
  65. package/dist/commands/id.d.ts +4 -0
  66. package/dist/commands/id.d.ts.map +1 -0
  67. package/dist/commands/id.js +97 -0
  68. package/dist/commands/id.js.map +1 -0
  69. package/dist/commands/join.d.ts +4 -0
  70. package/dist/commands/join.d.ts.map +1 -0
  71. package/dist/commands/join.js +152 -0
  72. package/dist/commands/join.js.map +1 -0
  73. package/dist/commands/less.d.ts.map +1 -1
  74. package/dist/commands/less.js +16 -7
  75. package/dist/commands/less.js.map +1 -1
  76. package/dist/commands/ln.d.ts.map +1 -1
  77. package/dist/commands/ln.js +54 -12
  78. package/dist/commands/ln.js.map +1 -1
  79. package/dist/commands/ls.d.ts.map +1 -1
  80. package/dist/commands/ls.js +19 -9
  81. package/dist/commands/ls.js.map +1 -1
  82. package/dist/commands/mkdir.d.ts.map +1 -1
  83. package/dist/commands/mkdir.js +34 -9
  84. package/dist/commands/mkdir.js.map +1 -1
  85. package/dist/commands/mv.d.ts.map +1 -1
  86. package/dist/commands/mv.js +25 -7
  87. package/dist/commands/mv.js.map +1 -1
  88. package/dist/commands/nc.d.ts +4 -0
  89. package/dist/commands/nc.d.ts.map +1 -0
  90. package/dist/commands/nc.js +451 -0
  91. package/dist/commands/nc.js.map +1 -0
  92. package/dist/commands/nl.d.ts +4 -0
  93. package/dist/commands/nl.d.ts.map +1 -0
  94. package/dist/commands/nl.js +208 -0
  95. package/dist/commands/nl.js.map +1 -0
  96. package/dist/commands/passkey.d.ts.map +1 -1
  97. package/dist/commands/passkey.js +44 -18
  98. package/dist/commands/passkey.js.map +1 -1
  99. package/dist/commands/paste.d.ts +4 -0
  100. package/dist/commands/paste.d.ts.map +1 -0
  101. package/dist/commands/paste.js +161 -0
  102. package/dist/commands/paste.js.map +1 -0
  103. package/dist/commands/pwd.d.ts.map +1 -1
  104. package/dist/commands/pwd.js +13 -4
  105. package/dist/commands/pwd.js.map +1 -1
  106. package/dist/commands/rm.d.ts.map +1 -1
  107. package/dist/commands/rm.js +105 -12
  108. package/dist/commands/rm.js.map +1 -1
  109. package/dist/commands/rmdir.d.ts.map +1 -1
  110. package/dist/commands/rmdir.js +34 -9
  111. package/dist/commands/rmdir.js.map +1 -1
  112. package/dist/commands/sed.d.ts.map +1 -1
  113. package/dist/commands/sed.js +73 -28
  114. package/dist/commands/sed.js.map +1 -1
  115. package/dist/commands/seq.d.ts +4 -0
  116. package/dist/commands/seq.d.ts.map +1 -0
  117. package/dist/commands/seq.js +148 -0
  118. package/dist/commands/seq.js.map +1 -0
  119. package/dist/commands/sockets.d.ts +4 -0
  120. package/dist/commands/sockets.d.ts.map +1 -0
  121. package/dist/commands/sockets.js +239 -0
  122. package/dist/commands/sockets.js.map +1 -0
  123. package/dist/commands/sort.d.ts +4 -0
  124. package/dist/commands/sort.d.ts.map +1 -0
  125. package/dist/commands/sort.js +175 -0
  126. package/dist/commands/sort.js.map +1 -0
  127. package/dist/commands/split.d.ts +4 -0
  128. package/dist/commands/split.d.ts.map +1 -0
  129. package/dist/commands/split.js +147 -0
  130. package/dist/commands/split.js.map +1 -0
  131. package/dist/commands/stat.d.ts.map +1 -1
  132. package/dist/commands/stat.js +14 -6
  133. package/dist/commands/stat.js.map +1 -1
  134. package/dist/commands/tail.d.ts +4 -0
  135. package/dist/commands/tail.d.ts.map +1 -0
  136. package/dist/commands/tail.js +189 -0
  137. package/dist/commands/tail.js.map +1 -0
  138. package/dist/commands/tee.d.ts.map +1 -1
  139. package/dist/commands/tee.js +45 -10
  140. package/dist/commands/tee.js.map +1 -1
  141. package/dist/commands/test.d.ts +4 -0
  142. package/dist/commands/test.d.ts.map +1 -0
  143. package/dist/commands/test.js +99 -0
  144. package/dist/commands/test.js.map +1 -0
  145. package/dist/commands/touch.d.ts.map +1 -1
  146. package/dist/commands/touch.js +34 -9
  147. package/dist/commands/touch.js.map +1 -1
  148. package/dist/commands/tr.d.ts +4 -0
  149. package/dist/commands/tr.d.ts.map +1 -0
  150. package/dist/commands/tr.js +162 -0
  151. package/dist/commands/tr.js.map +1 -0
  152. package/dist/commands/true.d.ts +4 -0
  153. package/dist/commands/true.d.ts.map +1 -0
  154. package/dist/commands/true.js +27 -0
  155. package/dist/commands/true.js.map +1 -0
  156. package/dist/commands/uniq.d.ts +4 -0
  157. package/dist/commands/uniq.d.ts.map +1 -0
  158. package/dist/commands/uniq.js +187 -0
  159. package/dist/commands/uniq.js.map +1 -0
  160. package/dist/commands/wc.d.ts +4 -0
  161. package/dist/commands/wc.d.ts.map +1 -0
  162. package/dist/commands/wc.js +178 -0
  163. package/dist/commands/wc.js.map +1 -0
  164. package/dist/commands/which.d.ts +4 -0
  165. package/dist/commands/which.d.ts.map +1 -0
  166. package/dist/commands/which.js +78 -0
  167. package/dist/commands/which.js.map +1 -0
  168. package/dist/commands/whoami.d.ts +4 -0
  169. package/dist/commands/whoami.d.ts.map +1 -0
  170. package/dist/commands/whoami.js +28 -0
  171. package/dist/commands/whoami.js.map +1 -0
  172. package/dist/index.d.ts +15 -0
  173. package/dist/index.d.ts.map +1 -1
  174. package/dist/index.js +72 -1
  175. package/dist/index.js.map +1 -1
  176. package/dist/shared/terminal-command.d.ts +8 -2
  177. package/dist/shared/terminal-command.d.ts.map +1 -1
  178. package/dist/shared/terminal-command.js +42 -17
  179. package/dist/shared/terminal-command.js.map +1 -1
  180. package/package.json +4 -2
  181. package/src/commands/basename.ts +84 -0
  182. package/src/commands/cal.ts +111 -0
  183. package/src/commands/cat.ts +55 -24
  184. package/src/commands/cd.ts +40 -12
  185. package/src/commands/chmod.ts +37 -11
  186. package/src/commands/comm.ts +169 -0
  187. package/src/commands/cp.ts +73 -15
  188. package/src/commands/cut.ts +214 -0
  189. package/src/commands/date.ts +97 -0
  190. package/src/commands/diff.ts +204 -0
  191. package/src/commands/dirname.ts +57 -0
  192. package/src/commands/echo.ts +53 -10
  193. package/src/commands/false.ts +31 -0
  194. package/src/commands/find.ts +184 -0
  195. package/src/commands/grep.ts +187 -0
  196. package/src/commands/head.ts +206 -0
  197. package/src/commands/hex.ts +93 -25
  198. package/src/commands/id.ts +94 -0
  199. package/src/commands/join.ts +162 -0
  200. package/src/commands/less.ts +20 -8
  201. package/src/commands/ln.ts +50 -13
  202. package/src/commands/ls.ts +21 -10
  203. package/src/commands/mkdir.ts +41 -12
  204. package/src/commands/mv.ts +31 -9
  205. package/src/commands/nc.ts +499 -0
  206. package/src/commands/nl.ts +201 -0
  207. package/src/commands/passkey.ts +46 -19
  208. package/src/commands/paste.ts +172 -0
  209. package/src/commands/pwd.ts +16 -4
  210. package/src/commands/rm.ts +118 -13
  211. package/src/commands/rmdir.ts +41 -12
  212. package/src/commands/sed.ts +64 -30
  213. package/src/commands/seq.ts +147 -0
  214. package/src/commands/sockets.ts +283 -0
  215. package/src/commands/sort.ts +175 -0
  216. package/src/commands/split.ts +154 -0
  217. package/src/commands/stat.ts +17 -8
  218. package/src/commands/tail.ts +200 -0
  219. package/src/commands/tee.ts +43 -11
  220. package/src/commands/test.ts +116 -0
  221. package/src/commands/touch.ts +41 -12
  222. package/src/commands/tr.ts +170 -0
  223. package/src/commands/true.ts +31 -0
  224. package/src/commands/uniq.ts +189 -0
  225. package/src/commands/wc.ts +181 -0
  226. package/src/commands/which.ts +89 -0
  227. package/src/commands/whoami.ts +32 -0
  228. package/src/index.ts +72 -1
  229. package/src/shared/terminal-command.ts +43 -16
@@ -0,0 +1,175 @@
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: sort [OPTION]... [FILE]...
9
+ Sort lines of text files.
10
+
11
+ -r, --reverse reverse the result of comparisons
12
+ -n, --numeric compare according to string numerical value
13
+ -u, --unique output only the first of an equal run
14
+ --help display this help and exit`
15
+ writelnStderr(process, terminal, usage)
16
+ }
17
+
18
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
19
+ return new TerminalCommand({
20
+ command: 'sort',
21
+ description: 'Sort lines of text files',
22
+ kernel,
23
+ shell,
24
+ terminal,
25
+ run: async (pid: number, argv: string[]) => {
26
+ const process = kernel.processes.get(pid) as Process | undefined
27
+
28
+ if (!process) return 1
29
+
30
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
31
+ printUsage(process, terminal)
32
+ return 0
33
+ }
34
+
35
+ const files: string[] = []
36
+ let reverse = false
37
+ let numeric = false
38
+ let unique = false
39
+
40
+ for (const arg of argv) {
41
+ if (arg === '--help' || arg === '-h') {
42
+ printUsage(process, terminal)
43
+ return 0
44
+ } else if (arg === '-r' || arg === '--reverse') {
45
+ reverse = true
46
+ } else if (arg === '-n' || arg === '--numeric') {
47
+ numeric = true
48
+ } else if (arg === '-u' || arg === '--unique') {
49
+ unique = true
50
+ } else if (arg.startsWith('-')) {
51
+ const flags = arg.slice(1).split('')
52
+ if (flags.includes('r')) reverse = true
53
+ if (flags.includes('n')) numeric = true
54
+ if (flags.includes('u')) unique = true
55
+ const invalidFlags = flags.filter(f => !['r', 'n', 'u'].includes(f))
56
+ if (invalidFlags.length > 0) {
57
+ await writelnStderr(process, terminal, `sort: invalid option -- '${invalidFlags[0]}'`)
58
+ return 1
59
+ }
60
+ } else {
61
+ files.push(arg)
62
+ }
63
+ }
64
+
65
+ const writer = process.stdout.getWriter()
66
+
67
+ try {
68
+ let lines: string[] = []
69
+
70
+ if (files.length === 0) {
71
+ if (!process.stdin) {
72
+ return 0
73
+ }
74
+
75
+ const reader = process.stdin.getReader()
76
+ const decoder = new TextDecoder()
77
+ let buffer = ''
78
+
79
+ try {
80
+ while (true) {
81
+ const { done, value } = await reader.read()
82
+ if (done) break
83
+ if (value) {
84
+ buffer += decoder.decode(value, { stream: true })
85
+ const newLines = buffer.split('\n')
86
+ buffer = newLines.pop() || ''
87
+ lines.push(...newLines)
88
+ }
89
+ }
90
+ if (buffer) {
91
+ lines.push(buffer)
92
+ }
93
+ } finally {
94
+ reader.releaseLock()
95
+ }
96
+ } else {
97
+ for (const file of files) {
98
+ const fullPath = path.resolve(shell.cwd, file)
99
+
100
+ let interrupted = false
101
+ const interruptHandler = () => { interrupted = true }
102
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
103
+
104
+ try {
105
+ if (fullPath.startsWith('/dev')) {
106
+ await writelnStderr(process, terminal, `sort: ${file}: cannot sort device files`)
107
+ continue
108
+ }
109
+
110
+ const handle = await shell.context.fs.promises.open(fullPath, 'r')
111
+ const stat = await shell.context.fs.promises.stat(fullPath)
112
+
113
+ const decoder = new TextDecoder()
114
+ let content = ''
115
+ let bytesRead = 0
116
+ const chunkSize = 1024
117
+
118
+ while (bytesRead < stat.size) {
119
+ if (interrupted) break
120
+ const data = new Uint8Array(chunkSize)
121
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
122
+ await handle.read(data, 0, readSize, bytesRead)
123
+ const chunk = data.subarray(0, readSize)
124
+ content += decoder.decode(chunk, { stream: true })
125
+ bytesRead += readSize
126
+ }
127
+
128
+ const fileLines = content.split('\n')
129
+ if (fileLines[fileLines.length - 1] === '') {
130
+ fileLines.pop()
131
+ }
132
+ lines.push(...fileLines)
133
+ } catch (error) {
134
+ await writelnStderr(process, terminal, `sort: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
135
+ } finally {
136
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
137
+ }
138
+ }
139
+ }
140
+
141
+ if (numeric) {
142
+ lines.sort((a, b) => {
143
+ const numA = parseFloat(a.trim())
144
+ const numB = parseFloat(b.trim())
145
+ if (isNaN(numA) && isNaN(numB)) return a.localeCompare(b)
146
+ if (isNaN(numA)) return 1
147
+ if (isNaN(numB)) return -1
148
+ return reverse ? numB - numA : numA - numB
149
+ })
150
+ } else {
151
+ lines.sort((a, b) => {
152
+ return reverse ? b.localeCompare(a) : a.localeCompare(b)
153
+ })
154
+ }
155
+
156
+ if (unique) {
157
+ const seen = new Set<string>()
158
+ lines = lines.filter(line => {
159
+ if (seen.has(line)) return false
160
+ seen.add(line)
161
+ return true
162
+ })
163
+ }
164
+
165
+ for (const line of lines) {
166
+ await writer.write(new TextEncoder().encode(line + '\n'))
167
+ }
168
+
169
+ return 0
170
+ } finally {
171
+ writer.releaseLock()
172
+ }
173
+ }
174
+ })
175
+ }
@@ -0,0 +1,154 @@
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: split [OPTION]... [INPUT [PREFIX]]
9
+ Split INPUT into fixed-size pieces.
10
+
11
+ -l, -lNUMBER put NUMBER lines per output file
12
+ -b, -bSIZE put SIZE bytes per output file
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: 'split',
20
+ description: 'Split a file into pieces',
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 file = ''
35
+ let lines: number | undefined
36
+ let bytes: number | undefined
37
+ let prefix = 'x'
38
+
39
+ for (let i = 0; i < argv.length; i++) {
40
+ const arg = argv[i]
41
+ if (!arg) continue
42
+
43
+ if (arg === '--help' || arg === '-h') {
44
+ printUsage(process, terminal)
45
+ return 0
46
+ } else if (arg === '-l' || arg.startsWith('-l')) {
47
+ if (arg === '-l' && i + 1 < argv.length) {
48
+ i++
49
+ const nextArg = argv[i]
50
+ if (nextArg !== undefined) {
51
+ lines = parseInt(nextArg, 10)
52
+ }
53
+ } else if (arg.startsWith('-l') && arg.length > 2) {
54
+ lines = parseInt(arg.slice(2), 10)
55
+ }
56
+ } else if (arg === '-b' || arg.startsWith('-b')) {
57
+ if (arg === '-b' && i + 1 < argv.length) {
58
+ i++
59
+ const nextArg = argv[i]
60
+ if (nextArg !== undefined) {
61
+ bytes = parseInt(nextArg, 10)
62
+ }
63
+ } else if (arg.startsWith('-b') && arg.length > 2) {
64
+ bytes = parseInt(arg.slice(2), 10)
65
+ }
66
+ } else if (!arg.startsWith('-')) {
67
+ if (!file) {
68
+ file = arg
69
+ } else if (prefix === 'x') {
70
+ prefix = arg
71
+ }
72
+ }
73
+ }
74
+
75
+ if (!file) {
76
+ await writelnStderr(process, terminal, 'split: missing file operand')
77
+ return 1
78
+ }
79
+
80
+ if (!lines && !bytes) {
81
+ await writelnStderr(process, terminal, 'split: you must specify -l or -b')
82
+ return 1
83
+ }
84
+
85
+ const fullPath = path.resolve(shell.cwd, file)
86
+
87
+ let interrupted = false
88
+ const interruptHandler = () => { interrupted = true }
89
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
90
+
91
+ try {
92
+ if (fullPath.startsWith('/dev')) {
93
+ await writelnStderr(process, terminal, `split: ${file}: cannot split device files`)
94
+ return 1
95
+ }
96
+
97
+ const handle = await shell.context.fs.promises.open(fullPath, 'r')
98
+ const stat = await shell.context.fs.promises.stat(fullPath)
99
+
100
+ const decoder = new TextDecoder()
101
+ let content = ''
102
+ let bytesRead = 0
103
+ const chunkSize = 1024
104
+
105
+ while (bytesRead < stat.size) {
106
+ if (interrupted) break
107
+ const data = new Uint8Array(chunkSize)
108
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
109
+ await handle.read(data, 0, readSize, bytesRead)
110
+ const chunk = data.subarray(0, readSize)
111
+ content += decoder.decode(chunk, { stream: true })
112
+ bytesRead += readSize
113
+ }
114
+
115
+ const dir = path.dirname(fullPath)
116
+ let fileIndex = 0
117
+
118
+ const getSuffix = (index: number): string => {
119
+ const first = Math.floor(index / 26)
120
+ const second = index % 26
121
+ return String.fromCharCode(97 + first) + String.fromCharCode(97 + second)
122
+ }
123
+
124
+ if (lines) {
125
+ const allLines = content.split('\n')
126
+ for (let i = 0; i < allLines.length; i += lines) {
127
+ const chunk = allLines.slice(i, i + lines).join('\n')
128
+ const suffix = getSuffix(fileIndex)
129
+ const outputPath = path.join(dir, `${prefix}${suffix}`)
130
+ await shell.context.fs.promises.writeFile(outputPath, chunk, 'utf-8')
131
+ fileIndex++
132
+ }
133
+ } else if (bytes) {
134
+ const encoder = new TextEncoder()
135
+ const contentBytes = encoder.encode(content)
136
+ for (let i = 0; i < contentBytes.length; i += bytes) {
137
+ const chunk = contentBytes.slice(i, i + bytes)
138
+ const suffix = getSuffix(fileIndex)
139
+ const outputPath = path.join(dir, `${prefix}${suffix}`)
140
+ await shell.context.fs.promises.writeFile(outputPath, chunk)
141
+ fileIndex++
142
+ }
143
+ }
144
+
145
+ return 0
146
+ } catch (error) {
147
+ await writelnStderr(process, terminal, `split: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
148
+ return 1
149
+ } finally {
150
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
151
+ }
152
+ }
153
+ })
154
+ }
@@ -1,11 +1,18 @@
1
1
  import path from 'path'
2
2
  import chalk from 'chalk'
3
3
  import * as zipjs from '@zip.js/zip.js'
4
- import type { CommandLineOptions } from 'command-line-args'
5
4
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
6
5
  import { TerminalCommand } from '../shared/terminal-command.js'
7
6
  import { writelnStdout } from '../shared/helpers.js'
8
7
 
8
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
9
+ const usage = `Usage: stat [OPTION]... FILE...
10
+ Display file or file system status.
11
+
12
+ --help display this help and exit`
13
+ writelnStdout(process, terminal, usage)
14
+ }
15
+
9
16
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
10
17
  return new TerminalCommand({
11
18
  command: 'stat',
@@ -13,12 +20,15 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
13
20
  kernel,
14
21
  shell,
15
22
  terminal,
16
- options: [
17
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
18
- { name: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, description: 'The path to the file or directory to display' }
19
- ],
20
- run: async (argv: CommandLineOptions, process?: Process) => {
21
- const argPath = (argv.path as string) || shell.cwd
23
+ run: async (pid: number, argv: string[]) => {
24
+ const process = kernel.processes.get(pid) as Process | undefined
25
+
26
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
27
+ printUsage(process, terminal)
28
+ return 0
29
+ }
30
+
31
+ const argPath = argv.length > 0 && argv[0] && !argv[0].startsWith('-') ? argv[0] : shell.cwd
22
32
  const fullPath = argPath ? path.resolve(shell.cwd, argPath) : shell.cwd
23
33
  const stats = await shell.context.fs.promises.stat(fullPath)
24
34
  await writelnStdout(process, terminal, JSON.stringify(stats, null, 2))
@@ -37,4 +47,3 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
37
47
  }
38
48
  })
39
49
  }
40
-
@@ -0,0 +1,200 @@
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 { writelnStdout } from '../shared/helpers.js'
6
+
7
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
8
+ const usage = `Usage: tail [OPTION]... [FILE]...
9
+ Print the last 10 lines of each FILE to standard output.
10
+
11
+ -n, -nNUMBER print the last NUMBER lines instead of 10
12
+ --help display this help and exit`
13
+ writelnStdout(process, terminal, usage)
14
+ }
15
+
16
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
17
+ return new TerminalCommand({
18
+ command: 'tail',
19
+ description: 'Print the last lines of files',
20
+ kernel,
21
+ shell,
22
+ terminal,
23
+ run: async (pid: number, argv: string[]) => {
24
+ const process = kernel.processes.get(pid) as Process | undefined
25
+
26
+ if (!process) return 1
27
+
28
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
29
+ printUsage(process, terminal)
30
+ return 0
31
+ }
32
+
33
+ let numLines = 10
34
+ const files: string[] = []
35
+
36
+ for (let i = 0; i < argv.length; i++) {
37
+ const arg = argv[i]
38
+ if (!arg) continue
39
+
40
+ if (arg === '--help' || arg === '-h') {
41
+ printUsage(process, terminal)
42
+ return 0
43
+ } else if (arg === '-n' || arg.startsWith('-n')) {
44
+ if (arg === '-n' && i + 1 < argv.length) {
45
+ i++
46
+ const nextArg = argv[i]
47
+ if (nextArg !== undefined) {
48
+ const num = parseInt(nextArg, 10)
49
+ if (!isNaN(num)) numLines = num
50
+ }
51
+ } else if (arg.startsWith('-n') && arg.length > 2) {
52
+ const num = parseInt(arg.slice(2), 10)
53
+ if (!isNaN(num)) numLines = num
54
+ }
55
+ } else if (!arg.startsWith('-')) {
56
+ files.push(arg)
57
+ }
58
+ }
59
+
60
+ const writer = process.stdout.getWriter()
61
+
62
+ try {
63
+ if (files.length === 0) {
64
+ if (!process.stdin) {
65
+ return 0
66
+ }
67
+
68
+ const reader = process.stdin.getReader()
69
+ const decoder = new TextDecoder()
70
+ const lines: string[] = []
71
+ let buffer = ''
72
+
73
+ try {
74
+ while (true) {
75
+ let readResult
76
+ try {
77
+ readResult = await reader.read()
78
+ } catch (error) {
79
+ if (error instanceof Error) {
80
+ throw error
81
+ }
82
+ break
83
+ }
84
+
85
+ const { done, value } = readResult
86
+ if (done) {
87
+ buffer += decoder.decode()
88
+ break
89
+ }
90
+ if (value) {
91
+ buffer += decoder.decode(value, { stream: true })
92
+ const newLines = buffer.split('\n')
93
+ buffer = newLines.pop() || ''
94
+ lines.push(...newLines)
95
+ }
96
+ }
97
+ if (buffer) {
98
+ lines.push(buffer)
99
+ }
100
+ } finally {
101
+ try {
102
+ reader.releaseLock()
103
+ } catch {
104
+ }
105
+ }
106
+
107
+ const output = lines.slice(-numLines).join('\n')
108
+ if (output) {
109
+ await writer.write(new TextEncoder().encode(output + '\n'))
110
+ }
111
+
112
+ return 0
113
+ }
114
+
115
+ const isMultipleFiles = files.length > 1
116
+
117
+ for (let i = 0; i < files.length; i++) {
118
+ const file = files[i]
119
+ if (!file) continue
120
+ const fullPath = path.resolve(shell.cwd, file)
121
+
122
+ if (isMultipleFiles) {
123
+ const header = i > 0 ? '\n' : ''
124
+ await writer.write(new TextEncoder().encode(`${header}==> ${file} <==\n`))
125
+ }
126
+
127
+ let interrupted = false
128
+ const interruptHandler = () => { interrupted = true }
129
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
130
+
131
+ try {
132
+ if (!fullPath.startsWith('/dev')) {
133
+ const handle = await shell.context.fs.promises.open(fullPath, 'r')
134
+ const stat = await shell.context.fs.promises.stat(fullPath)
135
+
136
+ const decoder = new TextDecoder()
137
+ let buffer = ''
138
+ let bytesRead = 0
139
+ const chunkSize = 1024
140
+
141
+ while (bytesRead < stat.size) {
142
+ if (interrupted) break
143
+ const data = new Uint8Array(chunkSize)
144
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
145
+ await handle.read(data, 0, readSize, bytesRead)
146
+ const chunk = data.subarray(0, readSize)
147
+ buffer += decoder.decode(chunk, { stream: true })
148
+ bytesRead += readSize
149
+ }
150
+
151
+ const lines = buffer.split('\n')
152
+ if (lines[lines.length - 1] === '') {
153
+ lines.pop()
154
+ }
155
+
156
+ const output = lines.slice(-numLines).join('\n')
157
+ if (output) {
158
+ await writer.write(new TextEncoder().encode(output + '\n'))
159
+ }
160
+ } else {
161
+ const device = await shell.context.fs.promises.open(fullPath)
162
+ const decoder = new TextDecoder()
163
+ const lines: string[] = []
164
+ let buffer = ''
165
+ const chunkSize = 1024
166
+ const data = new Uint8Array(chunkSize)
167
+ let bytesRead = 0
168
+
169
+ do {
170
+ if (interrupted) break
171
+ const result = await device.read(data)
172
+ bytesRead = result.bytesRead
173
+ if (bytesRead > 0) {
174
+ buffer += decoder.decode(data.subarray(0, bytesRead), { stream: true })
175
+ }
176
+ } while (bytesRead > 0)
177
+
178
+ const allLines = buffer.split('\n')
179
+ if (allLines[allLines.length - 1] === '') {
180
+ allLines.pop()
181
+ }
182
+ lines.push(...allLines)
183
+
184
+ const output = lines.slice(-numLines).join('\n')
185
+ if (output) {
186
+ await writer.write(new TextEncoder().encode(output + '\n'))
187
+ }
188
+ }
189
+ } finally {
190
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
191
+ }
192
+ }
193
+
194
+ return 0
195
+ } finally {
196
+ writer.releaseLock()
197
+ }
198
+ }
199
+ })
200
+ }
@@ -1,10 +1,19 @@
1
1
  import path from 'path'
2
- import type { CommandLineOptions } from 'command-line-args'
3
2
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
4
3
  import { TerminalEvents } from '@ecmaos/types'
5
4
  import { TerminalCommand } from '../shared/terminal-command.js'
6
5
  import { writelnStderr } from '../shared/helpers.js'
7
6
 
7
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
8
+ const usage = `Usage: tee [OPTION]... [FILE]...
9
+ Read from standard input and write to standard output and files.
10
+
11
+ -a, --append append to the given files, do not overwrite
12
+ -i, --ignore-interrupts ignore interrupt signals
13
+ --help display this help and exit`
14
+ writelnStderr(process, terminal, usage)
15
+ }
16
+
8
17
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
9
18
  return new TerminalCommand({
10
19
  command: 'tee',
@@ -12,23 +21,46 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
12
21
  kernel,
13
22
  shell,
14
23
  terminal,
15
- options: [
16
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
17
- { name: 'append', type: Boolean, alias: 'a', description: 'Append to the given files, do not overwrite' },
18
- { name: 'ignore-interrupts', type: Boolean, alias: 'i', description: 'Ignore interrupt signals' },
19
- { name: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, multiple: true, description: 'File(s) to write to' }
20
- ],
21
- run: async (argv: CommandLineOptions, process?: Process) => {
24
+ run: async (pid: number, argv: string[]) => {
25
+ const process = kernel.processes.get(pid) as Process | undefined
26
+
22
27
  if (!process) return 1
23
28
 
29
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
30
+ printUsage(process, terminal)
31
+ return 0
32
+ }
33
+
24
34
  if (!process.stdin) {
25
35
  await writelnStderr(process, terminal, 'tee: No input provided')
26
36
  return 1
27
37
  }
28
38
 
29
- const files = (argv.path as string[]) || []
30
- const append = (argv.append as boolean) || false
31
- const ignoreInterrupts = (argv['ignore-interrupts'] as boolean) || false
39
+ const files: string[] = []
40
+ let append = false
41
+ let ignoreInterrupts = false
42
+
43
+ for (const arg of argv) {
44
+ if (arg === '--help' || arg === '-h') {
45
+ printUsage(process, terminal)
46
+ return 0
47
+ } else if (arg === '-a' || arg === '--append') {
48
+ append = true
49
+ } else if (arg === '-i' || arg === '--ignore-interrupts') {
50
+ ignoreInterrupts = true
51
+ } else if (arg.startsWith('-')) {
52
+ const flags = arg.slice(1).split('')
53
+ if (flags.includes('a')) append = true
54
+ if (flags.includes('i')) ignoreInterrupts = true
55
+ const invalidFlags = flags.filter(f => !['a', 'i'].includes(f))
56
+ if (invalidFlags.length > 0) {
57
+ await writelnStderr(process, terminal, `tee: invalid option -- '${invalidFlags[0]}'`)
58
+ return 1
59
+ }
60
+ } else {
61
+ files.push(arg)
62
+ }
63
+ }
32
64
 
33
65
  const writer = process.stdout.getWriter()
34
66