@ecmaos/coreutils 0.5.1 → 0.5.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 +16 -0
- package/dist/commands/dd.d.ts +4 -0
- package/dist/commands/dd.d.ts.map +1 -0
- package/dist/commands/dd.js +525 -0
- package/dist/commands/dd.js.map +1 -0
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +25 -19
- package/dist/commands/ls.js.map +1 -1
- package/dist/commands/lsx.d.ts +4 -0
- package/dist/commands/lsx.d.ts.map +1 -0
- package/dist/commands/lsx.js +308 -0
- package/dist/commands/lsx.js.map +1 -0
- package/dist/commands/mkdir.d.ts.map +1 -1
- package/dist/commands/mkdir.js +155 -10
- package/dist/commands/mkdir.js.map +1 -1
- package/dist/commands/sed.d.ts.map +1 -1
- package/dist/commands/sed.js +63 -63
- package/dist/commands/sed.js.map +1 -1
- package/dist/commands/view.d.ts.map +1 -1
- package/dist/commands/view.js +0 -2
- package/dist/commands/view.js.map +1 -1
- package/dist/index.d.ts +1 -93
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -93
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/commands/dd.ts +547 -0
- package/src/commands/ls.ts +26 -20
- package/src/commands/mkdir.ts +150 -10
- package/src/commands/sed.ts +69 -70
- package/src/commands/view.ts +0 -3
- package/src/index.ts +3 -94
|
@@ -0,0 +1,547 @@
|
|
|
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: dd [OPERAND]...
|
|
8
|
+
Copy a file, converting and formatting according to the operands.
|
|
9
|
+
|
|
10
|
+
Operands:
|
|
11
|
+
|
|
12
|
+
if=FILE read from FILE instead of stdin
|
|
13
|
+
of=FILE write to FILE instead of stdout
|
|
14
|
+
bs=BYTES read and write up to BYTES bytes at a time
|
|
15
|
+
ibs=BYTES read up to BYTES bytes at a time (default: 512)
|
|
16
|
+
obs=BYTES write BYTES bytes at a time (default: 512)
|
|
17
|
+
count=N copy only N input blocks
|
|
18
|
+
skip=N skip N input blocks before copying
|
|
19
|
+
seek=N skip N output blocks before copying
|
|
20
|
+
conv=CONVS convert the file as per the comma separated symbol list:
|
|
21
|
+
ucase convert to uppercase
|
|
22
|
+
lcase convert to lowercase
|
|
23
|
+
swab swap every pair of input bytes
|
|
24
|
+
noerror continue after read errors
|
|
25
|
+
notrunc do not truncate the output file
|
|
26
|
+
sync pad every input block to ibs
|
|
27
|
+
|
|
28
|
+
status=LEVEL
|
|
29
|
+
The LEVEL of information to print to stderr:
|
|
30
|
+
'none' suppress all output
|
|
31
|
+
'noxfer' suppress final transfer statistics
|
|
32
|
+
'progress' show periodic transfer statistics
|
|
33
|
+
|
|
34
|
+
--help display this help and exit`
|
|
35
|
+
writelnStderr(process, terminal, usage)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseBytes(value: string): number {
|
|
39
|
+
const match = value.match(/^([0-9]+)([kmgKMG]?)$/)
|
|
40
|
+
if (!match?.[1]) return NaN
|
|
41
|
+
|
|
42
|
+
const num = parseInt(match[1], 10)
|
|
43
|
+
if (isNaN(num)) return NaN
|
|
44
|
+
|
|
45
|
+
const suffix = (match[2] || '').toLowerCase()
|
|
46
|
+
switch (suffix) {
|
|
47
|
+
case 'k':
|
|
48
|
+
return num * 1024
|
|
49
|
+
case 'm':
|
|
50
|
+
return num * 1024 * 1024
|
|
51
|
+
case 'g':
|
|
52
|
+
return num * 1024 * 1024 * 1024
|
|
53
|
+
default:
|
|
54
|
+
return num
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function parseBlocks(value: string): number {
|
|
59
|
+
const num = parseInt(value, 10)
|
|
60
|
+
return isNaN(num) ? NaN : num
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function applyConversions(data: Uint8Array, conversions: string[]): Uint8Array {
|
|
64
|
+
let result = new Uint8Array(data)
|
|
65
|
+
|
|
66
|
+
for (const conv of conversions) {
|
|
67
|
+
switch (conv) {
|
|
68
|
+
case 'ucase': {
|
|
69
|
+
const text = new TextDecoder().decode(result)
|
|
70
|
+
result = new TextEncoder().encode(text.toUpperCase())
|
|
71
|
+
break
|
|
72
|
+
}
|
|
73
|
+
case 'lcase': {
|
|
74
|
+
const text = new TextDecoder().decode(result)
|
|
75
|
+
result = new TextEncoder().encode(text.toLowerCase())
|
|
76
|
+
break
|
|
77
|
+
}
|
|
78
|
+
case 'swab': {
|
|
79
|
+
const swapped = new Uint8Array(result.length)
|
|
80
|
+
for (let i = 0; i < result.length - 1; i += 2) {
|
|
81
|
+
const a = result[i]
|
|
82
|
+
const b = result[i + 1]
|
|
83
|
+
if (a !== undefined && b !== undefined) {
|
|
84
|
+
swapped[i] = b
|
|
85
|
+
swapped[i + 1] = a
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (result.length % 2 === 1) {
|
|
89
|
+
const last = result[result.length - 1]
|
|
90
|
+
if (last !== undefined) {
|
|
91
|
+
swapped[result.length - 1] = last
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
result = swapped
|
|
95
|
+
break
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return result
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
104
|
+
return new TerminalCommand({
|
|
105
|
+
command: 'dd',
|
|
106
|
+
description: 'Copy and convert files with block-level operations',
|
|
107
|
+
kernel,
|
|
108
|
+
shell,
|
|
109
|
+
terminal,
|
|
110
|
+
run: async (pid: number, argv: string[]) => {
|
|
111
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
112
|
+
|
|
113
|
+
if (!process) return 1
|
|
114
|
+
|
|
115
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
116
|
+
printUsage(process, terminal)
|
|
117
|
+
return 0
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let inputFile: string | undefined
|
|
121
|
+
let outputFile: string | undefined
|
|
122
|
+
let blockSize: number | undefined
|
|
123
|
+
let inputBlockSize = 512
|
|
124
|
+
let outputBlockSize = 512
|
|
125
|
+
let count: number | undefined
|
|
126
|
+
let skip = 0
|
|
127
|
+
let seek = 0
|
|
128
|
+
const conversions: string[] = []
|
|
129
|
+
let status: 'none' | 'noxfer' | 'progress' = 'noxfer'
|
|
130
|
+
let noError = false
|
|
131
|
+
let noTrunc = false
|
|
132
|
+
let sync = false
|
|
133
|
+
|
|
134
|
+
for (const arg of argv) {
|
|
135
|
+
if (!arg) continue
|
|
136
|
+
|
|
137
|
+
if (arg === '--help' || arg === '-h') {
|
|
138
|
+
printUsage(process, terminal)
|
|
139
|
+
return 0
|
|
140
|
+
} else if (arg.startsWith('if=')) {
|
|
141
|
+
inputFile = arg.slice(3)
|
|
142
|
+
} else if (arg.startsWith('of=')) {
|
|
143
|
+
outputFile = arg.slice(3)
|
|
144
|
+
} else if (arg.startsWith('bs=')) {
|
|
145
|
+
const bytes = parseBytes(arg.slice(3))
|
|
146
|
+
if (isNaN(bytes)) {
|
|
147
|
+
await writelnStderr(process, terminal, `dd: invalid block size: ${arg.slice(3)}`)
|
|
148
|
+
return 1
|
|
149
|
+
}
|
|
150
|
+
blockSize = bytes
|
|
151
|
+
inputBlockSize = bytes
|
|
152
|
+
outputBlockSize = bytes
|
|
153
|
+
} else if (arg.startsWith('ibs=')) {
|
|
154
|
+
const bytes = parseBytes(arg.slice(4))
|
|
155
|
+
if (isNaN(bytes)) {
|
|
156
|
+
await writelnStderr(process, terminal, `dd: invalid input block size: ${arg.slice(4)}`)
|
|
157
|
+
return 1
|
|
158
|
+
}
|
|
159
|
+
inputBlockSize = bytes
|
|
160
|
+
} else if (arg.startsWith('obs=')) {
|
|
161
|
+
const bytes = parseBytes(arg.slice(4))
|
|
162
|
+
if (isNaN(bytes)) {
|
|
163
|
+
await writelnStderr(process, terminal, `dd: invalid output block size: ${arg.slice(4)}`)
|
|
164
|
+
return 1
|
|
165
|
+
}
|
|
166
|
+
outputBlockSize = bytes
|
|
167
|
+
} else if (arg.startsWith('count=')) {
|
|
168
|
+
const blocks = parseBlocks(arg.slice(6))
|
|
169
|
+
if (isNaN(blocks)) {
|
|
170
|
+
await writelnStderr(process, terminal, `dd: invalid count: ${arg.slice(6)}`)
|
|
171
|
+
return 1
|
|
172
|
+
}
|
|
173
|
+
count = blocks
|
|
174
|
+
} else if (arg.startsWith('skip=')) {
|
|
175
|
+
const blocks = parseBlocks(arg.slice(5))
|
|
176
|
+
if (isNaN(blocks)) {
|
|
177
|
+
await writelnStderr(process, terminal, `dd: invalid skip: ${arg.slice(5)}`)
|
|
178
|
+
return 1
|
|
179
|
+
}
|
|
180
|
+
skip = blocks
|
|
181
|
+
} else if (arg.startsWith('seek=')) {
|
|
182
|
+
const blocks = parseBlocks(arg.slice(5))
|
|
183
|
+
if (isNaN(blocks)) {
|
|
184
|
+
await writelnStderr(process, terminal, `dd: invalid seek: ${arg.slice(5)}`)
|
|
185
|
+
return 1
|
|
186
|
+
}
|
|
187
|
+
seek = blocks
|
|
188
|
+
} else if (arg.startsWith('conv=')) {
|
|
189
|
+
const convs = arg.slice(5).split(',').map(c => c.trim())
|
|
190
|
+
for (const conv of convs) {
|
|
191
|
+
if (['ucase', 'lcase', 'swab', 'noerror', 'notrunc', 'sync'].includes(conv)) {
|
|
192
|
+
if (conv === 'noerror') noError = true
|
|
193
|
+
else if (conv === 'notrunc') noTrunc = true
|
|
194
|
+
else if (conv === 'sync') sync = true
|
|
195
|
+
else conversions.push(conv)
|
|
196
|
+
} else {
|
|
197
|
+
await writelnStderr(process, terminal, `dd: invalid conversion: ${conv}`)
|
|
198
|
+
return 1
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} else if (arg.startsWith('status=')) {
|
|
202
|
+
const level = arg.slice(7)
|
|
203
|
+
if (['none', 'noxfer', 'progress'].includes(level)) {
|
|
204
|
+
status = level as 'none' | 'noxfer' | 'progress'
|
|
205
|
+
} else {
|
|
206
|
+
await writelnStderr(process, terminal, `dd: invalid status level: ${level}`)
|
|
207
|
+
return 1
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
await writelnStderr(process, terminal, `dd: invalid operand: ${arg}`)
|
|
211
|
+
await writelnStderr(process, terminal, "Try 'dd --help' for more information.")
|
|
212
|
+
return 1
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (blockSize !== undefined) {
|
|
217
|
+
inputBlockSize = blockSize
|
|
218
|
+
outputBlockSize = blockSize
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let inputFileHandle: Awaited<ReturnType<typeof shell.context.fs.promises.open>> | undefined
|
|
222
|
+
let inputReader: ReadableStreamDefaultReader<Uint8Array> | undefined
|
|
223
|
+
let outputFileHandle: Awaited<ReturnType<typeof shell.context.fs.promises.open>> | undefined
|
|
224
|
+
let outputWriter: WritableStreamDefaultWriter<Uint8Array> | { write: (chunk: Uint8Array) => Promise<void>, releaseLock: () => Promise<void> } | undefined
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
if (inputFile) {
|
|
228
|
+
const inputPath = path.resolve(shell.cwd, inputFile)
|
|
229
|
+
const isDevice = inputPath.startsWith('/dev')
|
|
230
|
+
inputFileHandle = await shell.context.fs.promises.open(inputPath, isDevice ? undefined : 'r')
|
|
231
|
+
} else {
|
|
232
|
+
if (!process.stdin) {
|
|
233
|
+
await writelnStderr(process, terminal, 'dd: stdin not available')
|
|
234
|
+
return 1
|
|
235
|
+
}
|
|
236
|
+
inputReader = process.stdin.getReader()
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (outputFile) {
|
|
240
|
+
const outputPath = path.resolve(shell.cwd, outputFile)
|
|
241
|
+
const flags = noTrunc ? 'r+' : 'w'
|
|
242
|
+
outputFileHandle = await shell.context.fs.promises.open(outputPath, flags).catch(async () => {
|
|
243
|
+
if (noTrunc) {
|
|
244
|
+
return await shell.context.fs.promises.open(outputPath, 'w')
|
|
245
|
+
}
|
|
246
|
+
throw new Error(`Cannot open ${outputFile}`)
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
let outputPosition = 0
|
|
250
|
+
if (seek > 0) {
|
|
251
|
+
outputPosition = seek * outputBlockSize
|
|
252
|
+
const zeros = new Uint8Array(outputPosition)
|
|
253
|
+
await outputFileHandle.write(zeros)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
outputWriter = {
|
|
257
|
+
write: async (chunk: Uint8Array) => {
|
|
258
|
+
await outputFileHandle!.write(chunk)
|
|
259
|
+
},
|
|
260
|
+
releaseLock: async () => {
|
|
261
|
+
await outputFileHandle!.close()
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
if (!process.stdout) {
|
|
266
|
+
await writelnStderr(process, terminal, 'dd: stdout not available')
|
|
267
|
+
return 1
|
|
268
|
+
}
|
|
269
|
+
outputWriter = process.stdout.getWriter()
|
|
270
|
+
|
|
271
|
+
if (seek > 0 && outputWriter) {
|
|
272
|
+
const seekBytes = seek * outputBlockSize
|
|
273
|
+
const zeros = new Uint8Array(seekBytes)
|
|
274
|
+
await outputWriter.write(zeros)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!outputWriter) {
|
|
279
|
+
await writelnStderr(process, terminal, 'dd: no output writer available')
|
|
280
|
+
return 1
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
let totalBytesRead = 0
|
|
284
|
+
let totalBytesWritten = 0
|
|
285
|
+
let blocksRead = 0
|
|
286
|
+
let blocksWritten = 0
|
|
287
|
+
|
|
288
|
+
if (inputFileHandle && inputFile) {
|
|
289
|
+
const inputPath = path.resolve(shell.cwd, inputFile)
|
|
290
|
+
const isDevice = inputPath.startsWith('/dev')
|
|
291
|
+
|
|
292
|
+
if (isDevice) {
|
|
293
|
+
const buffer = new Uint8Array(inputBlockSize)
|
|
294
|
+
let skipBytes = skip * inputBlockSize
|
|
295
|
+
let skipped = 0
|
|
296
|
+
|
|
297
|
+
while (true) {
|
|
298
|
+
if (count !== undefined && blocksRead >= count) {
|
|
299
|
+
break
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const result = await inputFileHandle.read(buffer)
|
|
303
|
+
const bytesRead = result.bytesRead
|
|
304
|
+
|
|
305
|
+
if (bytesRead === 0) {
|
|
306
|
+
if (sync && blocksRead > 0) {
|
|
307
|
+
const padded = new Uint8Array(inputBlockSize)
|
|
308
|
+
let data: Uint8Array = padded
|
|
309
|
+
if (conversions.length > 0) {
|
|
310
|
+
data = applyConversions(data, conversions)
|
|
311
|
+
}
|
|
312
|
+
await outputWriter.write(data)
|
|
313
|
+
totalBytesWritten += data.length
|
|
314
|
+
blocksWritten++
|
|
315
|
+
}
|
|
316
|
+
break
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
let data = buffer.subarray(0, bytesRead)
|
|
320
|
+
|
|
321
|
+
if (skipBytes > 0) {
|
|
322
|
+
const toSkip = Math.min(data.length, skipBytes - skipped)
|
|
323
|
+
skipped += toSkip
|
|
324
|
+
if (toSkip < data.length) {
|
|
325
|
+
data = data.subarray(toSkip)
|
|
326
|
+
} else {
|
|
327
|
+
continue
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
totalBytesRead += data.length
|
|
332
|
+
blocksRead++
|
|
333
|
+
|
|
334
|
+
if (data.length < inputBlockSize && sync) {
|
|
335
|
+
const padded = new Uint8Array(inputBlockSize)
|
|
336
|
+
padded.set(data)
|
|
337
|
+
data = padded
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (conversions.length > 0) {
|
|
341
|
+
const converted = applyConversions(data, conversions)
|
|
342
|
+
data = new Uint8Array(converted)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (data.length > outputBlockSize) {
|
|
346
|
+
let offset = 0
|
|
347
|
+
while (offset < data.length) {
|
|
348
|
+
const chunk = data.slice(offset, offset + outputBlockSize)
|
|
349
|
+
await outputWriter.write(chunk)
|
|
350
|
+
totalBytesWritten += chunk.length
|
|
351
|
+
blocksWritten++
|
|
352
|
+
offset += outputBlockSize
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
await outputWriter.write(data)
|
|
356
|
+
totalBytesWritten += data.length
|
|
357
|
+
blocksWritten++
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (status === 'progress' && blocksRead % 100 === 0) {
|
|
361
|
+
await writelnStderr(process, terminal, `dd: ${blocksRead} blocks read, ${blocksWritten} blocks written`)
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
} else {
|
|
365
|
+
const stat = await shell.context.fs.promises.stat(inputPath)
|
|
366
|
+
const fileSize = stat.size
|
|
367
|
+
let inputPosition = 0
|
|
368
|
+
|
|
369
|
+
if (skip > 0) {
|
|
370
|
+
inputPosition = skip * inputBlockSize
|
|
371
|
+
if (inputPosition > fileSize) {
|
|
372
|
+
inputPosition = fileSize
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const buffer = new Uint8Array(inputBlockSize)
|
|
377
|
+
|
|
378
|
+
while (true) {
|
|
379
|
+
if (count !== undefined && blocksRead >= count) {
|
|
380
|
+
break
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (inputPosition >= fileSize) {
|
|
384
|
+
if (sync && blocksRead > 0) {
|
|
385
|
+
const padded = new Uint8Array(inputBlockSize)
|
|
386
|
+
let data: Uint8Array = padded
|
|
387
|
+
if (conversions.length > 0) {
|
|
388
|
+
data = applyConversions(data, conversions)
|
|
389
|
+
}
|
|
390
|
+
await outputWriter.write(data)
|
|
391
|
+
totalBytesWritten += data.length
|
|
392
|
+
blocksWritten++
|
|
393
|
+
}
|
|
394
|
+
break
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const bytesToRead = Math.min(inputBlockSize, fileSize - inputPosition)
|
|
398
|
+
await inputFileHandle.read(buffer, 0, bytesToRead, inputPosition)
|
|
399
|
+
let data = new Uint8Array(buffer.buffer, buffer.byteOffset, bytesToRead)
|
|
400
|
+
|
|
401
|
+
totalBytesRead += data.length
|
|
402
|
+
blocksRead++
|
|
403
|
+
inputPosition += bytesToRead
|
|
404
|
+
|
|
405
|
+
if (data.length < inputBlockSize && sync) {
|
|
406
|
+
const padded = new Uint8Array(inputBlockSize)
|
|
407
|
+
padded.set(data)
|
|
408
|
+
data = padded
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (conversions.length > 0) {
|
|
412
|
+
const converted = applyConversions(data, conversions)
|
|
413
|
+
data = new Uint8Array(converted)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (data.length > outputBlockSize) {
|
|
417
|
+
let offset = 0
|
|
418
|
+
while (offset < data.length) {
|
|
419
|
+
const chunk = data.slice(offset, offset + outputBlockSize)
|
|
420
|
+
await outputWriter.write(chunk)
|
|
421
|
+
totalBytesWritten += chunk.length
|
|
422
|
+
blocksWritten++
|
|
423
|
+
offset += outputBlockSize
|
|
424
|
+
}
|
|
425
|
+
} else {
|
|
426
|
+
await outputWriter.write(data)
|
|
427
|
+
totalBytesWritten += data.length
|
|
428
|
+
blocksWritten++
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (status === 'progress' && blocksRead % 100 === 0) {
|
|
432
|
+
await writelnStderr(process, terminal, `dd: ${blocksRead} blocks read, ${blocksWritten} blocks written`)
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
} else if (inputReader) {
|
|
437
|
+
if (skip > 0) {
|
|
438
|
+
const skipBytes = skip * inputBlockSize
|
|
439
|
+
let skipped = 0
|
|
440
|
+
while (skipped < skipBytes) {
|
|
441
|
+
const { done, value } = await inputReader.read()
|
|
442
|
+
if (done) break
|
|
443
|
+
if (value) {
|
|
444
|
+
skipped += value.length
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
let partialBlock: Uint8Array | undefined
|
|
450
|
+
|
|
451
|
+
while (true) {
|
|
452
|
+
if (count !== undefined && blocksRead >= count) {
|
|
453
|
+
break
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
let data: Uint8Array | undefined
|
|
457
|
+
|
|
458
|
+
if (partialBlock) {
|
|
459
|
+
data = partialBlock
|
|
460
|
+
partialBlock = undefined
|
|
461
|
+
} else {
|
|
462
|
+
const result = await inputReader.read()
|
|
463
|
+
if (result.done) {
|
|
464
|
+
if (sync && blocksRead > 0) {
|
|
465
|
+
const padded = new Uint8Array(inputBlockSize)
|
|
466
|
+
data = padded
|
|
467
|
+
} else {
|
|
468
|
+
break
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
data = result.value
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (!data || data.length === 0) {
|
|
476
|
+
if (sync && blocksRead > 0) {
|
|
477
|
+
data = new Uint8Array(inputBlockSize)
|
|
478
|
+
} else {
|
|
479
|
+
break
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
totalBytesRead += data.length
|
|
484
|
+
blocksRead++
|
|
485
|
+
|
|
486
|
+
if (data.length < inputBlockSize && sync) {
|
|
487
|
+
const padded = new Uint8Array(inputBlockSize)
|
|
488
|
+
padded.set(data)
|
|
489
|
+
data = padded
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (conversions.length > 0) {
|
|
493
|
+
data = applyConversions(data, conversions)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (!data) {
|
|
497
|
+
break
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (data.length > outputBlockSize) {
|
|
501
|
+
let offset = 0
|
|
502
|
+
while (offset < data.length) {
|
|
503
|
+
const chunk = data.slice(offset, offset + outputBlockSize)
|
|
504
|
+
await outputWriter.write(chunk)
|
|
505
|
+
totalBytesWritten += chunk.length
|
|
506
|
+
blocksWritten++
|
|
507
|
+
offset += outputBlockSize
|
|
508
|
+
}
|
|
509
|
+
} else {
|
|
510
|
+
await outputWriter.write(data)
|
|
511
|
+
totalBytesWritten += data.length
|
|
512
|
+
blocksWritten++
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (status === 'progress' && blocksRead % 100 === 0) {
|
|
516
|
+
await writelnStderr(process, terminal, `dd: ${blocksRead} blocks read, ${blocksWritten} blocks written`)
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (status !== 'none') {
|
|
522
|
+
await writelnStderr(process, terminal, `${blocksRead}+${Math.floor((totalBytesRead % inputBlockSize) / (inputBlockSize || 1))} records in`)
|
|
523
|
+
await writelnStderr(process, terminal, `${blocksWritten}+${Math.floor((totalBytesWritten % outputBlockSize) / (outputBlockSize || 1))} records out`)
|
|
524
|
+
await writelnStderr(process, terminal, `${totalBytesWritten} bytes copied`)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return 0
|
|
528
|
+
} catch (error) {
|
|
529
|
+
if (!noError) {
|
|
530
|
+
await writelnStderr(process, terminal, `dd: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
531
|
+
return 1
|
|
532
|
+
}
|
|
533
|
+
return 0
|
|
534
|
+
} finally {
|
|
535
|
+
if (inputFileHandle) {
|
|
536
|
+
await inputFileHandle.close()
|
|
537
|
+
}
|
|
538
|
+
if (inputReader) {
|
|
539
|
+
inputReader.releaseLock()
|
|
540
|
+
}
|
|
541
|
+
if (outputWriter && 'releaseLock' in outputWriter) {
|
|
542
|
+
await outputWriter.releaseLock()
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
})
|
|
547
|
+
}
|
package/src/commands/ls.ts
CHANGED
|
@@ -5,7 +5,6 @@ import humanFormat from 'human-format'
|
|
|
5
5
|
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
6
6
|
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
7
7
|
import { writelnStdout, writelnStderr } from '../shared/helpers.js'
|
|
8
|
-
import { CoreutilsDescriptions } from '../index.js'
|
|
9
8
|
|
|
10
9
|
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
11
10
|
const usage = `Usage: ls [OPTION]... [FILE]...
|
|
@@ -64,8 +63,6 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
64
63
|
|
|
65
64
|
if (targets.length === 0) targets.push(shell.cwd)
|
|
66
65
|
|
|
67
|
-
const descriptions = kernel.filesystem.descriptions(kernel.i18n.t)
|
|
68
|
-
|
|
69
66
|
// Process each target and collect all entries
|
|
70
67
|
// We'll determine if each entry is a directory when we stat it later
|
|
71
68
|
const allEntries: Array<{ fullPath: string, entry: string }> = []
|
|
@@ -138,6 +135,8 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
138
135
|
return ''
|
|
139
136
|
}
|
|
140
137
|
|
|
138
|
+
const descriptions = kernel.filesystem.descriptions(kernel.i18n.ns.filesystem)
|
|
139
|
+
|
|
141
140
|
const filesMap = await Promise.all(allEntries
|
|
142
141
|
.map(async ({ fullPath: entryFullPath, entry }) => {
|
|
143
142
|
const target = path.resolve(entryFullPath, entry)
|
|
@@ -209,7 +208,9 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
209
208
|
|
|
210
209
|
// Check if any entry is in /dev directory
|
|
211
210
|
const isDevDirectory = allEntries.some(e => e.fullPath.startsWith('/dev'))
|
|
212
|
-
const columns = isDevDirectory
|
|
211
|
+
const columns = isDevDirectory
|
|
212
|
+
? [kernel.i18n.ns.common('Name'), kernel.i18n.ns.common('Mode'), kernel.i18n.ns.common('Owner'), kernel.i18n.ns.common('Info')]
|
|
213
|
+
: [kernel.i18n.ns.common('Name'), kernel.i18n.ns.common('Size'), kernel.i18n.ns.common('Modified'), kernel.i18n.ns.common('Mode'), kernel.i18n.ns.common('Owner'), kernel.i18n.ns.common('Info')]
|
|
213
214
|
|
|
214
215
|
const directoryRows = directories.sort((a, b) => a.name.localeCompare(b.name)).map(directory => {
|
|
215
216
|
const displayName = directory.linkTarget
|
|
@@ -229,15 +230,15 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
229
230
|
: chalk.green(displayName)
|
|
230
231
|
|
|
231
232
|
const row: Record<string, string> = {
|
|
232
|
-
Name: coloredName,
|
|
233
|
-
Mode: chalk.gray(modeString),
|
|
234
|
-
Owner: directory.stats ? chalk.gray(getOwnerString(directory.stats)) : '',
|
|
235
|
-
Info: truncateInfo(chalk.gray(linkInfo))
|
|
233
|
+
[kernel.i18n.ns.common('Name')]: coloredName,
|
|
234
|
+
[kernel.i18n.ns.common('Mode')]: chalk.gray(modeString),
|
|
235
|
+
[kernel.i18n.ns.common('Owner')]: directory.stats ? chalk.gray(getOwnerString(directory.stats)) : '',
|
|
236
|
+
[kernel.i18n.ns.common('Info')]: truncateInfo(chalk.gray(linkInfo))
|
|
236
237
|
}
|
|
237
238
|
|
|
238
239
|
if (!isDevDirectory) {
|
|
239
|
-
row.Size = ''
|
|
240
|
-
row.Modified = directory.stats ? chalk.gray(getTimestampString(directory.stats.mtime)) : ''
|
|
240
|
+
row[kernel.i18n.ns.common('Size')] = ''
|
|
241
|
+
row[kernel.i18n.ns.common('Modified')] = directory.stats ? chalk.gray(getTimestampString(directory.stats.mtime)) : ''
|
|
241
242
|
}
|
|
242
243
|
|
|
243
244
|
return row
|
|
@@ -263,11 +264,13 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
263
264
|
const linkInfo = getLinkInfo(file.linkTarget, file.linkStats, file.stats)
|
|
264
265
|
if (linkInfo) return linkInfo
|
|
265
266
|
|
|
266
|
-
// Check if this is a command in /bin/ and use
|
|
267
|
+
// Check if this is a command in /bin/ and use coreutils translations
|
|
267
268
|
if (file.target.startsWith('/bin/')) {
|
|
268
269
|
const commandName = path.basename(file.target)
|
|
269
|
-
|
|
270
|
-
|
|
270
|
+
const translatedDescription = kernel.i18n.ns.coreutils(commandName)
|
|
271
|
+
// Only use translation if it exists (i18next returns the key if translation is missing)
|
|
272
|
+
if (translatedDescription !== commandName) {
|
|
273
|
+
return translatedDescription
|
|
271
274
|
}
|
|
272
275
|
}
|
|
273
276
|
|
|
@@ -278,22 +281,25 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
278
281
|
}
|
|
279
282
|
if (!file.stats) return ''
|
|
280
283
|
if (file.stats.isBlockDevice() || file.stats.isCharacterDevice()) {
|
|
281
|
-
|
|
284
|
+
const devicePackage = kernel.devices.get(path.basename(file.target))
|
|
285
|
+
const description = devicePackage?.device?.pkg?.description || ''
|
|
286
|
+
const hasCLI = devicePackage?.device?.cli !== undefined
|
|
287
|
+
return `${description}${hasCLI ? ' ' + `${chalk.bold(`(${kernel.i18n.t('CLI')})`)}${chalk.reset()}` : ''}`
|
|
282
288
|
}
|
|
283
289
|
|
|
284
290
|
return ''
|
|
285
291
|
})()
|
|
286
292
|
|
|
287
293
|
const row: Record<string, string> = {
|
|
288
|
-
Name: coloredName,
|
|
289
|
-
Mode: chalk.gray(modeString),
|
|
290
|
-
Owner: file.stats ? chalk.gray(getOwnerString(file.stats)) : '',
|
|
291
|
-
Info: truncateInfo(chalk.gray(info))
|
|
294
|
+
[kernel.i18n.ns.common('Name')]: coloredName,
|
|
295
|
+
[kernel.i18n.ns.common('Mode')]: chalk.gray(modeString),
|
|
296
|
+
[kernel.i18n.ns.common('Owner')]: file.stats ? chalk.gray(getOwnerString(file.stats)) : '',
|
|
297
|
+
[kernel.i18n.ns.common('Info')]: truncateInfo(chalk.gray(info))
|
|
292
298
|
}
|
|
293
299
|
|
|
294
300
|
if (!isDevDirectory) {
|
|
295
|
-
row.Size = file.stats ? chalk.gray(humanFormat(file.stats.size)) : ''
|
|
296
|
-
row.Modified = file.stats ? chalk.gray(getTimestampString(file.stats.mtime)) : ''
|
|
301
|
+
row[kernel.i18n.ns.common('Size')] = file.stats ? chalk.gray(humanFormat(file.stats.size)) : ''
|
|
302
|
+
row[kernel.i18n.ns.common('Modified')] = file.stats ? chalk.gray(getTimestampString(file.stats.mtime)) : ''
|
|
297
303
|
}
|
|
298
304
|
|
|
299
305
|
return row
|