@ecmaos/coreutils 0.1.5 → 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 (226) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +31 -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 +33 -25
  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 +49 -10
  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.map +1 -1
  55. package/dist/commands/grep.js +45 -10
  56. package/dist/commands/grep.js.map +1 -1
  57. package/dist/commands/head.d.ts.map +1 -1
  58. package/dist/commands/head.js +46 -9
  59. package/dist/commands/head.js.map +1 -1
  60. package/dist/commands/hex.d.ts.map +1 -1
  61. package/dist/commands/hex.js +16 -6
  62. package/dist/commands/hex.js.map +1 -1
  63. package/dist/commands/id.d.ts +4 -0
  64. package/dist/commands/id.d.ts.map +1 -0
  65. package/dist/commands/id.js +97 -0
  66. package/dist/commands/id.js.map +1 -0
  67. package/dist/commands/join.d.ts +4 -0
  68. package/dist/commands/join.d.ts.map +1 -0
  69. package/dist/commands/join.js +152 -0
  70. package/dist/commands/join.js.map +1 -0
  71. package/dist/commands/less.d.ts.map +1 -1
  72. package/dist/commands/less.js +16 -7
  73. package/dist/commands/less.js.map +1 -1
  74. package/dist/commands/ln.d.ts.map +1 -1
  75. package/dist/commands/ln.js +54 -12
  76. package/dist/commands/ln.js.map +1 -1
  77. package/dist/commands/ls.d.ts.map +1 -1
  78. package/dist/commands/ls.js +14 -6
  79. package/dist/commands/ls.js.map +1 -1
  80. package/dist/commands/mkdir.d.ts.map +1 -1
  81. package/dist/commands/mkdir.js +34 -9
  82. package/dist/commands/mkdir.js.map +1 -1
  83. package/dist/commands/mv.d.ts.map +1 -1
  84. package/dist/commands/mv.js +25 -7
  85. package/dist/commands/mv.js.map +1 -1
  86. package/dist/commands/nc.d.ts +4 -0
  87. package/dist/commands/nc.d.ts.map +1 -0
  88. package/dist/commands/nc.js +451 -0
  89. package/dist/commands/nc.js.map +1 -0
  90. package/dist/commands/nl.d.ts +4 -0
  91. package/dist/commands/nl.d.ts.map +1 -0
  92. package/dist/commands/nl.js +208 -0
  93. package/dist/commands/nl.js.map +1 -0
  94. package/dist/commands/passkey.d.ts.map +1 -1
  95. package/dist/commands/passkey.js +44 -18
  96. package/dist/commands/passkey.js.map +1 -1
  97. package/dist/commands/paste.d.ts +4 -0
  98. package/dist/commands/paste.d.ts.map +1 -0
  99. package/dist/commands/paste.js +161 -0
  100. package/dist/commands/paste.js.map +1 -0
  101. package/dist/commands/pwd.d.ts.map +1 -1
  102. package/dist/commands/pwd.js +13 -4
  103. package/dist/commands/pwd.js.map +1 -1
  104. package/dist/commands/rm.d.ts.map +1 -1
  105. package/dist/commands/rm.js +105 -12
  106. package/dist/commands/rm.js.map +1 -1
  107. package/dist/commands/rmdir.d.ts.map +1 -1
  108. package/dist/commands/rmdir.js +34 -9
  109. package/dist/commands/rmdir.js.map +1 -1
  110. package/dist/commands/sed.d.ts.map +1 -1
  111. package/dist/commands/sed.js +73 -28
  112. package/dist/commands/sed.js.map +1 -1
  113. package/dist/commands/seq.d.ts +4 -0
  114. package/dist/commands/seq.d.ts.map +1 -0
  115. package/dist/commands/seq.js +148 -0
  116. package/dist/commands/seq.js.map +1 -0
  117. package/dist/commands/sockets.d.ts +4 -0
  118. package/dist/commands/sockets.d.ts.map +1 -0
  119. package/dist/commands/sockets.js +239 -0
  120. package/dist/commands/sockets.js.map +1 -0
  121. package/dist/commands/sort.d.ts +4 -0
  122. package/dist/commands/sort.d.ts.map +1 -0
  123. package/dist/commands/sort.js +175 -0
  124. package/dist/commands/sort.js.map +1 -0
  125. package/dist/commands/split.d.ts +4 -0
  126. package/dist/commands/split.d.ts.map +1 -0
  127. package/dist/commands/split.js +147 -0
  128. package/dist/commands/split.js.map +1 -0
  129. package/dist/commands/stat.d.ts.map +1 -1
  130. package/dist/commands/stat.js +14 -6
  131. package/dist/commands/stat.js.map +1 -1
  132. package/dist/commands/tail.d.ts.map +1 -1
  133. package/dist/commands/tail.js +46 -9
  134. package/dist/commands/tail.js.map +1 -1
  135. package/dist/commands/tee.d.ts.map +1 -1
  136. package/dist/commands/tee.js +45 -10
  137. package/dist/commands/tee.js.map +1 -1
  138. package/dist/commands/test.d.ts +4 -0
  139. package/dist/commands/test.d.ts.map +1 -0
  140. package/dist/commands/test.js +99 -0
  141. package/dist/commands/test.js.map +1 -0
  142. package/dist/commands/touch.d.ts.map +1 -1
  143. package/dist/commands/touch.js +34 -9
  144. package/dist/commands/touch.js.map +1 -1
  145. package/dist/commands/tr.d.ts +4 -0
  146. package/dist/commands/tr.d.ts.map +1 -0
  147. package/dist/commands/tr.js +162 -0
  148. package/dist/commands/tr.js.map +1 -0
  149. package/dist/commands/true.d.ts +4 -0
  150. package/dist/commands/true.d.ts.map +1 -0
  151. package/dist/commands/true.js +27 -0
  152. package/dist/commands/true.js.map +1 -0
  153. package/dist/commands/uniq.d.ts +4 -0
  154. package/dist/commands/uniq.d.ts.map +1 -0
  155. package/dist/commands/uniq.js +187 -0
  156. package/dist/commands/uniq.js.map +1 -0
  157. package/dist/commands/wc.d.ts +4 -0
  158. package/dist/commands/wc.d.ts.map +1 -0
  159. package/dist/commands/wc.js +178 -0
  160. package/dist/commands/wc.js.map +1 -0
  161. package/dist/commands/which.d.ts +4 -0
  162. package/dist/commands/which.d.ts.map +1 -0
  163. package/dist/commands/which.js +78 -0
  164. package/dist/commands/which.js.map +1 -0
  165. package/dist/commands/whoami.d.ts +4 -0
  166. package/dist/commands/whoami.d.ts.map +1 -0
  167. package/dist/commands/whoami.js +28 -0
  168. package/dist/commands/whoami.js.map +1 -0
  169. package/dist/index.d.ts +13 -0
  170. package/dist/index.d.ts.map +1 -1
  171. package/dist/index.js +64 -1
  172. package/dist/index.js.map +1 -1
  173. package/dist/shared/terminal-command.d.ts +8 -2
  174. package/dist/shared/terminal-command.d.ts.map +1 -1
  175. package/dist/shared/terminal-command.js +42 -17
  176. package/dist/shared/terminal-command.js.map +1 -1
  177. package/package.json +4 -2
  178. package/src/commands/basename.ts +84 -0
  179. package/src/commands/cal.ts +111 -0
  180. package/src/commands/cat.ts +37 -27
  181. package/src/commands/cd.ts +40 -12
  182. package/src/commands/chmod.ts +37 -11
  183. package/src/commands/comm.ts +169 -0
  184. package/src/commands/cp.ts +73 -15
  185. package/src/commands/cut.ts +214 -0
  186. package/src/commands/date.ts +97 -0
  187. package/src/commands/diff.ts +204 -0
  188. package/src/commands/dirname.ts +57 -0
  189. package/src/commands/echo.ts +51 -11
  190. package/src/commands/false.ts +31 -0
  191. package/src/commands/find.ts +184 -0
  192. package/src/commands/grep.ts +44 -11
  193. package/src/commands/head.ts +46 -10
  194. package/src/commands/hex.ts +19 -7
  195. package/src/commands/id.ts +94 -0
  196. package/src/commands/join.ts +162 -0
  197. package/src/commands/less.ts +20 -8
  198. package/src/commands/ln.ts +50 -13
  199. package/src/commands/ls.ts +17 -8
  200. package/src/commands/mkdir.ts +41 -12
  201. package/src/commands/mv.ts +31 -9
  202. package/src/commands/nc.ts +499 -0
  203. package/src/commands/nl.ts +201 -0
  204. package/src/commands/passkey.ts +46 -19
  205. package/src/commands/paste.ts +172 -0
  206. package/src/commands/pwd.ts +16 -4
  207. package/src/commands/rm.ts +118 -13
  208. package/src/commands/rmdir.ts +41 -12
  209. package/src/commands/sed.ts +64 -30
  210. package/src/commands/seq.ts +147 -0
  211. package/src/commands/sockets.ts +283 -0
  212. package/src/commands/sort.ts +175 -0
  213. package/src/commands/split.ts +154 -0
  214. package/src/commands/stat.ts +17 -8
  215. package/src/commands/tail.ts +46 -10
  216. package/src/commands/tee.ts +43 -11
  217. package/src/commands/test.ts +116 -0
  218. package/src/commands/touch.ts +41 -12
  219. package/src/commands/tr.ts +170 -0
  220. package/src/commands/true.ts +31 -0
  221. package/src/commands/uniq.ts +189 -0
  222. package/src/commands/wc.ts +181 -0
  223. package/src/commands/which.ts +89 -0
  224. package/src/commands/whoami.ts +32 -0
  225. package/src/index.ts +64 -1
  226. 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
