@ecmaos/coreutils 0.3.1 → 0.4.2

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 (199) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +48 -0
  3. package/dist/commands/awk.d.ts +4 -0
  4. package/dist/commands/awk.d.ts.map +1 -0
  5. package/dist/commands/awk.js +324 -0
  6. package/dist/commands/awk.js.map +1 -0
  7. package/dist/commands/chgrp.d.ts +4 -0
  8. package/dist/commands/chgrp.d.ts.map +1 -0
  9. package/dist/commands/chgrp.js +187 -0
  10. package/dist/commands/chgrp.js.map +1 -0
  11. package/dist/commands/chmod.d.ts.map +1 -1
  12. package/dist/commands/chmod.js +139 -2
  13. package/dist/commands/chmod.js.map +1 -1
  14. package/dist/commands/chown.d.ts +4 -0
  15. package/dist/commands/chown.d.ts.map +1 -0
  16. package/dist/commands/chown.js +257 -0
  17. package/dist/commands/chown.js.map +1 -0
  18. package/dist/commands/cksum.d.ts +4 -0
  19. package/dist/commands/cksum.d.ts.map +1 -0
  20. package/dist/commands/cksum.js +124 -0
  21. package/dist/commands/cksum.js.map +1 -0
  22. package/dist/commands/cmp.d.ts +4 -0
  23. package/dist/commands/cmp.d.ts.map +1 -0
  24. package/dist/commands/cmp.js +120 -0
  25. package/dist/commands/cmp.js.map +1 -0
  26. package/dist/commands/column.d.ts +4 -0
  27. package/dist/commands/column.d.ts.map +1 -0
  28. package/dist/commands/column.js +274 -0
  29. package/dist/commands/column.js.map +1 -0
  30. package/dist/commands/cp.d.ts.map +1 -1
  31. package/dist/commands/cp.js +81 -4
  32. package/dist/commands/cp.js.map +1 -1
  33. package/dist/commands/cron.d.ts.map +1 -1
  34. package/dist/commands/cron.js +116 -23
  35. package/dist/commands/cron.js.map +1 -1
  36. package/dist/commands/curl.d.ts +4 -0
  37. package/dist/commands/curl.d.ts.map +1 -0
  38. package/dist/commands/curl.js +238 -0
  39. package/dist/commands/curl.js.map +1 -0
  40. package/dist/commands/du.d.ts +4 -0
  41. package/dist/commands/du.d.ts.map +1 -0
  42. package/dist/commands/du.js +168 -0
  43. package/dist/commands/du.js.map +1 -0
  44. package/dist/commands/echo.d.ts.map +1 -1
  45. package/dist/commands/echo.js +125 -2
  46. package/dist/commands/echo.js.map +1 -1
  47. package/dist/commands/env.d.ts +4 -0
  48. package/dist/commands/env.d.ts.map +1 -0
  49. package/dist/commands/env.js +129 -0
  50. package/dist/commands/env.js.map +1 -0
  51. package/dist/commands/expand.d.ts +4 -0
  52. package/dist/commands/expand.d.ts.map +1 -0
  53. package/dist/commands/expand.js +197 -0
  54. package/dist/commands/expand.js.map +1 -0
  55. package/dist/commands/factor.d.ts +4 -0
  56. package/dist/commands/factor.d.ts.map +1 -0
  57. package/dist/commands/factor.js +141 -0
  58. package/dist/commands/factor.js.map +1 -0
  59. package/dist/commands/fmt.d.ts +4 -0
  60. package/dist/commands/fmt.d.ts.map +1 -0
  61. package/dist/commands/fmt.js +278 -0
  62. package/dist/commands/fmt.js.map +1 -0
  63. package/dist/commands/fold.d.ts +4 -0
  64. package/dist/commands/fold.d.ts.map +1 -0
  65. package/dist/commands/fold.js +253 -0
  66. package/dist/commands/fold.js.map +1 -0
  67. package/dist/commands/groups.d.ts +4 -0
  68. package/dist/commands/groups.d.ts.map +1 -0
  69. package/dist/commands/groups.js +61 -0
  70. package/dist/commands/groups.js.map +1 -0
  71. package/dist/commands/head.d.ts.map +1 -1
  72. package/dist/commands/head.js +184 -77
  73. package/dist/commands/head.js.map +1 -1
  74. package/dist/commands/hostname.d.ts +4 -0
  75. package/dist/commands/hostname.d.ts.map +1 -0
  76. package/dist/commands/hostname.js +80 -0
  77. package/dist/commands/hostname.js.map +1 -0
  78. package/dist/commands/less.d.ts.map +1 -1
  79. package/dist/commands/less.js +1 -0
  80. package/dist/commands/less.js.map +1 -1
  81. package/dist/commands/man.d.ts.map +1 -1
  82. package/dist/commands/man.js +3 -1
  83. package/dist/commands/man.js.map +1 -1
  84. package/dist/commands/mount.d.ts +4 -0
  85. package/dist/commands/mount.d.ts.map +1 -0
  86. package/dist/commands/mount.js +1136 -0
  87. package/dist/commands/mount.js.map +1 -0
  88. package/dist/commands/od.d.ts +4 -0
  89. package/dist/commands/od.d.ts.map +1 -0
  90. package/dist/commands/od.js +342 -0
  91. package/dist/commands/od.js.map +1 -0
  92. package/dist/commands/pr.d.ts +4 -0
  93. package/dist/commands/pr.d.ts.map +1 -0
  94. package/dist/commands/pr.js +298 -0
  95. package/dist/commands/pr.js.map +1 -0
  96. package/dist/commands/printf.d.ts +4 -0
  97. package/dist/commands/printf.d.ts.map +1 -0
  98. package/dist/commands/printf.js +271 -0
  99. package/dist/commands/printf.js.map +1 -0
  100. package/dist/commands/readlink.d.ts +4 -0
  101. package/dist/commands/readlink.d.ts.map +1 -0
  102. package/dist/commands/readlink.js +104 -0
  103. package/dist/commands/readlink.js.map +1 -0
  104. package/dist/commands/realpath.d.ts +4 -0
  105. package/dist/commands/realpath.d.ts.map +1 -0
  106. package/dist/commands/realpath.js +111 -0
  107. package/dist/commands/realpath.js.map +1 -0
  108. package/dist/commands/rev.d.ts +4 -0
  109. package/dist/commands/rev.d.ts.map +1 -0
  110. package/dist/commands/rev.js +134 -0
  111. package/dist/commands/rev.js.map +1 -0
  112. package/dist/commands/shuf.d.ts +4 -0
  113. package/dist/commands/shuf.d.ts.map +1 -0
  114. package/dist/commands/shuf.js +221 -0
  115. package/dist/commands/shuf.js.map +1 -0
  116. package/dist/commands/sleep.d.ts +4 -0
  117. package/dist/commands/sleep.d.ts.map +1 -0
  118. package/dist/commands/sleep.js +102 -0
  119. package/dist/commands/sleep.js.map +1 -0
  120. package/dist/commands/strings.d.ts +4 -0
  121. package/dist/commands/strings.d.ts.map +1 -0
  122. package/dist/commands/strings.js +170 -0
  123. package/dist/commands/strings.js.map +1 -0
  124. package/dist/commands/tac.d.ts +4 -0
  125. package/dist/commands/tac.d.ts.map +1 -0
  126. package/dist/commands/tac.js +130 -0
  127. package/dist/commands/tac.js.map +1 -0
  128. package/dist/commands/time.d.ts +4 -0
  129. package/dist/commands/time.d.ts.map +1 -0
  130. package/dist/commands/time.js +126 -0
  131. package/dist/commands/time.js.map +1 -0
  132. package/dist/commands/umount.d.ts +4 -0
  133. package/dist/commands/umount.d.ts.map +1 -0
  134. package/dist/commands/umount.js +103 -0
  135. package/dist/commands/umount.js.map +1 -0
  136. package/dist/commands/uname.d.ts +4 -0
  137. package/dist/commands/uname.d.ts.map +1 -0
  138. package/dist/commands/uname.js +149 -0
  139. package/dist/commands/uname.js.map +1 -0
  140. package/dist/commands/unexpand.d.ts +4 -0
  141. package/dist/commands/unexpand.d.ts.map +1 -0
  142. package/dist/commands/unexpand.js +286 -0
  143. package/dist/commands/unexpand.js.map +1 -0
  144. package/dist/commands/uptime.d.ts +4 -0
  145. package/dist/commands/uptime.d.ts.map +1 -0
  146. package/dist/commands/uptime.js +62 -0
  147. package/dist/commands/uptime.js.map +1 -0
  148. package/dist/commands/view.d.ts +1 -0
  149. package/dist/commands/view.d.ts.map +1 -1
  150. package/dist/commands/view.js +408 -66
  151. package/dist/commands/view.js.map +1 -1
  152. package/dist/commands/yes.d.ts +4 -0
  153. package/dist/commands/yes.d.ts.map +1 -0
  154. package/dist/commands/yes.js +58 -0
  155. package/dist/commands/yes.js.map +1 -0
  156. package/dist/index.d.ts +24 -0
  157. package/dist/index.d.ts.map +1 -1
  158. package/dist/index.js +82 -0
  159. package/dist/index.js.map +1 -1
  160. package/package.json +12 -3
  161. package/src/commands/awk.ts +340 -0
  162. package/src/commands/chmod.ts +141 -2
  163. package/src/commands/chown.ts +321 -0
  164. package/src/commands/cksum.ts +133 -0
  165. package/src/commands/cmp.ts +126 -0
  166. package/src/commands/column.ts +273 -0
  167. package/src/commands/cp.ts +93 -4
  168. package/src/commands/cron.ts +115 -23
  169. package/src/commands/curl.ts +231 -0
  170. package/src/commands/echo.ts +122 -2
  171. package/src/commands/env.ts +143 -0
  172. package/src/commands/expand.ts +207 -0
  173. package/src/commands/factor.ts +151 -0
  174. package/src/commands/fmt.ts +293 -0
  175. package/src/commands/fold.ts +257 -0
  176. package/src/commands/groups.ts +72 -0
  177. package/src/commands/head.ts +176 -77
  178. package/src/commands/hostname.ts +81 -0
  179. package/src/commands/less.ts +1 -0
  180. package/src/commands/man.ts +4 -1
  181. package/src/commands/mount.ts +1302 -0
  182. package/src/commands/od.ts +327 -0
  183. package/src/commands/pr.ts +291 -0
  184. package/src/commands/printf.ts +271 -0
  185. package/src/commands/readlink.ts +102 -0
  186. package/src/commands/realpath.ts +126 -0
  187. package/src/commands/rev.ts +143 -0
  188. package/src/commands/shuf.ts +218 -0
  189. package/src/commands/sleep.ts +109 -0
  190. package/src/commands/strings.ts +176 -0
  191. package/src/commands/tac.ts +138 -0
  192. package/src/commands/time.ts +144 -0
  193. package/src/commands/umount.ts +116 -0
  194. package/src/commands/uname.ts +130 -0
  195. package/src/commands/unexpand.ts +305 -0
  196. package/src/commands/uptime.ts +73 -0
  197. package/src/commands/view.ts +463 -73
  198. package/src/index.ts +82 -0
  199. package/tsconfig.json +4 -0
