@ecmaos/coreutils 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +31 -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 +33 -25
- 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 +49 -10
- 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.map +1 -1
- package/dist/commands/grep.js +45 -10
- package/dist/commands/grep.js.map +1 -1
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +46 -9
- package/dist/commands/head.js.map +1 -1
- package/dist/commands/hex.d.ts.map +1 -1
- package/dist/commands/hex.js +16 -6
- 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 +14 -6
- 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.map +1 -1
- package/dist/commands/tail.js +46 -9
- package/dist/commands/tail.js.map +1 -1
- 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 +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +64 -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 +37 -27
- 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 +51 -11
- package/src/commands/false.ts +31 -0
- package/src/commands/find.ts +184 -0
- package/src/commands/grep.ts +44 -11
- package/src/commands/head.ts +46 -10
- package/src/commands/hex.ts +19 -7
- 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 +17 -8
- 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 +46 -10
- 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 +64 -1
- package/src/shared/terminal-command.ts +43 -16
package/src/commands/cd.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
|
-
import type { Kernel, Shell, Terminal } from '@ecmaos/types'
|
|
2
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
3
3
|
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
4
|
+
import { writelnStdout } from '../shared/helpers.js'
|
|
5
|
+
|
|
6
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
7
|
+
const usage = `Usage: cd [DIRECTORY]
|
|
8
|
+
Change the shell working directory.
|
|
9
|
+
|
|
10
|
+
DIRECTORY the directory to change to (default: $HOME)
|
|
11
|
+
--help display this help and exit`
|
|
12
|
+
writelnStdout(process, terminal, usage)
|
|
13
|
+
}
|
|
4
14
|
|
|
5
15
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
6
16
|
return new TerminalCommand({
|
|
@@ -9,18 +19,36 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
9
19
|
kernel,
|
|
10
20
|
shell,
|
|
11
21
|
terminal,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
run: async (pid: number, argv: string[]) => {
|
|
23
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
24
|
+
|
|
25
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
26
|
+
printUsage(process, terminal)
|
|
27
|
+
return 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const destination = argv.length > 0 && argv[0] && !argv[0].startsWith('-') ? argv[0] : shell.cwd
|
|
18
31
|
const fullPath = destination ? path.resolve(shell.cwd, destination) : shell.cwd
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await shell.context.fs.promises.access(fullPath)
|
|
35
|
+
shell.cwd = fullPath
|
|
36
|
+
localStorage.setItem(`cwd:${shell.credentials.uid}`, fullPath)
|
|
37
|
+
return 0
|
|
38
|
+
} catch (error) {
|
|
39
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
40
|
+
if (process) {
|
|
41
|
+
const writer = process.stderr.getWriter()
|
|
42
|
+
try {
|
|
43
|
+
await writer.write(new TextEncoder().encode(`cd: ${destination}: ${errorMessage}\n`))
|
|
44
|
+
} finally {
|
|
45
|
+
writer.releaseLock()
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
terminal.write(`cd: ${destination}: ${errorMessage}\n`)
|
|
49
|
+
}
|
|
50
|
+
return 1
|
|
51
|
+
}
|
|
23
52
|
}
|
|
24
53
|
})
|
|
25
54
|
}
|
|
26
|
-
|
package/src/commands/chmod.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
|
-
import type { CommandLineOptions } from 'command-line-args'
|
|
4
3
|
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
5
4
|
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
6
5
|
import { writelnStderr } from '../shared/helpers.js'
|
|
7
6
|
|
|
7
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
8
|
+
const usage = `Usage: chmod [OPTION]... MODE[,MODE]... FILE...
|
|
9
|
+
Change the mode of each FILE to MODE.
|
|
10
|
+
|
|
11
|
+
--help display this help and exit`
|
|
12
|
+
writelnStderr(process, terminal, usage)
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
9
16
|
return new TerminalCommand({
|
|
10
17
|
command: 'chmod',
|
|
@@ -12,12 +19,21 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
12
19
|
kernel,
|
|
13
20
|
shell,
|
|
14
21
|
terminal,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
run: async (pid: number, argv: string[]) => {
|
|
23
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
24
|
+
|
|
25
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
26
|
+
printUsage(process, terminal)
|
|
27
|
+
return 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const args: string[] = []
|
|
31
|
+
for (const arg of argv) {
|
|
32
|
+
if (arg && !arg.startsWith('-')) {
|
|
33
|
+
args.push(arg)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
21
37
|
if (args.length === 0) {
|
|
22
38
|
await writelnStderr(process, terminal, chalk.red('chmod: missing operand'))
|
|
23
39
|
await writelnStderr(process, terminal, 'Try \'chmod --help\' for more information.')
|
|
@@ -25,11 +41,21 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
25
41
|
}
|
|
26
42
|
|
|
27
43
|
const [mode, target] = args
|
|
28
|
-
if (!mode || !target)
|
|
44
|
+
if (!mode || !target) {
|
|
45
|
+
await writelnStderr(process, terminal, chalk.red('chmod: missing operand'))
|
|
46
|
+
return 1
|
|
47
|
+
}
|
|
48
|
+
|
|
29
49
|
const fullPath = path.resolve(shell.cwd, target)
|
|
30
|
-
|
|
31
|
-
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
await shell.context.fs.promises.chmod(fullPath, mode)
|
|
53
|
+
return 0
|
|
54
|
+
} catch (error) {
|
|
55
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
56
|
+
await writelnStderr(process, terminal, `chmod: ${target}: ${errorMessage}`)
|
|
57
|
+
return 1
|
|
58
|
+
}
|
|
32
59
|
}
|
|
33
60
|
})
|
|
34
61
|
}
|
|
35
|
-
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
3
|
+
import { TerminalEvents } from '@ecmaos/types'
|
|
4
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
5
|
+
import { writelnStderr } from '../shared/helpers.js'
|
|
6
|
+
|
|
7
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
8
|
+
const usage = `Usage: comm [OPTION]... FILE1 FILE2
|
|
9
|
+
Compare two sorted files line by line.
|
|
10
|
+
|
|
11
|
+
-1 suppress lines unique to FILE1
|
|
12
|
+
-2 suppress lines unique to FILE2
|
|
13
|
+
-3 suppress lines that appear in both files
|
|
14
|
+
--help display this help and exit`
|
|
15
|
+
writelnStderr(process, terminal, usage)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
19
|
+
return new TerminalCommand({
|
|
20
|
+
command: 'comm',
|
|
21
|
+
description: 'Compare two sorted files line by line',
|
|
22
|
+
kernel,
|
|
23
|
+
shell,
|
|
24
|
+
terminal,
|
|
25
|
+
run: async (pid: number, argv: string[]) => {
|
|
26
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
27
|
+
|
|
28
|
+
if (!process) return 1
|
|
29
|
+
|
|
30
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
31
|
+
printUsage(process, terminal)
|
|
32
|
+
return 0
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const files: string[] = []
|
|
36
|
+
let suppress1 = false
|
|
37
|
+
let suppress2 = false
|
|
38
|
+
let suppress3 = false
|
|
39
|
+
|
|
40
|
+
for (const arg of argv) {
|
|
41
|
+
if (arg === '--help' || arg === '-h') {
|
|
42
|
+
printUsage(process, terminal)
|
|
43
|
+
return 0
|
|
44
|
+
} else if (arg === '-1') {
|
|
45
|
+
suppress1 = true
|
|
46
|
+
} else if (arg === '-2') {
|
|
47
|
+
suppress2 = true
|
|
48
|
+
} else if (arg === '-3') {
|
|
49
|
+
suppress3 = true
|
|
50
|
+
} else if (!arg.startsWith('-')) {
|
|
51
|
+
if (files.length < 2) {
|
|
52
|
+
files.push(arg)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (files.length !== 2) {
|
|
58
|
+
await writelnStderr(process, terminal, 'comm: exactly two files must be specified')
|
|
59
|
+
return 1
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const file1 = files[0]
|
|
63
|
+
const file2 = files[1]
|
|
64
|
+
if (!file1 || !file2) {
|
|
65
|
+
await writelnStderr(process, terminal, 'comm: exactly two files must be specified')
|
|
66
|
+
return 1
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const writer = process.stdout.getWriter()
|
|
70
|
+
|
|
71
|
+
const readFileLines = async (filePath: string): Promise<string[]> => {
|
|
72
|
+
if (filePath.startsWith('/dev')) {
|
|
73
|
+
throw new Error('cannot comm device files')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let interrupted = false
|
|
77
|
+
const interruptHandler = () => { interrupted = true }
|
|
78
|
+
kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const handle = await shell.context.fs.promises.open(filePath, 'r')
|
|
82
|
+
const stat = await shell.context.fs.promises.stat(filePath)
|
|
83
|
+
|
|
84
|
+
const decoder = new TextDecoder()
|
|
85
|
+
let content = ''
|
|
86
|
+
let bytesRead = 0
|
|
87
|
+
const chunkSize = 1024
|
|
88
|
+
|
|
89
|
+
while (bytesRead < stat.size) {
|
|
90
|
+
if (interrupted) break
|
|
91
|
+
const data = new Uint8Array(chunkSize)
|
|
92
|
+
const readSize = Math.min(chunkSize, stat.size - bytesRead)
|
|
93
|
+
await handle.read(data, 0, readSize, bytesRead)
|
|
94
|
+
const chunk = data.subarray(0, readSize)
|
|
95
|
+
content += decoder.decode(chunk, { stream: true })
|
|
96
|
+
bytesRead += readSize
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const lines = content.split('\n')
|
|
100
|
+
if (lines[lines.length - 1] === '') {
|
|
101
|
+
lines.pop()
|
|
102
|
+
}
|
|
103
|
+
return lines
|
|
104
|
+
} finally {
|
|
105
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const fullPath1 = path.resolve(shell.cwd || '/', file1)
|
|
111
|
+
const fullPath2 = path.resolve(shell.cwd || '/', file2)
|
|
112
|
+
|
|
113
|
+
const lines1 = await readFileLines(fullPath1)
|
|
114
|
+
const lines2 = await readFileLines(fullPath2)
|
|
115
|
+
|
|
116
|
+
let i = 0
|
|
117
|
+
let j = 0
|
|
118
|
+
|
|
119
|
+
while (i < lines1.length || j < lines2.length) {
|
|
120
|
+
if (i >= lines1.length) {
|
|
121
|
+
if (!suppress2) {
|
|
122
|
+
const prefix = suppress1 ? '' : '\t'
|
|
123
|
+
await writer.write(new TextEncoder().encode(prefix + lines2[j] + '\n'))
|
|
124
|
+
}
|
|
125
|
+
j++
|
|
126
|
+
} else if (j >= lines2.length) {
|
|
127
|
+
if (!suppress1) {
|
|
128
|
+
await writer.write(new TextEncoder().encode(lines1[i] + '\n'))
|
|
129
|
+
}
|
|
130
|
+
i++
|
|
131
|
+
} else {
|
|
132
|
+
const line1 = lines1[i]
|
|
133
|
+
const line2 = lines2[j]
|
|
134
|
+
if (!line1 || !line2) {
|
|
135
|
+
break
|
|
136
|
+
}
|
|
137
|
+
const cmp = line1.localeCompare(line2)
|
|
138
|
+
if (cmp < 0) {
|
|
139
|
+
if (!suppress1) {
|
|
140
|
+
await writer.write(new TextEncoder().encode(lines1[i] + '\n'))
|
|
141
|
+
}
|
|
142
|
+
i++
|
|
143
|
+
} else if (cmp > 0) {
|
|
144
|
+
if (!suppress2) {
|
|
145
|
+
const prefix = suppress1 ? '' : '\t'
|
|
146
|
+
await writer.write(new TextEncoder().encode(prefix + lines2[j] + '\n'))
|
|
147
|
+
}
|
|
148
|
+
j++
|
|
149
|
+
} else {
|
|
150
|
+
if (!suppress3) {
|
|
151
|
+
const prefix = suppress1 && suppress2 ? '' : suppress1 ? '\t' : suppress2 ? '' : '\t\t'
|
|
152
|
+
await writer.write(new TextEncoder().encode(prefix + lines1[i] + '\n'))
|
|
153
|
+
}
|
|
154
|
+
i++
|
|
155
|
+
j++
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return 0
|
|
161
|
+
} catch (error) {
|
|
162
|
+
await writelnStderr(process, terminal, `comm: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
163
|
+
return 1
|
|
164
|
+
} finally {
|
|
165
|
+
writer.releaseLock()
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
}
|
package/src/commands/cp.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: cp [OPTION]... SOURCE... DEST
|
|
8
|
+
Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.
|
|
9
|
+
|
|
10
|
+
--help display this help and exit`
|
|
11
|
+
writelnStdout(process, terminal, usage)
|
|
12
|
+
}
|
|
5
13
|
|
|
6
14
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
7
15
|
return new TerminalCommand({
|
|
@@ -10,19 +18,69 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
10
18
|
kernel,
|
|
11
19
|
shell,
|
|
12
20
|
terminal,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
run: async (pid: number, argv: string[]) => {
|
|
22
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
23
|
+
|
|
24
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
25
|
+
printUsage(process, terminal)
|
|
26
|
+
return 0
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const args: string[] = []
|
|
30
|
+
for (const arg of argv) {
|
|
31
|
+
if (arg && !arg.startsWith('-')) {
|
|
32
|
+
args.push(arg)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (args.length < 2) {
|
|
37
|
+
await writelnStderr(process, terminal, 'cp: missing file operand')
|
|
38
|
+
await writelnStderr(process, terminal, "Try 'cp --help' for more information.")
|
|
39
|
+
return 1
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const sources = args.slice(0, -1)
|
|
43
|
+
const destination = args[args.length - 1]
|
|
44
|
+
|
|
45
|
+
if (!destination) {
|
|
46
|
+
await writelnStderr(process, terminal, 'cp: missing destination file operand')
|
|
47
|
+
return 1
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let hasError = false
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const destinationStats = await shell.context.fs.promises.stat(path.resolve(shell.cwd, destination)).catch(() => null)
|
|
54
|
+
const isDestinationDir = destinationStats?.isDirectory()
|
|
55
|
+
|
|
56
|
+
if (sources.length > 1 && !isDestinationDir) {
|
|
57
|
+
await writelnStderr(process, terminal, `cp: target '${destination}' is not a directory`)
|
|
58
|
+
return 1
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const source of sources) {
|
|
62
|
+
if (!source) continue
|
|
63
|
+
|
|
64
|
+
const sourcePath = path.resolve(shell.cwd, source)
|
|
65
|
+
const finalDestination = isDestinationDir
|
|
66
|
+
? path.join(path.resolve(shell.cwd, destination), path.basename(source))
|
|
67
|
+
: path.resolve(shell.cwd, destination)
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
await shell.context.fs.promises.copyFile(sourcePath, finalDestination)
|
|
71
|
+
} catch (error) {
|
|
72
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
73
|
+
await writelnStderr(process, terminal, `cp: ${source}: ${errorMessage}`)
|
|
74
|
+
hasError = true
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
79
|
+
await writelnStderr(process, terminal, `cp: ${errorMessage}`)
|
|
80
|
+
hasError = true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return hasError ? 1 : 0
|
|
25
84
|
}
|
|
26
85
|
})
|
|
27
86
|
}
|
|
28
|
-
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
3
|
+
import { TerminalEvents } from '@ecmaos/types'
|
|
4
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
5
|
+
import { writelnStderr } from '../shared/helpers.js'
|
|
6
|
+
|
|
7
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
8
|
+
const usage = `Usage: cut OPTION... [FILE]...
|
|
9
|
+
Remove sections from each line of files.
|
|
10
|
+
|
|
11
|
+
-f, --fields=LIST select only these fields
|
|
12
|
+
-d, --delimiter=DELIM use DELIM instead of TAB for field delimiter
|
|
13
|
+
-c, --characters=LIST select only these characters
|
|
14
|
+
--help display this help and exit`
|
|
15
|
+
writelnStderr(process, terminal, usage)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
19
|
+
return new TerminalCommand({
|
|
20
|
+
command: 'cut',
|
|
21
|
+
description: 'Remove sections from each line of files',
|
|
22
|
+
kernel,
|
|
23
|
+
shell,
|
|
24
|
+
terminal,
|
|
25
|
+
run: async (pid: number, argv: string[]) => {
|
|
26
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
27
|
+
|
|
28
|
+
if (!process) return 1
|
|
29
|
+
|
|
30
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
31
|
+
printUsage(process, terminal)
|
|
32
|
+
return 0
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let fields: string | undefined
|
|
36
|
+
let delimiter: string = '\t'
|
|
37
|
+
let characters: string | undefined
|
|
38
|
+
const files: string[] = []
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < argv.length; i++) {
|
|
41
|
+
const arg = argv[i]
|
|
42
|
+
if (!arg) continue
|
|
43
|
+
|
|
44
|
+
if (arg === '--help' || arg === '-h') {
|
|
45
|
+
printUsage(process, terminal)
|
|
46
|
+
return 0
|
|
47
|
+
} else if (arg === '-f' || arg.startsWith('-f')) {
|
|
48
|
+
if (arg === '-f' && i + 1 < argv.length) {
|
|
49
|
+
i++
|
|
50
|
+
const nextArg = argv[i]
|
|
51
|
+
if (nextArg !== undefined) {
|
|
52
|
+
fields = nextArg
|
|
53
|
+
}
|
|
54
|
+
} else if (arg.startsWith('-f') && arg.length > 2) {
|
|
55
|
+
fields = arg.slice(2)
|
|
56
|
+
} else if (arg.startsWith('--fields=')) {
|
|
57
|
+
fields = arg.slice(9)
|
|
58
|
+
}
|
|
59
|
+
} else if (arg === '-c' || arg.startsWith('-c')) {
|
|
60
|
+
if (arg === '-c' && i + 1 < argv.length) {
|
|
61
|
+
i++
|
|
62
|
+
const nextArg = argv[i]
|
|
63
|
+
if (nextArg !== undefined) {
|
|
64
|
+
characters = nextArg
|
|
65
|
+
}
|
|
66
|
+
} else if (arg.startsWith('-c') && arg.length > 2) {
|
|
67
|
+
characters = arg.slice(2)
|
|
68
|
+
} else if (arg.startsWith('--characters=')) {
|
|
69
|
+
characters = arg.slice(13)
|
|
70
|
+
}
|
|
71
|
+
} else if (arg === '-d' || arg.startsWith('-d')) {
|
|
72
|
+
if (arg === '-d' && i + 1 < argv.length) {
|
|
73
|
+
i++
|
|
74
|
+
const nextArg = argv[i]
|
|
75
|
+
if (nextArg !== undefined) {
|
|
76
|
+
delimiter = nextArg
|
|
77
|
+
}
|
|
78
|
+
} else if (arg.startsWith('-d') && arg.length > 2) {
|
|
79
|
+
delimiter = arg.slice(2)
|
|
80
|
+
} else if (arg.startsWith('--delimiter=')) {
|
|
81
|
+
delimiter = arg.slice(12)
|
|
82
|
+
}
|
|
83
|
+
} else if (!arg.startsWith('-')) {
|
|
84
|
+
files.push(arg)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!fields && !characters) {
|
|
89
|
+
await writelnStderr(process, terminal, 'cut: you must specify a list of bytes, characters, or fields')
|
|
90
|
+
return 1
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const writer = process.stdout.getWriter()
|
|
94
|
+
|
|
95
|
+
const parseRange = (range: string): number[] => {
|
|
96
|
+
const result: number[] = []
|
|
97
|
+
const parts = range.split(',')
|
|
98
|
+
|
|
99
|
+
for (const part of parts) {
|
|
100
|
+
if (part.includes('-')) {
|
|
101
|
+
const splitParts = part.split('-')
|
|
102
|
+
const start = splitParts[0]
|
|
103
|
+
const end = splitParts[1]
|
|
104
|
+
const startNum = (start === '' || start === undefined) ? 1 : parseInt(start, 10)
|
|
105
|
+
const endNum = (end === '' || end === undefined) ? Infinity : parseInt(end, 10)
|
|
106
|
+
|
|
107
|
+
for (let i = startNum; i <= endNum; i++) {
|
|
108
|
+
result.push(i)
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
result.push(parseInt(part, 10))
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return result.sort((a, b) => a - b)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
let lines: string[] = []
|
|
120
|
+
|
|
121
|
+
if (files.length === 0) {
|
|
122
|
+
if (!process.stdin) {
|
|
123
|
+
return 0
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const reader = process.stdin.getReader()
|
|
127
|
+
const decoder = new TextDecoder()
|
|
128
|
+
let buffer = ''
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
while (true) {
|
|
132
|
+
const { done, value } = await reader.read()
|
|
133
|
+
if (done) break
|
|
134
|
+
if (value) {
|
|
135
|
+
buffer += decoder.decode(value, { stream: true })
|
|
136
|
+
const newLines = buffer.split('\n')
|
|
137
|
+
buffer = newLines.pop() || ''
|
|
138
|
+
lines.push(...newLines)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (buffer) {
|
|
142
|
+
lines.push(buffer)
|
|
143
|
+
}
|
|
144
|
+
} finally {
|
|
145
|
+
reader.releaseLock()
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
for (const file of files) {
|
|
149
|
+
const fullPath = path.resolve(shell.cwd, file)
|
|
150
|
+
|
|
151
|
+
let interrupted = false
|
|
152
|
+
const interruptHandler = () => { interrupted = true }
|
|
153
|
+
kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
if (fullPath.startsWith('/dev')) {
|
|
157
|
+
await writelnStderr(process, terminal, `cut: ${file}: cannot process device files`)
|
|
158
|
+
continue
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const handle = await shell.context.fs.promises.open(fullPath, 'r')
|
|
162
|
+
const stat = await shell.context.fs.promises.stat(fullPath)
|
|
163
|
+
|
|
164
|
+
const decoder = new TextDecoder()
|
|
165
|
+
let content = ''
|
|
166
|
+
let bytesRead = 0
|
|
167
|
+
const chunkSize = 1024
|
|
168
|
+
|
|
169
|
+
while (bytesRead < stat.size) {
|
|
170
|
+
if (interrupted) break
|
|
171
|
+
const data = new Uint8Array(chunkSize)
|
|
172
|
+
const readSize = Math.min(chunkSize, stat.size - bytesRead)
|
|
173
|
+
await handle.read(data, 0, readSize, bytesRead)
|
|
174
|
+
const chunk = data.subarray(0, readSize)
|
|
175
|
+
content += decoder.decode(chunk, { stream: true })
|
|
176
|
+
bytesRead += readSize
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const fileLines = content.split('\n')
|
|
180
|
+
if (fileLines[fileLines.length - 1] === '') {
|
|
181
|
+
fileLines.pop()
|
|
182
|
+
}
|
|
183
|
+
lines.push(...fileLines)
|
|
184
|
+
} catch (error) {
|
|
185
|
+
await writelnStderr(process, terminal, `cut: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
186
|
+
} finally {
|
|
187
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
for (const line of lines) {
|
|
193
|
+
let output = ''
|
|
194
|
+
|
|
195
|
+
if (characters) {
|
|
196
|
+
const indices = parseRange(characters)
|
|
197
|
+
const chars = line.split('')
|
|
198
|
+
output = indices.map(i => chars[i - 1] || '').join('')
|
|
199
|
+
} else if (fields) {
|
|
200
|
+
const indices = parseRange(fields)
|
|
201
|
+
const parts = line.split(delimiter)
|
|
202
|
+
output = indices.map(i => (parts[i - 1] || '')).join(delimiter)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
await writer.write(new TextEncoder().encode(output + '\n'))
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return 0
|
|
209
|
+
} finally {
|
|
210
|
+
writer.releaseLock()
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
}
|