@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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +55 -0
- package/dist/commands/basename.d.ts +4 -0
- package/dist/commands/basename.d.ts.map +1 -0
- package/dist/commands/basename.js +78 -0
- package/dist/commands/basename.js.map +1 -0
- package/dist/commands/cal.d.ts +4 -0
- package/dist/commands/cal.d.ts.map +1 -0
- package/dist/commands/cal.js +105 -0
- package/dist/commands/cal.js.map +1 -0
- package/dist/commands/cat.d.ts.map +1 -1
- package/dist/commands/cat.js +49 -22
- package/dist/commands/cat.js.map +1 -1
- package/dist/commands/cd.d.ts.map +1 -1
- package/dist/commands/cd.js +38 -10
- package/dist/commands/cd.js.map +1 -1
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +31 -9
- package/dist/commands/chmod.js.map +1 -1
- package/dist/commands/comm.d.ts +4 -0
- package/dist/commands/comm.d.ts.map +1 -0
- package/dist/commands/comm.js +162 -0
- package/dist/commands/comm.js.map +1 -0
- package/dist/commands/cp.d.ts.map +1 -1
- package/dist/commands/cp.js +61 -12
- package/dist/commands/cp.js.map +1 -1
- package/dist/commands/cut.d.ts +4 -0
- package/dist/commands/cut.d.ts.map +1 -0
- package/dist/commands/cut.js +208 -0
- package/dist/commands/cut.js.map +1 -0
- package/dist/commands/date.d.ts +4 -0
- package/dist/commands/date.d.ts.map +1 -0
- package/dist/commands/date.js +100 -0
- package/dist/commands/date.js.map +1 -0
- package/dist/commands/diff.d.ts +4 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +194 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/dirname.d.ts +4 -0
- package/dist/commands/dirname.d.ts.map +1 -0
- package/dist/commands/dirname.js +50 -0
- package/dist/commands/dirname.js.map +1 -0
- package/dist/commands/echo.d.ts.map +1 -1
- package/dist/commands/echo.js +51 -9
- package/dist/commands/echo.js.map +1 -1
- package/dist/commands/false.d.ts +4 -0
- package/dist/commands/false.d.ts.map +1 -0
- package/dist/commands/false.js +27 -0
- package/dist/commands/false.js.map +1 -0
- package/dist/commands/find.d.ts +4 -0
- package/dist/commands/find.d.ts.map +1 -0
- package/dist/commands/find.js +181 -0
- package/dist/commands/find.js.map +1 -0
- package/dist/commands/grep.d.ts +4 -0
- package/dist/commands/grep.d.ts.map +1 -0
- package/dist/commands/grep.js +175 -0
- package/dist/commands/grep.js.map +1 -0
- package/dist/commands/head.d.ts +4 -0
- package/dist/commands/head.d.ts.map +1 -0
- package/dist/commands/head.js +199 -0
- package/dist/commands/head.js.map +1 -0
- package/dist/commands/hex.d.ts.map +1 -1
- package/dist/commands/hex.js +82 -20
- package/dist/commands/hex.js.map +1 -1
- package/dist/commands/id.d.ts +4 -0
- package/dist/commands/id.d.ts.map +1 -0
- package/dist/commands/id.js +97 -0
- package/dist/commands/id.js.map +1 -0
- package/dist/commands/join.d.ts +4 -0
- package/dist/commands/join.d.ts.map +1 -0
- package/dist/commands/join.js +152 -0
- package/dist/commands/join.js.map +1 -0
- package/dist/commands/less.d.ts.map +1 -1
- package/dist/commands/less.js +16 -7
- package/dist/commands/less.js.map +1 -1
- package/dist/commands/ln.d.ts.map +1 -1
- package/dist/commands/ln.js +54 -12
- package/dist/commands/ln.js.map +1 -1
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +19 -9
- package/dist/commands/ls.js.map +1 -1
- package/dist/commands/mkdir.d.ts.map +1 -1
- package/dist/commands/mkdir.js +34 -9
- package/dist/commands/mkdir.js.map +1 -1
- package/dist/commands/mv.d.ts.map +1 -1
- package/dist/commands/mv.js +25 -7
- package/dist/commands/mv.js.map +1 -1
- package/dist/commands/nc.d.ts +4 -0
- package/dist/commands/nc.d.ts.map +1 -0
- package/dist/commands/nc.js +451 -0
- package/dist/commands/nc.js.map +1 -0
- package/dist/commands/nl.d.ts +4 -0
- package/dist/commands/nl.d.ts.map +1 -0
- package/dist/commands/nl.js +208 -0
- package/dist/commands/nl.js.map +1 -0
- package/dist/commands/passkey.d.ts.map +1 -1
- package/dist/commands/passkey.js +44 -18
- package/dist/commands/passkey.js.map +1 -1
- package/dist/commands/paste.d.ts +4 -0
- package/dist/commands/paste.d.ts.map +1 -0
- package/dist/commands/paste.js +161 -0
- package/dist/commands/paste.js.map +1 -0
- package/dist/commands/pwd.d.ts.map +1 -1
- package/dist/commands/pwd.js +13 -4
- package/dist/commands/pwd.js.map +1 -1
- package/dist/commands/rm.d.ts.map +1 -1
- package/dist/commands/rm.js +105 -12
- package/dist/commands/rm.js.map +1 -1
- package/dist/commands/rmdir.d.ts.map +1 -1
- package/dist/commands/rmdir.js +34 -9
- package/dist/commands/rmdir.js.map +1 -1
- package/dist/commands/sed.d.ts.map +1 -1
- package/dist/commands/sed.js +73 -28
- package/dist/commands/sed.js.map +1 -1
- package/dist/commands/seq.d.ts +4 -0
- package/dist/commands/seq.d.ts.map +1 -0
- package/dist/commands/seq.js +148 -0
- package/dist/commands/seq.js.map +1 -0
- package/dist/commands/sockets.d.ts +4 -0
- package/dist/commands/sockets.d.ts.map +1 -0
- package/dist/commands/sockets.js +239 -0
- package/dist/commands/sockets.js.map +1 -0
- package/dist/commands/sort.d.ts +4 -0
- package/dist/commands/sort.d.ts.map +1 -0
- package/dist/commands/sort.js +175 -0
- package/dist/commands/sort.js.map +1 -0
- package/dist/commands/split.d.ts +4 -0
- package/dist/commands/split.d.ts.map +1 -0
- package/dist/commands/split.js +147 -0
- package/dist/commands/split.js.map +1 -0
- package/dist/commands/stat.d.ts.map +1 -1
- package/dist/commands/stat.js +14 -6
- package/dist/commands/stat.js.map +1 -1
- package/dist/commands/tail.d.ts +4 -0
- package/dist/commands/tail.d.ts.map +1 -0
- package/dist/commands/tail.js +189 -0
- package/dist/commands/tail.js.map +1 -0
- package/dist/commands/tee.d.ts.map +1 -1
- package/dist/commands/tee.js +45 -10
- package/dist/commands/tee.js.map +1 -1
- package/dist/commands/test.d.ts +4 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +99 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/touch.d.ts.map +1 -1
- package/dist/commands/touch.js +34 -9
- package/dist/commands/touch.js.map +1 -1
- package/dist/commands/tr.d.ts +4 -0
- package/dist/commands/tr.d.ts.map +1 -0
- package/dist/commands/tr.js +162 -0
- package/dist/commands/tr.js.map +1 -0
- package/dist/commands/true.d.ts +4 -0
- package/dist/commands/true.d.ts.map +1 -0
- package/dist/commands/true.js +27 -0
- package/dist/commands/true.js.map +1 -0
- package/dist/commands/uniq.d.ts +4 -0
- package/dist/commands/uniq.d.ts.map +1 -0
- package/dist/commands/uniq.js +187 -0
- package/dist/commands/uniq.js.map +1 -0
- package/dist/commands/wc.d.ts +4 -0
- package/dist/commands/wc.d.ts.map +1 -0
- package/dist/commands/wc.js +178 -0
- package/dist/commands/wc.js.map +1 -0
- package/dist/commands/which.d.ts +4 -0
- package/dist/commands/which.d.ts.map +1 -0
- package/dist/commands/which.js +78 -0
- package/dist/commands/which.js.map +1 -0
- package/dist/commands/whoami.d.ts +4 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +28 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +72 -1
- package/dist/index.js.map +1 -1
- package/dist/shared/terminal-command.d.ts +8 -2
- package/dist/shared/terminal-command.d.ts.map +1 -1
- package/dist/shared/terminal-command.js +42 -17
- package/dist/shared/terminal-command.js.map +1 -1
- package/package.json +4 -2
- package/src/commands/basename.ts +84 -0
- package/src/commands/cal.ts +111 -0
- package/src/commands/cat.ts +55 -24
- package/src/commands/cd.ts +40 -12
- package/src/commands/chmod.ts +37 -11
- package/src/commands/comm.ts +169 -0
- package/src/commands/cp.ts +73 -15
- package/src/commands/cut.ts +214 -0
- package/src/commands/date.ts +97 -0
- package/src/commands/diff.ts +204 -0
- package/src/commands/dirname.ts +57 -0
- package/src/commands/echo.ts +53 -10
- package/src/commands/false.ts +31 -0
- package/src/commands/find.ts +184 -0
- package/src/commands/grep.ts +187 -0
- package/src/commands/head.ts +206 -0
- package/src/commands/hex.ts +93 -25
- package/src/commands/id.ts +94 -0
- package/src/commands/join.ts +162 -0
- package/src/commands/less.ts +20 -8
- package/src/commands/ln.ts +50 -13
- package/src/commands/ls.ts +21 -10
- package/src/commands/mkdir.ts +41 -12
- package/src/commands/mv.ts +31 -9
- package/src/commands/nc.ts +499 -0
- package/src/commands/nl.ts +201 -0
- package/src/commands/passkey.ts +46 -19
- package/src/commands/paste.ts +172 -0
- package/src/commands/pwd.ts +16 -4
- package/src/commands/rm.ts +118 -13
- package/src/commands/rmdir.ts +41 -12
- package/src/commands/sed.ts +64 -30
- package/src/commands/seq.ts +147 -0
- package/src/commands/sockets.ts +283 -0
- package/src/commands/sort.ts +175 -0
- package/src/commands/split.ts +154 -0
- package/src/commands/stat.ts +17 -8
- package/src/commands/tail.ts +200 -0
- package/src/commands/tee.ts +43 -11
- package/src/commands/test.ts +116 -0
- package/src/commands/touch.ts +41 -12
- package/src/commands/tr.ts +170 -0
- package/src/commands/true.ts +31 -0
- package/src/commands/uniq.ts +189 -0
- package/src/commands/wc.ts +181 -0
- package/src/commands/which.ts +89 -0
- package/src/commands/whoami.ts +32 -0
- package/src/index.ts +72 -1
- package/src/shared/terminal-command.ts +43 -16
package/src/commands/hex.ts
CHANGED
|
@@ -1,44 +1,111 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
|
-
import type { CommandLineOptions } from 'command-line-args'
|
|
3
2
|
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
4
3
|
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
5
4
|
import { writelnStdout, writelnStderr } from '../shared/helpers.js'
|
|
6
5
|
|
|
6
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
7
|
+
const usage = `Usage: hex [FILE]
|
|
8
|
+
Display file contents or stdin in hexadecimal format.
|
|
9
|
+
|
|
10
|
+
FILE the file to display (if omitted, reads from stdin)
|
|
11
|
+
--help display this help and exit`
|
|
12
|
+
writelnStderr(process, terminal, usage)
|
|
13
|
+
}
|
|
14
|
+
|
|
7
15
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
8
16
|
return new TerminalCommand({
|
|
9
17
|
command: 'hex',
|
|
10
|
-
description: 'Display file contents in hexadecimal format',
|
|
18
|
+
description: 'Display file contents or stdin in hexadecimal format',
|
|
11
19
|
kernel,
|
|
12
20
|
shell,
|
|
13
21
|
terminal,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
await writelnStderr(process, terminal, 'Usage: hex <file>')
|
|
23
|
-
return 1
|
|
22
|
+
run: async (pid: number, argv: string[]) => {
|
|
23
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
24
|
+
|
|
25
|
+
if (!process) return 1
|
|
26
|
+
|
|
27
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
28
|
+
printUsage(process, terminal)
|
|
29
|
+
return 0
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
const
|
|
32
|
+
const firstArg = argv.length > 0 ? argv[0] : undefined
|
|
33
|
+
const filePath = firstArg !== undefined && !firstArg.startsWith('-') ? firstArg : undefined
|
|
34
|
+
let data: Uint8Array
|
|
27
35
|
|
|
28
36
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
if (!filePath) {
|
|
38
|
+
if (!process.stdin) {
|
|
39
|
+
await writelnStderr(process, terminal, 'Usage: hex <file>')
|
|
40
|
+
await writelnStderr(process, terminal, ' or: <command> | hex')
|
|
41
|
+
return 1
|
|
42
|
+
}
|
|
34
43
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
if (process.stdinIsTTY) {
|
|
45
|
+
await writelnStderr(process, terminal, 'Usage: hex <file>')
|
|
46
|
+
await writelnStderr(process, terminal, ' or: <command> | hex')
|
|
47
|
+
return 1
|
|
48
|
+
}
|
|
40
49
|
|
|
41
|
-
|
|
50
|
+
const reader = process.stdin.getReader()
|
|
51
|
+
const chunks: Uint8Array[] = []
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const first = await reader.read()
|
|
55
|
+
|
|
56
|
+
if (first.done && !first.value) {
|
|
57
|
+
await writelnStderr(process, terminal, 'Usage: hex <file>')
|
|
58
|
+
await writelnStderr(process, terminal, ' or: <command> | hex')
|
|
59
|
+
return 1
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (first.value) {
|
|
63
|
+
chunks.push(first.value)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!first.done) {
|
|
67
|
+
while (true) {
|
|
68
|
+
const { done, value } = await reader.read()
|
|
69
|
+
if (done) break
|
|
70
|
+
if (value) {
|
|
71
|
+
chunks.push(value)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} finally {
|
|
76
|
+
reader.releaseLock()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0)
|
|
80
|
+
if (totalLength === 0) {
|
|
81
|
+
await writelnStderr(process, terminal, 'Usage: hex <file>')
|
|
82
|
+
await writelnStderr(process, terminal, ' or: <command> | hex')
|
|
83
|
+
return 1
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
data = new Uint8Array(totalLength)
|
|
87
|
+
let offset = 0
|
|
88
|
+
for (const chunk of chunks) {
|
|
89
|
+
data.set(chunk, offset)
|
|
90
|
+
offset += chunk.length
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
const fullPath = path.resolve(shell.cwd, filePath)
|
|
94
|
+
|
|
95
|
+
const exists = await shell.context.fs.promises.exists(fullPath)
|
|
96
|
+
if (!exists) {
|
|
97
|
+
await writelnStderr(process, terminal, `hex: ${filePath}: No such file or directory`)
|
|
98
|
+
return 1
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const stats = await shell.context.fs.promises.stat(fullPath)
|
|
102
|
+
if (stats.isDirectory()) {
|
|
103
|
+
await writelnStderr(process, terminal, `hex: ${filePath}: Is a directory`)
|
|
104
|
+
return 1
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
data = await shell.context.fs.promises.readFile(fullPath)
|
|
108
|
+
}
|
|
42
109
|
const bytesPerLine = 16
|
|
43
110
|
|
|
44
111
|
for (let offset = 0; offset < data.length; offset += bytesPerLine) {
|
|
@@ -84,7 +151,8 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
84
151
|
|
|
85
152
|
return 0
|
|
86
153
|
} catch (error) {
|
|
87
|
-
|
|
154
|
+
const errorPath = filePath || 'stdin'
|
|
155
|
+
await writelnStderr(process, terminal, `hex: ${errorPath}: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
88
156
|
return 1
|
|
89
157
|
}
|
|
90
158
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
2
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
3
|
+
import { writelnStdout } from '../shared/helpers.js'
|
|
4
|
+
|
|
5
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
6
|
+
const usage = `Usage: id [OPTION]...
|
|
7
|
+
Print user and group IDs.
|
|
8
|
+
|
|
9
|
+
-u, --user print only the effective user ID
|
|
10
|
+
-g, --group print only the effective group ID
|
|
11
|
+
-G, --groups print all group IDs
|
|
12
|
+
-n, --name print names instead of numeric IDs
|
|
13
|
+
--help display this help and exit`
|
|
14
|
+
writelnStdout(process, terminal, usage)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
18
|
+
return new TerminalCommand({
|
|
19
|
+
command: 'id',
|
|
20
|
+
description: 'Print user and group IDs',
|
|
21
|
+
kernel,
|
|
22
|
+
shell,
|
|
23
|
+
terminal,
|
|
24
|
+
run: async (pid: number, argv: string[]) => {
|
|
25
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
26
|
+
|
|
27
|
+
let userOnly = false
|
|
28
|
+
let groupOnly = false
|
|
29
|
+
let groupsOnly = false
|
|
30
|
+
let nameOnly = false
|
|
31
|
+
|
|
32
|
+
for (const arg of argv) {
|
|
33
|
+
if (arg === '--help' || arg === '-h') {
|
|
34
|
+
printUsage(process, terminal)
|
|
35
|
+
return 0
|
|
36
|
+
} else if (arg === '-u' || arg === '--user') {
|
|
37
|
+
userOnly = true
|
|
38
|
+
} else if (arg === '-g' || arg === '--group') {
|
|
39
|
+
groupOnly = true
|
|
40
|
+
} else if (arg === '-G' || arg === '--groups') {
|
|
41
|
+
groupsOnly = true
|
|
42
|
+
} else if (arg === '-n' || arg === '--name') {
|
|
43
|
+
nameOnly = true
|
|
44
|
+
} else if (arg.startsWith('-')) {
|
|
45
|
+
const flags = arg.slice(1).split('')
|
|
46
|
+
if (flags.includes('u')) userOnly = true
|
|
47
|
+
if (flags.includes('g')) groupOnly = true
|
|
48
|
+
if (flags.includes('G')) groupsOnly = true
|
|
49
|
+
if (flags.includes('n')) nameOnly = true
|
|
50
|
+
const invalidFlags = flags.filter(f => !['u', 'g', 'G', 'n'].includes(f))
|
|
51
|
+
if (invalidFlags.length > 0) {
|
|
52
|
+
await writelnStdout(process, terminal, `id: invalid option -- '${invalidFlags[0]}'`)
|
|
53
|
+
return 1
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const user = kernel.users.get(shell.credentials.uid)
|
|
59
|
+
const group = kernel.users.get(shell.credentials.gid)
|
|
60
|
+
const groups = shell.credentials.groups || []
|
|
61
|
+
|
|
62
|
+
let output = ''
|
|
63
|
+
|
|
64
|
+
if (userOnly) {
|
|
65
|
+
output = nameOnly ? (user?.username || shell.credentials.uid.toString()) : shell.credentials.euid.toString()
|
|
66
|
+
} else if (groupOnly) {
|
|
67
|
+
output = nameOnly ? (group?.username || shell.credentials.gid.toString()) : shell.credentials.egid.toString()
|
|
68
|
+
} else if (groupsOnly) {
|
|
69
|
+
output = groups.map(gid => {
|
|
70
|
+
if (nameOnly) {
|
|
71
|
+
const g = kernel.users.get(gid)
|
|
72
|
+
return g?.username || gid.toString()
|
|
73
|
+
}
|
|
74
|
+
return gid.toString()
|
|
75
|
+
}).join(' ')
|
|
76
|
+
} else {
|
|
77
|
+
const uid = nameOnly ? (user?.username || shell.credentials.uid.toString()) : shell.credentials.uid.toString()
|
|
78
|
+
const gid = nameOnly ? (group?.username || shell.credentials.gid.toString()) : shell.credentials.gid.toString()
|
|
79
|
+
const groupsStr = groups.map(gid => {
|
|
80
|
+
if (nameOnly) {
|
|
81
|
+
const g = kernel.users.get(gid)
|
|
82
|
+
return g?.username || gid.toString()
|
|
83
|
+
}
|
|
84
|
+
return gid.toString()
|
|
85
|
+
}).join(',')
|
|
86
|
+
|
|
87
|
+
output = `uid=${uid} gid=${gid} groups=${groupsStr}`
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
await writelnStdout(process, terminal, output)
|
|
91
|
+
return 0
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
3
|
+
import { TerminalEvents } from '@ecmaos/types'
|
|
4
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
5
|
+
import { writelnStderr } from '../shared/helpers.js'
|
|
6
|
+
|
|
7
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
8
|
+
const usage = `Usage: join [OPTION]... FILE1 FILE2
|
|
9
|
+
Join lines of two files on a common field.
|
|
10
|
+
|
|
11
|
+
-1 FIELD join on this FIELD of file 1
|
|
12
|
+
-2 FIELD join on this FIELD of file 2
|
|
13
|
+
-t CHAR use CHAR as input and output field separator
|
|
14
|
+
--help display this help and exit`
|
|
15
|
+
writelnStderr(process, terminal, usage)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
19
|
+
return new TerminalCommand({
|
|
20
|
+
command: 'join',
|
|
21
|
+
description: 'Join lines of two files on a common field',
|
|
22
|
+
kernel,
|
|
23
|
+
shell,
|
|
24
|
+
terminal,
|
|
25
|
+
run: async (pid: number, argv: string[]) => {
|
|
26
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
27
|
+
|
|
28
|
+
if (!process) return 1
|
|
29
|
+
|
|
30
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
31
|
+
printUsage(process, terminal)
|
|
32
|
+
return 0
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const files: string[] = []
|
|
36
|
+
let field1 = 1
|
|
37
|
+
let field2 = 1
|
|
38
|
+
let delimiter = ' '
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < argv.length; i++) {
|
|
41
|
+
const arg = argv[i]
|
|
42
|
+
if (!arg) continue
|
|
43
|
+
|
|
44
|
+
if (arg === '--help' || arg === '-h') {
|
|
45
|
+
printUsage(process, terminal)
|
|
46
|
+
return 0
|
|
47
|
+
} else if (arg === '-1' && i + 1 < argv.length) {
|
|
48
|
+
const nextArg = argv[++i]
|
|
49
|
+
if (nextArg !== undefined) {
|
|
50
|
+
field1 = parseInt(nextArg, 10) || 1
|
|
51
|
+
}
|
|
52
|
+
} else if (arg === '-2' && i + 1 < argv.length) {
|
|
53
|
+
const nextArg = argv[++i]
|
|
54
|
+
if (nextArg !== undefined) {
|
|
55
|
+
field2 = parseInt(nextArg, 10) || 1
|
|
56
|
+
}
|
|
57
|
+
} else if (arg.startsWith('-t')) {
|
|
58
|
+
delimiter = arg.slice(2) || ' '
|
|
59
|
+
} else if (arg === '-t' && i + 1 < argv.length) {
|
|
60
|
+
const nextArg = argv[++i]
|
|
61
|
+
if (nextArg !== undefined) {
|
|
62
|
+
delimiter = nextArg
|
|
63
|
+
}
|
|
64
|
+
} else if (!arg.startsWith('-')) {
|
|
65
|
+
if (files.length < 2) {
|
|
66
|
+
files.push(arg)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (files.length !== 2) {
|
|
72
|
+
await writelnStderr(process, terminal, 'join: exactly two files must be specified')
|
|
73
|
+
return 1
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const file1 = files[0]
|
|
77
|
+
const file2 = files[1]
|
|
78
|
+
if (!file1 || !file2) {
|
|
79
|
+
await writelnStderr(process, terminal, 'join: exactly two files must be specified')
|
|
80
|
+
return 1
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const writer = process.stdout.getWriter()
|
|
84
|
+
|
|
85
|
+
const readFileLines = async (filePath: string): Promise<string[]> => {
|
|
86
|
+
if (filePath.startsWith('/dev')) {
|
|
87
|
+
throw new Error('cannot join device files')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let interrupted = false
|
|
91
|
+
const interruptHandler = () => { interrupted = true }
|
|
92
|
+
kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const handle = await shell.context.fs.promises.open(filePath, 'r')
|
|
96
|
+
const stat = await shell.context.fs.promises.stat(filePath)
|
|
97
|
+
|
|
98
|
+
const decoder = new TextDecoder()
|
|
99
|
+
let content = ''
|
|
100
|
+
let bytesRead = 0
|
|
101
|
+
const chunkSize = 1024
|
|
102
|
+
|
|
103
|
+
while (bytesRead < stat.size) {
|
|
104
|
+
if (interrupted) break
|
|
105
|
+
const data = new Uint8Array(chunkSize)
|
|
106
|
+
const readSize = Math.min(chunkSize, stat.size - bytesRead)
|
|
107
|
+
await handle.read(data, 0, readSize, bytesRead)
|
|
108
|
+
const chunk = data.subarray(0, readSize)
|
|
109
|
+
content += decoder.decode(chunk, { stream: true })
|
|
110
|
+
bytesRead += readSize
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const lines = content.split('\n')
|
|
114
|
+
if (lines[lines.length - 1] === '') {
|
|
115
|
+
lines.pop()
|
|
116
|
+
}
|
|
117
|
+
return lines
|
|
118
|
+
} finally {
|
|
119
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const fullPath1 = path.resolve(shell.cwd || '/', file1)
|
|
125
|
+
const fullPath2 = path.resolve(shell.cwd || '/', file2)
|
|
126
|
+
|
|
127
|
+
const lines1 = await readFileLines(fullPath1)
|
|
128
|
+
const lines2 = await readFileLines(fullPath2)
|
|
129
|
+
|
|
130
|
+
const map1 = new Map<string, string[]>()
|
|
131
|
+
for (const line of lines1) {
|
|
132
|
+
const parts = line.split(delimiter)
|
|
133
|
+
const key = parts[field1 - 1] || ''
|
|
134
|
+
if (!map1.has(key)) {
|
|
135
|
+
map1.set(key, [])
|
|
136
|
+
}
|
|
137
|
+
map1.get(key)!.push(line)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
for (const line of lines2) {
|
|
141
|
+
const parts = line.split(delimiter)
|
|
142
|
+
const key = parts[field2 - 1] || ''
|
|
143
|
+
const matches = map1.get(key)
|
|
144
|
+
if (matches) {
|
|
145
|
+
for (const match of matches) {
|
|
146
|
+
const matchParts = match.split(delimiter)
|
|
147
|
+
const output = [...matchParts, ...parts.slice(field2)].join(delimiter)
|
|
148
|
+
await writer.write(new TextEncoder().encode(output + '\n'))
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return 0
|
|
154
|
+
} catch (error) {
|
|
155
|
+
await writelnStderr(process, terminal, `join: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
156
|
+
return 1
|
|
157
|
+
} finally {
|
|
158
|
+
writer.releaseLock()
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
}
|
package/src/commands/less.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
import ansi from 'ansi-escape-sequences'
|
|
3
|
-
import type { CommandLineOptions } from 'command-line-args'
|
|
4
3
|
import type { IDisposable } from '@xterm/xterm'
|
|
5
4
|
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
6
5
|
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
7
6
|
import { writelnStderr } from '../shared/helpers.js'
|
|
8
7
|
|
|
8
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
9
|
+
const usage = `Usage: less [OPTION]... FILE
|
|
10
|
+
View file contents interactively.
|
|
11
|
+
|
|
12
|
+
FILE the file to view (if omitted, reads from stdin)
|
|
13
|
+
--help display this help and exit`
|
|
14
|
+
writelnStderr(process, terminal, usage)
|
|
15
|
+
}
|
|
16
|
+
|
|
9
17
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
10
18
|
return new TerminalCommand({
|
|
11
19
|
command: 'less',
|
|
@@ -13,21 +21,25 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
13
21
|
kernel,
|
|
14
22
|
shell,
|
|
15
23
|
terminal,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
],
|
|
20
|
-
run: async (argv: CommandLineOptions, process?: Process) => {
|
|
24
|
+
run: async (pid: number, argv: string[]) => {
|
|
25
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
26
|
+
|
|
21
27
|
if (!process) return 1
|
|
22
28
|
|
|
29
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
30
|
+
printUsage(process, terminal)
|
|
31
|
+
return 0
|
|
32
|
+
}
|
|
33
|
+
|
|
23
34
|
let lines: string[] = []
|
|
24
35
|
let currentLine = 0
|
|
25
36
|
let keyListener: IDisposable | null = null
|
|
26
37
|
let linesRendered = 0
|
|
27
38
|
|
|
28
39
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
40
|
+
const filePath = argv.length > 0 && argv[0] !== undefined && !argv[0].startsWith('-') ? argv[0] : undefined
|
|
41
|
+
|
|
42
|
+
if (filePath) {
|
|
31
43
|
const expandedPath = shell.expandTilde(filePath)
|
|
32
44
|
const fullPath = path.resolve(shell.cwd, expandedPath)
|
|
33
45
|
|
package/src/commands/ln.ts
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
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 { writelnStdout, writelnStderr } from '../shared/helpers.js'
|
|
7
6
|
|
|
7
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
8
|
+
const usage = `Usage: ln [OPTION]... [-T] TARGET LINK_NAME
|
|
9
|
+
or: ln [OPTION]... TARGET
|
|
10
|
+
or: ln [OPTION]... TARGET... DIRECTORY
|
|
11
|
+
Create links between files.
|
|
12
|
+
|
|
13
|
+
-s, --symbolic make symbolic links instead of hard links
|
|
14
|
+
-f, --force remove existing destination files
|
|
15
|
+
-v, --verbose print name of each linked file
|
|
16
|
+
--help display this help and exit`
|
|
17
|
+
writelnStderr(process, terminal, usage)
|
|
18
|
+
}
|
|
19
|
+
|
|
8
20
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
9
21
|
return new TerminalCommand({
|
|
10
22
|
command: 'ln',
|
|
@@ -12,18 +24,43 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
12
24
|
kernel,
|
|
13
25
|
shell,
|
|
14
26
|
terminal,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const args
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
run: async (pid: number, argv: string[]) => {
|
|
28
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
29
|
+
|
|
30
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
31
|
+
printUsage(process, terminal)
|
|
32
|
+
return 0
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const args: string[] = []
|
|
36
|
+
let symbolic = false
|
|
37
|
+
let force = false
|
|
38
|
+
let verbose = 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 === '-s' || arg === '--symbolic') {
|
|
45
|
+
symbolic = true
|
|
46
|
+
} else if (arg === '-f' || arg === '--force') {
|
|
47
|
+
force = true
|
|
48
|
+
} else if (arg === '-v' || arg === '--verbose') {
|
|
49
|
+
verbose = true
|
|
50
|
+
} else if (arg.startsWith('-')) {
|
|
51
|
+
const flags = arg.slice(1).split('')
|
|
52
|
+
if (flags.includes('s')) symbolic = true
|
|
53
|
+
if (flags.includes('f')) force = true
|
|
54
|
+
if (flags.includes('v')) verbose = true
|
|
55
|
+
const invalidFlags = flags.filter(f => !['s', 'f', 'v'].includes(f))
|
|
56
|
+
if (invalidFlags.length > 0) {
|
|
57
|
+
await writelnStderr(process, terminal, `ln: invalid option -- '${invalidFlags[0]}'`)
|
|
58
|
+
return 1
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
args.push(arg)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
27
64
|
|
|
28
65
|
if (args.length === 0) {
|
|
29
66
|
await writelnStderr(process, terminal, chalk.red('ln: missing file operand'))
|
package/src/commands/ls.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import humanFormat from 'human-format'
|
|
4
|
-
import type { CommandLineOptions } from 'command-line-args'
|
|
5
4
|
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
6
5
|
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
7
6
|
import { writelnStdout } from '../shared/helpers.js'
|
|
8
7
|
|
|
8
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
9
|
+
const usage = `Usage: ls [OPTION]... [FILE]...
|
|
10
|
+
List information about the FILEs (the current directory by default).
|
|
11
|
+
|
|
12
|
+
--help display this help and exit`
|
|
13
|
+
writelnStdout(process, terminal, usage)
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
10
17
|
return new TerminalCommand({
|
|
11
18
|
command: 'ls',
|
|
@@ -13,12 +20,15 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
13
20
|
kernel,
|
|
14
21
|
shell,
|
|
15
22
|
terminal,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
run: async (pid: number, argv: string[]) => {
|
|
24
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
25
|
+
|
|
26
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
27
|
+
printUsage(process, terminal)
|
|
28
|
+
return 0
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const target = argv.length > 0 && argv[0] !== undefined && !argv[0].startsWith('-') ? argv[0] : shell.cwd
|
|
22
32
|
const fullPath = target ? path.resolve(shell.cwd, target === '' ? '.' : target) : shell.cwd
|
|
23
33
|
const stats = await shell.context.fs.promises.stat(fullPath)
|
|
24
34
|
const entries: string[] = stats.isDirectory() ? await shell.context.fs.promises.readdir(fullPath) : [fullPath]
|
|
@@ -186,8 +196,10 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
186
196
|
if (linkInfo) return linkInfo
|
|
187
197
|
|
|
188
198
|
if (descriptions.has(path.resolve(fullPath, file.name))) return descriptions.get(path.resolve(fullPath, file.name))
|
|
189
|
-
|
|
190
|
-
|
|
199
|
+
if (file.name.includes('.')) {
|
|
200
|
+
const ext = file.name.split('.').pop()
|
|
201
|
+
if (ext && descriptions.has('.' + ext)) return descriptions.get('.' + ext)
|
|
202
|
+
}
|
|
191
203
|
if (!file.stats) return ''
|
|
192
204
|
if (file.stats.isBlockDevice() || file.stats.isCharacterDevice()) {
|
|
193
205
|
// TODO: zenfs `fs.mounts` is deprecated - use a better way of getting device info
|
|
@@ -228,4 +240,3 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
228
240
|
}
|
|
229
241
|
})
|
|
230
242
|
}
|
|
231
|
-
|
package/src/commands/mkdir.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
|
-
import type {
|
|
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: mkdir [OPTION]... DIRECTORY...
|
|
8
|
+
Create the DIRECTORY(ies), if they do not already exist.
|
|
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,16 +18,37 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
10
18
|
kernel,
|
|
11
19
|
shell,
|
|
12
20
|
terminal,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
+
if (argv.length === 0) {
|
|
30
|
+
await writelnStderr(process, terminal, 'mkdir: missing operand')
|
|
31
|
+
await writelnStderr(process, terminal, "Try 'mkdir --help' for more information.")
|
|
32
|
+
return 1
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let hasError = false
|
|
36
|
+
|
|
37
|
+
for (const target of argv) {
|
|
38
|
+
if (!target || target.startsWith('-')) continue
|
|
39
|
+
|
|
40
|
+
const fullPath = target ? path.resolve(shell.cwd, target) : shell.cwd
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
await shell.context.fs.promises.mkdir(fullPath)
|
|
44
|
+
} catch (error) {
|
|
45
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
46
|
+
await writelnStderr(process, terminal, `mkdir: ${target}: ${errorMessage}`)
|
|
47
|
+
hasError = true
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return hasError ? 1 : 0
|
|
22
52
|
}
|
|
23
53
|
})
|
|
24
54
|
}
|
|
25
|
-
|