@ecmaos/coreutils 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +55 -0
  3. package/dist/commands/basename.d.ts +4 -0
  4. package/dist/commands/basename.d.ts.map +1 -0
  5. package/dist/commands/basename.js +78 -0
  6. package/dist/commands/basename.js.map +1 -0
  7. package/dist/commands/cal.d.ts +4 -0
  8. package/dist/commands/cal.d.ts.map +1 -0
  9. package/dist/commands/cal.js +105 -0
  10. package/dist/commands/cal.js.map +1 -0
  11. package/dist/commands/cat.d.ts.map +1 -1
  12. package/dist/commands/cat.js +49 -22
  13. package/dist/commands/cat.js.map +1 -1
  14. package/dist/commands/cd.d.ts.map +1 -1
  15. package/dist/commands/cd.js +38 -10
  16. package/dist/commands/cd.js.map +1 -1
  17. package/dist/commands/chmod.d.ts.map +1 -1
  18. package/dist/commands/chmod.js +31 -9
  19. package/dist/commands/chmod.js.map +1 -1
  20. package/dist/commands/comm.d.ts +4 -0
  21. package/dist/commands/comm.d.ts.map +1 -0
  22. package/dist/commands/comm.js +162 -0
  23. package/dist/commands/comm.js.map +1 -0
  24. package/dist/commands/cp.d.ts.map +1 -1
  25. package/dist/commands/cp.js +61 -12
  26. package/dist/commands/cp.js.map +1 -1
  27. package/dist/commands/cut.d.ts +4 -0
  28. package/dist/commands/cut.d.ts.map +1 -0
  29. package/dist/commands/cut.js +208 -0
  30. package/dist/commands/cut.js.map +1 -0
  31. package/dist/commands/date.d.ts +4 -0
  32. package/dist/commands/date.d.ts.map +1 -0
  33. package/dist/commands/date.js +100 -0
  34. package/dist/commands/date.js.map +1 -0
  35. package/dist/commands/diff.d.ts +4 -0
  36. package/dist/commands/diff.d.ts.map +1 -0
  37. package/dist/commands/diff.js +194 -0
  38. package/dist/commands/diff.js.map +1 -0
  39. package/dist/commands/dirname.d.ts +4 -0
  40. package/dist/commands/dirname.d.ts.map +1 -0
  41. package/dist/commands/dirname.js +50 -0
  42. package/dist/commands/dirname.js.map +1 -0
  43. package/dist/commands/echo.d.ts.map +1 -1
  44. package/dist/commands/echo.js +51 -9
  45. package/dist/commands/echo.js.map +1 -1
  46. package/dist/commands/false.d.ts +4 -0
  47. package/dist/commands/false.d.ts.map +1 -0
  48. package/dist/commands/false.js +27 -0
  49. package/dist/commands/false.js.map +1 -0
  50. package/dist/commands/find.d.ts +4 -0
  51. package/dist/commands/find.d.ts.map +1 -0
  52. package/dist/commands/find.js +181 -0
  53. package/dist/commands/find.js.map +1 -0
  54. package/dist/commands/grep.d.ts +4 -0
  55. package/dist/commands/grep.d.ts.map +1 -0
  56. package/dist/commands/grep.js +175 -0
  57. package/dist/commands/grep.js.map +1 -0
  58. package/dist/commands/head.d.ts +4 -0
  59. package/dist/commands/head.d.ts.map +1 -0
  60. package/dist/commands/head.js +199 -0
  61. package/dist/commands/head.js.map +1 -0
  62. package/dist/commands/hex.d.ts.map +1 -1
  63. package/dist/commands/hex.js +82 -20
  64. package/dist/commands/hex.js.map +1 -1
  65. package/dist/commands/id.d.ts +4 -0
  66. package/dist/commands/id.d.ts.map +1 -0
  67. package/dist/commands/id.js +97 -0
  68. package/dist/commands/id.js.map +1 -0
  69. package/dist/commands/join.d.ts +4 -0
  70. package/dist/commands/join.d.ts.map +1 -0
  71. package/dist/commands/join.js +152 -0
  72. package/dist/commands/join.js.map +1 -0
  73. package/dist/commands/less.d.ts.map +1 -1
  74. package/dist/commands/less.js +16 -7
  75. package/dist/commands/less.js.map +1 -1
  76. package/dist/commands/ln.d.ts.map +1 -1
  77. package/dist/commands/ln.js +54 -12
  78. package/dist/commands/ln.js.map +1 -1
  79. package/dist/commands/ls.d.ts.map +1 -1
  80. package/dist/commands/ls.js +19 -9
  81. package/dist/commands/ls.js.map +1 -1
  82. package/dist/commands/mkdir.d.ts.map +1 -1
  83. package/dist/commands/mkdir.js +34 -9
  84. package/dist/commands/mkdir.js.map +1 -1
  85. package/dist/commands/mv.d.ts.map +1 -1
  86. package/dist/commands/mv.js +25 -7
  87. package/dist/commands/mv.js.map +1 -1
  88. package/dist/commands/nc.d.ts +4 -0
  89. package/dist/commands/nc.d.ts.map +1 -0
  90. package/dist/commands/nc.js +451 -0
  91. package/dist/commands/nc.js.map +1 -0
  92. package/dist/commands/nl.d.ts +4 -0
  93. package/dist/commands/nl.d.ts.map +1 -0
  94. package/dist/commands/nl.js +208 -0
  95. package/dist/commands/nl.js.map +1 -0
  96. package/dist/commands/passkey.d.ts.map +1 -1
  97. package/dist/commands/passkey.js +44 -18
  98. package/dist/commands/passkey.js.map +1 -1
  99. package/dist/commands/paste.d.ts +4 -0
  100. package/dist/commands/paste.d.ts.map +1 -0
  101. package/dist/commands/paste.js +161 -0
  102. package/dist/commands/paste.js.map +1 -0
  103. package/dist/commands/pwd.d.ts.map +1 -1
  104. package/dist/commands/pwd.js +13 -4
  105. package/dist/commands/pwd.js.map +1 -1
  106. package/dist/commands/rm.d.ts.map +1 -1
  107. package/dist/commands/rm.js +105 -12
  108. package/dist/commands/rm.js.map +1 -1
  109. package/dist/commands/rmdir.d.ts.map +1 -1
  110. package/dist/commands/rmdir.js +34 -9
  111. package/dist/commands/rmdir.js.map +1 -1
  112. package/dist/commands/sed.d.ts.map +1 -1
  113. package/dist/commands/sed.js +73 -28
  114. package/dist/commands/sed.js.map +1 -1
  115. package/dist/commands/seq.d.ts +4 -0
  116. package/dist/commands/seq.d.ts.map +1 -0
  117. package/dist/commands/seq.js +148 -0
  118. package/dist/commands/seq.js.map +1 -0
  119. package/dist/commands/sockets.d.ts +4 -0
  120. package/dist/commands/sockets.d.ts.map +1 -0
  121. package/dist/commands/sockets.js +239 -0
  122. package/dist/commands/sockets.js.map +1 -0
  123. package/dist/commands/sort.d.ts +4 -0
  124. package/dist/commands/sort.d.ts.map +1 -0
  125. package/dist/commands/sort.js +175 -0
  126. package/dist/commands/sort.js.map +1 -0
  127. package/dist/commands/split.d.ts +4 -0
  128. package/dist/commands/split.d.ts.map +1 -0
  129. package/dist/commands/split.js +147 -0
  130. package/dist/commands/split.js.map +1 -0
  131. package/dist/commands/stat.d.ts.map +1 -1
  132. package/dist/commands/stat.js +14 -6
  133. package/dist/commands/stat.js.map +1 -1
  134. package/dist/commands/tail.d.ts +4 -0
  135. package/dist/commands/tail.d.ts.map +1 -0
  136. package/dist/commands/tail.js +189 -0
  137. package/dist/commands/tail.js.map +1 -0
  138. package/dist/commands/tee.d.ts.map +1 -1
  139. package/dist/commands/tee.js +45 -10
  140. package/dist/commands/tee.js.map +1 -1
  141. package/dist/commands/test.d.ts +4 -0
  142. package/dist/commands/test.d.ts.map +1 -0
  143. package/dist/commands/test.js +99 -0
  144. package/dist/commands/test.js.map +1 -0
  145. package/dist/commands/touch.d.ts.map +1 -1
  146. package/dist/commands/touch.js +34 -9
  147. package/dist/commands/touch.js.map +1 -1
  148. package/dist/commands/tr.d.ts +4 -0
  149. package/dist/commands/tr.d.ts.map +1 -0
  150. package/dist/commands/tr.js +162 -0
  151. package/dist/commands/tr.js.map +1 -0
  152. package/dist/commands/true.d.ts +4 -0
  153. package/dist/commands/true.d.ts.map +1 -0
  154. package/dist/commands/true.js +27 -0
  155. package/dist/commands/true.js.map +1 -0
  156. package/dist/commands/uniq.d.ts +4 -0
  157. package/dist/commands/uniq.d.ts.map +1 -0
  158. package/dist/commands/uniq.js +187 -0
  159. package/dist/commands/uniq.js.map +1 -0
  160. package/dist/commands/wc.d.ts +4 -0
  161. package/dist/commands/wc.d.ts.map +1 -0
  162. package/dist/commands/wc.js +178 -0
  163. package/dist/commands/wc.js.map +1 -0
  164. package/dist/commands/which.d.ts +4 -0
  165. package/dist/commands/which.d.ts.map +1 -0
  166. package/dist/commands/which.js +78 -0
  167. package/dist/commands/which.js.map +1 -0
  168. package/dist/commands/whoami.d.ts +4 -0
  169. package/dist/commands/whoami.d.ts.map +1 -0
  170. package/dist/commands/whoami.js +28 -0
  171. package/dist/commands/whoami.js.map +1 -0
  172. package/dist/index.d.ts +15 -0
  173. package/dist/index.d.ts.map +1 -1
  174. package/dist/index.js +72 -1
  175. package/dist/index.js.map +1 -1
  176. package/dist/shared/terminal-command.d.ts +8 -2
  177. package/dist/shared/terminal-command.d.ts.map +1 -1
  178. package/dist/shared/terminal-command.js +42 -17
  179. package/dist/shared/terminal-command.js.map +1 -1
  180. package/package.json +4 -2
  181. package/src/commands/basename.ts +84 -0
  182. package/src/commands/cal.ts +111 -0
  183. package/src/commands/cat.ts +55 -24
  184. package/src/commands/cd.ts +40 -12
  185. package/src/commands/chmod.ts +37 -11
  186. package/src/commands/comm.ts +169 -0
  187. package/src/commands/cp.ts +73 -15
  188. package/src/commands/cut.ts +214 -0
  189. package/src/commands/date.ts +97 -0
  190. package/src/commands/diff.ts +204 -0
  191. package/src/commands/dirname.ts +57 -0
  192. package/src/commands/echo.ts +53 -10
  193. package/src/commands/false.ts +31 -0
  194. package/src/commands/find.ts +184 -0
  195. package/src/commands/grep.ts +187 -0
  196. package/src/commands/head.ts +206 -0
  197. package/src/commands/hex.ts +93 -25
  198. package/src/commands/id.ts +94 -0
  199. package/src/commands/join.ts +162 -0
  200. package/src/commands/less.ts +20 -8
  201. package/src/commands/ln.ts +50 -13
  202. package/src/commands/ls.ts +21 -10
  203. package/src/commands/mkdir.ts +41 -12
  204. package/src/commands/mv.ts +31 -9
  205. package/src/commands/nc.ts +499 -0
  206. package/src/commands/nl.ts +201 -0
  207. package/src/commands/passkey.ts +46 -19
  208. package/src/commands/paste.ts +172 -0
  209. package/src/commands/pwd.ts +16 -4
  210. package/src/commands/rm.ts +118 -13
  211. package/src/commands/rmdir.ts +41 -12
  212. package/src/commands/sed.ts +64 -30
  213. package/src/commands/seq.ts +147 -0
  214. package/src/commands/sockets.ts +283 -0
  215. package/src/commands/sort.ts +175 -0
  216. package/src/commands/split.ts +154 -0
  217. package/src/commands/stat.ts +17 -8
  218. package/src/commands/tail.ts +200 -0
  219. package/src/commands/tee.ts +43 -11
  220. package/src/commands/test.ts +116 -0
  221. package/src/commands/touch.ts +41 -12
  222. package/src/commands/tr.ts +170 -0
  223. package/src/commands/true.ts +31 -0
  224. package/src/commands/uniq.ts +189 -0
  225. package/src/commands/wc.ts +181 -0
  226. package/src/commands/which.ts +89 -0
  227. package/src/commands/whoami.ts +32 -0
  228. package/src/index.ts +72 -1
  229. package/src/shared/terminal-command.ts +43 -16
