@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
|
@@ -0,0 +1,175 @@
|
|
|
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: sort [OPTION]... [FILE]...
|
|
9
|
+
Sort lines of text files.
|
|
10
|
+
|
|
11
|
+
-r, --reverse reverse the result of comparisons
|
|
12
|
+
-n, --numeric compare according to string numerical value
|
|
13
|
+
-u, --unique output only the first of an equal run
|
|
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: 'sort',
|
|
21
|
+
description: 'Sort lines of text 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
|
+
const files: string[] = []
|
|
36
|
+
let reverse = false
|
|
37
|
+
let numeric = false
|
|
38
|
+
let unique = 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 === '-r' || arg === '--reverse') {
|
|
45
|
+
reverse = true
|
|
46
|
+
} else if (arg === '-n' || arg === '--numeric') {
|
|
47
|
+
numeric = true
|
|
48
|
+
} else if (arg === '-u' || arg === '--unique') {
|
|
49
|
+
unique = true
|
|
50
|
+
} else if (arg.startsWith('-')) {
|
|
51
|
+
const flags = arg.slice(1).split('')
|
|
52
|
+
if (flags.includes('r')) reverse = true
|
|
53
|
+
if (flags.includes('n')) numeric = true
|
|
54
|
+
if (flags.includes('u')) unique = true
|
|
55
|
+
const invalidFlags = flags.filter(f => !['r', 'n', 'u'].includes(f))
|
|
56
|
+
if (invalidFlags.length > 0) {
|
|
57
|
+
await writelnStderr(process, terminal, `sort: invalid option -- '${invalidFlags[0]}'`)
|
|
58
|
+
return 1
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
files.push(arg)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const writer = process.stdout.getWriter()
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
let lines: string[] = []
|
|
69
|
+
|
|
70
|
+
if (files.length === 0) {
|
|
71
|
+
if (!process.stdin) {
|
|
72
|
+
return 0
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const reader = process.stdin.getReader()
|
|
76
|
+
const decoder = new TextDecoder()
|
|
77
|
+
let buffer = ''
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
while (true) {
|
|
81
|
+
const { done, value } = await reader.read()
|
|
82
|
+
if (done) break
|
|
83
|
+
if (value) {
|
|
84
|
+
buffer += decoder.decode(value, { stream: true })
|
|
85
|
+
const newLines = buffer.split('\n')
|
|
86
|
+
buffer = newLines.pop() || ''
|
|
87
|
+
lines.push(...newLines)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (buffer) {
|
|
91
|
+
lines.push(buffer)
|
|
92
|
+
}
|
|
93
|
+
} finally {
|
|
94
|
+
reader.releaseLock()
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
for (const file of files) {
|
|
98
|
+
const fullPath = path.resolve(shell.cwd, file)
|
|
99
|
+
|
|
100
|
+
let interrupted = false
|
|
101
|
+
const interruptHandler = () => { interrupted = true }
|
|
102
|
+
kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
if (fullPath.startsWith('/dev')) {
|
|
106
|
+
await writelnStderr(process, terminal, `sort: ${file}: cannot sort device files`)
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const handle = await shell.context.fs.promises.open(fullPath, 'r')
|
|
111
|
+
const stat = await shell.context.fs.promises.stat(fullPath)
|
|
112
|
+
|
|
113
|
+
const decoder = new TextDecoder()
|
|
114
|
+
let content = ''
|
|
115
|
+
let bytesRead = 0
|
|
116
|
+
const chunkSize = 1024
|
|
117
|
+
|
|
118
|
+
while (bytesRead < stat.size) {
|
|
119
|
+
if (interrupted) break
|
|
120
|
+
const data = new Uint8Array(chunkSize)
|
|
121
|
+
const readSize = Math.min(chunkSize, stat.size - bytesRead)
|
|
122
|
+
await handle.read(data, 0, readSize, bytesRead)
|
|
123
|
+
const chunk = data.subarray(0, readSize)
|
|
124
|
+
content += decoder.decode(chunk, { stream: true })
|
|
125
|
+
bytesRead += readSize
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const fileLines = content.split('\n')
|
|
129
|
+
if (fileLines[fileLines.length - 1] === '') {
|
|
130
|
+
fileLines.pop()
|
|
131
|
+
}
|
|
132
|
+
lines.push(...fileLines)
|
|
133
|
+
} catch (error) {
|
|
134
|
+
await writelnStderr(process, terminal, `sort: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
135
|
+
} finally {
|
|
136
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (numeric) {
|
|
142
|
+
lines.sort((a, b) => {
|
|
143
|
+
const numA = parseFloat(a.trim())
|
|
144
|
+
const numB = parseFloat(b.trim())
|
|
145
|
+
if (isNaN(numA) && isNaN(numB)) return a.localeCompare(b)
|
|
146
|
+
if (isNaN(numA)) return 1
|
|
147
|
+
if (isNaN(numB)) return -1
|
|
148
|
+
return reverse ? numB - numA : numA - numB
|
|
149
|
+
})
|
|
150
|
+
} else {
|
|
151
|
+
lines.sort((a, b) => {
|
|
152
|
+
return reverse ? b.localeCompare(a) : a.localeCompare(b)
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (unique) {
|
|
157
|
+
const seen = new Set<string>()
|
|
158
|
+
lines = lines.filter(line => {
|
|
159
|
+
if (seen.has(line)) return false
|
|
160
|
+
seen.add(line)
|
|
161
|
+
return true
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
for (const line of lines) {
|
|
166
|
+
await writer.write(new TextEncoder().encode(line + '\n'))
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return 0
|
|
170
|
+
} finally {
|
|
171
|
+
writer.releaseLock()
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
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: split [OPTION]... [INPUT [PREFIX]]
|
|
9
|
+
Split INPUT into fixed-size pieces.
|
|
10
|
+
|
|
11
|
+
-l, -lNUMBER put NUMBER lines per output file
|
|
12
|
+
-b, -bSIZE put SIZE bytes per output file
|
|
13
|
+
--help display this help and exit`
|
|
14
|
+
writelnStderr(process, terminal, usage)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
18
|
+
return new TerminalCommand({
|
|
19
|
+
command: 'split',
|
|
20
|
+
description: 'Split a file into pieces',
|
|
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
|
+
if (!process) return 1
|
|
28
|
+
|
|
29
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
30
|
+
printUsage(process, terminal)
|
|
31
|
+
return 0
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let file = ''
|
|
35
|
+
let lines: number | undefined
|
|
36
|
+
let bytes: number | undefined
|
|
37
|
+
let prefix = 'x'
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < argv.length; i++) {
|
|
40
|
+
const arg = argv[i]
|
|
41
|
+
if (!arg) continue
|
|
42
|
+
|
|
43
|
+
if (arg === '--help' || arg === '-h') {
|
|
44
|
+
printUsage(process, terminal)
|
|
45
|
+
return 0
|
|
46
|
+
} else if (arg === '-l' || arg.startsWith('-l')) {
|
|
47
|
+
if (arg === '-l' && i + 1 < argv.length) {
|
|
48
|
+
i++
|
|
49
|
+
const nextArg = argv[i]
|
|
50
|
+
if (nextArg !== undefined) {
|
|
51
|
+
lines = parseInt(nextArg, 10)
|
|
52
|
+
}
|
|
53
|
+
} else if (arg.startsWith('-l') && arg.length > 2) {
|
|
54
|
+
lines = parseInt(arg.slice(2), 10)
|
|
55
|
+
}
|
|
56
|
+
} else if (arg === '-b' || arg.startsWith('-b')) {
|
|
57
|
+
if (arg === '-b' && i + 1 < argv.length) {
|
|
58
|
+
i++
|
|
59
|
+
const nextArg = argv[i]
|
|
60
|
+
if (nextArg !== undefined) {
|
|
61
|
+
bytes = parseInt(nextArg, 10)
|
|
62
|
+
}
|
|
63
|
+
} else if (arg.startsWith('-b') && arg.length > 2) {
|
|
64
|
+
bytes = parseInt(arg.slice(2), 10)
|
|
65
|
+
}
|
|
66
|
+
} else if (!arg.startsWith('-')) {
|
|
67
|
+
if (!file) {
|
|
68
|
+
file = arg
|
|
69
|
+
} else if (prefix === 'x') {
|
|
70
|
+
prefix = arg
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!file) {
|
|
76
|
+
await writelnStderr(process, terminal, 'split: missing file operand')
|
|
77
|
+
return 1
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!lines && !bytes) {
|
|
81
|
+
await writelnStderr(process, terminal, 'split: you must specify -l or -b')
|
|
82
|
+
return 1
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const fullPath = path.resolve(shell.cwd, file)
|
|
86
|
+
|
|
87
|
+
let interrupted = false
|
|
88
|
+
const interruptHandler = () => { interrupted = true }
|
|
89
|
+
kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
if (fullPath.startsWith('/dev')) {
|
|
93
|
+
await writelnStderr(process, terminal, `split: ${file}: cannot split device files`)
|
|
94
|
+
return 1
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const handle = await shell.context.fs.promises.open(fullPath, 'r')
|
|
98
|
+
const stat = await shell.context.fs.promises.stat(fullPath)
|
|
99
|
+
|
|
100
|
+
const decoder = new TextDecoder()
|
|
101
|
+
let content = ''
|
|
102
|
+
let bytesRead = 0
|
|
103
|
+
const chunkSize = 1024
|
|
104
|
+
|
|
105
|
+
while (bytesRead < stat.size) {
|
|
106
|
+
if (interrupted) break
|
|
107
|
+
const data = new Uint8Array(chunkSize)
|
|
108
|
+
const readSize = Math.min(chunkSize, stat.size - bytesRead)
|
|
109
|
+
await handle.read(data, 0, readSize, bytesRead)
|
|
110
|
+
const chunk = data.subarray(0, readSize)
|
|
111
|
+
content += decoder.decode(chunk, { stream: true })
|
|
112
|
+
bytesRead += readSize
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const dir = path.dirname(fullPath)
|
|
116
|
+
let fileIndex = 0
|
|
117
|
+
|
|
118
|
+
const getSuffix = (index: number): string => {
|
|
119
|
+
const first = Math.floor(index / 26)
|
|
120
|
+
const second = index % 26
|
|
121
|
+
return String.fromCharCode(97 + first) + String.fromCharCode(97 + second)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (lines) {
|
|
125
|
+
const allLines = content.split('\n')
|
|
126
|
+
for (let i = 0; i < allLines.length; i += lines) {
|
|
127
|
+
const chunk = allLines.slice(i, i + lines).join('\n')
|
|
128
|
+
const suffix = getSuffix(fileIndex)
|
|
129
|
+
const outputPath = path.join(dir, `${prefix}${suffix}`)
|
|
130
|
+
await shell.context.fs.promises.writeFile(outputPath, chunk, 'utf-8')
|
|
131
|
+
fileIndex++
|
|
132
|
+
}
|
|
133
|
+
} else if (bytes) {
|
|
134
|
+
const encoder = new TextEncoder()
|
|
135
|
+
const contentBytes = encoder.encode(content)
|
|
136
|
+
for (let i = 0; i < contentBytes.length; i += bytes) {
|
|
137
|
+
const chunk = contentBytes.slice(i, i + bytes)
|
|
138
|
+
const suffix = getSuffix(fileIndex)
|
|
139
|
+
const outputPath = path.join(dir, `${prefix}${suffix}`)
|
|
140
|
+
await shell.context.fs.promises.writeFile(outputPath, chunk)
|
|
141
|
+
fileIndex++
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return 0
|
|
146
|
+
} catch (error) {
|
|
147
|
+
await writelnStderr(process, terminal, `split: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
148
|
+
return 1
|
|
149
|
+
} finally {
|
|
150
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
}
|
package/src/commands/stat.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import * as zipjs from '@zip.js/zip.js'
|
|
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: stat [OPTION]... FILE...
|
|
10
|
+
Display file or file system status.
|
|
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: 'stat',
|
|
@@ -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 argPath = argv.length > 0 && argv[0] && !argv[0].startsWith('-') ? argv[0] : shell.cwd
|
|
22
32
|
const fullPath = argPath ? path.resolve(shell.cwd, argPath) : shell.cwd
|
|
23
33
|
const stats = await shell.context.fs.promises.stat(fullPath)
|
|
24
34
|
await writelnStdout(process, terminal, JSON.stringify(stats, null, 2))
|
|
@@ -37,4 +47,3 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
37
47
|
}
|
|
38
48
|
})
|
|
39
49
|
}
|
|
40
|
-
|
|
@@ -0,0 +1,200 @@
|
|
|
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 { writelnStdout } from '../shared/helpers.js'
|
|
6
|
+
|
|
7
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
8
|
+
const usage = `Usage: tail [OPTION]... [FILE]...
|
|
9
|
+
Print the last 10 lines of each FILE to standard output.
|
|
10
|
+
|
|
11
|
+
-n, -nNUMBER print the last NUMBER lines instead of 10
|
|
12
|
+
--help display this help and exit`
|
|
13
|
+
writelnStdout(process, terminal, usage)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
17
|
+
return new TerminalCommand({
|
|
18
|
+
command: 'tail',
|
|
19
|
+
description: 'Print the last lines of files',
|
|
20
|
+
kernel,
|
|
21
|
+
shell,
|
|
22
|
+
terminal,
|
|
23
|
+
run: async (pid: number, argv: string[]) => {
|
|
24
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
25
|
+
|
|
26
|
+
if (!process) return 1
|
|
27
|
+
|
|
28
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
29
|
+
printUsage(process, terminal)
|
|
30
|
+
return 0
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let numLines = 10
|
|
34
|
+
const files: string[] = []
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < argv.length; i++) {
|
|
37
|
+
const arg = argv[i]
|
|
38
|
+
if (!arg) continue
|
|
39
|
+
|
|
40
|
+
if (arg === '--help' || arg === '-h') {
|
|
41
|
+
printUsage(process, terminal)
|
|
42
|
+
return 0
|
|
43
|
+
} else if (arg === '-n' || arg.startsWith('-n')) {
|
|
44
|
+
if (arg === '-n' && i + 1 < argv.length) {
|
|
45
|
+
i++
|
|
46
|
+
const nextArg = argv[i]
|
|
47
|
+
if (nextArg !== undefined) {
|
|
48
|
+
const num = parseInt(nextArg, 10)
|
|
49
|
+
if (!isNaN(num)) numLines = num
|
|
50
|
+
}
|
|
51
|
+
} else if (arg.startsWith('-n') && arg.length > 2) {
|
|
52
|
+
const num = parseInt(arg.slice(2), 10)
|
|
53
|
+
if (!isNaN(num)) numLines = num
|
|
54
|
+
}
|
|
55
|
+
} else if (!arg.startsWith('-')) {
|
|
56
|
+
files.push(arg)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const writer = process.stdout.getWriter()
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
if (files.length === 0) {
|
|
64
|
+
if (!process.stdin) {
|
|
65
|
+
return 0
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const reader = process.stdin.getReader()
|
|
69
|
+
const decoder = new TextDecoder()
|
|
70
|
+
const lines: string[] = []
|
|
71
|
+
let buffer = ''
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
while (true) {
|
|
75
|
+
let readResult
|
|
76
|
+
try {
|
|
77
|
+
readResult = await reader.read()
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (error instanceof Error) {
|
|
80
|
+
throw error
|
|
81
|
+
}
|
|
82
|
+
break
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const { done, value } = readResult
|
|
86
|
+
if (done) {
|
|
87
|
+
buffer += decoder.decode()
|
|
88
|
+
break
|
|
89
|
+
}
|
|
90
|
+
if (value) {
|
|
91
|
+
buffer += decoder.decode(value, { stream: true })
|
|
92
|
+
const newLines = buffer.split('\n')
|
|
93
|
+
buffer = newLines.pop() || ''
|
|
94
|
+
lines.push(...newLines)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (buffer) {
|
|
98
|
+
lines.push(buffer)
|
|
99
|
+
}
|
|
100
|
+
} finally {
|
|
101
|
+
try {
|
|
102
|
+
reader.releaseLock()
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const output = lines.slice(-numLines).join('\n')
|
|
108
|
+
if (output) {
|
|
109
|
+
await writer.write(new TextEncoder().encode(output + '\n'))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return 0
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const isMultipleFiles = files.length > 1
|
|
116
|
+
|
|
117
|
+
for (let i = 0; i < files.length; i++) {
|
|
118
|
+
const file = files[i]
|
|
119
|
+
if (!file) continue
|
|
120
|
+
const fullPath = path.resolve(shell.cwd, file)
|
|
121
|
+
|
|
122
|
+
if (isMultipleFiles) {
|
|
123
|
+
const header = i > 0 ? '\n' : ''
|
|
124
|
+
await writer.write(new TextEncoder().encode(`${header}==> ${file} <==\n`))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let interrupted = false
|
|
128
|
+
const interruptHandler = () => { interrupted = true }
|
|
129
|
+
kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
if (!fullPath.startsWith('/dev')) {
|
|
133
|
+
const handle = await shell.context.fs.promises.open(fullPath, 'r')
|
|
134
|
+
const stat = await shell.context.fs.promises.stat(fullPath)
|
|
135
|
+
|
|
136
|
+
const decoder = new TextDecoder()
|
|
137
|
+
let buffer = ''
|
|
138
|
+
let bytesRead = 0
|
|
139
|
+
const chunkSize = 1024
|
|
140
|
+
|
|
141
|
+
while (bytesRead < stat.size) {
|
|
142
|
+
if (interrupted) break
|
|
143
|
+
const data = new Uint8Array(chunkSize)
|
|
144
|
+
const readSize = Math.min(chunkSize, stat.size - bytesRead)
|
|
145
|
+
await handle.read(data, 0, readSize, bytesRead)
|
|
146
|
+
const chunk = data.subarray(0, readSize)
|
|
147
|
+
buffer += decoder.decode(chunk, { stream: true })
|
|
148
|
+
bytesRead += readSize
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const lines = buffer.split('\n')
|
|
152
|
+
if (lines[lines.length - 1] === '') {
|
|
153
|
+
lines.pop()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const output = lines.slice(-numLines).join('\n')
|
|
157
|
+
if (output) {
|
|
158
|
+
await writer.write(new TextEncoder().encode(output + '\n'))
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
const device = await shell.context.fs.promises.open(fullPath)
|
|
162
|
+
const decoder = new TextDecoder()
|
|
163
|
+
const lines: string[] = []
|
|
164
|
+
let buffer = ''
|
|
165
|
+
const chunkSize = 1024
|
|
166
|
+
const data = new Uint8Array(chunkSize)
|
|
167
|
+
let bytesRead = 0
|
|
168
|
+
|
|
169
|
+
do {
|
|
170
|
+
if (interrupted) break
|
|
171
|
+
const result = await device.read(data)
|
|
172
|
+
bytesRead = result.bytesRead
|
|
173
|
+
if (bytesRead > 0) {
|
|
174
|
+
buffer += decoder.decode(data.subarray(0, bytesRead), { stream: true })
|
|
175
|
+
}
|
|
176
|
+
} while (bytesRead > 0)
|
|
177
|
+
|
|
178
|
+
const allLines = buffer.split('\n')
|
|
179
|
+
if (allLines[allLines.length - 1] === '') {
|
|
180
|
+
allLines.pop()
|
|
181
|
+
}
|
|
182
|
+
lines.push(...allLines)
|
|
183
|
+
|
|
184
|
+
const output = lines.slice(-numLines).join('\n')
|
|
185
|
+
if (output) {
|
|
186
|
+
await writer.write(new TextEncoder().encode(output + '\n'))
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
} finally {
|
|
190
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return 0
|
|
195
|
+
} finally {
|
|
196
|
+
writer.releaseLock()
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
}
|
package/src/commands/tee.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
|
-
import type { CommandLineOptions } from 'command-line-args'
|
|
3
2
|
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
4
3
|
import { TerminalEvents } from '@ecmaos/types'
|
|
5
4
|
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
6
5
|
import { writelnStderr } from '../shared/helpers.js'
|
|
7
6
|
|
|
7
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
8
|
+
const usage = `Usage: tee [OPTION]... [FILE]...
|
|
9
|
+
Read from standard input and write to standard output and files.
|
|
10
|
+
|
|
11
|
+
-a, --append append to the given files, do not overwrite
|
|
12
|
+
-i, --ignore-interrupts ignore interrupt signals
|
|
13
|
+
--help display this help and exit`
|
|
14
|
+
writelnStderr(process, terminal, usage)
|
|
15
|
+
}
|
|
16
|
+
|
|
8
17
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
9
18
|
return new TerminalCommand({
|
|
10
19
|
command: 'tee',
|
|
@@ -12,23 +21,46 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
12
21
|
kernel,
|
|
13
22
|
shell,
|
|
14
23
|
terminal,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
{ name: 'ignore-interrupts', type: Boolean, alias: 'i', description: 'Ignore interrupt signals' },
|
|
19
|
-
{ name: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, multiple: true, description: 'File(s) to write to' }
|
|
20
|
-
],
|
|
21
|
-
run: async (argv: CommandLineOptions, process?: Process) => {
|
|
24
|
+
run: async (pid: number, argv: string[]) => {
|
|
25
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
26
|
+
|
|
22
27
|
if (!process) return 1
|
|
23
28
|
|
|
29
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
30
|
+
printUsage(process, terminal)
|
|
31
|
+
return 0
|
|
32
|
+
}
|
|
33
|
+
|
|
24
34
|
if (!process.stdin) {
|
|
25
35
|
await writelnStderr(process, terminal, 'tee: No input provided')
|
|
26
36
|
return 1
|
|
27
37
|
}
|
|
28
38
|
|
|
29
|
-
const files
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
const files: string[] = []
|
|
40
|
+
let append = false
|
|
41
|
+
let ignoreInterrupts = false
|
|
42
|
+
|
|
43
|
+
for (const arg of argv) {
|
|
44
|
+
if (arg === '--help' || arg === '-h') {
|
|
45
|
+
printUsage(process, terminal)
|
|
46
|
+
return 0
|
|
47
|
+
} else if (arg === '-a' || arg === '--append') {
|
|
48
|
+
append = true
|
|
49
|
+
} else if (arg === '-i' || arg === '--ignore-interrupts') {
|
|
50
|
+
ignoreInterrupts = true
|
|
51
|
+
} else if (arg.startsWith('-')) {
|
|
52
|
+
const flags = arg.slice(1).split('')
|
|
53
|
+
if (flags.includes('a')) append = true
|
|
54
|
+
if (flags.includes('i')) ignoreInterrupts = true
|
|
55
|
+
const invalidFlags = flags.filter(f => !['a', 'i'].includes(f))
|
|
56
|
+
if (invalidFlags.length > 0) {
|
|
57
|
+
await writelnStderr(process, terminal, `tee: invalid option -- '${invalidFlags[0]}'`)
|
|
58
|
+
return 1
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
files.push(arg)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
32
64
|
|
|
33
65
|
const writer = process.stdout.getWriter()
|
|
34
66
|
|