@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
@@ -1,6 +1,16 @@
1
1
  import path from 'path'
2
- import type { Kernel, Shell, Terminal } from '@ecmaos/types'
2
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
3
3
  import { TerminalCommand } from '../shared/terminal-command.js'
4
+ import { writelnStdout } from '../shared/helpers.js'
5
+
6
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
7
+ const usage = `Usage: cd [DIRECTORY]
8
+ Change the shell working directory.
9
+
10
+ DIRECTORY the directory to change to (default: $HOME)
11
+ --help display this help and exit`
12
+ writelnStdout(process, terminal, usage)
13
+ }
4
14
 
5
15
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
6
16
  return new TerminalCommand({
@@ -9,18 +19,36 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
9
19
  kernel,
10
20
  shell,
11
21
  terminal,
12
- options: [
13
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
14
- { name: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, description: 'The path to the directory to change to' }
15
- ],
16
- run: async (argv) => {
17
- const destination = (argv.path as string) || shell.cwd
22
+ run: async (pid: number, argv: string[]) => {
23
+ const process = kernel.processes.get(pid) as Process | undefined
24
+
25
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
26
+ printUsage(process, terminal)
27
+ return 0
28
+ }
29
+
30
+ const destination = argv.length > 0 && argv[0] && !argv[0].startsWith('-') ? argv[0] : shell.cwd
18
31
  const fullPath = destination ? path.resolve(shell.cwd, destination) : shell.cwd
19
- await shell.context.fs.promises.access(fullPath)
20
- shell.cwd = fullPath
21
- localStorage.setItem(`cwd:${shell.credentials.uid}`, fullPath)
22
- return 0
32
+
33
+ try {
34
+ await shell.context.fs.promises.access(fullPath)
35
+ shell.cwd = fullPath
36
+ localStorage.setItem(`cwd:${shell.credentials.uid}`, fullPath)
37
+ return 0
38
+ } catch (error) {
39
+ const errorMessage = error instanceof Error ? error.message : String(error)
40
+ if (process) {
41
+ const writer = process.stderr.getWriter()
42
+ try {
43
+ await writer.write(new TextEncoder().encode(`cd: ${destination}: ${errorMessage}\n`))
44
+ } finally {
45
+ writer.releaseLock()
46
+ }
47
+ } else {
48
+ terminal.write(`cd: ${destination}: ${errorMessage}\n`)
49
+ }
50
+ return 1
51
+ }
23
52
  }
24
53
  })
25
54
  }
26
-
@@ -1,10 +1,17 @@
1
1
  import path from 'path'
2
2
  import chalk from 'chalk'
3
- import type { CommandLineOptions } from 'command-line-args'
4
3
  import type { Kernel, Process, Shell, Terminal } 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: chmod [OPTION]... MODE[,MODE]... FILE...
