This commit is contained in:
Raven Scott
2026-04-04 18:41:34 -04:00
parent 7cf1a8d1c5
commit d75f4e7383
29 changed files with 1219 additions and 469 deletions
+43 -1
View File
@@ -2,6 +2,48 @@
* rm — remove files or directories.
* Flags: -r -R --recursive, -f --force, -d --dir, -- ; bundled e.g. -rf
*/
/**
* @param {Record<string, unknown>} vfs
* @param {string} p logical path
* @param {boolean} force
*/
async function rmTreeManual(vfs, p, force) {
/** @type {{ type?: string } | null} */
let st = null
try {
st = await vfs.lstat(p)
} catch (e) {
if (!force) throw e
return
}
if (!st) {
if (!force) throw new Error('No such file')
return
}
if (st.type === 'directory') {
/** @type {string[]} */
const names = []
try {
for await (const n of vfs.readdir(p)) names.push(n)
} catch (e) {
if (!force) throw e
return
}
for (const n of names) {
const child =
p.replace(/\/+$/, '') === ''
? '/' + n
: p.replace(/\/$/, '') + '/' + n
await rmTreeManual(vfs, child, force)
}
if (typeof vfs.rmdir === 'function') await vfs.rmdir(p)
else await vfs.unlink(p)
return
}
await vfs.unlink(p)
}
async function run(ctx, argv) {
const vfs = ctx.vfs
let recursive = false
@@ -56,7 +98,7 @@ async function run(ctx, argv) {
vfs && typeof vfs.rm === 'function'
? (p) => vfs.rm(p, { recursive, force })
: async (p) => {
if (recursive) throw new Error('recursive rm not supported')
if (recursive) return rmTreeManual(vfs, p, force)
return vfs.unlink(p)
}
for (const f of files) {
+46 -16
View File
@@ -1,27 +1,51 @@
/**
* @param {string} raw
* @param {number} curLen
* @returns {number | null}
*/
function resolveTruncateSize(raw, curLen) {
const s = String(raw).trim()
if (!s) return null
if (/%$/.test(s)) {
const inner = s.startsWith('+') || s.startsWith('-') ? s.slice(1) : s
const pct = Number.parseFloat(inner.replace(/%$/, ''))
if (!Number.isFinite(pct)) return null
const delta = Math.floor((curLen * pct) / 100)
if (s.startsWith('+')) return Math.max(0, curLen + delta)
if (s.startsWith('-')) return Math.max(0, curLen - delta)
return Math.max(0, Math.floor((curLen * pct) / 100))
}
if (s.startsWith('+')) {
const n = Number.parseInt(s.slice(1), 10)
if (!Number.isFinite(n)) return null
return Math.max(0, curLen + n)
}
if (s.startsWith('-')) {
const n = Number.parseInt(s.slice(1), 10)
if (!Number.isFinite(n)) return null
return Math.max(0, curLen - n)
}
const abs = Number.parseInt(s, 10)
if (!Number.isFinite(abs) || abs < 0) return null
return abs
}
async function run(ctx, argv) {
let size = null
/** @type {string | null} */
let sizeSpec = null
const paths = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-h' || a === '--help') {
ctx.console.log(
'usage: truncate -s SIZE FILE\nSet file length to SIZE bytes (padded with zeros if growing).'
'usage: truncate -s SIZE FILE\n' +
'SIZE: bytes, +N, -N, +N%, -N%, or N% (of current length).\n' +
'Sets file length (padded with zeros if growing).'
)
return
}
if ((a === '-s' || a === '--size') && argv[i + 1]) {
const raw = argv[++i]
if (raw.startsWith('+') || raw.startsWith('-')) {
ctx.console.error('truncate: relative sizes not supported')
ctx.exitCode = 1
return
}
size = parseInt(raw, 10)
if (!Number.isFinite(size) || size < 0) {
ctx.console.error('truncate: invalid size')
ctx.exitCode = 1
return
}
sizeSpec = argv[++i]
continue
}
if (a.startsWith('-')) {
@@ -31,7 +55,7 @@ async function run(ctx, argv) {
}
paths.push(a)
}
if (size == null || paths.length !== 1) {
if (!sizeSpec || paths.length !== 1) {
ctx.console.error('usage: truncate -s SIZE FILE')
ctx.exitCode = 1
return
@@ -42,7 +66,13 @@ async function run(ctx, argv) {
const b = await ctx.vfs.readFile(file)
if (b) cur = b instanceof Uint8Array ? b : new Uint8Array(b)
} catch {
/* new file */
/* new file — relative sizes use curLen 0 */
}
const size = resolveTruncateSize(sizeSpec, cur.length)
if (size == null) {
ctx.console.error('truncate: invalid size: ' + sizeSpec)
ctx.exitCode = 1
return
}
const out = new Uint8Array(size)
out.set(cur.subarray(0, Math.min(cur.length, size)))