@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,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
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
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: 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
|
+
|
|
17
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
18
|
+
return new TerminalCommand({
|
|
19
|
+
command: 'grep',
|
|
20
|
+
description: 'Search for patterns in files or standard input',
|
|
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 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
|
+
|
|
60
|
+
if (args.length === 0 || !args[0]) {
|
|
61
|
+
await writelnStderr(process, terminal, 'grep: pattern is required')
|
|
62
|
+
return 1
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const pattern = args[0]
|
|
66
|
+
const files = args.slice(1)
|
|
67
|
+
|
|
68
|
+
const flags = ignoreCase ? 'i' : ''
|
|
69
|
+
let regex: RegExp
|
|
70
|
+
try {
|
|
71
|
+
regex = new RegExp(pattern, flags)
|
|
72
|
+
} catch (error) {
|
|
73
|
+
await writelnStderr(process, terminal, `grep: invalid pattern: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
74
|
+
return 1
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const writer = process.stdout.getWriter()
|
|
78
|
+
let exitCode = 0
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
if (files.length === 0) {
|
|
82
|
+
if (!process.stdin) {
|
|
83
|
+
await writelnStderr(process, terminal, 'grep: No input provided')
|
|
84
|
+
return 1
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const reader = process.stdin.getReader()
|
|
88
|
+
let currentLineNumber = 1
|
|
89
|
+
let buffer = ''
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
while (true) {
|
|
93
|
+
const { done, value } = await reader.read()
|
|
94
|
+
if (done) break
|
|
95
|
+
|
|
96
|
+
const chunk = new TextDecoder().decode(value, { stream: true })
|
|
97
|
+
buffer += chunk
|
|
98
|
+
|
|
99
|
+
const lines = buffer.split('\n')
|
|
100
|
+
buffer = lines.pop() || ''
|
|
101
|
+
|
|
102
|
+
for (const line of lines) {
|
|
103
|
+
if (regex.test(line)) {
|
|
104
|
+
const output = showLineNumbers ? `${currentLineNumber}:${line}\n` : `${line}\n`
|
|
105
|
+
await writer.write(new TextEncoder().encode(output))
|
|
106
|
+
}
|
|
107
|
+
currentLineNumber++
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (buffer && regex.test(buffer)) {
|
|
112
|
+
const output = showLineNumbers ? `${currentLineNumber}:${buffer}\n` : `${buffer}\n`
|
|
113
|
+
await writer.write(new TextEncoder().encode(output))
|
|
114
|
+
}
|
|
115
|
+
} finally {
|
|
116
|
+
reader.releaseLock()
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
for (const file of files) {
|
|
120
|
+
const fullPath = path.resolve(shell.cwd, file)
|
|
121
|
+
|
|
122
|
+
let interrupted = false
|
|
123
|
+
const interruptHandler = () => { interrupted = true }
|
|
124
|
+
kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
if (fullPath.startsWith('/dev')) {
|
|
128
|
+
await writelnStderr(process, terminal, `grep: ${file}: cannot search device files`)
|
|
129
|
+
exitCode = 1
|
|
130
|
+
continue
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const handle = await shell.context.fs.promises.open(fullPath, 'r')
|
|
134
|
+
const stat = await shell.context.fs.promises.stat(fullPath)
|
|
135
|
+
|
|
136
|
+
let bytesRead = 0
|
|
137
|
+
const chunkSize = 1024
|
|
138
|
+
let buffer = ''
|
|
139
|
+
let currentLineNumber = 1
|
|
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
|
+
const text = new TextDecoder().decode(chunk, { stream: true })
|
|
148
|
+
buffer += text
|
|
149
|
+
|
|
150
|
+
const lines = buffer.split('\n')
|
|
151
|
+
buffer = lines.pop() || ''
|
|
152
|
+
|
|
153
|
+
for (const line of lines) {
|
|
154
|
+
if (regex.test(line)) {
|
|
155
|
+
const prefix = files.length > 1 ? `${file}:` : ''
|
|
156
|
+
const lineNumPrefix = showLineNumbers ? `${currentLineNumber}:` : ''
|
|
157
|
+
const output = `${prefix}${lineNumPrefix}${line}\n`
|
|
158
|
+
await writer.write(new TextEncoder().encode(output))
|
|
159
|
+
}
|
|
160
|
+
currentLineNumber++
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
bytesRead += readSize
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (buffer && regex.test(buffer)) {
|
|
167
|
+
const prefix = files.length > 1 ? `${file}:` : ''
|
|
168
|
+
const lineNumPrefix = showLineNumbers ? `${currentLineNumber}:` : ''
|
|
169
|
+
const output = `${prefix}${lineNumPrefix}${buffer}\n`
|
|
170
|
+
await writer.write(new TextEncoder().encode(output))
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
await writelnStderr(process, terminal, `grep: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
174
|
+
exitCode = 1
|
|
175
|
+
} finally {
|
|
176
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return exitCode
|
|
182
|
+
} finally {
|
|
183
|
+
writer.releaseLock()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
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: 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
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
17
|
+
return new TerminalCommand({
|
|
18
|
+
command: 'head',
|
|
19
|
+
description: 'Print the first 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
|
+
if (lines.length >= numLines) break
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (buffer && lines.length < numLines) {
|
|
99
|
+
lines.push(buffer)
|
|
100
|
+
}
|
|
101
|
+
} finally {
|
|
102
|
+
try {
|
|
103
|
+
reader.releaseLock()
|
|
104
|
+
} catch {
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const output = lines.slice(0, numLines).join('\n')
|
|
109
|
+
if (output) {
|
|
110
|
+
await writer.write(new TextEncoder().encode(output + '\n'))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return 0
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const isMultipleFiles = files.length > 1
|
|
117
|
+
|
|
118
|
+
for (let i = 0; i < files.length; i++) {
|
|
119
|
+
const file = files[i]
|
|
120
|
+
if (!file) continue
|
|
121
|
+
const fullPath = path.resolve(shell.cwd, file)
|
|
122
|
+
|
|
123
|
+
if (isMultipleFiles) {
|
|
124
|
+
const header = i > 0 ? '\n' : ''
|
|
125
|
+
await writer.write(new TextEncoder().encode(`${header}==> ${file} <==\n`))
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let interrupted = false
|
|
129
|
+
const interruptHandler = () => { interrupted = true }
|
|
130
|
+
kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
if (!fullPath.startsWith('/dev')) {
|
|
134
|
+
const handle = await shell.context.fs.promises.open(fullPath, 'r')
|
|
135
|
+
const stat = await shell.context.fs.promises.stat(fullPath)
|
|
136
|
+
|
|
137
|
+
const decoder = new TextDecoder()
|
|
138
|
+
const lines: string[] = []
|
|
139
|
+
let buffer = ''
|
|
140
|
+
let bytesRead = 0
|
|
141
|
+
const chunkSize = 1024
|
|
142
|
+
|
|
143
|
+
while (bytesRead < stat.size && lines.length < numLines) {
|
|
144
|
+
if (interrupted) break
|
|
145
|
+
const data = new Uint8Array(chunkSize)
|
|
146
|
+
const readSize = Math.min(chunkSize, stat.size - bytesRead)
|
|
147
|
+
await handle.read(data, 0, readSize, bytesRead)
|
|
148
|
+
const chunk = data.subarray(0, readSize)
|
|
149
|
+
buffer += decoder.decode(chunk, { stream: true })
|
|
150
|
+
const newLines = buffer.split('\n')
|
|
151
|
+
buffer = newLines.pop() || ''
|
|
152
|
+
lines.push(...newLines)
|
|
153
|
+
bytesRead += readSize
|
|
154
|
+
if (lines.length >= numLines) break
|
|
155
|
+
}
|
|
156
|
+
if (buffer && lines.length < numLines) {
|
|
157
|
+
lines.push(buffer)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const output = lines.slice(0, numLines).join('\n')
|
|
161
|
+
if (output) {
|
|
162
|
+
await writer.write(new TextEncoder().encode(output + '\n'))
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
const device = await shell.context.fs.promises.open(fullPath)
|
|
166
|
+
const decoder = new TextDecoder()
|
|
167
|
+
const lines: string[] = []
|
|
168
|
+
let buffer = ''
|
|
169
|
+
const chunkSize = 1024
|
|
170
|
+
const data = new Uint8Array(chunkSize)
|
|
171
|
+
let bytesRead = 0
|
|
172
|
+
|
|
173
|
+
do {
|
|
174
|
+
if (interrupted) break
|
|
175
|
+
const result = await device.read(data)
|
|
176
|
+
bytesRead = result.bytesRead
|
|
177
|
+
if (bytesRead > 0) {
|
|
178
|
+
buffer += decoder.decode(data.subarray(0, bytesRead), { stream: true })
|
|
179
|
+
const newLines = buffer.split('\n')
|
|
180
|
+
buffer = newLines.pop() || ''
|
|
181
|
+
lines.push(...newLines)
|
|
182
|
+
if (lines.length >= numLines) break
|
|
183
|
+
}
|
|
184
|
+
} while (bytesRead > 0 && lines.length < numLines)
|
|
185
|
+
|
|
186
|
+
if (buffer && lines.length < numLines) {
|
|
187
|
+
lines.push(buffer)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const output = lines.slice(0, numLines).join('\n')
|
|
191
|
+
if (output) {
|
|
192
|
+
await writer.write(new TextEncoder().encode(output + '\n'))
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} finally {
|
|
196
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return 0
|
|
201
|
+
} finally {
|
|
202
|
+
writer.releaseLock()
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
}
|