@ecmaos/coreutils 0.1.5 → 0.2.1
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 +45 -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 +96 -91
- 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/user.d.ts +4 -0
- package/dist/commands/user.d.ts.map +1 -0
- package/dist/commands/user.js +427 -0
- package/dist/commands/user.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 +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +67 -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 +110 -87
- 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/user.ts +436 -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 +67 -1
- package/src/shared/terminal-command.ts +43 -16
|
@@ -0,0 +1,184 @@
|
|
|
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: find [PATH]... [OPTION]...
|
|
9
|
+
Search for files in a directory hierarchy.
|
|
10
|
+
|
|
11
|
+
-name PATTERN file name matches shell pattern PATTERN
|
|
12
|
+
-type TYPE file is of type TYPE (f=file, d=directory, l=symlink)
|
|
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: 'find',
|
|
20
|
+
description: 'Search for files in a directory hierarchy',
|
|
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
|
+
if (argv.length === 0) {
|
|
35
|
+
await writelnStderr(process, terminal, 'find: missing path argument')
|
|
36
|
+
return 1
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let startPaths: string[] = []
|
|
40
|
+
let namePattern: string | undefined
|
|
41
|
+
let fileType: string | undefined
|
|
42
|
+
|
|
43
|
+
for (let i = 0; i < argv.length; i++) {
|
|
44
|
+
const arg = argv[i]
|
|
45
|
+
if (arg === undefined) continue
|
|
46
|
+
|
|
47
|
+
if (arg === '-name') {
|
|
48
|
+
if (i + 1 < argv.length) {
|
|
49
|
+
i++
|
|
50
|
+
const nextArg = argv[i]
|
|
51
|
+
if (nextArg !== undefined && typeof nextArg === 'string' && !nextArg.startsWith('-')) {
|
|
52
|
+
namePattern = nextArg
|
|
53
|
+
} else {
|
|
54
|
+
await writelnStderr(process, terminal, 'find: missing argument to -name')
|
|
55
|
+
return 1
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
await writelnStderr(process, terminal, 'find: missing argument to -name')
|
|
59
|
+
return 1
|
|
60
|
+
}
|
|
61
|
+
} else if (arg === '-type') {
|
|
62
|
+
if (i + 1 < argv.length) {
|
|
63
|
+
i++
|
|
64
|
+
const nextArg = argv[i]
|
|
65
|
+
if (nextArg !== undefined && typeof nextArg === 'string' && !nextArg.startsWith('-')) {
|
|
66
|
+
fileType = nextArg
|
|
67
|
+
} else {
|
|
68
|
+
await writelnStderr(process, terminal, 'find: missing argument to -type')
|
|
69
|
+
return 1
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
await writelnStderr(process, terminal, 'find: missing argument to -type')
|
|
73
|
+
return 1
|
|
74
|
+
}
|
|
75
|
+
} else if (typeof arg === 'string' && !arg.startsWith('-')) {
|
|
76
|
+
startPaths.push(arg)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (startPaths.length === 0) {
|
|
81
|
+
startPaths = [shell.cwd]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const writer = process.stdout.getWriter()
|
|
85
|
+
|
|
86
|
+
const matchesPattern = (filename: string, pattern: string): boolean => {
|
|
87
|
+
if (!pattern) return false
|
|
88
|
+
|
|
89
|
+
let regexPattern = ''
|
|
90
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
91
|
+
const char = pattern[i]
|
|
92
|
+
if (char === undefined) continue
|
|
93
|
+
if (char === '*') {
|
|
94
|
+
regexPattern += '.*'
|
|
95
|
+
} else if (char === '?') {
|
|
96
|
+
regexPattern += '.'
|
|
97
|
+
} else if (char === '.') {
|
|
98
|
+
regexPattern += '\\.'
|
|
99
|
+
} else {
|
|
100
|
+
const needsEscaping = /[+^${}()|[\]\\]/.test(char)
|
|
101
|
+
regexPattern += needsEscaping ? '\\' + char : char
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const regex = new RegExp(`^${regexPattern}$`)
|
|
106
|
+
return regex.test(filename)
|
|
107
|
+
} catch {
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const searchDirectory = async (dirPath: string): Promise<void> => {
|
|
113
|
+
let interrupted = false
|
|
114
|
+
const interruptHandler = () => { interrupted = true }
|
|
115
|
+
kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
if (interrupted) return
|
|
119
|
+
|
|
120
|
+
const entries = await shell.context.fs.promises.readdir(dirPath)
|
|
121
|
+
|
|
122
|
+
for (const entry of entries) {
|
|
123
|
+
if (interrupted) break
|
|
124
|
+
|
|
125
|
+
const fullPath = path.join(dirPath, entry)
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const stat = await shell.context.fs.promises.stat(fullPath)
|
|
129
|
+
|
|
130
|
+
let matches = true
|
|
131
|
+
|
|
132
|
+
if (namePattern) {
|
|
133
|
+
const matchesName = matchesPattern(entry, namePattern)
|
|
134
|
+
if (!matchesName) {
|
|
135
|
+
matches = false
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (fileType && matches) {
|
|
140
|
+
if (fileType === 'f' && !stat.isFile()) matches = false
|
|
141
|
+
else if (fileType === 'd' && !stat.isDirectory()) matches = false
|
|
142
|
+
else if (fileType === 'l' && !stat.isSymbolicLink()) matches = false
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (matches) {
|
|
146
|
+
await writer.write(new TextEncoder().encode(fullPath + '\n'))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
|
150
|
+
await searchDirectory(fullPath)
|
|
151
|
+
}
|
|
152
|
+
} catch {
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
} finally {
|
|
157
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
for (const startPath of startPaths) {
|
|
163
|
+
const fullPath = path.resolve(shell.cwd, startPath)
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const stat = await shell.context.fs.promises.stat(fullPath)
|
|
167
|
+
if (!stat.isDirectory()) {
|
|
168
|
+
await writelnStderr(process, terminal, `find: ${startPath}: not a directory`)
|
|
169
|
+
continue
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
await searchDirectory(fullPath)
|
|
173
|
+
} catch (error) {
|
|
174
|
+
await writelnStderr(process, terminal, `find: ${startPath}: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return 0
|
|
179
|
+
} finally {
|
|
180
|
+
writer.releaseLock()
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
}
|
package/src/commands/grep.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: grep [OPTION]... PATTERN [FILE]...
|
|
9
|
+
Search for PATTERN in each FILE.
|
|
10
|
+
|
|
11
|
+
-i, --ignore-case ignore case distinctions
|
|
12
|
+
-n, --line-number print line number with output lines
|
|
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: 'grep',
|
|
@@ -12,16 +21,42 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
12
21
|
kernel,
|
|
13
22
|
shell,
|
|
14
23
|
terminal,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
{ name: 'line-number', type: Boolean, alias: 'n', description: 'Print line number with output lines' },
|
|
19
|
-
{ name: 'args', type: String, defaultOption: true, multiple: true, description: 'Pattern and file(s) to search' }
|
|
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
|
|
|
24
|
-
|
|
29
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
30
|
+
printUsage(process, terminal)
|
|
31
|
+
return 0
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let ignoreCase = false
|
|
35
|
+
let showLineNumbers = false
|
|
36
|
+
const args: string[] = []
|
|
37
|
+
|
|
38
|
+
for (const arg of argv) {
|
|
39
|
+
if (arg === '--help' || arg === '-h') {
|
|
40
|
+
printUsage(process, terminal)
|
|
41
|
+
return 0
|
|
42
|
+
} else if (arg === '-i' || arg === '--ignore-case') {
|
|
43
|
+
ignoreCase = true
|
|
44
|
+
} else if (arg === '-n' || arg === '--line-number') {
|
|
45
|
+
showLineNumbers = true
|
|
46
|
+
} else if (arg.startsWith('-')) {
|
|
47
|
+
const flags = arg.slice(1).split('')
|
|
48
|
+
if (flags.includes('i')) ignoreCase = true
|
|
49
|
+
if (flags.includes('n')) showLineNumbers = true
|
|
50
|
+
const invalidFlags = flags.filter(f => !['i', 'n'].includes(f))
|
|
51
|
+
if (invalidFlags.length > 0) {
|
|
52
|
+
await writelnStderr(process, terminal, `grep: invalid option -- '${invalidFlags[0]}'`)
|
|
53
|
+
return 1
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
args.push(arg)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
25
60
|
if (args.length === 0 || !args[0]) {
|
|
26
61
|
await writelnStderr(process, terminal, 'grep: pattern is required')
|
|
27
62
|
return 1
|
|
@@ -29,8 +64,6 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
29
64
|
|
|
30
65
|
const pattern = args[0]
|
|
31
66
|
const files = args.slice(1)
|
|
32
|
-
const ignoreCase = (argv['ignore-case'] as boolean) || false
|
|
33
|
-
const showLineNumbers = (argv['line-number'] as boolean) || false
|
|
34
67
|
|
|
35
68
|
const flags = ignoreCase ? 'i' : ''
|
|
36
69
|
let regex: RegExp
|
package/src/commands/head.ts
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
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'
|
|
5
|
+
import { writelnStdout } from '../shared/helpers.js'
|
|
6
|
+
|
|
7
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
8
|
+
const usage = `Usage: head [OPTION]... [FILE]...
|
|
9
|
+
Print the first 10 lines of each FILE to standard output.
|
|
10
|
+
|
|
11
|
+
-n, -nNUMBER print the first NUMBER lines instead of 10
|
|
12
|
+
--help display this help and exit`
|
|
13
|
+
writelnStdout(process, terminal, usage)
|
|
14
|
+
}
|
|
6
15
|
|
|
7
16
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
8
17
|
return new TerminalCommand({
|
|
@@ -11,19 +20,47 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
11
20
|
kernel,
|
|
12
21
|
shell,
|
|
13
22
|
terminal,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
{ name: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, multiple: true, description: 'The path(s) to the file(s) to read' }
|
|
18
|
-
],
|
|
19
|
-
run: async (argv: CommandLineOptions, process?: Process) => {
|
|
23
|
+
run: async (pid: number, argv: string[]) => {
|
|
24
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
25
|
+
|
|
20
26
|
if (!process) return 1
|
|
21
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
|
+
|
|
22
60
|
const writer = process.stdout.getWriter()
|
|
23
|
-
const numLines = (argv.lines as number) ?? 10
|
|
24
61
|
|
|
25
62
|
try {
|
|
26
|
-
if (
|
|
63
|
+
if (files.length === 0) {
|
|
27
64
|
if (!process.stdin) {
|
|
28
65
|
return 0
|
|
29
66
|
}
|
|
@@ -76,7 +113,6 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
76
113
|
return 0
|
|
77
114
|
}
|
|
78
115
|
|
|
79
|
-
const files = (argv.path as string[]) || []
|
|
80
116
|
const isMultipleFiles = files.length > 1
|
|
81
117
|
|
|
82
118
|
for (let i = 0; i < files.length; i++) {
|
package/src/commands/hex.ts
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
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',
|
|
@@ -11,14 +19,18 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
11
19
|
kernel,
|
|
12
20
|
shell,
|
|
13
21
|
terminal,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
],
|
|
18
|
-
run: async (argv: CommandLineOptions, process?: Process) => {
|
|
22
|
+
run: async (pid: number, argv: string[]) => {
|
|
23
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
24
|
+
|
|
19
25
|
if (!process) return 1
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
28
|
+
printUsage(process, terminal)
|
|
29
|
+
return 0
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const firstArg = argv.length > 0 ? argv[0] : undefined
|
|
33
|
+
const filePath = firstArg !== undefined && !firstArg.startsWith('-') ? firstArg : undefined
|
|
22
34
|
let data: Uint8Array
|
|
23
35
|
|
|
24
36
|
try {
|
|
@@ -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
|
+
}
|