@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,214 @@
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: cut OPTION... [FILE]...
9
+ Remove sections from each line of files.
10
+
11
+ -f, --fields=LIST select only these fields
12
+ -d, --delimiter=DELIM use DELIM instead of TAB for field delimiter
13
+ -c, --characters=LIST select only these characters
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: 'cut',
21
+ description: 'Remove sections from each line of 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
+ let fields: string | undefined
36
+ let delimiter: string = '\t'
37
+ let characters: string | undefined
38
+ const files: string[] = []
39
+
40
+ for (let i = 0; i < argv.length; i++) {
41
+ const arg = argv[i]
42
+ if (!arg) continue
43
+
44
+ if (arg === '--help' || arg === '-h') {
45
+ printUsage(process, terminal)
46
+ return 0
47
+ } else if (arg === '-f' || arg.startsWith('-f')) {
48
+ if (arg === '-f' && i + 1 < argv.length) {
49
+ i++
50
+ const nextArg = argv[i]
51
+ if (nextArg !== undefined) {
52
+ fields = nextArg
53
+ }
54
+ } else if (arg.startsWith('-f') && arg.length > 2) {
55
+ fields = arg.slice(2)
56
+ } else if (arg.startsWith('--fields=')) {
57
+ fields = arg.slice(9)
58
+ }
59
+ } else if (arg === '-c' || arg.startsWith('-c')) {
60
+ if (arg === '-c' && i + 1 < argv.length) {
61
+ i++
62
+ const nextArg = argv[i]
63
+ if (nextArg !== undefined) {
64
+ characters = nextArg
65
+ }
66
+ } else if (arg.startsWith('-c') && arg.length > 2) {
67
+ characters = arg.slice(2)
68
+ } else if (arg.startsWith('--characters=')) {
69
+ characters = arg.slice(13)
70
+ }
71
+ } else if (arg === '-d' || arg.startsWith('-d')) {
72
+ if (arg === '-d' && i + 1 < argv.length) {
73
+ i++
74
+ const nextArg = argv[i]
75
+ if (nextArg !== undefined) {
76
+ delimiter = nextArg
77
+ }
78
+ } else if (arg.startsWith('-d') && arg.length > 2) {
79
+ delimiter = arg.slice(2)
80
+ } else if (arg.startsWith('--delimiter=')) {
81
+ delimiter = arg.slice(12)
82
+ }
83
+ } else if (!arg.startsWith('-')) {
84
+ files.push(arg)
85
+ }
86
+ }
87
+
88
+ if (!fields && !characters) {
89
+ await writelnStderr(process, terminal, 'cut: you must specify a list of bytes, characters, or fields')
90
+ return 1
91
+ }
92
+
93
+ const writer = process.stdout.getWriter()
94
+
95
+ const parseRange = (range: string): number[] => {
96
+ const result: number[] = []
97
+ const parts = range.split(',')
98
+
99
+ for (const part of parts) {
100
+ if (part.includes('-')) {
101
+ const splitParts = part.split('-')
102
+ const start = splitParts[0]
103
+ const end = splitParts[1]
104
+ const startNum = (start === '' || start === undefined) ? 1 : parseInt(start, 10)
105
+ const endNum = (end === '' || end === undefined) ? Infinity : parseInt(end, 10)
106
+
107
+ for (let i = startNum; i <= endNum; i++) {
108
+ result.push(i)
109
+ }
110
+ } else {
111
+ result.push(parseInt(part, 10))
112
+ }
113
+ }
114
+
115
+ return result.sort((a, b) => a - b)
116
+ }
117
+
118
+ try {
119
+ let lines: string[] = []
120
+
121
+ if (files.length === 0) {
122
+ if (!process.stdin) {
123
+ return 0
124
+ }
125
+
126
+ const reader = process.stdin.getReader()
127
+ const decoder = new TextDecoder()
128
+ let buffer = ''
129
+
130
+ try {
131
+ while (true) {
132
+ const { done, value } = await reader.read()
133
+ if (done) break
134
+ if (value) {
135
+ buffer += decoder.decode(value, { stream: true })
136
+ const newLines = buffer.split('\n')
137
+ buffer = newLines.pop() || ''
138
+ lines.push(...newLines)
139
+ }
140
+ }
141
+ if (buffer) {
142
+ lines.push(buffer)
143
+ }
144
+ } finally {
145
+ reader.releaseLock()
146
+ }
147
+ } else {
148
+ for (const file of files) {
149
+ const fullPath = path.resolve(shell.cwd, file)
150
+
151
+ let interrupted = false
152
+ const interruptHandler = () => { interrupted = true }
153
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
154
+
155
+ try {
156
+ if (fullPath.startsWith('/dev')) {
157
+ await writelnStderr(process, terminal, `cut: ${file}: cannot process device files`)
158
+ continue
159
+ }
160
+
161
+ const handle = await shell.context.fs.promises.open(fullPath, 'r')
162
+ const stat = await shell.context.fs.promises.stat(fullPath)
163
+
164
+ const decoder = new TextDecoder()
165
+ let content = ''
166
+ let bytesRead = 0
167
+ const chunkSize = 1024
168
+
169
+ while (bytesRead < stat.size) {
170
+ if (interrupted) break
171
+ const data = new Uint8Array(chunkSize)
172
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
173
+ await handle.read(data, 0, readSize, bytesRead)
174
+ const chunk = data.subarray(0, readSize)
175
+ content += decoder.decode(chunk, { stream: true })
176
+ bytesRead += readSize
177
+ }
178
+
179
+ const fileLines = content.split('\n')
180
+ if (fileLines[fileLines.length - 1] === '') {
181
+ fileLines.pop()
182
+ }
183
+ lines.push(...fileLines)
184
+ } catch (error) {
185
+ await writelnStderr(process, terminal, `cut: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
186
+ } finally {
187
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
188
+ }
189
+ }
190
+ }
191
+
192
+ for (const line of lines) {
193
+ let output = ''
194
+
195
+ if (characters) {
196
+ const indices = parseRange(characters)
197
+ const chars = line.split('')
198
+ output = indices.map(i => chars[i - 1] || '').join('')
199
+ } else if (fields) {
200
+ const indices = parseRange(fields)
201
+ const parts = line.split(delimiter)
202
+ output = indices.map(i => (parts[i - 1] || '')).join(delimiter)
203
+ }
204
+
205
+ await writer.write(new TextEncoder().encode(output + '\n'))
206
+ }
207
+
208
+ return 0
209
+ } finally {
210
+ writer.releaseLock()
211
+ }
212
+ }
213
+ })
214
+ }
@@ -0,0 +1,97 @@
1
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
2
+ import { TerminalCommand } from '../shared/terminal-command.js'
3
+ import { writelnStdout } from '../shared/helpers.js'
4
+
5
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
6
+ const usage = `Usage: date [OPTION]... [+FORMAT]
7
+ Print or set the system date and time.
8
+
9
+ -I, --iso-8601[=TIMESPEC] output date/time in ISO 8601 format
10
+ -R, --rfc-2822 output date and time in RFC 2822 format
11
+ -f, --format=FORMAT output date/time in specified format (strftime-like)
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: 'date',
19
+ description: 'Print or set the system date and time',
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 (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
27
+ printUsage(process, terminal)
28
+ return 0
29
+ }
30
+
31
+ const now = new Date()
32
+ let output = ''
33
+ let iso8601 = false
34
+ let rfc2822 = false
35
+ let format: string | undefined
36
+
37
+ for (let i = 0; i < argv.length; i++) {
38
+ const arg = argv[i]
39
+ if (!arg) continue
40
+
41
+ if (arg === '--help' || arg === '-h') {
42
+ printUsage(process, terminal)
43
+ return 0
44
+ } else if (arg === '-I' || arg === '--iso-8601') {
45
+ iso8601 = true
46
+ } else if (arg === '-R' || arg === '--rfc-2822') {
47
+ rfc2822 = true
48
+ } else if (arg === '-f' || arg === '--format') {
49
+ if (i + 1 < argv.length) {
50
+ format = argv[++i]
51
+ } else {
52
+ await writelnStdout(process, terminal, 'date: option requires an argument -- \'f\'')
53
+ return 1
54
+ }
55
+ } else if (arg.startsWith('--format=')) {
56
+ format = arg.slice(9)
57
+ } else if (arg.startsWith('--iso-8601=')) {
58
+ iso8601 = true
59
+ } else if (arg.startsWith('-f')) {
60
+ format = arg.slice(2)
61
+ } else if (arg.startsWith('+')) {
62
+ format = arg.slice(1)
63
+ }
64
+ }
65
+
66
+ if (iso8601) {
67
+ output = now.toISOString()
68
+ } else if (rfc2822) {
69
+ output = now.toUTCString()
70
+ } else if (format) {
71
+ const year = now.getFullYear()
72
+ const month = String(now.getMonth() + 1).padStart(2, '0')
73
+ const day = String(now.getDate()).padStart(2, '0')
74
+ const hours = String(now.getHours()).padStart(2, '0')
75
+ const minutes = String(now.getMinutes()).padStart(2, '0')
76
+ const seconds = String(now.getSeconds()).padStart(2, '0')
77
+ const milliseconds = String(now.getMilliseconds()).padStart(3, '0')
78
+
79
+ output = format
80
+ .replace(/%Y/g, String(year))
81
+ .replace(/%m/g, month)
82
+ .replace(/%d/g, day)
83
+ .replace(/%H/g, hours)
84
+ .replace(/%M/g, minutes)
85
+ .replace(/%S/g, seconds)
86
+ .replace(/%s/g, String(Math.floor(now.getTime() / 1000)))
87
+ .replace(/%f/g, milliseconds)
88
+ .replace(/%z/g, now.getTimezoneOffset().toString())
89
+ } else {
90
+ output = now.toString()
91
+ }
92
+
93
+ await writelnStdout(process, terminal, output)
94
+ return 0
95
+ }
96
+ })
97
+ }
@@ -0,0 +1,204 @@
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: diff [OPTION]... FILE1 FILE2
9
+ Compare files line by line.
10
+
11
+ -u, --unified=NUM output NUM (default 3) lines of unified context
12
+ -c, --context=NUM output NUM (default 3) lines of copied context
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: 'diff',
20
+ description: 'Compare files line by line',
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
+ const files: string[] = []
35
+ // TODO: Implement unified and context options in diff output
36
+ // @ts-ignore - reserved for future implementation
37
+ let _unified = 3
38
+ // @ts-ignore - reserved for future implementation
39
+ let _context = 3
40
+
41
+ for (let i = 0; i < argv.length; i++) {
42
+ const arg = argv[i]
43
+ if (!arg) continue
44
+
45
+ if (arg === '--help' || arg === '-h') {
46
+ printUsage(process, terminal)
47
+ return 0
48
+ } else if (arg === '-u' || arg === '--unified') {
49
+ if (i + 1 < argv.length) {
50
+ const nextArg = argv[++i]
51
+ if (nextArg !== undefined) {
52
+ const num = parseInt(nextArg, 10)
53
+ if (!isNaN(num)) _unified = num
54
+ }
55
+ }
56
+ } else if (arg.startsWith('--unified=')) {
57
+ const num = parseInt(arg.slice(10), 10)
58
+ if (!isNaN(num)) _unified = num
59
+ } else if (arg === '-c' || arg === '--context') {
60
+ if (i + 1 < argv.length) {
61
+ const nextArg = argv[++i]
62
+ if (nextArg !== undefined) {
63
+ const num = parseInt(nextArg, 10)
64
+ if (!isNaN(num)) _context = num
65
+ }
66
+ }
67
+ } else if (arg.startsWith('--context=')) {
68
+ const num = parseInt(arg.slice(10), 10)
69
+ if (!isNaN(num)) _context = num
70
+ } else if (!arg.startsWith('-')) {
71
+ files.push(arg)
72
+ }
73
+ }
74
+
75
+ if (files.length !== 2) {
76
+ await writelnStderr(process, terminal, 'diff: exactly two files must be specified')
77
+ return 1
78
+ }
79
+
80
+ const file1 = files[0]
81
+ const file2 = files[1]
82
+ if (!file1 || !file2) {
83
+ await writelnStderr(process, terminal, 'diff: exactly two files must be specified')
84
+ return 1
85
+ }
86
+ const fullPath1 = path.resolve(shell.cwd || '/', file1)
87
+ const fullPath2 = path.resolve(shell.cwd || '/', file2)
88
+
89
+ const writer = process.stdout.getWriter()
90
+
91
+ const readFile = async (filePath: string): Promise<string> => {
92
+ if (filePath.startsWith('/dev')) {
93
+ throw new Error('cannot diff device files')
94
+ }
95
+
96
+ let interrupted = false
97
+ const interruptHandler = () => { interrupted = true }
98
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
99
+
100
+ try {
101
+ const handle = await shell.context.fs.promises.open(filePath, 'r')
102
+ const stat = await shell.context.fs.promises.stat(filePath)
103
+
104
+ const decoder = new TextDecoder()
105
+ let content = ''
106
+ let bytesRead = 0
107
+ const chunkSize = 1024
108
+
109
+ while (bytesRead < stat.size) {
110
+ if (interrupted) break
111
+ const data = new Uint8Array(chunkSize)
112
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
113
+ await handle.read(data, 0, readSize, bytesRead)
114
+ const chunk = data.subarray(0, readSize)
115
+ content += decoder.decode(chunk, { stream: true })
116
+ bytesRead += readSize
117
+ }
118
+
119
+ return content
120
+ } finally {
121
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
122
+ }
123
+ }
124
+
125
+ try {
126
+ const content1 = await readFile(fullPath1)
127
+ const content2 = await readFile(fullPath2)
128
+
129
+ const lines1 = content1.split('\n')
130
+ const lines2 = content2.split('\n')
131
+
132
+ if (lines1[lines1.length - 1] === '') lines1.pop()
133
+ if (lines2[lines2.length - 1] === '') lines2.pop()
134
+
135
+ const lcs = (a: string[], b: string[]): number[][] => {
136
+ const m = a.length
137
+ const n = b.length
138
+ const dp: number[][] = []
139
+ for (let i = 0; i <= m; i++) {
140
+ dp[i] = Array(n + 1).fill(0)
141
+ }
142
+
143
+ for (let i = 1; i <= m; i++) {
144
+ const row = dp[i]!
145
+ for (let j = 1; j <= n; j++) {
146
+ if (a[i - 1] === b[j - 1]) {
147
+ const prev = dp[i - 1]?.[j - 1] ?? 0
148
+ row[j] = prev + 1
149
+ } else {
150
+ const left = dp[i - 1]?.[j] ?? 0
151
+ const up = row[j - 1] ?? 0
152
+ row[j] = Math.max(left, up)
153
+ }
154
+ }
155
+ }
156
+
157
+ return dp
158
+ }
159
+
160
+ const diff = (a: string[], b: string[]): string[] => {
161
+ const dp = lcs(a, b)
162
+ const result: string[] = []
163
+ let i = a.length
164
+ let j = b.length
165
+
166
+ while (i > 0 || j > 0) {
167
+ if (i > 0 && j > 0 && a[i - 1] === b[j - 1]) {
168
+ result.unshift(` ${a[i - 1]}`)
169
+ i--
170
+ j--
171
+ } else if (j > 0 && (i === 0 || (dp[i]?.[j - 1] ?? 0) >= (dp[i - 1]?.[j] ?? 0))) {
172
+ result.unshift(`+ ${b[j - 1]}`)
173
+ j--
174
+ } else if (i > 0) {
175
+ result.unshift(`- ${a[i - 1]}`)
176
+ i--
177
+ }
178
+ }
179
+
180
+ return result
181
+ }
182
+
183
+ const diffLines = diff(lines1, lines2)
184
+
185
+ if (diffLines.length === 0 || diffLines.every(line => line.startsWith(' '))) {
186
+ return 0
187
+ }
188
+
189
+ await writer.write(new TextEncoder().encode(`--- ${file1}\n+++ ${file2}\n`))
190
+
191
+ for (const line of diffLines) {
192
+ await writer.write(new TextEncoder().encode(line + '\n'))
193
+ }
194
+
195
+ return 1
196
+ } catch (error) {
197
+ await writelnStderr(process, terminal, `diff: ${error instanceof Error ? error.message : 'Unknown error'}`)
198
+ return 1
199
+ } finally {
200
+ writer.releaseLock()
201
+ }
202
+ }
203
+ })
204
+ }
@@ -0,0 +1,57 @@
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: dirname [OPTION] NAME...
8
+ Output each NAME with its last non-slash component and trailing slashes removed.
9
+
10
+ --help display this help and exit`
11
+ writelnStderr(process, terminal, usage)
12
+ }
13
+
14
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
15
+ return new TerminalCommand({
16
+ command: 'dirname',
17
+ description: 'Strip last component from file path',
18
+ kernel,
19
+ shell,
20
+ terminal,
21
+ run: async (pid: number, argv: string[]) => {
22
+ const process = kernel.processes.get(pid) as Process | undefined
23
+
24
+ if (!process) return 1
25
+
26
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
27
+ printUsage(process, terminal)
28
+ return 0
29
+ }
30
+
31
+ const paths: string[] = []
32
+ for (const arg of argv) {
33
+ if (arg !== '--help' && arg !== '-h' && !arg.startsWith('-')) {
34
+ paths.push(arg)
35
+ }
36
+ }
37
+
38
+ if (paths.length === 0) {
39
+ await writelnStderr(process, terminal, 'dirname: missing operand')
40
+ return 1
41
+ }
42
+
43
+ const writer = process.stdout.getWriter()
44
+
45
+ try {
46
+ for (const filePath of paths) {
47
+ const dir = path.dirname(filePath)
48
+ await writer.write(new TextEncoder().encode(dir + '\n'))
49
+ }
50
+
51
+ return 0
52
+ } finally {
53
+ writer.releaseLock()
54
+ }
55
+ }
56
+ })
57
+ }
@@ -1,6 +1,15 @@
1
- import type { CommandLineOptions } from 'command-line-args'
2
1
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
3
2
  import { TerminalCommand } from '../shared/terminal-command.js'
3
+ import { writelnStdout } from '../shared/helpers.js'
4
+
5
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
6
+ const usage = `Usage: echo [OPTION]... [STRING]...
7
+ Echo the STRING(s) to standard output.
8
+
9
+ -n do not output the trailing newline
10
+ --help display this help and exit`
11
+ writelnStdout(process, terminal, usage)
12
+ }
4
13
 
5
14
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
6
15
  return new TerminalCommand({
@@ -9,23 +18,57 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
9
18
  kernel,
10
19
  shell,
11
20
  terminal,
12
- options: [
13
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
14
- { name: 'text', type: String, typeLabel: '{underline text}', defaultOption: true, multiple: true, description: 'The text to print' }
15
- ],
16
- run: async (argv: CommandLineOptions, process?: Process) => {
17
- const text = ((argv.text as string[]) || []).join(' ')
18
- const data = new TextEncoder().encode(text + '\n')
21
+ run: async (pid: number, argv: string[]) => {
22
+ const process = kernel.processes.get(pid) as Process | undefined
23
+
24
+ if (argv.length === 0) {
25
+ await writelnStdout(process, terminal, '')
26
+ return 0
27
+ }
28
+
29
+ let noNewline = false
30
+ const textParts: string[] = []
31
+ let i = 0
32
+
33
+ while (i < argv.length) {
34
+ const arg = argv[i]
35
+ if (!arg) {
36
+ i++
37
+ continue
38
+ }
39
+ if (arg === '--help' || arg === '-h') {
40
+ printUsage(process, terminal)
41
+ return 0
42
+ } else if (arg === '-n') {
43
+ noNewline = true
44
+ } else if (arg.startsWith('-') && arg.length > 1 && arg !== '--') {
45
+ const flags = arg.slice(1).split('')
46
+ if (flags.includes('n')) {
47
+ noNewline = true
48
+ }
49
+ const invalidFlag = flags.find(f => f !== 'n')
50
+ if (invalidFlag) {
51
+ await writelnStdout(process, terminal, `echo: invalid option -- '${invalidFlag}'`)
52
+ return 1
53
+ }
54
+ } else {
55
+ textParts.push(arg)
56
+ }
57
+ i++
58
+ }
59
+
60
+ const text = textParts.join(' ')
61
+ const output = noNewline ? text : text + '\n'
19
62
 
20
63
  if (process) {
21
64
  const writer = process.stdout.getWriter()
22
65
  try {
23
- await writer.write(data)
66
+ await writer.write(new TextEncoder().encode(output))
24
67
  } finally {
25
68
  writer.releaseLock()
26
69
  }
27
70
  } else {
28
- terminal.write(text + '\n')
71
+ terminal.write(output)
29
72
  }
30
73
 
31
74
  return 0
@@ -0,0 +1,31 @@
1
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
2
+ import { TerminalCommand } from '../shared/terminal-command.js'
3
+ import { writelnStdout } from '../shared/helpers.js'
4
+
5
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
6
+ const usage = `Usage: false
7
+ Return an unsuccessful exit status.
8
+
9
+ --help display this help and exit`
10
+ writelnStdout(process, terminal, usage)
11
+ }
12
+
13
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
14
+ return new TerminalCommand({
15
+ command: 'false',
16
+ description: 'Return an unsuccessful exit status',
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 (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
24
+ printUsage(process, terminal)
25
+ return 0
26
+ }
27
+
28
+ return 1
29
+ }
30
+ })
31
+ }