@ecmaos/coreutils 0.3.1 → 0.4.2
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 +48 -0
- package/dist/commands/awk.d.ts +4 -0
- package/dist/commands/awk.d.ts.map +1 -0
- package/dist/commands/awk.js +324 -0
- package/dist/commands/awk.js.map +1 -0
- package/dist/commands/chgrp.d.ts +4 -0
- package/dist/commands/chgrp.d.ts.map +1 -0
- package/dist/commands/chgrp.js +187 -0
- package/dist/commands/chgrp.js.map +1 -0
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +139 -2
- package/dist/commands/chmod.js.map +1 -1
- package/dist/commands/chown.d.ts +4 -0
- package/dist/commands/chown.d.ts.map +1 -0
- package/dist/commands/chown.js +257 -0
- package/dist/commands/chown.js.map +1 -0
- package/dist/commands/cksum.d.ts +4 -0
- package/dist/commands/cksum.d.ts.map +1 -0
- package/dist/commands/cksum.js +124 -0
- package/dist/commands/cksum.js.map +1 -0
- package/dist/commands/cmp.d.ts +4 -0
- package/dist/commands/cmp.d.ts.map +1 -0
- package/dist/commands/cmp.js +120 -0
- package/dist/commands/cmp.js.map +1 -0
- package/dist/commands/column.d.ts +4 -0
- package/dist/commands/column.d.ts.map +1 -0
- package/dist/commands/column.js +274 -0
- package/dist/commands/column.js.map +1 -0
- package/dist/commands/cp.d.ts.map +1 -1
- package/dist/commands/cp.js +81 -4
- package/dist/commands/cp.js.map +1 -1
- package/dist/commands/cron.d.ts.map +1 -1
- package/dist/commands/cron.js +116 -23
- package/dist/commands/cron.js.map +1 -1
- package/dist/commands/curl.d.ts +4 -0
- package/dist/commands/curl.d.ts.map +1 -0
- package/dist/commands/curl.js +238 -0
- package/dist/commands/curl.js.map +1 -0
- package/dist/commands/du.d.ts +4 -0
- package/dist/commands/du.d.ts.map +1 -0
- package/dist/commands/du.js +168 -0
- package/dist/commands/du.js.map +1 -0
- package/dist/commands/echo.d.ts.map +1 -1
- package/dist/commands/echo.js +125 -2
- package/dist/commands/echo.js.map +1 -1
- package/dist/commands/env.d.ts +4 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +129 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/expand.d.ts +4 -0
- package/dist/commands/expand.d.ts.map +1 -0
- package/dist/commands/expand.js +197 -0
- package/dist/commands/expand.js.map +1 -0
- package/dist/commands/factor.d.ts +4 -0
- package/dist/commands/factor.d.ts.map +1 -0
- package/dist/commands/factor.js +141 -0
- package/dist/commands/factor.js.map +1 -0
- package/dist/commands/fmt.d.ts +4 -0
- package/dist/commands/fmt.d.ts.map +1 -0
- package/dist/commands/fmt.js +278 -0
- package/dist/commands/fmt.js.map +1 -0
- package/dist/commands/fold.d.ts +4 -0
- package/dist/commands/fold.d.ts.map +1 -0
- package/dist/commands/fold.js +253 -0
- package/dist/commands/fold.js.map +1 -0
- package/dist/commands/groups.d.ts +4 -0
- package/dist/commands/groups.d.ts.map +1 -0
- package/dist/commands/groups.js +61 -0
- package/dist/commands/groups.js.map +1 -0
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +184 -77
- package/dist/commands/head.js.map +1 -1
- package/dist/commands/hostname.d.ts +4 -0
- package/dist/commands/hostname.d.ts.map +1 -0
- package/dist/commands/hostname.js +80 -0
- package/dist/commands/hostname.js.map +1 -0
- package/dist/commands/less.d.ts.map +1 -1
- package/dist/commands/less.js +1 -0
- package/dist/commands/less.js.map +1 -1
- package/dist/commands/man.d.ts.map +1 -1
- package/dist/commands/man.js +3 -1
- package/dist/commands/man.js.map +1 -1
- package/dist/commands/mount.d.ts +4 -0
- package/dist/commands/mount.d.ts.map +1 -0
- package/dist/commands/mount.js +1136 -0
- package/dist/commands/mount.js.map +1 -0
- package/dist/commands/od.d.ts +4 -0
- package/dist/commands/od.d.ts.map +1 -0
- package/dist/commands/od.js +342 -0
- package/dist/commands/od.js.map +1 -0
- package/dist/commands/pr.d.ts +4 -0
- package/dist/commands/pr.d.ts.map +1 -0
- package/dist/commands/pr.js +298 -0
- package/dist/commands/pr.js.map +1 -0
- package/dist/commands/printf.d.ts +4 -0
- package/dist/commands/printf.d.ts.map +1 -0
- package/dist/commands/printf.js +271 -0
- package/dist/commands/printf.js.map +1 -0
- package/dist/commands/readlink.d.ts +4 -0
- package/dist/commands/readlink.d.ts.map +1 -0
- package/dist/commands/readlink.js +104 -0
- package/dist/commands/readlink.js.map +1 -0
- package/dist/commands/realpath.d.ts +4 -0
- package/dist/commands/realpath.d.ts.map +1 -0
- package/dist/commands/realpath.js +111 -0
- package/dist/commands/realpath.js.map +1 -0
- package/dist/commands/rev.d.ts +4 -0
- package/dist/commands/rev.d.ts.map +1 -0
- package/dist/commands/rev.js +134 -0
- package/dist/commands/rev.js.map +1 -0
- package/dist/commands/shuf.d.ts +4 -0
- package/dist/commands/shuf.d.ts.map +1 -0
- package/dist/commands/shuf.js +221 -0
- package/dist/commands/shuf.js.map +1 -0
- package/dist/commands/sleep.d.ts +4 -0
- package/dist/commands/sleep.d.ts.map +1 -0
- package/dist/commands/sleep.js +102 -0
- package/dist/commands/sleep.js.map +1 -0
- package/dist/commands/strings.d.ts +4 -0
- package/dist/commands/strings.d.ts.map +1 -0
- package/dist/commands/strings.js +170 -0
- package/dist/commands/strings.js.map +1 -0
- package/dist/commands/tac.d.ts +4 -0
- package/dist/commands/tac.d.ts.map +1 -0
- package/dist/commands/tac.js +130 -0
- package/dist/commands/tac.js.map +1 -0
- package/dist/commands/time.d.ts +4 -0
- package/dist/commands/time.d.ts.map +1 -0
- package/dist/commands/time.js +126 -0
- package/dist/commands/time.js.map +1 -0
- package/dist/commands/umount.d.ts +4 -0
- package/dist/commands/umount.d.ts.map +1 -0
- package/dist/commands/umount.js +103 -0
- package/dist/commands/umount.js.map +1 -0
- package/dist/commands/uname.d.ts +4 -0
- package/dist/commands/uname.d.ts.map +1 -0
- package/dist/commands/uname.js +149 -0
- package/dist/commands/uname.js.map +1 -0
- package/dist/commands/unexpand.d.ts +4 -0
- package/dist/commands/unexpand.d.ts.map +1 -0
- package/dist/commands/unexpand.js +286 -0
- package/dist/commands/unexpand.js.map +1 -0
- package/dist/commands/uptime.d.ts +4 -0
- package/dist/commands/uptime.d.ts.map +1 -0
- package/dist/commands/uptime.js +62 -0
- package/dist/commands/uptime.js.map +1 -0
- package/dist/commands/view.d.ts +1 -0
- package/dist/commands/view.d.ts.map +1 -1
- package/dist/commands/view.js +408 -66
- package/dist/commands/view.js.map +1 -1
- package/dist/commands/yes.d.ts +4 -0
- package/dist/commands/yes.d.ts.map +1 -0
- package/dist/commands/yes.js +58 -0
- package/dist/commands/yes.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -1
- package/package.json +12 -3
- package/src/commands/awk.ts +340 -0
- package/src/commands/chmod.ts +141 -2
- package/src/commands/chown.ts +321 -0
- package/src/commands/cksum.ts +133 -0
- package/src/commands/cmp.ts +126 -0
- package/src/commands/column.ts +273 -0
- package/src/commands/cp.ts +93 -4
- package/src/commands/cron.ts +115 -23
- package/src/commands/curl.ts +231 -0
- package/src/commands/echo.ts +122 -2
- package/src/commands/env.ts +143 -0
- package/src/commands/expand.ts +207 -0
- package/src/commands/factor.ts +151 -0
- package/src/commands/fmt.ts +293 -0
- package/src/commands/fold.ts +257 -0
- package/src/commands/groups.ts +72 -0
- package/src/commands/head.ts +176 -77
- package/src/commands/hostname.ts +81 -0
- package/src/commands/less.ts +1 -0
- package/src/commands/man.ts +4 -1
- package/src/commands/mount.ts +1302 -0
- package/src/commands/od.ts +327 -0
- package/src/commands/pr.ts +291 -0
- package/src/commands/printf.ts +271 -0
- package/src/commands/readlink.ts +102 -0
- package/src/commands/realpath.ts +126 -0
- package/src/commands/rev.ts +143 -0
- package/src/commands/shuf.ts +218 -0
- package/src/commands/sleep.ts +109 -0
- package/src/commands/strings.ts +176 -0
- package/src/commands/tac.ts +138 -0
- package/src/commands/time.ts +144 -0
- package/src/commands/umount.ts +116 -0
- package/src/commands/uname.ts +130 -0
- package/src/commands/unexpand.ts +305 -0
- package/src/commands/uptime.ts +73 -0
- package/src/commands/view.ts +463 -73
- package/src/index.ts +82 -0
- package/tsconfig.json +4 -0
|
@@ -0,0 +1,340 @@
|
|
|
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: awk [OPTION]... 'program' [FILE]...
|
|
9
|
+
Pattern scanning and text processing language.
|
|
10
|
+
|
|
11
|
+
-F, --field-separator=FS set field separator (default: whitespace)
|
|
12
|
+
-v, --assign=VAR=VAL assign variable VAR to value VAL
|
|
13
|
+
--help display this help and exit
|
|
14
|
+
|
|
15
|
+
Basic usage:
|
|
16
|
+
awk '{ print $1 }' file Print first field of each line
|
|
17
|
+
awk '/pattern/ { print }' file Print lines matching pattern
|
|
18
|
+
awk 'BEGIN { print "start" } { print } END { print "end" }' file
|
|
19
|
+
|
|
20
|
+
Variables:
|
|
21
|
+
$0 whole line
|
|
22
|
+
$1, $2, ... field numbers
|
|
23
|
+
NR record number (line number)
|
|
24
|
+
NF number of fields`
|
|
25
|
+
writelnStderr(process, terminal, usage)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface AwkProgram {
|
|
29
|
+
begin?: string[]
|
|
30
|
+
pattern?: string
|
|
31
|
+
action?: string
|
|
32
|
+
end?: string[]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseAwkProgram(program: string): AwkProgram | null {
|
|
36
|
+
const result: AwkProgram = {}
|
|
37
|
+
|
|
38
|
+
let beginMatch = program.match(/BEGIN\s*\{([^}]*)\}/)
|
|
39
|
+
if (beginMatch) {
|
|
40
|
+
result.begin = beginMatch[1]?.split(';').map(s => s.trim()).filter(s => s) ?? []
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let endMatch = program.match(/END\s*\{([^}]*)\}/)
|
|
44
|
+
if (endMatch) {
|
|
45
|
+
result.end = endMatch[1]?.split(';').map(s => s.trim()).filter(s => s) ?? []
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let mainMatch = program.match(/(?:BEGIN\s*\{[^}]*\})?\s*([^}]*?)\s*(?:\{([^}]*)\})?\s*(?:END\s*\{[^}]*\})?/)
|
|
49
|
+
if (!mainMatch) {
|
|
50
|
+
const simpleMatch = program.match(/\{([^}]*)\}/)
|
|
51
|
+
if (simpleMatch) {
|
|
52
|
+
result.action = simpleMatch[1]?.trim() ?? ''
|
|
53
|
+
} else {
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
const patternPart = mainMatch[1]?.trim()
|
|
58
|
+
const actionPart = mainMatch[2]?.trim()
|
|
59
|
+
|
|
60
|
+
if (patternPart && !patternPart.startsWith('{')) {
|
|
61
|
+
if (patternPart.startsWith('/') && patternPart.endsWith('/')) {
|
|
62
|
+
result.pattern = patternPart.slice(1, -1)
|
|
63
|
+
} else {
|
|
64
|
+
result.pattern = patternPart
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (actionPart) {
|
|
69
|
+
result.action = actionPart
|
|
70
|
+
} else if (!patternPart) {
|
|
71
|
+
result.action = 'print'
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!result.action && !result.begin && !result.end) {
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return result
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function splitFields(line: string, fs: string): string[] {
|
|
83
|
+
if (fs === ' ') {
|
|
84
|
+
return line.trim().split(/\s+/)
|
|
85
|
+
}
|
|
86
|
+
return line.split(fs)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function executeAction(action: string, fields: string[], line: string, NR: number, NF: number): string {
|
|
90
|
+
if (!action || action.trim() === 'print' || action.trim() === '') {
|
|
91
|
+
return line
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const printMatch = action.match(/print\s+(.+)/)
|
|
95
|
+
if (printMatch) {
|
|
96
|
+
const args = printMatch[1]?.trim() ?? ''
|
|
97
|
+
const parts = args.split(',').map(s => s.trim())
|
|
98
|
+
const output: string[] = []
|
|
99
|
+
|
|
100
|
+
for (const part of parts) {
|
|
101
|
+
if (part === '$0') {
|
|
102
|
+
output.push(line)
|
|
103
|
+
} else if (part.match(/^\$\d+$/)) {
|
|
104
|
+
const fieldNum = parseInt(part.slice(1), 10)
|
|
105
|
+
if (fieldNum >= 1 && fieldNum <= fields.length) {
|
|
106
|
+
output.push(fields[fieldNum - 1] || '')
|
|
107
|
+
}
|
|
108
|
+
} else if (part === 'NR') {
|
|
109
|
+
output.push(String(NR))
|
|
110
|
+
} else if (part === 'NF') {
|
|
111
|
+
output.push(String(NF))
|
|
112
|
+
} else if (part.startsWith('"') && part.endsWith('"')) {
|
|
113
|
+
output.push(part.slice(1, -1))
|
|
114
|
+
} else if (part.startsWith("'") && part.endsWith("'")) {
|
|
115
|
+
output.push(part.slice(1, -1))
|
|
116
|
+
} else {
|
|
117
|
+
output.push(part)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return output.join(' ')
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return line
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
128
|
+
return new TerminalCommand({
|
|
129
|
+
command: 'awk',
|
|
130
|
+
description: 'Pattern scanning and text processing language',
|
|
131
|
+
kernel,
|
|
132
|
+
shell,
|
|
133
|
+
terminal,
|
|
134
|
+
run: async (pid: number, argv: string[]) => {
|
|
135
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
136
|
+
|
|
137
|
+
if (!process) return 1
|
|
138
|
+
|
|
139
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
140
|
+
printUsage(process, terminal)
|
|
141
|
+
return 0
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let fieldSeparator = ' '
|
|
145
|
+
const variables: Record<string, string> = {}
|
|
146
|
+
const args: string[] = []
|
|
147
|
+
let program: string | undefined
|
|
148
|
+
|
|
149
|
+
for (let i = 0; i < argv.length; i++) {
|
|
150
|
+
const arg = argv[i]
|
|
151
|
+
if (!arg) continue
|
|
152
|
+
|
|
153
|
+
if (arg === '--help' || arg === '-h') {
|
|
154
|
+
printUsage(process, terminal)
|
|
155
|
+
return 0
|
|
156
|
+
} else if (arg === '-F' || arg === '--field-separator') {
|
|
157
|
+
if (i + 1 < argv.length) {
|
|
158
|
+
fieldSeparator = argv[++i] || ' '
|
|
159
|
+
}
|
|
160
|
+
} else if (arg.startsWith('--field-separator=')) {
|
|
161
|
+
fieldSeparator = arg.slice(18)
|
|
162
|
+
} else if (arg.startsWith('-F')) {
|
|
163
|
+
fieldSeparator = arg.slice(2) || ' '
|
|
164
|
+
} else if (arg === '-v' || arg === '--assign') {
|
|
165
|
+
if (i + 1 < argv.length) {
|
|
166
|
+
const assign = argv[++i] || ''
|
|
167
|
+
const [key, ...valueParts] = assign.split('=')
|
|
168
|
+
if (key) {
|
|
169
|
+
variables[key] = valueParts.join('=')
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} else if (arg.startsWith('--assign=')) {
|
|
173
|
+
const assign = arg.slice(9)
|
|
174
|
+
const [key, ...valueParts] = assign.split('=')
|
|
175
|
+
if (key) {
|
|
176
|
+
variables[key] = valueParts.join('=')
|
|
177
|
+
}
|
|
178
|
+
} else if (arg.startsWith('-v')) {
|
|
179
|
+
const assign = arg.slice(2)
|
|
180
|
+
const [key, ...valueParts] = assign.split('=')
|
|
181
|
+
if (key) {
|
|
182
|
+
variables[key] = valueParts.join('=')
|
|
183
|
+
}
|
|
184
|
+
} else if (!arg.startsWith('-')) {
|
|
185
|
+
if (!program && (arg.startsWith("'") || arg.startsWith('"'))) {
|
|
186
|
+
program = arg.slice(1, -1)
|
|
187
|
+
} else if (!program) {
|
|
188
|
+
program = arg
|
|
189
|
+
} else {
|
|
190
|
+
args.push(arg)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!program) {
|
|
196
|
+
await writelnStderr(process, terminal, 'awk: program is required')
|
|
197
|
+
await writelnStderr(process, terminal, "Try 'awk --help' for more information.")
|
|
198
|
+
return 1
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const parsedProgram = parseAwkProgram(program)
|
|
202
|
+
if (!parsedProgram) {
|
|
203
|
+
await writelnStderr(process, terminal, 'awk: invalid program')
|
|
204
|
+
return 1
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const writer = process.stdout.getWriter()
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
let lines: string[] = []
|
|
211
|
+
|
|
212
|
+
if (args.length === 0) {
|
|
213
|
+
if (!process.stdin) {
|
|
214
|
+
return 0
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const reader = process.stdin.getReader()
|
|
218
|
+
const decoder = new TextDecoder()
|
|
219
|
+
let buffer = ''
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
while (true) {
|
|
223
|
+
const { done, value } = await reader.read()
|
|
224
|
+
if (done) break
|
|
225
|
+
if (value) {
|
|
226
|
+
buffer += decoder.decode(value, { stream: true })
|
|
227
|
+
const newLines = buffer.split('\n')
|
|
228
|
+
buffer = newLines.pop() || ''
|
|
229
|
+
lines.push(...newLines)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (buffer) {
|
|
233
|
+
lines.push(buffer)
|
|
234
|
+
}
|
|
235
|
+
} finally {
|
|
236
|
+
reader.releaseLock()
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
for (const file of args) {
|
|
240
|
+
const fullPath = path.resolve(shell.cwd, file)
|
|
241
|
+
|
|
242
|
+
let interrupted = false
|
|
243
|
+
const interruptHandler = () => { interrupted = true }
|
|
244
|
+
kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
if (fullPath.startsWith('/dev')) {
|
|
248
|
+
await writelnStderr(process, terminal, `awk: ${file}: cannot process device files`)
|
|
249
|
+
continue
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const handle = await shell.context.fs.promises.open(fullPath, 'r')
|
|
253
|
+
const stat = await shell.context.fs.promises.stat(fullPath)
|
|
254
|
+
|
|
255
|
+
const decoder = new TextDecoder()
|
|
256
|
+
let content = ''
|
|
257
|
+
let bytesRead = 0
|
|
258
|
+
const chunkSize = 1024
|
|
259
|
+
|
|
260
|
+
while (bytesRead < stat.size) {
|
|
261
|
+
if (interrupted) break
|
|
262
|
+
const data = new Uint8Array(chunkSize)
|
|
263
|
+
const readSize = Math.min(chunkSize, stat.size - bytesRead)
|
|
264
|
+
await handle.read(data, 0, readSize, bytesRead)
|
|
265
|
+
const chunk = data.subarray(0, readSize)
|
|
266
|
+
content += decoder.decode(chunk, { stream: true })
|
|
267
|
+
bytesRead += readSize
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const fileLines = content.split('\n')
|
|
271
|
+
if (fileLines[fileLines.length - 1] === '') {
|
|
272
|
+
fileLines.pop()
|
|
273
|
+
}
|
|
274
|
+
lines.push(...fileLines)
|
|
275
|
+
} catch (error) {
|
|
276
|
+
await writelnStderr(process, terminal, `awk: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
277
|
+
} finally {
|
|
278
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (parsedProgram.begin) {
|
|
284
|
+
for (const stmt of parsedProgram.begin) {
|
|
285
|
+
if (stmt.trim() === 'print' || stmt.trim().startsWith('print ')) {
|
|
286
|
+
const output = executeAction(stmt, [], '', 0, 0)
|
|
287
|
+
if (output) {
|
|
288
|
+
await writer.write(new TextEncoder().encode(output + '\n'))
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
let NR = 0
|
|
295
|
+
for (const line of lines) {
|
|
296
|
+
NR++
|
|
297
|
+
const fields = splitFields(line, fieldSeparator)
|
|
298
|
+
const NF = fields.length
|
|
299
|
+
|
|
300
|
+
let shouldProcess = true
|
|
301
|
+
if (parsedProgram.pattern) {
|
|
302
|
+
try {
|
|
303
|
+
const regex = new RegExp(parsedProgram.pattern)
|
|
304
|
+
shouldProcess = regex.test(line)
|
|
305
|
+
} catch {
|
|
306
|
+
shouldProcess = false
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (shouldProcess && parsedProgram.action) {
|
|
311
|
+
const output = executeAction(parsedProgram.action, fields, line, NR, NF)
|
|
312
|
+
if (output !== null) {
|
|
313
|
+
await writer.write(new TextEncoder().encode(output + '\n'))
|
|
314
|
+
}
|
|
315
|
+
} else if (shouldProcess && !parsedProgram.action && !parsedProgram.pattern) {
|
|
316
|
+
await writer.write(new TextEncoder().encode(line + '\n'))
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (parsedProgram.end) {
|
|
321
|
+
for (const stmt of parsedProgram.end) {
|
|
322
|
+
if (stmt.trim() === 'print' || stmt.trim().startsWith('print ')) {
|
|
323
|
+
const output = executeAction(stmt, [], '', NR + 1, 0)
|
|
324
|
+
if (output) {
|
|
325
|
+
await writer.write(new TextEncoder().encode(output + '\n'))
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return 0
|
|
332
|
+
} catch (error) {
|
|
333
|
+
await writelnStderr(process, terminal, `awk: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
334
|
+
return 1
|
|
335
|
+
} finally {
|
|
336
|
+
writer.releaseLock()
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
})
|
|
340
|
+
}
|
package/src/commands/chmod.ts
CHANGED
|
@@ -6,12 +6,150 @@ import { writelnStderr } from '../shared/helpers.js'
|
|
|
6
6
|
|
|
7
7
|
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
8
8
|
const usage = `Usage: chmod [OPTION]... MODE[,MODE]... FILE...
|
|
9
|
+
or: chmod [OPTION]... OCTAL-MODE FILE...
|
|
9
10
|
Change the mode of each FILE to MODE.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
--help display this help and exit
|
|
13
|
+
|
|
14
|
+
Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+'.
|
|
15
|
+
|
|
16
|
+
MODE can be specified in two ways:
|
|
17
|
+
|
|
18
|
+
Numeric mode (octal):
|
|
19
|
+
MODE is an octal number representing the permissions:
|
|
20
|
+
4 = read (r)
|
|
21
|
+
2 = write (w)
|
|
22
|
+
1 = execute (x)
|
|
23
|
+
|
|
24
|
+
The three digits represent user, group, and other permissions.
|
|
25
|
+
Each digit is the sum of the desired permissions:
|
|
26
|
+
- 7 = 4+2+1 = read + write + execute (rwx)
|
|
27
|
+
- 6 = 4+2 = read + write (rw-)
|
|
28
|
+
- 5 = 4+1 = read + execute (r-x)
|
|
29
|
+
- 4 = 4 = read only (r--)
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
755 = rwxr-xr-x (user: rwx, group: r-x, other: r-x)
|
|
33
|
+
644 = rw-r--r-- (user: rw-, group: r--, other: r--)
|
|
34
|
+
600 = rw------- (user: rw-, group: ---, other: ---)
|
|
35
|
+
|
|
36
|
+
Symbolic mode:
|
|
37
|
+
[ugoa]*[-+=][rwxXst]*
|
|
38
|
+
|
|
39
|
+
The format is: [who][operator][permissions]
|
|
40
|
+
|
|
41
|
+
'who' (optional, defaults to 'a' if omitted):
|
|
42
|
+
u user (owner)
|
|
43
|
+
g group
|
|
44
|
+
o other
|
|
45
|
+
a all (user, group, and other)
|
|
46
|
+
|
|
47
|
+
'operator':
|
|
48
|
+
+ add the specified permissions
|
|
49
|
+
- remove the specified permissions
|
|
50
|
+
= set the exact permissions (clears others)
|
|
51
|
+
|
|
52
|
+
'permissions' (one or more):
|
|
53
|
+
r read
|
|
54
|
+
w write
|
|
55
|
+
x execute
|
|
56
|
+
X execute only if file is a directory or already has execute
|
|
57
|
+
s setuid/setgid
|
|
58
|
+
t sticky bit
|
|
59
|
+
|
|
60
|
+
Examples:
|
|
61
|
+
chmod 755 file Set file to rwxr-xr-x
|
|
62
|
+
chmod +x file Add execute permission for all
|
|
63
|
+
chmod u+x file Add execute permission for user
|
|
64
|
+
chmod g-w file Remove write permission for group
|
|
65
|
+
chmod o=r file Set other permissions to read-only
|
|
66
|
+
chmod u=rwx,g=rx,o=r file Set specific permissions for each class
|
|
67
|
+
chmod a-w file Remove write permission for all
|
|
68
|
+
chmod u+x,g+x file Add execute for user and group
|
|
69
|
+
|
|
70
|
+
Multiple modes can be specified separated by commas:
|
|
71
|
+
chmod u+x,g-w file Add execute for user, remove write for group`
|
|
12
72
|
writelnStderr(process, terminal, usage)
|
|
13
73
|
}
|
|
14
74
|
|
|
75
|
+
function parseNumericMode(mode: string): number | null {
|
|
76
|
+
if (/^0?[0-7]{1,4}$/.test(mode)) {
|
|
77
|
+
return parseInt(mode, 8)
|
|
78
|
+
}
|
|
79
|
+
if (/^0o[0-7]{1,4}$/i.test(mode)) {
|
|
80
|
+
return parseInt(mode.slice(2), 8)
|
|
81
|
+
}
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function parseSymbolicMode(mode: string, currentMode: number): number {
|
|
86
|
+
const parts = mode.split(',')
|
|
87
|
+
let newMode = currentMode
|
|
88
|
+
|
|
89
|
+
for (const part of parts) {
|
|
90
|
+
const match = part.match(/^([ugoa]*)([+\-=])([rwxXst]*)$/)
|
|
91
|
+
if (!match) {
|
|
92
|
+
throw new Error(`Invalid mode: ${part}`)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const [, who, op, perms = ''] = match
|
|
96
|
+
const whoSet = who || 'a'
|
|
97
|
+
|
|
98
|
+
if ((op === '+' || op === '-') && !perms) {
|
|
99
|
+
throw new Error(`Invalid mode: ${part} (missing permissions)`)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const userBits = 0o400 | 0o200 | 0o100
|
|
103
|
+
const groupBits = 0o040 | 0o020 | 0o010
|
|
104
|
+
const otherBits = 0o004 | 0o002 | 0o001
|
|
105
|
+
|
|
106
|
+
let permBits = 0
|
|
107
|
+
if (perms.includes('r')) permBits |= 0o444
|
|
108
|
+
if (perms.includes('w')) permBits |= 0o222
|
|
109
|
+
if (perms.includes('x')) permBits |= 0o111
|
|
110
|
+
if (perms.includes('X')) {
|
|
111
|
+
if (currentMode & 0o111) permBits |= 0o111
|
|
112
|
+
}
|
|
113
|
+
if (perms.includes('s')) permBits |= 0o6000
|
|
114
|
+
if (perms.includes('t')) permBits |= 0o1000
|
|
115
|
+
|
|
116
|
+
let targetBits = 0
|
|
117
|
+
if (whoSet.includes('u') || whoSet.includes('a')) targetBits |= userBits
|
|
118
|
+
if (whoSet.includes('g') || whoSet.includes('a')) targetBits |= groupBits
|
|
119
|
+
if (whoSet.includes('o') || whoSet.includes('a')) targetBits |= otherBits
|
|
120
|
+
|
|
121
|
+
switch (op) {
|
|
122
|
+
case '+':
|
|
123
|
+
newMode |= (permBits & targetBits)
|
|
124
|
+
break
|
|
125
|
+
case '-':
|
|
126
|
+
newMode &= ~(permBits & targetBits)
|
|
127
|
+
break
|
|
128
|
+
case '=':
|
|
129
|
+
newMode &= ~targetBits
|
|
130
|
+
newMode |= (permBits & targetBits)
|
|
131
|
+
break
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return newMode
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function parseMode(mode: string, fs: typeof import('@zenfs/core').fs.promises, filePath: string): Promise<number> {
|
|
139
|
+
const numericMode = parseNumericMode(mode)
|
|
140
|
+
if (numericMode !== null) {
|
|
141
|
+
return numericMode
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const stats = await fs.stat(filePath)
|
|
146
|
+
const currentMode = stats.mode & 0o7777
|
|
147
|
+
return parseSymbolicMode(mode, currentMode)
|
|
148
|
+
} catch (error) {
|
|
149
|
+
throw new Error(`Cannot access ${filePath}: ${error instanceof Error ? error.message : String(error)}`)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
15
153
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
16
154
|
return new TerminalCommand({
|
|
17
155
|
command: 'chmod',
|
|
@@ -55,7 +193,8 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
55
193
|
const fullPath = path.resolve(shell.cwd, target)
|
|
56
194
|
|
|
57
195
|
try {
|
|
58
|
-
await shell.context.fs.promises
|
|
196
|
+
const numericMode = await parseMode(mode, shell.context.fs.promises, fullPath)
|
|
197
|
+
await shell.context.fs.promises.chmod(fullPath, numericMode)
|
|
59
198
|
} catch (error) {
|
|
60
199
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
61
200
|
await writelnStderr(process, terminal, `chmod: ${target}: ${errorMessage}`)
|