@ecmaos/coreutils 0.1.5 → 0.2.1

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 (231) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +45 -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 +96 -91
  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/user.d.ts +4 -0
  158. package/dist/commands/user.d.ts.map +1 -0
  159. package/dist/commands/user.js +427 -0
  160. package/dist/commands/user.js.map +1 -0
  161. package/dist/commands/wc.d.ts +4 -0
  162. package/dist/commands/wc.d.ts.map +1 -0
  163. package/dist/commands/wc.js +178 -0
  164. package/dist/commands/wc.js.map +1 -0
  165. package/dist/commands/which.d.ts +4 -0
  166. package/dist/commands/which.d.ts.map +1 -0
  167. package/dist/commands/which.js +78 -0
  168. package/dist/commands/which.js.map +1 -0
  169. package/dist/commands/whoami.d.ts +4 -0
  170. package/dist/commands/whoami.d.ts.map +1 -0
  171. package/dist/commands/whoami.js +28 -0
  172. package/dist/commands/whoami.js.map +1 -0
  173. package/dist/index.d.ts +14 -0
  174. package/dist/index.d.ts.map +1 -1
  175. package/dist/index.js +67 -1
  176. package/dist/index.js.map +1 -1
  177. package/dist/shared/terminal-command.d.ts +8 -2
  178. package/dist/shared/terminal-command.d.ts.map +1 -1
  179. package/dist/shared/terminal-command.js +42 -17
  180. package/dist/shared/terminal-command.js.map +1 -1
  181. package/package.json +4 -2
  182. package/src/commands/basename.ts +84 -0
  183. package/src/commands/cal.ts +111 -0
  184. package/src/commands/cat.ts +37 -27
  185. package/src/commands/cd.ts +40 -12
  186. package/src/commands/chmod.ts +37 -11
  187. package/src/commands/comm.ts +169 -0
  188. package/src/commands/cp.ts +73 -15
  189. package/src/commands/cut.ts +214 -0
  190. package/src/commands/date.ts +97 -0
  191. package/src/commands/diff.ts +204 -0
  192. package/src/commands/dirname.ts +57 -0
  193. package/src/commands/echo.ts +51 -11
  194. package/src/commands/false.ts +31 -0
  195. package/src/commands/find.ts +184 -0
  196. package/src/commands/grep.ts +44 -11
  197. package/src/commands/head.ts +46 -10
  198. package/src/commands/hex.ts +19 -7
  199. package/src/commands/id.ts +94 -0
  200. package/src/commands/join.ts +162 -0
  201. package/src/commands/less.ts +20 -8
  202. package/src/commands/ln.ts +50 -13
  203. package/src/commands/ls.ts +110 -87
  204. package/src/commands/mkdir.ts +41 -12
  205. package/src/commands/mv.ts +31 -9
  206. package/src/commands/nc.ts +499 -0
  207. package/src/commands/nl.ts +201 -0
  208. package/src/commands/passkey.ts +46 -19
  209. package/src/commands/paste.ts +172 -0
  210. package/src/commands/pwd.ts +16 -4
  211. package/src/commands/rm.ts +118 -13
  212. package/src/commands/rmdir.ts +41 -12
  213. package/src/commands/sed.ts +64 -30
  214. package/src/commands/seq.ts +147 -0
  215. package/src/commands/sockets.ts +283 -0
  216. package/src/commands/sort.ts +175 -0
  217. package/src/commands/split.ts +154 -0
  218. package/src/commands/stat.ts +17 -8
  219. package/src/commands/tail.ts +46 -10
  220. package/src/commands/tee.ts +43 -11
  221. package/src/commands/test.ts +116 -0
  222. package/src/commands/touch.ts +41 -12
  223. package/src/commands/tr.ts +170 -0
  224. package/src/commands/true.ts +31 -0
  225. package/src/commands/uniq.ts +189 -0
  226. package/src/commands/user.ts +436 -0
  227. package/src/commands/wc.ts +181 -0
  228. package/src/commands/which.ts +89 -0
  229. package/src/commands/whoami.ts +32 -0
  230. package/src/index.ts +67 -1
  231. package/src/shared/terminal-command.ts +43 -16