-
@@ -1,8 +1,17 @@
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'
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
+ }
6
15
 
7
16
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
8
17
  return new TerminalCommand({
@@ -11,19 +20,47 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
11
20
  kernel,
12
21
  shell,
13
22
  terminal,
14
- options: [
15
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
16
- { name: 'lines', type: Number, alias: 'n', description: 'Print the last NUM lines instead of the last 10' },
17
- { name: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, multiple: true, description: 'The path(s) to the file(s) to read' }
18
- ],
19
- run: async (argv: CommandLineOptions, process?: Process) => {
23
+ run: async (pid: number, argv: string[]) => {
24
+ const process = kernel.processes.get(pid) as Process | undefined
25
+
20
26
  if (!process) return 1
21
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
+
22
60
  const writer = process.stdout.getWriter()
23
- const numLines = (argv.lines as number) ?? 10
24
61
 
25
62
  try {
26
- if (!argv.path || !(argv.path as string[])[0]) {
63
+ if (files.length === 0) {
27
64
  if (!process.stdin) {
28
65
  return 0
29
66
  }
@@ -75,7 +112,6 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
75
112
  return 0
76
113
  }
77
114
 
78
- const files = (argv.path as string[]) || []
79
115
  const isMultipleFiles = files.length > 1
80
116
 
81
117
  for (let i = 0; i < files.length; i++) {
@@ -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
 
@@ -0,0 +1,116 @@
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: test EXPRESSION
8
+ test [OPTION]
9
+ Check file types and compare values.
10
+
11
+ -f FILE FILE exists and is a regular file
12
+ -d FILE FILE exists and is a directory
13
+ -e FILE FILE exists
14
+ -r FILE FILE exists and is readable
15
+ -w FILE FILE exists and is writable
16
+ -x FILE FILE exists and is executable
17
+ -n STRING STRING is not empty
18
+ -z STRING STRING is empty (zero length)
19
+ STRING1 = STRING2 strings are equal
20
+ STRING1 != STRING2 strings are not equal
21
+ --help display this help and exit`
22
+ writelnStderr(process, terminal, usage)
23
+ }
24
+
25
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
26
+ return new TerminalCommand({
27
+ command: 'test',
28
+ description: 'Check file types and compare values',
29
+ kernel,
30
+ shell,
31
+ terminal,
32
+ run: async (pid: number, argv: string[]) => {
33
+ const process = kernel.processes.get(pid) as Process | undefined
34
+
35
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
36
+ printUsage(process, terminal)
37
+ return 0
38
+ }
39
+
40
+ const checkFile = async (filePath: string, check: string): Promise<boolean> => {
41
+ const fullPath = path.resolve(shell.cwd, filePath)
42
+
43
+ try {
44
+ const stat = await shell.context.fs.promises.stat(fullPath)
45
+
46
+ switch (check) {
47
+ case 'f':
48
+ return stat.isFile()
49
+ case 'd':
50
+ return stat.isDirectory()
51
+ case 'e':
52
+ return true
53
+ case 'r':
54
+ case 'w':
55
+ case 'x':
56
+ return true
57
+ default:
58
+ return false
59
+ }
60
+ } catch {
61
+ return false
62
+ }
63
+ }
64
+
65
+ if (argv.length === 0) {
66
+ return 1
67
+ }
68
+
69
+ const operator = argv[0]
70
+
71
+ if (operator === '-f' && argv[1]) {
72
+ return (await checkFile(argv[1], 'f')) ? 0 : 1
73
+ }
74
+
75
+ if (operator === '-d' && argv[1]) {
76
+ return (await checkFile(argv[1], 'd')) ? 0 : 1
77
+ }
78
+
79
+ if (operator === '-e' && argv[1]) {
80
+ return (await checkFile(argv[1], 'e')) ? 0 : 1
81
+ }
82
+
83
+ if (operator === '-r' && argv[1]) {
84
+ return (await checkFile(argv[1], 'r')) ? 0 : 1
85
+ }
86
+
87
+ if (operator === '-w' && argv[1]) {
88
+ return (await checkFile(argv[1], 'w')) ? 0 : 1
89
+ }
90
+
91
+ if (operator === '-x' && argv[1]) {
92
+ return (await checkFile(argv[1], 'x')) ? 0 : 1
93
+ }
94
+
95
+ if (operator === '-n' && argv[1]) {
96
+ return argv[1].length > 0 ? 0 : 1
97
+ }
98
+
99
+ if (operator === '-z' && argv[1]) {
100
+ return argv[1].length === 0 ? 0 : 1
101
+ }
102
+
103
+ if (argv.length === 3) {
104
+ const [left, op, right] = argv
105
+ if (op === '=') {
106
+ return left === right ? 0 : 1
107
+ }
108
+ if (op === '!=') {
109
+ return left !== right ? 0 : 1
110
+ }
111
+ }
112
+
113
+ return 1
114
+ }
115
+ })
116
+ }