9
+ Change the mode of each FILE to MODE.
10
+
11
+ --help display this help and exit`
12
+ writelnStderr(process, terminal, usage)
13
+ }
14
+
8
15
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
9
16
  return new TerminalCommand({
10
17
  command: 'chmod',
@@ -12,12 +19,21 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
12
19
  kernel,
13
20
  shell,
14
21
  terminal,
15
- options: [
16
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
17
- { name: 'args', type: String, multiple: true, defaultOption: true, description: 'The mode and path to the file or directory' }
18
- ],
19
- run: async (argv: CommandLineOptions, process?: Process) => {
20
- const args = (argv.args as string[]) || []
22
+ run: async (pid: number, argv: string[]) => {
23
+ const process = kernel.processes.get(pid) as Process | undefined
24
+
25
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
26
+ printUsage(process, terminal)
27
+ return 0
28
+ }
29
+
30
+ const args: string[] = []
31
+ for (const arg of argv) {
32
+ if (arg && !arg.startsWith('-')) {
33
+ args.push(arg)
34
+ }
35
+ }
36
+
21
37
  if (args.length === 0) {
22
38
  await writelnStderr(process, terminal, chalk.red('chmod: missing operand'))
23
39
  await writelnStderr(process, terminal, 'Try \'chmod --help\' for more information.')
@@ -25,11 +41,21 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
25
41
  }
26
42
 
27
43
  const [mode, target] = args
28
- if (!mode || !target) return 1
44
+ if (!mode || !target) {
45
+ await writelnStderr(process, terminal, chalk.red('chmod: missing operand'))
46
+ return 1
47
+ }
48
+
29
49
  const fullPath = path.resolve(shell.cwd, target)
30
- await shell.context.fs.promises.chmod(fullPath, mode)
31
- return 0
50
+
51
+ try {
52
+ await shell.context.fs.promises.chmod(fullPath, mode)
53
+ return 0
54
+ } catch (error) {
55
+ const errorMessage = error instanceof Error ? error.message : String(error)
56
+ await writelnStderr(process, terminal, `chmod: ${target}: ${errorMessage}`)
57
+ return 1
58
+ }
32
59
  }
33
60
  })
34
61
  }
35
-
@@ -0,0 +1,169 @@
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: comm [OPTION]... FILE1 FILE2
9
+ Compare two sorted files line by line.
10
+
11
+ -1 suppress lines unique to FILE1
12
+ -2 suppress lines unique to FILE2
13
+ -3 suppress lines that appear in both files
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: 'comm',
21
+ description: 'Compare two sorted files line by line',
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 suppress1 = false
37
+ let suppress2 = false
38
+ let suppress3 = false
39
+
40
+ for (const arg of argv) {
41
+ if (arg === '--help' || arg === '-h') {
42
+ printUsage(process, terminal)
43
+ return 0
44
+ } else if (arg === '-1') {
45
+ suppress1 = true
46
+ } else if (arg === '-2') {
47
+ suppress2 = true
48
+ } else if (arg === '-3') {
49
+ suppress3 = true
50
+ } else if (!arg.startsWith('-')) {
51
+ if (files.length < 2) {
52
+ files.push(arg)
53
+ }
54
+ }
55
+ }
56
+
57
+ if (files.length !== 2) {
58
+ await writelnStderr(process, terminal, 'comm: exactly two files must be specified')
59
+ return 1
60
+ }
61
+
62
+ const file1 = files[0]
63
+ const file2 = files[1]
64
+ if (!file1 || !file2) {
65
+ await writelnStderr(process, terminal, 'comm: exactly two files must be specified')
66
+ return 1
67
+ }
68
+
69
+ const writer = process.stdout.getWriter()
70
+
71
+ const readFileLines = async (filePath: string): Promise<string[]> => {
72
+ if (filePath.startsWith('/dev')) {
73
+ throw new Error('cannot comm device files')
74
+ }
75
+
76
+ let interrupted = false
77
+ const interruptHandler = () => { interrupted = true }
78
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
79
+
80
+ try {
81
+ const handle = await shell.context.fs.promises.open(filePath, 'r')
82
+ const stat = await shell.context.fs.promises.stat(filePath)
83
+
84
+ const decoder = new TextDecoder()
85
+ let content = ''
86
+ let bytesRead = 0
87
+ const chunkSize = 1024
88
+
89
+ while (bytesRead < stat.size) {
90
+ if (interrupted) break
91
+ const data = new Uint8Array(chunkSize)
92
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
93
+ await handle.read(data, 0, readSize, bytesRead)
94
+ const chunk = data.subarray(0, readSize)
95
+ content += decoder.decode(chunk, { stream: true })
96
+ bytesRead += readSize
97
+ }
98
+
99
+ const lines = content.split('\n')
100
+ if (lines[lines.length - 1] === '') {
101
+ lines.pop()
102
+ }
103
+ return lines
104
+ } finally {
105
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
106
+ }
107
+ }
108
+
109
+ try {
110
+ const fullPath1 = path.resolve(shell.cwd || '/', file1)
111
+ const fullPath2 = path.resolve(shell.cwd || '/', file2)
112
+
113
+ const lines1 = await readFileLines(fullPath1)
114
+ const lines2 = await readFileLines(fullPath2)
115
+
116
+ let i = 0
117
+ let j = 0
118
+
119
+ while (i < lines1.length || j < lines2.length) {
120
+ if (i >= lines1.length) {
121
+ if (!suppress2) {
122
+ const prefix = suppress1 ? '' : '\t'
123
+ await writer.write(new TextEncoder().encode(prefix + lines2[j] + '\n'))
124
+ }
125
+ j++
126
+ } else if (j >= lines2.length) {
127
+ if (!suppress1) {
128
+ await writer.write(new TextEncoder().encode(lines1[i] + '\n'))
129
+ }
130
+ i++
131
+ } else {
132
+ const line1 = lines1[i]
133
+ const line2 = lines2[j]
134
+ if (!line1 || !line2) {
135
+ break
136
+ }
137
+ const cmp = line1.localeCompare(line2)
138
+ if (cmp < 0) {
139
+ if (!suppress1) {
140
+ await writer.write(new TextEncoder().encode(lines1[i] + '\n'))
141
+ }
142
+ i++
143
+ } else if (cmp > 0) {
144
+ if (!suppress2) {
145
+ const prefix = suppress1 ? '' : '\t'
146
+ await writer.write(new TextEncoder().encode(prefix + lines2[j] + '\n'))
147
+ }
148
+ j++
149
+ } else {
150
+ if (!suppress3) {
151
+ const prefix = suppress1 && suppress2 ? '' : suppress1 ? '\t' : suppress2 ? '' : '\t\t'
152
+ await writer.write(new TextEncoder().encode(prefix + lines1[i] + '\n'))
153
+ }
154
+ i++
155
+ j++
156
+ }
157
+ }
158
+ }
159
+
160
+ return 0
161
+ } catch (error) {
162
+ await writelnStderr(process, terminal, `comm: ${error instanceof Error ? error.message : 'Unknown error'}`)
163
+ return 1
164
+ } finally {
165
+ writer.releaseLock()
166
+ }
167
+ }
168
+ })
169
+ }
@@ -1,7 +1,15 @@
1
1
  import path from 'path'
2
- import type { CommandLineOptions } from 'command-line-args'
3
- import type { Kernel, Shell, Terminal } from '@ecmaos/types'
2
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
4
3
  import { TerminalCommand } from '../shared/terminal-command.js'
4
+ import { writelnStdout, writelnStderr } from '../shared/helpers.js'
5
+
6
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
7
+ const usage = `Usage: cp [OPTION]... SOURCE... DEST
8
+ Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.
9
+
10
+ --help display this help and exit`
11
+ writelnStdout(process, terminal, usage)
12
+ }
5
13
 
6
14
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
7
15
  return new TerminalCommand({
@@ -10,19 +18,69 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
10
18
  kernel,
11
19
  shell,
12
20
  terminal,
13
- options: [
14
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
15
- { name: 'args', type: String, multiple: true, defaultOption: true, description: 'The source and destination paths' }
16
- ],
17
- run: async (argv: CommandLineOptions) => {
18
- const args = (argv.args as string[]) || []
19
- const [source, destination] = args.map(arg => path.resolve(shell.cwd, arg))
20
- if (!source || !destination) return 1
21
- const destinationStats = await shell.context.fs.promises.stat(destination).catch(() => null)
22
- const finalDestination = destinationStats?.isDirectory() ? path.join(destination, path.basename(source)) : destination
23
- await shell.context.fs.promises.copyFile(source, finalDestination)
24
- return 0
21
+ run: async (pid: number, argv: string[]) => {
22
+ const process = kernel.processes.get(pid) as Process | undefined
23
+
24
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
25
+ printUsage(process, terminal)
26
+ return 0
27
+ }
28
+
29
+ const args: string[] = []
30
+ for (const arg of argv) {
31
+ if (arg && !arg.startsWith('-')) {
32
+ args.push(arg)
33
+ }
34
+ }
35
+
36
+ if (args.length < 2) {
37
+ await writelnStderr(process, terminal, 'cp: missing file operand')
38
+ await writelnStderr(process, terminal, "Try 'cp --help' for more information.")
39
+ return 1
40
+ }
41
+
42
+ const sources = args.slice(0, -1)
43
+ const destination = args[args.length - 1]
44
+
45
+ if (!destination) {
46
+ await writelnStderr(process, terminal, 'cp: missing destination file operand')
47
+ return 1
48
+ }
49
+
50
+ let hasError = false
51
+
52
+ try {
53
+ const destinationStats = await shell.context.fs.promises.stat(path.resolve(shell.cwd, destination)).catch(() => null)
54
+ const isDestinationDir = destinationStats?.isDirectory()
55
+
56
+ if (sources.length > 1 && !isDestinationDir) {
57
+ await writelnStderr(process, terminal, `cp: target '${destination}' is not a directory`)
58
+ return 1
59
+ }
60
+
61
+ for (const source of sources) {
62
+ if (!source) continue
63
+
64
+ const sourcePath = path.resolve(shell.cwd, source)
65
+ const finalDestination = isDestinationDir
66
+ ? path.join(path.resolve(shell.cwd, destination), path.basename(source))
67
+ : path.resolve(shell.cwd, destination)
68
+
69
+ try {
70
+ await shell.context.fs.promises.copyFile(sourcePath, finalDestination)
71
+ } catch (error) {
72
+ const errorMessage = error instanceof Error ? error.message : String(error)
73
+ await writelnStderr(process, terminal, `cp: ${source}: ${errorMessage}`)
74
+ hasError = true
75
+ }
76
+ }
77
+ } catch (error) {
78
+ const errorMessage = error instanceof Error ? error.message : String(error)
79
+ await writelnStderr(process, terminal, `cp: ${errorMessage}`)
80
+ hasError = true
81
+ }
82
+
83
+ return hasError ? 1 : 0
25
84
  }
26
85
  })
27
86
  }
28
-
@@ -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
+ }