@@ -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
+ }
@@ -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: grep [OPTION]... PATTERN [FILE]...
9
+ Search for PATTERN in each FILE.
10
+
11
+ -i, --ignore-case ignore case distinctions
12
+ -n, --line-number print line number with output lines
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: 'grep',
@@ -12,16 +21,42 @@ 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: 'ignore-case', type: Boolean, alias: 'i', description: 'Ignore case distinctions' },
18
- { name: 'line-number', type: Boolean, alias: 'n', description: 'Print line number with output lines' },
19
- { name: 'args', type: String, defaultOption: true, multiple: true, description: 'Pattern and file(s) to search' }
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
 
24
- const args = (argv.args as string[]) || []
29
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
30
+ printUsage(process, terminal)
31
+ return 0
32
+ }
33
+
34
+ let ignoreCase = false
35
+ let showLineNumbers = false
36
+ const args: string[] = []
37
+
38
+ for (const arg of argv) {
39
+ if (arg === '--help' || arg === '-h') {
40
+ printUsage(process, terminal)
41
+ return 0
42
+ } else if (arg === '-i' || arg === '--ignore-case') {
43
+ ignoreCase = true
44
+ } else if (arg === '-n' || arg === '--line-number') {
45
+ showLineNumbers = true
46
+ } else if (arg.startsWith('-')) {
47
+ const flags = arg.slice(1).split('')
48
+ if (flags.includes('i')) ignoreCase = true
49
+ if (flags.includes('n')) showLineNumbers = true
50
+ const invalidFlags = flags.filter(f => !['i', 'n'].includes(f))
51
+ if (invalidFlags.length > 0) {
52
+ await writelnStderr(process, terminal, `grep: invalid option -- '${invalidFlags[0]}'`)
53
+ return 1
54
+ }
55
+ } else {
56
+ args.push(arg)
57
+ }
58
+ }
59
+
25
60
  if (args.length === 0 || !args[0]) {
26
61
  await writelnStderr(process, terminal, 'grep: pattern is required')
27
62
  return 1
@@ -29,8 +64,6 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
29
64
 
30
65
  const pattern = args[0]
31
66
  const files = args.slice(1)
32
- const ignoreCase = (argv['ignore-case'] as boolean) || false
33
- const showLineNumbers = (argv['line-number'] as boolean) || false
34
67
 
35
68
  const flags = ignoreCase ? 'i' : ''
36
69
  let regex: RegExp
@@ -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: head [OPTION]... [FILE]...
9
+ Print the first 10 lines of each FILE to standard output.
10
+
11
+ -n, -nNUMBER print the first 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 first NUM lines instead of the first 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
  }
@@ -76,7 +113,6 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
76
113
  return 0
77
114
  }
78
115
 
79
- const files = (argv.path as string[]) || []
80
116
  const isMultipleFiles = files.length > 1
81
117
 
