@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,214 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
3
|
+
import { TerminalEvents } from '@ecmaos/types'
|
|
4
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
5
|
+
import { writelnStderr } from '../shared/helpers.js'
|
|
6
|
+
|
|
7
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
8
|
+
const usage = `Usage: cut OPTION... [FILE]...
|
|
9
|
+
Remove sections from each line of files.
|
|
10
|
+
|
|
11
|
+
-f, --fields=LIST select only these fields
|
|
12
|
+
-d, --delimiter=DELIM use DELIM instead of TAB for field delimiter
|
|
13
|
+
-c, --characters=LIST select only these characters
|
|
14
|
+
--help display this help and exit`
|
|
15
|
+
writelnStderr(process, terminal, usage)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
19
|
+
return new TerminalCommand({
|
|
20
|
+
command: 'cut',
|
|
21
|
+
description: 'Remove sections from each line of files',
|
|
22
|
+
kernel,
|
|
23
|
+
shell,
|
|
24
|
+
terminal,
|
|
25
|
+
run: async (pid: number, argv: string[]) => {
|
|
26
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
27
|
+
|
|
28
|
+
if (!process) return 1
|
|
29
|
+
|
|
30
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
31
|
+
printUsage(process, terminal)
|
|
32
|
+
return 0
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let fields: string | undefined
|
|
36
|
+
let delimiter: string = '\t'
|
|
37
|
+
let characters: string | undefined
|
|
38
|
+
const files: string[] = []
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < argv.length; i++) {
|
|
41
|
+
const arg = argv[i]
|
|
42
|
+
if (!arg) continue
|
|
43
|
+
|
|
44
|
+
if (arg === '--help' || arg === '-h') {
|
|
45
|
+
printUsage(process, terminal)
|
|
46
|
+
return 0
|
|
47
|
+
} else if (arg === '-f' || arg.startsWith('-f')) {
|
|
48
|
+
if (arg === '-f' && i + 1 < argv.length) {
|
|
49
|
+
i++
|
|
50
|
+
const nextArg = argv[i]
|
|
51
|
+
if (nextArg !== undefined) {
|
|
52
|
+
fields = nextArg
|
|
53
|
+
}
|
|
54
|
+
} else if (arg.startsWith('-f') && arg.length > 2) {
|
|
55
|
+
fields = arg.slice(2)
|
|
56
|
+
} else if (arg.startsWith('--fields=')) {
|
|
57
|
+
fields = arg.slice(9)
|
|
58
|
+
}
|
|
59
|
+
} else if (arg === '-c' || arg.startsWith('-c')) {
|
|
60
|
+
if (arg === '-c' && i + 1 < argv.length) {
|
|
61
|
+
i++
|
|
62
|
+
const nextArg = argv[i]
|
|
63
|
+
if (nextArg !== undefined) {
|
|
64
|
+
characters = nextArg
|
|
65
|
+
}
|
|
66
|
+
} else if (arg.startsWith('-c') && arg.length > 2) {
|
|
67
|
+
characters = arg.slice(2)
|
|
68
|
+
} else if (arg.startsWith('--characters=')) {
|
|
69
|
+
characters = arg.slice(13)
|
|
70
|
+
}
|
|
71
|
+
} else if (arg === '-d' || arg.startsWith('-d')) {
|
|
72
|
+
if (arg === '-d' && i + 1 < argv.length) {
|
|
73
|
+
i++
|
|
74
|
+
const nextArg = argv[i]
|
|
75
|
+
if (nextArg !== undefined) {
|
|
76
|
+
delimiter = nextArg
|
|
77
|
+
}
|
|
78
|
+
} else if (arg.startsWith('-d') && arg.length > 2) {
|
|
79
|
+
delimiter = arg.slice(2)
|
|
80
|
+
} else if (arg.startsWith('--delimiter=')) {
|
|
81
|
+
delimiter = arg.slice(12)
|
|
82
|
+
}
|
|
83
|
+
} else if (!arg.startsWith('-')) {
|
|
84
|
+
files.push(arg)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!fields && !characters) {
|
|
89
|
+
await writelnStderr(process, terminal, 'cut: you must specify a list of bytes, characters, or fields')
|
|
90
|
+
return 1
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const writer = process.stdout.getWriter()
|
|
94
|
+
|
|
95
|
+
const parseRange = (range: string): number[] => {
|
|
96
|
+
const result: number[] = []
|
|
97
|
+
const parts = range.split(',')
|
|
98
|
+
|
|
99
|
+
for (const part of parts) {
|
|
100
|
+
if (part.includes('-')) {
|
|
101
|
+
const splitParts = part.split('-')
|
|
102
|
+
const start = splitParts[0]
|
|
103
|
+
const end = splitParts[1]
|
|
104
|
+
const startNum = (start === '' || start === undefined) ? 1 : parseInt(start, 10)
|
|
105
|
+
const endNum = (end === '' || end === undefined) ? Infinity : parseInt(end, 10)
|
|
106
|
+
|
|
107
|
+
for (let i = startNum; i <= endNum; i++) {
|
|
108
|
+
result.push(i)
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
result.push(parseInt(part, 10))
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return result.sort((a, b) => a - b)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
let lines: string[] = []
|
|
120
|
+
|
|
121
|
+
if (files.length === 0) {
|
|
122
|
+
if (!process.stdin) {
|
|
123
|
+
return 0
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const reader = process.stdin.getReader()
|
|
127
|
+
const decoder = new TextDecoder()
|
|
128
|
+
let buffer = ''
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
while (true) {
|
|
132
|
+
const { done, value } = await reader.read()
|
|
133
|
+
if (done) break
|
|
134
|
+
if (value) {
|
|
135
|
+
buffer += decoder.decode(value, { stream: true })
|
|
136
|
+
const newLines = buffer.split('\n')
|
|
137
|
+
buffer = newLines.pop() || ''
|
|
138
|
+
lines.push(...newLines)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (buffer) {
|
|
142
|
+
lines.push(buffer)
|
|
143
|
+
}
|
|
144
|
+
} finally {
|
|
145
|
+
reader.releaseLock()
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
for (const file of files) {
|
|
149
|
+
const fullPath = path.resolve(shell.cwd, file)
|
|
150
|
+
|
|
151
|
+
let interrupted = false
|
|
152
|
+
const interruptHandler = () => { interrupted = true }
|
|
153
|
+
kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
if (fullPath.startsWith('/dev')) {
|
|
157
|
+
await writelnStderr(process, terminal, `cut: ${file}: cannot process device files`)
|
|
158
|
+
continue
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const handle = await shell.context.fs.promises.open(fullPath, 'r')
|
|
162
|
+
const stat = await shell.context.fs.promises.stat(fullPath)
|
|
163
|
+
|
|
164
|
+
const decoder = new TextDecoder()
|
|
165
|
+
let content = ''
|
|
166
|
+
let bytesRead = 0
|
|
167
|
+
const chunkSize = 1024
|
|
168
|
+
|
|
169
|
+
while (bytesRead < stat.size) {
|
|
170
|
+
if (interrupted) break
|
|
171
|
+
const data = new Uint8Array(chunkSize)
|
|
172
|
+
const readSize = Math.min(chunkSize, stat.size - bytesRead)
|
|
173
|
+
await handle.read(data, 0, readSize, bytesRead)
|
|
174
|
+
const chunk = data.subarray(0, readSize)
|
|
175
|
+
content += decoder.decode(chunk, { stream: true })
|
|
176
|
+
bytesRead += readSize
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const fileLines = content.split('\n')
|
|
180
|
+
if (fileLines[fileLines.length - 1] === '') {
|
|
181
|
+
fileLines.pop()
|
|
182
|
+
}
|
|
183
|
+
lines.push(...fileLines)
|
|
184
|
+
} catch (error) {
|
|
185
|
+
await writelnStderr(process, terminal, `cut: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
186
|
+
} finally {
|
|
187
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
for (const line of lines) {
|
|
193
|
+
let output = ''
|
|
194
|
+
|
|
195
|
+
if (characters) {
|
|
196
|
+
const indices = parseRange(characters)
|
|
197
|
+
const chars = line.split('')
|
|
198
|
+
output = indices.map(i => chars[i - 1] || '').join('')
|
|
199
|
+
} else if (fields) {
|
|
200
|
+
const indices = parseRange(fields)
|
|
201
|
+
const parts = line.split(delimiter)
|
|
202
|
+
output = indices.map(i => (parts[i - 1] || '')).join(delimiter)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
await writer.write(new TextEncoder().encode(output + '\n'))
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return 0
|
|
209
|
+
} finally {
|
|
210
|
+
writer.releaseLock()
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
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: date [OPTION]... [+FORMAT]
|
|
7
|
+
Print or set the system date and time.
|
|
8
|
+
|
|
9
|
+
-I, --iso-8601[=TIMESPEC] output date/time in ISO 8601 format
|
|
10
|
+
-R, --rfc-2822 output date and time in RFC 2822 format
|
|
11
|
+
-f, --format=FORMAT output date/time in specified format (strftime-like)
|
|
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: 'date',
|
|
19
|
+
description: 'Print or set the system date and time',
|
|
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 (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
27
|
+
printUsage(process, terminal)
|
|
28
|
+
return 0
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const now = new Date()
|
|
32
|
+
let output = ''
|
|
33
|
+
let iso8601 = false
|
|
34
|
+
let rfc2822 = false
|
|
35
|
+
let format: string | undefined
|
|
36
|
+
|
|
37
|
+
for (let i = 0; i < argv.length; i++) {
|
|
38
|
+
const arg = argv[i]
|
|
39
|
+
if (!arg) continue
|
|
40
|
+
|
|
41
|
+
if (arg === '--help' || arg === '-h') {
|
|
42
|
+
printUsage(process, terminal)
|
|
43
|
+
return 0
|
|
44
|
+
} else if (arg === '-I' || arg === '--iso-8601') {
|
|
45
|
+
iso8601 = true
|
|
46
|
+
} else if (arg === '-R' || arg === '--rfc-2822') {
|
|
47
|
+
rfc2822 = true
|
|
48
|
+
} else if (arg === '-f' || arg === '--format') {
|
|
49
|
+
if (i + 1 < argv.length) {
|
|
50
|
+
format = argv[++i]
|
|
51
|
+
} else {
|
|
52
|
+
await writelnStdout(process, terminal, 'date: option requires an argument -- \'f\'')
|
|
53
|
+
return 1
|
|
54
|
+
}
|
|
55
|
+
} else if (arg.startsWith('--format=')) {
|
|
56
|
+
format = arg.slice(9)
|
|
57
|
+
} else if (arg.startsWith('--iso-8601=')) {
|
|
58
|
+
iso8601 = true
|
|
59
|
+
} else if (arg.startsWith('-f')) {
|
|
60
|
+
format = arg.slice(2)
|
|
61
|
+
} else if (arg.startsWith('+')) {
|
|
62
|
+
format = arg.slice(1)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (iso8601) {
|
|
67
|
+
output = now.toISOString()
|
|
68
|
+
} else if (rfc2822) {
|
|
69
|
+
output = now.toUTCString()
|
|
70
|
+
} else if (format) {
|
|
71
|
+
const year = now.getFullYear()
|
|
72
|
+
const month = String(now.getMonth() + 1).padStart(2, '0')
|
|
73
|
+
const day = String(now.getDate()).padStart(2, '0')
|
|
74
|
+
const hours = String(now.getHours()).padStart(2, '0')
|
|
75
|
+
const minutes = String(now.getMinutes()).padStart(2, '0')
|
|
76
|
+
const seconds = String(now.getSeconds()).padStart(2, '0')
|
|
77
|
+
const milliseconds = String(now.getMilliseconds()).padStart(3, '0')
|
|
78
|
+
|
|
79
|
+
output = format
|
|
80
|
+
.replace(/%Y/g, String(year))
|
|
81
|
+
.replace(/%m/g, month)
|
|
82
|
+
.replace(/%d/g, day)
|
|
83
|
+
.replace(/%H/g, hours)
|
|
84
|
+
.replace(/%M/g, minutes)
|
|
85
|
+
.replace(/%S/g, seconds)
|
|
86
|
+
.replace(/%s/g, String(Math.floor(now.getTime() / 1000)))
|
|
87
|
+
.replace(/%f/g, milliseconds)
|
|
88
|
+
.replace(/%z/g, now.getTimezoneOffset().toString())
|
|
89
|
+
} else {
|
|
90
|
+
output = now.toString()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
await writelnStdout(process, terminal, output)
|
|
94
|
+
return 0
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
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: diff [OPTION]... FILE1 FILE2
|
|
9
|
+
Compare files line by line.
|
|
10
|
+
|
|
11
|
+
-u, --unified=NUM output NUM (default 3) lines of unified context
|
|
12
|
+
-c, --context=NUM output NUM (default 3) lines of copied context
|
|
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: 'diff',
|
|
20
|
+
description: 'Compare files line by line',
|
|
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
|
+
const files: string[] = []
|
|
35
|
+
// TODO: Implement unified and context options in diff output
|
|
36
|
+
// @ts-ignore - reserved for future implementation
|
|
37
|
+
let _unified = 3
|
|
38
|
+
// @ts-ignore - reserved for future implementation
|
|
39
|
+
let _context = 3
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < argv.length; i++) {
|
|
42
|
+
const arg = argv[i]
|
|
43
|
+
if (!arg) continue
|
|
44
|
+
|
|
45
|
+
if (arg === '--help' || arg === '-h') {
|
|
46
|
+
printUsage(process, terminal)
|
|
47
|
+
return 0
|
|
48
|
+
} else if (arg === '-u' || arg === '--unified') {
|
|
49
|
+
if (i + 1 < argv.length) {
|
|
50
|
+
const nextArg = argv[++i]
|
|
51
|
+
if (nextArg !== undefined) {
|
|
52
|
+
const num = parseInt(nextArg, 10)
|
|
53
|
+
if (!isNaN(num)) _unified = num
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} else if (arg.startsWith('--unified=')) {
|
|
57
|
+
const num = parseInt(arg.slice(10), 10)
|
|
58
|
+
if (!isNaN(num)) _unified = num
|
|
59
|
+
} else if (arg === '-c' || arg === '--context') {
|
|
60
|
+
if (i + 1 < argv.length) {
|
|
61
|
+
const nextArg = argv[++i]
|
|
62
|
+
if (nextArg !== undefined) {
|
|
63
|
+
const num = parseInt(nextArg, 10)
|
|
64
|
+
if (!isNaN(num)) _context = num
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} else if (arg.startsWith('--context=')) {
|
|
68
|
+
const num = parseInt(arg.slice(10), 10)
|
|
69
|
+
if (!isNaN(num)) _context = num
|
|
70
|
+
} else if (!arg.startsWith('-')) {
|
|
71
|
+
files.push(arg)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (files.length !== 2) {
|
|
76
|
+
await writelnStderr(process, terminal, 'diff: exactly two files must be specified')
|
|
77
|
+
return 1
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const file1 = files[0]
|
|
81
|
+
const file2 = files[1]
|
|
82
|
+
if (!file1 || !file2) {
|
|
83
|
+
await writelnStderr(process, terminal, 'diff: exactly two files must be specified')
|
|
84
|
+
return 1
|
|
85
|
+
}
|
|
86
|
+
const fullPath1 = path.resolve(shell.cwd || '/', file1)
|
|
87
|
+
const fullPath2 = path.resolve(shell.cwd || '/', file2)
|
|
88
|
+
|
|
89
|
+
const writer = process.stdout.getWriter()
|
|
90
|
+
|
|
91
|
+
const readFile = async (filePath: string): Promise<string> => {
|
|
92
|
+
if (filePath.startsWith('/dev')) {
|
|
93
|
+
throw new Error('cannot diff device files')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let interrupted = false
|
|
97
|
+
const interruptHandler = () => { interrupted = true }
|
|
98
|
+
kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const handle = await shell.context.fs.promises.open(filePath, 'r')
|
|
102
|
+
const stat = await shell.context.fs.promises.stat(filePath)
|
|
103
|
+
|
|
104
|
+
const decoder = new TextDecoder()
|
|
105
|
+
let content = ''
|
|
106
|
+
let bytesRead = 0
|
|
107
|
+
const chunkSize = 1024
|
|
108
|
+
|
|
109
|
+
while (bytesRead < stat.size) {
|
|
110
|
+
if (interrupted) break
|
|
111
|
+
const data = new Uint8Array(chunkSize)
|
|
112
|
+
const readSize = Math.min(chunkSize, stat.size - bytesRead)
|
|
113
|
+
await handle.read(data, 0, readSize, bytesRead)
|
|
114
|
+
const chunk = data.subarray(0, readSize)
|
|
115
|
+
content += decoder.decode(chunk, { stream: true })
|
|
116
|
+
bytesRead += readSize
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return content
|
|
120
|
+
} finally {
|
|
121
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const content1 = await readFile(fullPath1)
|
|
127
|
+
const content2 = await readFile(fullPath2)
|
|
128
|
+
|
|
129
|
+
const lines1 = content1.split('\n')
|
|
130
|
+
const lines2 = content2.split('\n')
|
|
131
|
+
|
|
132
|
+
if (lines1[lines1.length - 1] === '') lines1.pop()
|
|
133
|
+
if (lines2[lines2.length - 1] === '') lines2.pop()
|
|
134
|
+
|
|
135
|
+
const lcs = (a: string[], b: string[]): number[][] => {
|
|
136
|
+
const m = a.length
|
|
137
|
+
const n = b.length
|
|
138
|
+
const dp: number[][] = []
|
|
139
|
+
for (let i = 0; i <= m; i++) {
|
|
140
|
+
dp[i] = Array(n + 1).fill(0)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
for (let i = 1; i <= m; i++) {
|
|
144
|
+
const row = dp[i]!
|
|
145
|
+
for (let j = 1; j <= n; j++) {
|
|
146
|
+
if (a[i - 1] === b[j - 1]) {
|
|
147
|
+
const prev = dp[i - 1]?.[j - 1] ?? 0
|
|
148
|
+
row[j] = prev + 1
|
|
149
|
+
} else {
|
|
150
|
+
const left = dp[i - 1]?.[j] ?? 0
|
|
151
|
+
const up = row[j - 1] ?? 0
|
|
152
|
+
row[j] = Math.max(left, up)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return dp
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const diff = (a: string[], b: string[]): string[] => {
|
|
161
|
+
const dp = lcs(a, b)
|
|
162
|
+
const result: string[] = []
|
|
163
|
+
let i = a.length
|
|
164
|
+
let j = b.length
|
|
165
|
+
|
|
166
|
+
while (i > 0 || j > 0) {
|
|
167
|
+
if (i > 0 && j > 0 && a[i - 1] === b[j - 1]) {
|
|
168
|
+
result.unshift(` ${a[i - 1]}`)
|
|
169
|
+
i--
|
|
170
|
+
j--
|
|
171
|
+
} else if (j > 0 && (i === 0 || (dp[i]?.[j - 1] ?? 0) >= (dp[i - 1]?.[j] ?? 0))) {
|
|
172
|
+
result.unshift(`+ ${b[j - 1]}`)
|
|
173
|
+
j--
|
|
174
|
+
} else if (i > 0) {
|
|
175
|
+
result.unshift(`- ${a[i - 1]}`)
|
|
176
|
+
i--
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return result
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const diffLines = diff(lines1, lines2)
|
|
184
|
+
|
|
185
|
+
if (diffLines.length === 0 || diffLines.every(line => line.startsWith(' '))) {
|
|
186
|
+
return 0
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
await writer.write(new TextEncoder().encode(`--- ${file1}\n+++ ${file2}\n`))
|
|
190
|
+
|
|
191
|
+
for (const line of diffLines) {
|
|
192
|
+
await writer.write(new TextEncoder().encode(line + '\n'))
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return 1
|
|
196
|
+
} catch (error) {
|
|
197
|
+
await writelnStderr(process, terminal, `diff: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
198
|
+
return 1
|
|
199
|
+
} finally {
|
|
200
|
+
writer.releaseLock()
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
3
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
4
|
+
import { writelnStderr } from '../shared/helpers.js'
|
|
5
|
+
|
|
6
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
7
|
+
const usage = `Usage: dirname [OPTION] NAME...
|
|
8
|
+
Output each NAME with its last non-slash component and trailing slashes removed.
|
|
9
|
+
|
|
10
|
+
--help display this help and exit`
|
|
11
|
+
writelnStderr(process, terminal, usage)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
15
|
+
return new TerminalCommand({
|
|
16
|
+
command: 'dirname',
|
|
17
|
+
description: 'Strip last component from file path',
|
|
18
|
+
kernel,
|
|
19
|
+
shell,
|
|
20
|
+
terminal,
|
|
21
|
+
run: async (pid: number, argv: string[]) => {
|
|
22
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
23
|
+
|
|
24
|
+
if (!process) return 1
|
|
25
|
+
|
|
26
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
27
|
+
printUsage(process, terminal)
|
|
28
|
+
return 0
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const paths: string[] = []
|
|
32
|
+
for (const arg of argv) {
|
|
33
|
+
if (arg !== '--help' && arg !== '-h' && !arg.startsWith('-')) {
|
|
34
|
+
paths.push(arg)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (paths.length === 0) {
|
|
39
|
+
await writelnStderr(process, terminal, 'dirname: missing operand')
|
|
40
|
+
return 1
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const writer = process.stdout.getWriter()
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
for (const filePath of paths) {
|
|
47
|
+
const dir = path.dirname(filePath)
|
|
48
|
+
await writer.write(new TextEncoder().encode(dir + '\n'))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return 0
|
|
52
|
+
} finally {
|
|
53
|
+
writer.releaseLock()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
}
|
package/src/commands/echo.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import type { CommandLineOptions } from 'command-line-args'
|
|
2
1
|
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
3
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: echo [OPTION]... [STRING]...
|
|
7
|
+
Echo the STRING(s) to standard output.
|
|
8
|
+
|
|
9
|
+
-n do not output the trailing newline
|
|
10
|
+
--help display this help and exit`
|
|
11
|
+
writelnStdout(process, terminal, usage)
|
|
12
|
+
}
|
|
4
13
|
|
|
5
14
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
6
15
|
return new TerminalCommand({
|
|
@@ -9,23 +18,57 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
9
18
|
kernel,
|
|
10
19
|
shell,
|
|
11
20
|
terminal,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
run: async (pid: number, argv: string[]) => {
|
|
22
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
23
|
+
|
|
24
|
+
if (argv.length === 0) {
|
|
25
|
+
await writelnStdout(process, terminal, '')
|
|
26
|
+
return 0
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let noNewline = false
|
|
30
|
+
const textParts: string[] = []
|
|
31
|
+
let i = 0
|
|
32
|
+
|
|
33
|
+
while (i < argv.length) {
|
|
34
|
+
const arg = argv[i]
|
|
35
|
+
if (!arg) {
|
|
36
|
+
i++
|
|
37
|
+
continue
|
|
38
|
+
}
|
|
39
|
+
if (arg === '--help' || arg === '-h') {
|
|
40
|
+
printUsage(process, terminal)
|
|
41
|
+
return 0
|
|
42
|
+
} else if (arg === '-n') {
|
|
43
|
+
noNewline = true
|
|
44
|
+
} else if (arg.startsWith('-') && arg.length > 1 && arg !== '--') {
|
|
45
|
+
const flags = arg.slice(1).split('')
|
|
46
|
+
if (flags.includes('n')) {
|
|
47
|
+
noNewline = true
|
|
48
|
+
}
|
|
49
|
+
const invalidFlag = flags.find(f => f !== 'n')
|
|
50
|
+
if (invalidFlag) {
|
|
51
|
+
await writelnStdout(process, terminal, `echo: invalid option -- '${invalidFlag}'`)
|
|
52
|
+
return 1
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
textParts.push(arg)
|
|
56
|
+
}
|
|
57
|
+
i++
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const text = textParts.join(' ')
|
|
61
|
+
const output = noNewline ? text : text + '\n'
|
|
19
62
|
|
|
20
63
|
if (process) {
|
|
21
64
|
const writer = process.stdout.getWriter()
|
|
22
65
|
try {
|
|
23
|
-
await writer.write(
|
|
66
|
+
await writer.write(new TextEncoder().encode(output))
|
|
24
67
|
} finally {
|
|
25
68
|
writer.releaseLock()
|
|
26
69
|
}
|
|
27
70
|
} else {
|
|
28
|
-
terminal.write(
|
|
71
|
+
terminal.write(output)
|
|
29
72
|
}
|
|
30
73
|
|
|
31
74
|
return 0
|
|
@@ -0,0 +1,31 @@
|
|
|
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: false
|
|
7
|
+
Return an unsuccessful exit status.
|
|
8
|
+
|
|
9
|
+
--help display this help and exit`
|
|
10
|
+
writelnStdout(process, terminal, usage)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
14
|
+
return new TerminalCommand({
|
|
15
|
+
command: 'false',
|
|
16
|
+
description: 'Return an unsuccessful exit status',
|
|
17
|
+
kernel,
|
|
18
|
+
shell,
|
|
19
|
+
terminal,
|
|
20
|
+
run: async (pid: number, argv: string[]) => {
|
|
21
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
22
|
+
|
|
23
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
24
|
+
printUsage(process, terminal)
|
|
25
|
+
return 0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return 1
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
}
|