@@ -0,0 +1,144 @@
1
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
2
+ import { TerminalCommand } from '../shared/terminal-command.js'
3
+ import { writelnStderr } from '../shared/helpers.js'
4
+ import path from 'path'
5
+
6
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
7
+ const usage = `Usage: time COMMAND [ARG]...
8
+ Run COMMAND and print a summary of the real, user, and system time used.
9
+
10
+ --help display this help and exit
11
+
12
+ Note: This is a simplified version that measures real (wall clock) time.`
13
+ writelnStderr(process, terminal, usage)
14
+ }
15
+
16
+ function formatTime(seconds: number): string {
17
+ if (seconds < 1) {
18
+ return `${(seconds * 1000).toFixed(0)}ms`
19
+ }
20
+ if (seconds < 60) {
21
+ return `${seconds.toFixed(2)}s`
22
+ }
23
+ const mins = Math.floor(seconds / 60)
24
+ const secs = (seconds % 60).toFixed(2)
25
+ return `${mins}m${secs}s`
26
+ }
27
+
28
+ async function resolveCommand(shell: Shell, command: string): Promise<string | undefined> {
29
+ const DefaultShellPath = '$HOME/bin:/bin:/usr/bin:/usr/local/bin:/usr/local/sbin:/usr/sbin:/sbin'
30
+
31
+ if (command.startsWith('./')) {
32
+ const cwdCommand = path.join(shell.cwd, command.slice(2))
33
+ if (await shell.context.fs.promises.exists(cwdCommand)) {
34
+ return cwdCommand
35
+ }
36
+ return undefined
37
+ }
38
+
39
+ const paths = shell.env.get('PATH')?.split(':') || DefaultShellPath.split(':')
40
+ const resolvedCommand = path.resolve(command)
41
+
42
+ if (await shell.context.fs.promises.exists(resolvedCommand)) {
43
+ return resolvedCommand
44
+ }
45
+
46
+ for (const pathDir of paths) {
47
+ const expandedPath = pathDir.replace(/\$([A-Z_]+)/g, (_, name) => shell.env.get(name) || '')
48
+ const fullPath = `${expandedPath}/${command}`
49
+ if (await shell.context.fs.promises.exists(fullPath)) return fullPath
50
+ }
51
+
52
+ return undefined
53
+ }
54
+
55
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
56
+ return new TerminalCommand({
57
+ command: 'time',
58
+ description: 'Measure command execution time',
59
+ kernel,
60
+ shell,
61
+ terminal,
62
+ run: async (pid: number, argv: string[]) => {
63
+ const process = kernel.processes.get(pid) as Process | undefined
64
+
65
+ if (!process) return 1
66
+
67
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
68
+ printUsage(process, terminal)
69
+ return 0
70
+ }
71
+
72
+ if (argv.length === 0 || !argv[0]) {
73
+ await writelnStderr(process, terminal, 'time: missing command')
74
+ await writelnStderr(process, terminal, "Try 'time --help' for more information.")
75
+ return 1
76
+ }
77
+
78
+ const command = argv[0]
79
+ const commandArgs = argv.slice(1)
80
+
81
+ const resolvedCommand = await resolveCommand(shell, command)
82
+ if (!resolvedCommand) {
83
+ await writelnStderr(process, terminal, `time: command not found: ${command}`)
84
+ return 127
85
+ }
86
+
87
+ const startTime = performance.now()
88
+
89
+ const subcommandStdout = new WritableStream<Uint8Array>({
90
+ write: async (chunk) => {
91
+ const writer = process.stdout.getWriter()
92
+ try {
93
+ await writer.write(chunk)
94
+ } finally {
95
+ writer.releaseLock()
96
+ }
97
+ }
98
+ })
99
+
100
+ const subcommandStderr = new WritableStream<Uint8Array>({
101
+ write: async (chunk) => {
102
+ const writer = process.stderr.getWriter()
103
+ try {
104
+ await writer.write(chunk)
105
+ } finally {
106
+ writer.releaseLock()
107
+ }
108
+ }
109
+ })
110
+
111
+ try {
112
+ const exitCode = await kernel.execute({
113
+ command: resolvedCommand,
114
+ args: commandArgs,
115
+ shell: shell,
116
+ terminal: terminal,
117
+ stdin: process.stdin,
118
+ stdout: subcommandStdout,
119
+ stderr: subcommandStderr
120
+ })
121
+
122
+ const endTime = performance.now()
123
+ const elapsedSeconds = (endTime - startTime) / 1000
124
+
125
+ await writelnStderr(process, terminal, `\nreal ${formatTime(elapsedSeconds)}`)
126
+ await writelnStderr(process, terminal, `user ${formatTime(elapsedSeconds)}`)
127
+ await writelnStderr(process, terminal, `sys ${formatTime(0)}`)
128
+
129
+ return exitCode
130
+ } catch (error) {
131
+ const endTime = performance.now()
132
+ const elapsedSeconds = (endTime - startTime) / 1000
133
+
134
+ await writelnStderr(process, terminal, `\nreal ${formatTime(elapsedSeconds)}`)
135
+ await writelnStderr(process, terminal, `user ${formatTime(elapsedSeconds)}`)
136
+ await writelnStderr(process, terminal, `sys ${formatTime(0)}`)
137
+
138
+ const errorMessage = error instanceof Error ? error.message : String(error)
139
+ await writelnStderr(process, terminal, `time: ${errorMessage}`)
140
+ return 1
141
+ }
142
+ }
143
+ })
144
+ }
@@ -0,0 +1,116 @@
1
+ import path from 'path'
2
+ import chalk from 'chalk'
3
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
4
+ import { TerminalCommand } from '../shared/terminal-command.js'
5
+ import { writelnStdout, writelnStderr } from '../shared/helpers.js'
6
+
7
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
8
+ const usage = `Usage: umount [OPTIONS] TARGET
9
+ umount [-a|--all]
10
+
11
+ Unmount a filesystem.
12
+
13
+ Options:
14
+ -a, --all unmount all filesystems (except root)
15
+ --help display this help and exit
16
+
17
+ Examples:
18
+ umount /mnt/tmp unmount filesystem at /mnt/tmp
19
+ umount -a unmount all filesystems`
20
+ writelnStderr(process, terminal, usage)
21
+ }
22
+
23
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
24
+ return new TerminalCommand({
25
+ command: 'umount',
26
+ description: 'Unmount a filesystem',
27
+ kernel,
28
+ shell,
29
+ terminal,
30
+ run: async (pid: number, argv: string[]) => {
31
+ const process = kernel.processes.get(pid) as Process | undefined
32
+
33
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
34
+ printUsage(process, terminal)
35
+ return 0
36
+ }
37
+
38
+ let allMode = false
39
+ const positionalArgs: string[] = []
40
+
41
+ for (let i = 0; i < argv.length; i++) {
42
+ const arg = argv[i]
43
+ if (arg === '-a' || arg === '--all') {
44
+ allMode = true
45
+ } else if (arg && !arg.startsWith('-')) {
46
+ positionalArgs.push(arg)
47
+ }
48
+ }
49
+
50
+
51
+ if (allMode) {
52
+ const mountList = Array.from(kernel.filesystem.mounts.keys())
53
+ let unmountedCount = 0
54
+ let errorCount = 0
55
+
56
+ for (const target of mountList) {
57
+ if (target === '/') continue
58
+
59
+ try {
60
+ kernel.filesystem.fsSync.umount(target)
61
+ unmountedCount++
62
+ await writelnStdout(process, terminal, chalk.green(`Unmounted ${target}`))
63
+ } catch (error) {
64
+ errorCount++
65
+ await writelnStderr(process, terminal, chalk.red(`umount: failed to unmount ${target}: ${error instanceof Error ? error.message : 'Unknown error'}`))
66
+ }
67
+ }
68
+
69
+ if (unmountedCount === 0 && errorCount === 0) {
70
+ await writelnStdout(process, terminal, 'No filesystems to unmount.')
71
+ }
72
+
73
+ return errorCount > 0 ? 1 : 0
74
+ }
75
+
76
+ if (positionalArgs.length === 0) {
77
+ await writelnStderr(process, terminal, chalk.red('umount: missing target argument'))
78
+ await writelnStderr(process, terminal, 'Try \'umount --help\' for more information.')
79
+ return 1
80
+ }
81
+
82
+ if (positionalArgs.length > 1) {
83
+ await writelnStderr(process, terminal, chalk.red('umount: too many arguments'))
84
+ await writelnStderr(process, terminal, 'Try \'umount --help\' for more information.')
85
+ return 1
86
+ }
87
+
88
+ const targetArg = positionalArgs[0]
89
+ if (!targetArg) {
90
+ await writelnStderr(process, terminal, chalk.red('umount: missing target argument'))
91
+ return 1
92
+ }
93
+ const target = path.resolve(shell.cwd, targetArg)
94
+
95
+ if (target === '/') {
96
+ await writelnStderr(process, terminal, chalk.red('umount: cannot unmount root filesystem'))
97
+ return 1
98
+ }
99
+
100
+ const mountList = Array.from(kernel.filesystem.mounts.keys())
101
+ if (!mountList.includes(target)) {
102
+ await writelnStderr(process, terminal, chalk.red(`umount: ${target} is not mounted`))
103
+ return 1
104
+ }
105
+
106
+ try {
107
+ kernel.filesystem.fsSync.umount(target)
108
+ await writelnStdout(process, terminal, chalk.green(`Unmounted ${target}`))
109
+ return 0
110
+ } catch (error) {
111
+ await writelnStderr(process, terminal, chalk.red(`umount: failed to unmount ${target}: ${error instanceof Error ? error.message : 'Unknown error'}`))
112
+ return 1
113
+ }
114
+ }
115
+ })
116
+ }
@@ -0,0 +1,130 @@
1
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
2
+ import { TerminalCommand } from '../shared/terminal-command.js'
3
+ import { writelnStderr, writelnStdout } from '../shared/helpers.js'
4
+
5
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
6
+ const usage = `Usage: uname [OPTION]...
7
+ Print system information.
8
+
9
+ -a, --all print all information
10
+ -s, --kernel-name print the kernel name
11
+ -n, --nodename print the network node hostname
12
+ -r, --kernel-release print the kernel release
13
+ -v, --kernel-version print the kernel version
14
+ -m, --machine print the machine hardware name
15
+ -p, --processor print the processor type
16
+ -i, --hardware-platform print the hardware platform
17
+ -o, --operating-system print the operating system
18
+ --help display this help and exit`
19
+ writelnStderr(process, terminal, usage)
20
+ }
21
+
22
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
23
+ return new TerminalCommand({
24
+ command: 'uname',
25
+ description: 'Print system information',
26
+ kernel,
27
+ shell,
28
+ terminal,
29
+ run: async (pid: number, argv: string[]) => {
30
+ const process = kernel.processes.get(pid) as Process | undefined
31
+
32
+ if (!process) return 1
33
+
34
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
35
+ printUsage(process, terminal)
36
+ return 0
37
+ }
38
+
39
+ let showAll = false
40
+ let showKernelName = false
41
+ let showNodename = false
42
+ let showKernelRelease = false
43
+ let showKernelVersion = false
44
+ let showMachine = false
45
+ let showProcessor = false
46
+ let showHardwarePlatform = false
47
+ let showOperatingSystem = false
48
+
49
+ for (const arg of argv) {
50
+ if (!arg) continue
51
+
52
+ if (arg === '--help' || arg === '-h') {
53
+ printUsage(process, terminal)
54
+ return 0
55
+ } else if (arg === '-a' || arg === '--all') {
56
+ showAll = true
57
+ } else if (arg === '-s' || arg === '--kernel-name') {
58
+ showKernelName = true
59
+ } else if (arg === '-n' || arg === '--nodename') {
60
+ showNodename = true
61
+ } else if (arg === '-r' || arg === '--kernel-release') {
62
+ showKernelRelease = true
63
+ } else if (arg === '-v' || arg === '--kernel-version') {
64
+ showKernelVersion = true
65
+ } else if (arg === '-m' || arg === '--machine') {
66
+ showMachine = true
67
+ } else if (arg === '-p' || arg === '--processor') {
68
+ showProcessor = true
69
+ } else if (arg === '-i' || arg === '--hardware-platform') {
70
+ showHardwarePlatform = true
71
+ } else if (arg === '-o' || arg === '--operating-system') {
72
+ showOperatingSystem = true
73
+ } else if (arg.startsWith('-')) {
74
+ const flags = arg.slice(1).split('')
75
+ if (flags.includes('a')) showAll = true
76
+ if (flags.includes('s')) showKernelName = true
77
+ if (flags.includes('n')) showNodename = true
78
+ if (flags.includes('r')) showKernelRelease = true
79
+ if (flags.includes('v')) showKernelVersion = true
80
+ if (flags.includes('m')) showMachine = true
81
+ if (flags.includes('p')) showProcessor = true
82
+ if (flags.includes('i')) showHardwarePlatform = true
83
+ if (flags.includes('o')) showOperatingSystem = true
84
+ const invalidFlags = flags.filter(f => !['a', 's', 'n', 'r', 'v', 'm', 'p', 'i', 'o'].includes(f))
85
+ if (invalidFlags.length > 0) {
86
+ await writelnStderr(process, terminal, `uname: invalid option -- '${invalidFlags[0]}'`)
87
+ await writelnStderr(process, terminal, "Try 'uname --help' for more information.")
88
+ return 1
89
+ }
90
+ }
91
+ }
92
+
93
+ const highEntropyValues = await navigator.userAgentData?.getHighEntropyValues([
94
+ "architecture",
95
+ "bitness",
96
+ "formFactor",
97
+ "fullVersionList",
98
+ "model",
99
+ "platformVersion",
100
+ "wow64"
101
+ ]) ?? {}
102
+
103
+ const kernelName = kernel.name
104
+ const kernelVersion = kernel.version
105
+ const nodename = typeof window !== 'undefined' ? window.location.hostname : 'localhost'
106
+ const machine = navigator.userAgentData?.platform || navigator.platform || 'unknown'
107
+ const processor = highEntropyValues.architecture || 'unknown'
108
+ const hardwarePlatform = highEntropyValues.model || 'unknown'
109
+ const operatingSystem = kernelName
110
+
111
+ if (showAll || (!showKernelName && !showNodename && !showKernelRelease && !showKernelVersion && !showMachine && !showProcessor && !showHardwarePlatform && !showOperatingSystem)) {
112
+ const output = `${kernelName} ${nodename} ${kernelVersion} ${machine} ${processor} ${hardwarePlatform} ${operatingSystem}`
113
+ await writelnStdout(process, terminal, output)
114
+ } else {
115
+ const parts: string[] = []
116
+ if (showAll || showKernelName) parts.push(kernelName)
117
+ if (showAll || showNodename) parts.push(nodename)
118
+ if (showAll || showKernelRelease) parts.push(kernelVersion)
119
+ if (showAll || showKernelVersion) parts.push(kernelVersion)
120
+ if (showAll || showMachine) parts.push(machine)
121
+ if (showAll || showProcessor) parts.push(processor)
122
+ if (showAll || showHardwarePlatform) parts.push(hardwarePlatform)
123
+ if (showAll || showOperatingSystem) parts.push(operatingSystem)
124
+ await writelnStdout(process, terminal, parts.join(' '))
125
+ }
126
+
127
+ return 0
128
+ }
129
+ })
130
+ }
@@ -0,0 +1,305 @@
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: unexpand [OPTION]... [FILE]...
9
+ Convert spaces to tabs in each FILE.
10
+
11
+ -t, --tabs=NUMBER have tabs NUMBER characters apart, not 8
12
+ -t, --tabs=LIST use comma separated list of tab positions
13
+ -a, --all convert all spaces, not just leading spaces
14
+ --help display this help and exit`
15
+ writelnStderr(process, terminal, usage)
16
+ }
17
+
18
+ function parseTabStops(tabStr: string): number[] {
19
+ if (tabStr.includes(',')) {
20
+ const stops = tabStr.split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n) && n > 0)
21
+ return stops.length > 0 ? stops : [8]
22
+ }
23
+ const single = parseInt(tabStr, 10)
24
+ return !isNaN(single) && single > 0 ? [single] : [8]
25
+ }
26
+
27
+ function unexpandTabs(line: string, tabStops: number[], all: boolean): string {
28
+ if (all) {
29
+ return unexpandAllSpaces(line, tabStops)
30
+ } else {
31
+ return unexpandLeadingSpaces(line, tabStops)
32
+ }
33
+ }
34
+
35
+ function getNextTabStop(column: number, tabStops: number[]): number {
36
+ if (tabStops.length === 1) {
37
+ const interval = tabStops[0] ?? 8
38
+ return Math.ceil((column + 1) / interval) * interval
39
+ }
40
+
41
+ for (const stop of tabStops) {
42
+ if (stop > column) {
43
+ return stop
44
+ }
45
+ }
46
+
47
+ const lastStop = tabStops[tabStops.length - 1]
48
+ let nextStop = lastStop ?? 8
49
+ while (nextStop <= column) {
50
+ nextStop += lastStop ?? 8
51
+ }
52
+ return nextStop
53
+ }
54
+
55
+ function unexpandLeadingSpaces(line: string, tabStops: number[]): string {
56
+ let leadingSpaces = 0
57
+ let i = 0
58
+
59
+ while (i < line.length && line[i] === ' ') {
60
+ leadingSpaces++
61
+ i++
62
+ }
63
+
64
+ if (leadingSpaces === 0) {
65
+ return line
66
+ }
67
+
68
+ let result = ''
69
+ let column = 0
70
+ let spaceIdx = 0
71
+
72
+ while (spaceIdx < leadingSpaces) {
73
+ const nextStop = getNextTabStop(column, tabStops)
74
+ const spacesToNextStop = nextStop - column
75
+ const remainingSpaces = leadingSpaces - spaceIdx
76
+
77
+ if (spacesToNextStop <= remainingSpaces) {
78
+ result += '\t'
79
+ column = nextStop
80
+ spaceIdx += spacesToNextStop
81
+ } else {
82
+ result += ' '
83
+ column++
84
+ spaceIdx++
85
+ }
86
+ }
87
+
88
+ return result + line.slice(leadingSpaces)
89
+ }
90
+
91
+ function unexpandAllSpaces(line: string, tabStops: number[]): string {
92
+ let result = ''
93
+ let column = 0
94
+ let spaceCount = 0
95
+ let spaceStartColumn = 0
96
+
97
+ for (let i = 0; i < line.length; i++) {
98
+ const char = line[i]
99
+
100
+ if (char === ' ') {
101
+ if (spaceCount === 0) {
102
+ spaceStartColumn = column
103
+ }
104
+ spaceCount++
105
+ column++
106
+ } else {
107
+ if (spaceCount > 0) {
108
+ result += convertSpacesToTabs(spaceStartColumn, spaceCount, tabStops)
109
+ spaceCount = 0
110
+ }
111
+
112
+ result += char
113
+ if (char === '\n' || char === '\r') {
114
+ column = 0
115
+ } else {
116
+ column++
117
+ }
118
+ }
119
+ }
120
+
121
+ if (spaceCount > 0) {
122
+ result += ' '.repeat(spaceCount)
123
+ }
124
+
125
+ return result
126
+ }
127
+
128
+ function convertSpacesToTabs(startColumn: number, spaceCount: number, tabStops: number[]): string {
129
+ let result = ''
130
+ let column = startColumn
131
+ let spaceIdx = 0
132
+
133
+ while (spaceIdx < spaceCount) {
134
+ const nextStop = getNextTabStop(column, tabStops)
135
+ const spacesToNextStop = nextStop - column
136
+ const remainingSpaces = spaceCount - spaceIdx
137
+
138
+ if (spacesToNextStop <= remainingSpaces && spacesToNextStop > 1) {
139
+ result += '\t'
140
+ column = nextStop
141
+ spaceIdx += spacesToNextStop
142
+ } else if (spacesToNextStop === 1 && remainingSpaces >= 1) {
143
+ result += '\t'
144
+ column = nextStop
145
+ spaceIdx += 1
146
+ } else {
147
+ result += ' '
148
+ column++
149
+ spaceIdx++
150
+ }
151
+ }
152
+
153
+ return result
154
+ }
155
+
156
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
157
+ return new TerminalCommand({
158
+ command: 'unexpand',
159
+ description: 'Convert spaces to tabs',
160
+ kernel,
161
+ shell,
162
+ terminal,
163
+ run: async (pid: number, argv: string[]) => {
164
+ const process = kernel.processes.get(pid) as Process | undefined
165
+
166
+ if (!process) return 1
167
+
168
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
169
+ printUsage(process, terminal)
170
+ return 0
171
+ }
172
+
173
+ let tabStops: number[] = [8]
174
+ let all = false
175
+ const files: string[] = []
176
+
177
+ for (let i = 0; i < argv.length; i++) {
178
+ const arg = argv[i]
179
+ if (!arg) continue
180
+
181
+ if (arg === '--help' || arg === '-h') {
182
+ printUsage(process, terminal)
183
+ return 0
184
+ } else if (arg === '-t' || arg === '--tabs') {
185
+ if (i + 1 < argv.length) {
186
+ const tabStr = argv[++i]
187
+ if (tabStr !== undefined) {
188
+ tabStops = parseTabStops(tabStr)
189
+ }
190
+ }
191
+ } else if (arg.startsWith('--tabs=')) {
192
+ const tabStr = arg.slice(7)
193
+ tabStops = parseTabStops(tabStr)
194
+ } else if (arg.startsWith('-t')) {
195
+ const tabStr = arg.slice(2)
196
+ if (tabStr) {
197
+ tabStops = parseTabStops(tabStr)
198
+ }
199
+ } else if (arg === '-a' || arg === '--all') {
200
+ all = true
201
+ } else if (arg.startsWith('-')) {
202
+ const flags = arg.slice(1).split('')
203
+ if (flags.includes('a')) all = true
204
+ const invalidFlags = flags.filter(f => f !== 'a')
205
+ if (invalidFlags.length > 0) {
206
+ await writelnStderr(process, terminal, `unexpand: invalid option -- '${invalidFlags[0]}'`)
207
+ await writelnStderr(process, terminal, "Try 'unexpand --help' for more information.")
208
+ return 1
209
+ }
210
+ } else {
211
+ files.push(arg)
212
+ }
213
+ }
214
+
215
+ const writer = process.stdout.getWriter()
216
+
217
+ try {
218
+ let lines: string[] = []
219
+
220
+ if (files.length === 0) {
221
+ if (!process.stdin) {
222
+ return 0
223
+ }
224
+
225
+ const reader = process.stdin.getReader()
226
+ const decoder = new TextDecoder()
227
+ let buffer = ''
228
+
229
+ try {
230
+ while (true) {
231
+ const { done, value } = await reader.read()
232
+ if (done) break
233
+ if (value) {
234
+ buffer += decoder.decode(value, { stream: true })
235
+ const newLines = buffer.split('\n')
236
+ buffer = newLines.pop() || ''
237
+ lines.push(...newLines)
238
+ }
239
+ }
240
+ if (buffer) {
241
+ lines.push(buffer)
242
+ }
243
+ } finally {
244
+ reader.releaseLock()
245
+ }
246
+ } else {
247
+ for (const file of files) {
248
+ const fullPath = path.resolve(shell.cwd, file)
249
+
250
+ let interrupted = false
251
+ const interruptHandler = () => { interrupted = true }
252
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
253
+
254
+ try {
255
+ if (fullPath.startsWith('/dev')) {
256
+ await writelnStderr(process, terminal, `unexpand: ${file}: cannot process device files`)
257
+ continue
258
+ }
259
+
260
+ const handle = await shell.context.fs.promises.open(fullPath, 'r')
261
+ const stat = await shell.context.fs.promises.stat(fullPath)
262
+
263
+ const decoder = new TextDecoder()
264
+ let content = ''
265
+ let bytesRead = 0
266
+ const chunkSize = 1024
267
+
268
+ while (bytesRead < stat.size) {
269
+ if (interrupted) break
270
+ const data = new Uint8Array(chunkSize)
271
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
272
+ await handle.read(data, 0, readSize, bytesRead)
273
+ const chunk = data.subarray(0, readSize)
274
+ content += decoder.decode(chunk, { stream: true })
275
+ bytesRead += readSize
276
+ }
277
+
278
+ const fileLines = content.split('\n')
279
+ if (fileLines[fileLines.length - 1] === '') {
280
+ fileLines.pop()
281
+ }
282
+ lines.push(...fileLines)
283
+ } catch (error) {
284
+ await writelnStderr(process, terminal, `unexpand: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
285
+ } finally {
286
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
287
+ }
288
+ }
289
+ }
290
+
291
+ for (const line of lines) {
292
+ const unexpanded = unexpandTabs(line, tabStops, all)
293
+ await writer.write(new TextEncoder().encode(unexpanded + '\n'))
294
+ }
295
+
296
+ return 0
297
+ } catch (error) {
298
+ await writelnStderr(process, terminal, `unexpand: ${error instanceof Error ? error.message : 'Unknown error'}`)
299
+ return 1
300
+ } finally {
301
+ writer.releaseLock()
302
+ }
303
+ }
304
+ })
305
+ }