82
118
  for (let i = 0; i < files.length; i++) {
@@ -1,9 +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 { TerminalCommand } from '../shared/terminal-command.js'
5
4
  import { writelnStdout, writelnStderr } from '../shared/helpers.js'
6
5
 
6
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
7
+ const usage = `Usage: hex [FILE]
8
+ Display file contents or stdin in hexadecimal format.
9
+
10
+ FILE the file to display (if omitted, reads from stdin)
11
+ --help display this help and exit`
12
+ writelnStderr(process, terminal, usage)
13
+ }
14
+
7
15
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
8
16
  return new TerminalCommand({
9
17
  command: 'hex',
@@ -11,14 +19,18 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
11
19
  kernel,
12
20
  shell,
13
21
  terminal,
14
- options: [
15
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
16
- { name: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, description: 'The path to the file to display (if omitted, reads from stdin)' }
17
- ],
18
- run: async (argv: CommandLineOptions, process?: Process) => {
22
+ run: async (pid: number, argv: string[]) => {
23
+ const process = kernel.processes.get(pid) as Process | undefined
24
+
19
25
  if (!process) return 1
20
26
 
21
- const filePath = argv.path as string | undefined
27
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
28
+ printUsage(process, terminal)
29
+ return 0
30
+ }
31
+
32
+ const firstArg = argv.length > 0 ? argv[0] : undefined
33
+ const filePath = firstArg !== undefined && !firstArg.startsWith('-') ? firstArg : undefined
22
34
  let data: Uint8Array
23
35
 
24
36
  try {
@@ -0,0 +1,94 @@
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: id [OPTION]...
7
+ Print user and group IDs.
8
+
9
+ -u, --user print only the effective user ID
10
+ -g, --group print only the effective group ID
11
+ -G, --groups print all group IDs
12
+ -n, --name print names instead of numeric IDs
13
+ --help display this help and exit`
14
+ writelnStdout(process, terminal, usage)
15
+ }
16
+
17
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
18
+ return new TerminalCommand({
19
+ command: 'id',
20
+ description: 'Print user and group IDs',
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
+ let userOnly = false
28
+ let groupOnly = false
29
+ let groupsOnly = false
30
+ let nameOnly = false
31
+
32
+ for (const arg of argv) {
33
+ if (arg === '--help' || arg === '-h') {
34
+ printUsage(process, terminal)
35
+ return 0
36
+ } else if (arg === '-u' || arg === '--user') {
37
+ userOnly = true
38
+ } else if (arg === '-g' || arg === '--group') {
39
+ groupOnly = true
40
+ } else if (arg === '-G' || arg === '--groups') {
41
+ groupsOnly = true
42
+ } else if (arg === '-n' || arg === '--name') {
43
+ nameOnly = true
44
+ } else if (arg.startsWith('-')) {
45
+ const flags = arg.slice(1).split('')
46
+ if (flags.includes('u')) userOnly = true
47
+ if (flags.includes('g')) groupOnly = true
48
+ if (flags.includes('G')) groupsOnly = true
49
+ if (flags.includes('n')) nameOnly = true
50
+ const invalidFlags = flags.filter(f => !['u', 'g', 'G', 'n'].includes(f))
51
+ if (invalidFlags.length > 0) {
52
+ await writelnStdout(process, terminal, `id: invalid option -- '${invalidFlags[0]}'`)
53
+ return 1
54
+ }
55
+ }
56
+ }
57
+
58
+ const user = kernel.users.get(shell.credentials.uid)
59
+ const group = kernel.users.get(shell.credentials.gid)
60
+ const groups = shell.credentials.groups || []
61
+
62
+ let output = ''
63
+
64
+ if (userOnly) {
65
+ output = nameOnly ? (user?.username || shell.credentials.uid.toString()) : shell.credentials.euid.toString()
66
+ } else if (groupOnly) {
67
+ output = nameOnly ? (group?.username || shell.credentials.gid.toString()) : shell.credentials.egid.toString()
68
+ } else if (groupsOnly) {
69
+ output = groups.map(gid => {
70
+ if (nameOnly) {
71
+ const g = kernel.users.get(gid)
72
+ return g?.username || gid.toString()
73
+ }
74
+ return gid.toString()
75
+ }).join(' ')
76
+ } else {
77
+ const uid = nameOnly ? (user?.username || shell.credentials.uid.toString()) : shell.credentials.uid.toString()
78
+ const gid = nameOnly ? (group?.username || shell.credentials.gid.toString()) : shell.credentials.gid.toString()
79
+ const groupsStr = groups.map(gid => {
80
+ if (nameOnly) {
81
+ const g = kernel.users.get(gid)
82
+ return g?.username || gid.toString()
83
+ }
84
+ return gid.toString()
85
+ }).join(',')
86
+
87
+ output = `uid=${uid} gid=${gid} groups=${groupsStr}`
88
+ }
89
+
90
+ await writelnStdout(process, terminal, output)
91
+ return 0
92
+ }
93
+ })
94
+ }
@@ -0,0 +1,162 @@
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: join [OPTION]... FILE1 FILE2
9
+ Join lines of two files on a common field.
10
+
11
+ -1 FIELD join on this FIELD of file 1
12
+ -2 FIELD join on this FIELD of file 2
13
+ -t CHAR use CHAR as input and output field separator
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: 'join',
21
+ description: 'Join lines of two files on a common field',
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 field1 = 1
37
+ let field2 = 1
38
+ let delimiter = ' '
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 === '-1' && i + 1 < argv.length) {
48
+ const nextArg = argv[++i]
49
+ if (nextArg !== undefined) {
50
+ field1 = parseInt(nextArg, 10) || 1
51
+ }
52
+ } else if (arg === '-2' && i + 1 < argv.length) {
53
+ const nextArg = argv[++i]
54
+ if (nextArg !== undefined) {
55
+ field2 = parseInt(nextArg, 10) || 1
56
+ }
57
+ } else if (arg.startsWith('-t')) {
58
+ delimiter = arg.slice(2) || ' '
59
+ } else if (arg === '-t' && i + 1 < argv.length) {
60
+ const nextArg = argv[++i]
61
+ if (nextArg !== undefined) {
62
+ delimiter = nextArg
63
+ }
64
+ } else if (!arg.startsWith('-')) {
65
+ if (files.length < 2) {
66
+ files.push(arg)
67
+ }
68
+ }
69
+ }
70
+
71
+ if (files.length !== 2) {
72
+ await writelnStderr(process, terminal, 'join: exactly two files must be specified')
73
+ return 1
74
+ }
75
+
76
+ const file1 = files[0]
77
+ const file2 = files[1]
78
+ if (!file1 || !file2) {
79
+ await writelnStderr(process, terminal, 'join: exactly two files must be specified')
80
+ return 1
81
+ }
82
+
83
+ const writer = process.stdout.getWriter()
84
+
85
+ const readFileLines = async (filePath: string): Promise<string[]> => {
86
+ if (filePath.startsWith('/dev')) {
87
+ throw new Error('cannot join device files')
88
+ }
89
+
90
+ let interrupted = false
91
+ const interruptHandler = () => { interrupted = true }
92
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
93
+
94
+ try {
95
+ const handle = await shell.context.fs.promises.open(filePath, 'r')
96
+ const stat = await shell.context.fs.promises.stat(filePath)
97
+
98
+ const decoder = new TextDecoder()
99
+ let content = ''
100
+ let bytesRead = 0
101
+ const chunkSize = 1024
102
+
103
+ while (bytesRead < stat.size) {
104
+ if (interrupted) break
105
+ const data = new Uint8Array(chunkSize)
106
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
107
+ await handle.read(data, 0, readSize, bytesRead)
108
+ const chunk = data.subarray(0, readSize)
109
+ content += decoder.decode(chunk, { stream: true })
110
+ bytesRead += readSize
111
+ }
112
+
113
+ const lines = content.split('\n')
114
+ if (lines[lines.length - 1] === '') {
115
+ lines.pop()
116
+ }
117
+ return lines
118
+ } finally {
119
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
120
+ }
121
+ }
122
+
123
+ try {
124
+ const fullPath1 = path.resolve(shell.cwd || '/', file1)
125
+ const fullPath2 = path.resolve(shell.cwd || '/', file2)
126
+
127
+ const lines1 = await readFileLines(fullPath1)
128
+ const lines2 = await readFileLines(fullPath2)
129
+
130
+ const map1 = new Map<string, string[]>()
131
+ for (const line of lines1) {
132
+ const parts = line.split(delimiter)
133
+ const key = parts[field1 - 1] || ''
134
+ if (!map1.has(key)) {
135
+ map1.set(key, [])
136
+ }
137
+ map1.get(key)!.push(line)
138
+ }
139
+
140
+ for (const line of lines2) {
141
+ const parts = line.split(delimiter)
142
+ const key = parts[field2 - 1] || ''
143
+ const matches = map1.get(key)
144
+ if (matches) {
145
+ for (const match of matches) {
146
+ const matchParts = match.split(delimiter)
147
+ const output = [...matchParts, ...parts.slice(field2)].join(delimiter)
148
+ await writer.write(new TextEncoder().encode(output + '\n'))
149
+ }
150
+ }
151
+ }
152
+
153
+ return 0
154
+ } catch (error) {
155
+ await writelnStderr(process, terminal, `join: ${error instanceof Error ? error.message : 'Unknown error'}`)
156
+ return 1
157
+ } finally {
158
+ writer.releaseLock()
159
+ }
160
+ }
161
+ })
162
+ }