@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,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,21 +18,52 @@ 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: 'n', type: Boolean, alias: 'n', description: 'Do not output the trailing newline' },
15
- { name: 'text', type: String, typeLabel: '{underline text}', defaultOption: true, multiple: true, description: 'The text to print' }
16
- ],
17
- run: async (argv: CommandLineOptions, process?: Process) => {
18
- const noNewline = (argv.n as boolean) || false
19
- const text = ((argv.text as string[]) || []).join(' ')
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(' ')
20
61
  const output = noNewline ? text : text + '\n'
21
- const data = new TextEncoder().encode(output)
22
62
 
23
63
  if (process) {
24
64
  const writer = process.stdout.getWriter()
25
65
  try {
26
- await writer.write(data)
66
+ await writer.write(new TextEncoder().encode(output))
27
67
  } finally {
28
68
  writer.releaseLock()
29
69
  }
@@ -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
+ }
@@ -0,0 +1,184 @@
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: find [PATH]... [OPTION]...
9
+ Search for files in a directory hierarchy.
10
+
11
+ -name PATTERN file name matches shell pattern PATTERN
12
+ -type TYPE file is of type TYPE (f=file, d=directory, l=symlink)
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: 'find',
20
+ description: 'Search for files in a directory hierarchy',
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
+ if (argv.length === 0) {
35
+ await writelnStderr(process, terminal, 'find: missing path argument')
36
+ return 1
37
+ }
38
+
39
+ let startPaths: string[] = []
40
+ let namePattern: string | undefined
41
+ let fileType: string | undefined
42
+
43
+ for (let i = 0; i < argv.length; i++) {
44
+ const arg = argv[i]
45
+ if (arg === undefined) continue
46
+
47
+ if (arg === '-name') {
48
+ if (i + 1 < argv.length) {
49
+ i++
50
+ const nextArg = argv[i]
51
+ if (nextArg !== undefined && typeof nextArg === 'string' && !nextArg.startsWith('-')) {
52
+ namePattern = nextArg
53
+ } else {
54
+ await writelnStderr(process, terminal, 'find: missing argument to -name')
55
+ return 1
56
+ }
57
+ } else {
58
+ await writelnStderr(process, terminal, 'find: missing argument to -name')
59
+ return 1
60
+ }
61
+ } else if (arg === '-type') {
62
+ if (i + 1 < argv.length) {
63
+ i++
64
+ const nextArg = argv[i]
65
+ if (nextArg !== undefined && typeof nextArg === 'string' && !nextArg.startsWith('-')) {
66
+ fileType = nextArg
67
+ } else {
68
+ await writelnStderr(process, terminal, 'find: missing argument to -type')
69
+ return 1
70
+ }
71
+ } else {
72
+ await writelnStderr(process, terminal, 'find: missing argument to -type')
73
+ return 1
74
+ }
75
+ } else if (typeof arg === 'string' && !arg.startsWith('-')) {
76
+ startPaths.push(arg)
77
+ }
78
+ }
79
+
80
+ if (startPaths.length === 0) {
81
+ startPaths = [shell.cwd]
82
+ }
83
+
84
+ const writer = process.stdout.getWriter()
85
+
86
+ const matchesPattern = (filename: string, pattern: string): boolean => {
87
+ if (!pattern) return false
88
+
89
+ let regexPattern = ''
90
+ for (let i = 0; i < pattern.length; i++) {
91
+ const char = pattern[i]
92
+ if (char === undefined) continue
93
+ if (char === '*') {
94
+ regexPattern += '.*'
95
+ } else if (char === '?') {
96
+ regexPattern += '.'
97
+ } else if (char === '.') {
98
+ regexPattern += '\\.'
99
+ } else {
100
+ const needsEscaping = /[+^${}()|[\]\\]/.test(char)
101
+ regexPattern += needsEscaping ? '\\' + char : char
102
+ }
103
+ }
104
+ try {
105
+ const regex = new RegExp(`^${regexPattern}$`)
106
+ return regex.test(filename)
107
+ } catch {
108
+ return false
109
+ }
110
+ }
111
+
112
+ const searchDirectory = async (dirPath: string): Promise<void> => {
113
+ let interrupted = false
114
+ const interruptHandler = () => { interrupted = true }
115
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
116
+
117
+ try {
118
+ if (interrupted) return
119
+
120
+ const entries = await shell.context.fs.promises.readdir(dirPath)
121
+
122
+ for (const entry of entries) {
123
+ if (interrupted) break
124
+
125
+ const fullPath = path.join(dirPath, entry)
126
+
127
+ try {
128
+ const stat = await shell.context.fs.promises.stat(fullPath)
129
+
130
+ let matches = true
131
+
132
+ if (namePattern) {
133
+ const matchesName = matchesPattern(entry, namePattern)
134
+ if (!matchesName) {
135
+ matches = false
136
+ }
137
+ }
138
+
139
+ if (fileType && matches) {
140
+ if (fileType === 'f' && !stat.isFile()) matches = false
141
+ else if (fileType === 'd' && !stat.isDirectory()) matches = false
142
+ else if (fileType === 'l' && !stat.isSymbolicLink()) matches = false
143
+ }
144
+
145
+ if (matches) {
146
+ await writer.write(new TextEncoder().encode(fullPath + '\n'))
147
+ }
148
+
149
+ if (stat.isDirectory() && !stat.isSymbolicLink()) {
150
+ await searchDirectory(fullPath)
151
+ }
152
+ } catch {
153
+ }
154
+ }
155
+ } catch {
156
+ } finally {
157
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
158
+ }
159
+ }
160
+
161
+ try {
162
+ for (const startPath of startPaths) {
163
+ const fullPath = path.resolve(shell.cwd, startPath)
164
+
165
+ try {
166
+ const stat = await shell.context.fs.promises.stat(fullPath)
167
+ if (!stat.isDirectory()) {
168
+ await writelnStderr(process, terminal, `find: ${startPath}: not a directory`)
169
+ continue
170
+ }
171
+
172
+ await searchDirectory(fullPath)
173
+ } catch (error) {
174
+ await writelnStderr(process, terminal, `find: ${startPath}: ${error instanceof Error ? error.message : 'Unknown error'}`)
175
+ }
176
+ }
177
+
178
+ return 0
179
+ } finally {
180
+ writer.releaseLock()
181
+ }
182
+ }
183
+ })
184
+ }