@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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +48 -0
- package/dist/commands/awk.d.ts +4 -0
- package/dist/commands/awk.d.ts.map +1 -0
- package/dist/commands/awk.js +324 -0
- package/dist/commands/awk.js.map +1 -0
- package/dist/commands/chgrp.d.ts +4 -0
- package/dist/commands/chgrp.d.ts.map +1 -0
- package/dist/commands/chgrp.js +187 -0
- package/dist/commands/chgrp.js.map +1 -0
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +139 -2
- package/dist/commands/chmod.js.map +1 -1
- package/dist/commands/chown.d.ts +4 -0
- package/dist/commands/chown.d.ts.map +1 -0
- package/dist/commands/chown.js +257 -0
- package/dist/commands/chown.js.map +1 -0
- package/dist/commands/cksum.d.ts +4 -0
- package/dist/commands/cksum.d.ts.map +1 -0
- package/dist/commands/cksum.js +124 -0
- package/dist/commands/cksum.js.map +1 -0
- package/dist/commands/cmp.d.ts +4 -0
- package/dist/commands/cmp.d.ts.map +1 -0
- package/dist/commands/cmp.js +120 -0
- package/dist/commands/cmp.js.map +1 -0
- package/dist/commands/column.d.ts +4 -0
- package/dist/commands/column.d.ts.map +1 -0
- package/dist/commands/column.js +274 -0
- package/dist/commands/column.js.map +1 -0
- package/dist/commands/cp.d.ts.map +1 -1
- package/dist/commands/cp.js +81 -4
- package/dist/commands/cp.js.map +1 -1
- package/dist/commands/cron.d.ts.map +1 -1
- package/dist/commands/cron.js +116 -23
- package/dist/commands/cron.js.map +1 -1
- package/dist/commands/curl.d.ts +4 -0
- package/dist/commands/curl.d.ts.map +1 -0
- package/dist/commands/curl.js +238 -0
- package/dist/commands/curl.js.map +1 -0
- package/dist/commands/du.d.ts +4 -0
- package/dist/commands/du.d.ts.map +1 -0
- package/dist/commands/du.js +168 -0
- package/dist/commands/du.js.map +1 -0
- package/dist/commands/echo.d.ts.map +1 -1
- package/dist/commands/echo.js +125 -2
- package/dist/commands/echo.js.map +1 -1
- package/dist/commands/env.d.ts +4 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +129 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/expand.d.ts +4 -0
- package/dist/commands/expand.d.ts.map +1 -0
- package/dist/commands/expand.js +197 -0
- package/dist/commands/expand.js.map +1 -0
- package/dist/commands/factor.d.ts +4 -0
- package/dist/commands/factor.d.ts.map +1 -0
- package/dist/commands/factor.js +141 -0
- package/dist/commands/factor.js.map +1 -0
- package/dist/commands/fmt.d.ts +4 -0
- package/dist/commands/fmt.d.ts.map +1 -0
- package/dist/commands/fmt.js +278 -0
- package/dist/commands/fmt.js.map +1 -0
- package/dist/commands/fold.d.ts +4 -0
- package/dist/commands/fold.d.ts.map +1 -0
- package/dist/commands/fold.js +253 -0
- package/dist/commands/fold.js.map +1 -0
- package/dist/commands/groups.d.ts +4 -0
- package/dist/commands/groups.d.ts.map +1 -0
- package/dist/commands/groups.js +61 -0
- package/dist/commands/groups.js.map +1 -0
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +184 -77
- package/dist/commands/head.js.map +1 -1
- package/dist/commands/hostname.d.ts +4 -0
- package/dist/commands/hostname.d.ts.map +1 -0
- package/dist/commands/hostname.js +80 -0
- package/dist/commands/hostname.js.map +1 -0
- package/dist/commands/less.d.ts.map +1 -1
- package/dist/commands/less.js +1 -0
- package/dist/commands/less.js.map +1 -1
- package/dist/commands/man.d.ts.map +1 -1
- package/dist/commands/man.js +3 -1
- package/dist/commands/man.js.map +1 -1
- package/dist/commands/mount.d.ts +4 -0
- package/dist/commands/mount.d.ts.map +1 -0
- package/dist/commands/mount.js +1136 -0
- package/dist/commands/mount.js.map +1 -0
- package/dist/commands/od.d.ts +4 -0
- package/dist/commands/od.d.ts.map +1 -0
- package/dist/commands/od.js +342 -0
- package/dist/commands/od.js.map +1 -0
- package/dist/commands/pr.d.ts +4 -0
- package/dist/commands/pr.d.ts.map +1 -0
- package/dist/commands/pr.js +298 -0
- package/dist/commands/pr.js.map +1 -0
- package/dist/commands/printf.d.ts +4 -0
- package/dist/commands/printf.d.ts.map +1 -0
- package/dist/commands/printf.js +271 -0
- package/dist/commands/printf.js.map +1 -0
- package/dist/commands/readlink.d.ts +4 -0
- package/dist/commands/readlink.d.ts.map +1 -0
- package/dist/commands/readlink.js +104 -0
- package/dist/commands/readlink.js.map +1 -0
- package/dist/commands/realpath.d.ts +4 -0
- package/dist/commands/realpath.d.ts.map +1 -0
- package/dist/commands/realpath.js +111 -0
- package/dist/commands/realpath.js.map +1 -0
- package/dist/commands/rev.d.ts +4 -0
- package/dist/commands/rev.d.ts.map +1 -0
- package/dist/commands/rev.js +134 -0
- package/dist/commands/rev.js.map +1 -0
- package/dist/commands/shuf.d.ts +4 -0
- package/dist/commands/shuf.d.ts.map +1 -0
- package/dist/commands/shuf.js +221 -0
- package/dist/commands/shuf.js.map +1 -0
- package/dist/commands/sleep.d.ts +4 -0
- package/dist/commands/sleep.d.ts.map +1 -0
- package/dist/commands/sleep.js +102 -0
- package/dist/commands/sleep.js.map +1 -0
- package/dist/commands/strings.d.ts +4 -0
- package/dist/commands/strings.d.ts.map +1 -0
- package/dist/commands/strings.js +170 -0
- package/dist/commands/strings.js.map +1 -0
- package/dist/commands/tac.d.ts +4 -0
- package/dist/commands/tac.d.ts.map +1 -0
- package/dist/commands/tac.js +130 -0
- package/dist/commands/tac.js.map +1 -0
- package/dist/commands/time.d.ts +4 -0
- package/dist/commands/time.d.ts.map +1 -0
- package/dist/commands/time.js +126 -0
- package/dist/commands/time.js.map +1 -0
- package/dist/commands/umount.d.ts +4 -0
- package/dist/commands/umount.d.ts.map +1 -0
- package/dist/commands/umount.js +103 -0
- package/dist/commands/umount.js.map +1 -0
- package/dist/commands/uname.d.ts +4 -0
- package/dist/commands/uname.d.ts.map +1 -0
- package/dist/commands/uname.js +149 -0
- package/dist/commands/uname.js.map +1 -0
- package/dist/commands/unexpand.d.ts +4 -0
- package/dist/commands/unexpand.d.ts.map +1 -0
- package/dist/commands/unexpand.js +286 -0
- package/dist/commands/unexpand.js.map +1 -0
- package/dist/commands/uptime.d.ts +4 -0
- package/dist/commands/uptime.d.ts.map +1 -0
- package/dist/commands/uptime.js +62 -0
- package/dist/commands/uptime.js.map +1 -0
- package/dist/commands/view.d.ts +1 -0
- package/dist/commands/view.d.ts.map +1 -1
- package/dist/commands/view.js +408 -66
- package/dist/commands/view.js.map +1 -1
- package/dist/commands/yes.d.ts +4 -0
- package/dist/commands/yes.d.ts.map +1 -0
- package/dist/commands/yes.js +58 -0
- package/dist/commands/yes.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -1
- package/package.json +12 -3
- package/src/commands/awk.ts +340 -0
- package/src/commands/chmod.ts +141 -2
- package/src/commands/chown.ts +321 -0
- package/src/commands/cksum.ts +133 -0
- package/src/commands/cmp.ts +126 -0
- package/src/commands/column.ts +273 -0
- package/src/commands/cp.ts +93 -4
- package/src/commands/cron.ts +115 -23
- package/src/commands/curl.ts +231 -0
- package/src/commands/echo.ts +122 -2
- package/src/commands/env.ts +143 -0
- package/src/commands/expand.ts +207 -0
- package/src/commands/factor.ts +151 -0
- package/src/commands/fmt.ts +293 -0
- package/src/commands/fold.ts +257 -0
- package/src/commands/groups.ts +72 -0
- package/src/commands/head.ts +176 -77
- package/src/commands/hostname.ts +81 -0
- package/src/commands/less.ts +1 -0
- package/src/commands/man.ts +4 -1
- package/src/commands/mount.ts +1302 -0
- package/src/commands/od.ts +327 -0
- package/src/commands/pr.ts +291 -0
- package/src/commands/printf.ts +271 -0
- package/src/commands/readlink.ts +102 -0
- package/src/commands/realpath.ts +126 -0
- package/src/commands/rev.ts +143 -0
- package/src/commands/shuf.ts +218 -0
- package/src/commands/sleep.ts +109 -0
- package/src/commands/strings.ts +176 -0
- package/src/commands/tac.ts +138 -0
- package/src/commands/time.ts +144 -0
- package/src/commands/umount.ts +116 -0
- package/src/commands/uname.ts +130 -0
- package/src/commands/unexpand.ts +305 -0
- package/src/commands/uptime.ts +73 -0
- package/src/commands/view.ts +463 -73
- package/src/index.ts +82 -0
- 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
|
+
}
|