@@ -0,0 +1,111 @@
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: cal [MONTH] [YEAR]
7
+ Display a calendar.
8
+
9
+ MONTH month (1-12)
10
+ YEAR year
11
+ --help display this help and exit`
12
+ writelnStdout(process, terminal, usage)
13
+ }
14
+
15
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
16
+ return new TerminalCommand({
17
+ command: 'cal',
18
+ description: 'Display a calendar',
19
+ kernel,
20
+ shell,
21
+ terminal,
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 now = new Date()
31
+ let month: number | undefined
32
+ let year: number | undefined
33
+
34
+ if (argv.length === 1) {
35
+ const argStr = argv[0]
36
+ if (!argStr) {
37
+ month = now.getMonth() + 1
38
+ year = now.getFullYear()
39
+ } else {
40
+ const arg = parseInt(argStr, 10)
41
+ if (isNaN(arg)) {
42
+ await writelnStdout(process, terminal, 'cal: invalid argument')
43
+ return 1
44
+ }
45
+ if (arg >= 1 && arg <= 12) {
46
+ month = arg
47
+ year = now.getFullYear()
48
+ } else {
49
+ year = arg
50
+ month = now.getMonth() + 1
51
+ }
52
+ }
53
+ } else if (argv.length === 2) {
54
+ const monthStr = argv[0]
55
+ const yearStr = argv[1]
56
+ if (!monthStr || !yearStr) {
57
+ await writelnStdout(process, terminal, 'cal: invalid arguments')
58
+ return 1
59
+ }
60
+ month = parseInt(monthStr, 10)
61
+ year = parseInt(yearStr, 10)
62
+ if (isNaN(month) || isNaN(year)) {
63
+ await writelnStdout(process, terminal, 'cal: invalid arguments')
64
+ return 1
65
+ }
66
+ } else {
67
+ month = now.getMonth() + 1
68
+ year = now.getFullYear()
69
+ }
70
+
71
+ if (month < 1 || month > 12) {
72
+ await writelnStdout(process, terminal, 'cal: invalid month')
73
+ return 1
74
+ }
75
+
76
+ const monthNames = ['January', 'February', 'March', 'April', 'May', 'June',
77
+ 'July', 'August', 'September', 'October', 'November', 'December']
78
+ const dayNames = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
79
+
80
+ const firstDay = new Date(year, month - 1, 1)
81
+ const lastDay = new Date(year, month, 0)
82
+ const daysInMonth = lastDay.getDate()
83
+ const startDayOfWeek = firstDay.getDay()
84
+
85
+ let output = ` ${monthNames[month - 1]} ${year}\n`
86
+ output += dayNames.join(' ') + '\n'
87
+
88
+ let day = 1
89
+ let isFirstWeek = true
90
+
91
+ while (day <= daysInMonth) {
92
+ let line = ''
93
+ for (let i = 0; i < 7; i++) {
94
+ if (isFirstWeek && i < startDayOfWeek) {
95
+ line += ' '
96
+ } else if (day <= daysInMonth) {
97
+ line += day.toString().padStart(2) + ' '
98
+ day++
99
+ } else {
100
+ line += ' '
101
+ }
102
+ }
103
+ output += line.trimEnd() + '\n'
104
+ isFirstWeek = false
105
+ }
106
+
107
+ await writelnStdout(process, terminal, output)
108
+ return 0
109
+ }
110
+ })
111
+ }
@@ -1,9 +1,16 @@
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
- import { writeStdout } from '../shared/helpers.js'
5
+ import { writelnStdout } from '../shared/helpers.js'
6
+
7
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
8
+ const usage = `Usage: cat [OPTION]... [FILE]...
9
+ Concatenate files and print on the standard output.
10
+
11
+ --help display this help and exit`
12
+ writelnStdout(process, terminal, usage)
13
+ }
7
14
 
8
15
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
9
16
  return new TerminalCommand({
@@ -12,38 +19,58 @@ 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: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, multiple: true, description: 'The path(s) to the file(s) to concatenate' },
18
- { name: 'bytes', type: Number, description: 'The number of bytes to read from the file' }
19
- ],
20
- run: async (argv: CommandLineOptions, process?: Process) => {
22
+ run: async (pid: number, argv: string[]) => {
23
+ const process = kernel.processes.get(pid) as Process | undefined
24
+
21
25
  if (!process) return 1
22
26
 
23
- // Get a single writer for the entire operation
27
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
28
+ printUsage(process, terminal)
29
+ return 0
30
+ }
31
+
32
+ const files: string[] = []
33
+
34
+ for (let i = 0; i < argv.length; i++) {
35
+ const arg = argv[i]
36
+ if (arg === undefined) continue
37
+
38
+ if (arg === '--help' || arg === '-h') {
39
+ printUsage(process, terminal)
40
+ return 0
41
+ } else if (!arg.startsWith('-')) {
42
+ files.push(arg)
43
+ }
44
+ }
45
+
24
46
  const writer = process.stdout.getWriter()
47
+ const isTTY = process.stdoutIsTTY ?? false
48
+ let lastByte: number | undefined
25
49
 
26
50
  try {
27
- // If no files specified, read from stdin
28
- if (!argv.path || !(argv.path as string[])[0]) {
51
+ if (files.length === 0) {
29
52
  const reader = process.stdin!.getReader()
30
53
 
31
54
  try {
32
55
  while (true) {
33
56
  const { done, value } = await reader.read()
34
57
  if (done) break
58
+ if (value.length > 0) {
59
+ lastByte = value[value.length - 1]
60
+ }
35
61
  await writer.write(value)
36
62
  }
37
63
  } finally {
38
64
  reader.releaseLock()
39
65
  }
40
66
 
67
+ if (isTTY && lastByte !== undefined && lastByte !== 0x0A) {
68
+ await writer.write(new Uint8Array([0x0A]))
69
+ }
70
+
41
71
  return 0
42
72
  }
43
73
 
44
- // Otherwise process files
45
- const files = (argv.path as string[]) || []
46
- const bytes = argv.bytes as string | undefined
47
74
  for (const file of files) {
48
75
  const fullPath = path.resolve(shell.cwd, file)
49
76
 
@@ -64,13 +91,15 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
64
91
  const data = new Uint8Array(chunkSize)
65
92
  const readSize = Math.min(chunkSize, stat.size - bytesRead)
66
93
  await handle.read(data, 0, readSize, bytesRead)
67
- await writer.write(data.subarray(0, readSize))
94
+ const chunk = data.subarray(0, readSize)
95
+ if (chunk.length > 0) {
96
+ lastByte = chunk[chunk.length - 1]
97
+ }
98
+ await writer.write(chunk)
68
99
  bytesRead += readSize
69
100
  }
70
101
  } else {
71
102
  const device = await shell.context.fs.promises.open(fullPath)
72
- const maxBytes = bytes ? parseInt(bytes) : undefined
73
- let totalBytesRead = 0
74
103
  const chunkSize = 1024
75
104
  const data = new Uint8Array(chunkSize)
76
105
  let bytesRead = 0
@@ -80,25 +109,27 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
80
109
  const result = await device.read(data)
81
110
  bytesRead = result.bytesRead
82
111
  if (bytesRead > 0) {
83
- const bytesToWrite = maxBytes ? Math.min(bytesRead, maxBytes - totalBytesRead) : bytesRead
84
- if (bytesToWrite > 0) {
85
- await writer.write(data.subarray(0, bytesToWrite))
86
- totalBytesRead += bytesToWrite
112
+ const chunk = data.subarray(0, bytesRead)
113
+ if (chunk.length > 0) {
114
+ lastByte = chunk[chunk.length - 1]
87
115
  }
116
+ await writer.write(chunk)
88
117
  }
89
- } while (bytesRead > 0 && (!maxBytes || totalBytesRead < maxBytes))
118
+ } while (bytesRead > 0)
90
119
  }
91
120
  } finally {
92
121
  kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
93
122
  }
94
123
  }
95
124
 
125
+ if (isTTY && lastByte !== undefined && lastByte !== 0x0A) {
126
+ await writer.write(new Uint8Array([0x0A]))
127
+ }
128
+
96
129
  return 0
97
130
  } finally {
98
131
  writer.releaseLock()
99
- await writeStdout(process, terminal, '\n')
100
132
  }
101
133
  }
102
134
  })
103
135
  }
104
-
@@ -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
-