core utils updates

This commit is contained in:
Raven Scott
2026-04-03 22:34:08 -04:00
parent 32d3eaa833
commit 35ac6633ea
206 changed files with 12728 additions and 1080 deletions
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* POSIX-oriented awk interpreter for Bare OS (no import; concatenated before src/awk.js).
* Supports: BEGIN/END, /re/, line patterns, {}, print/printf, if/else, while, for(;;), for(x in a),
+85 -11
View File
@@ -60,20 +60,94 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
const parts = argv.slice(1).filter((a) => a !== '--')
if (!parts.length) {
let all = false
/** @type {string | null} */
let suffix = null
let i = 1
while (i < argv.length) {
const a = argv[i]
if (a === '--') {
i++
break
}
if (a === '-a' || a === '--multiple') {
all = true
i++
continue
}
if (a === '-s' || a === '--suffix') {
const next = argv[i + 1]
if (next == null) {
ctx.console.error('basename: option requires an argument -- s')
ctx.exitCode = 1
return
}
suffix = String(next)
i += 2
continue
}
if (a.startsWith('-')) {
ctx.console.error('basename: unsupported option: ' + a)
ctx.exitCode = 1
return
}
break
}
const paths = argv.slice(i).filter((x) => x !== '--')
if (!paths.length) {
ctx.console.error('basename: missing operand')
ctx.exitCode = 1
return
}
const path = parts[0]
const suffix = parts[1] || ''
let base = path.replace(/\/+$/, '')
const slash = base.lastIndexOf('/')
base = slash === -1 ? base : base.slice(slash + 1)
if (!base) base = path
if (suffix && base.endsWith(suffix) && base.length > suffix.length) {
base = base.slice(0, -suffix.length)
if (!all && paths.length > 2) {
ctx.console.error('basename: extra operand (use -a for multiple paths)')
ctx.exitCode = 1
return
}
function take(path, suf) {
let base = path.replace(/\/+$/, '')
const slash = base.lastIndexOf('/')
base = slash === -1 ? base : base.slice(slash + 1)
if (!base) base = path
const s = suf != null ? suf : ''
if (s && base.endsWith(s) && base.length > s.length) base = base.slice(0, -s.length)
ctx.console.log(base)
}
if (!all && paths.length === 2 && suffix == null) {
take(paths[0], paths[1])
return
}
for (const p of paths) {
take(p, suffix)
}
ctx.console.log(base)
}
+182 -3
View File
@@ -60,20 +60,199 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
function bareCatOut(ctx, s) {
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, s)
} else {
ctx.console.log(s)
}
}
/** @param {string} line @param {{ showTabs: boolean, showEnds: boolean, showNonprinting: boolean }} o */
function catTransformLine(line, o) {
let out = ''
for (let k = 0; k < line.length; k++) {
const ch = line[k]
const code = line.charCodeAt(k)
if (o.showTabs && ch === '\t') {
out += '^I'
continue
}
if (o.showNonprinting && code < 32 && ch !== '\t') {
out += '^' + String.fromCharCode(code + 64)
continue
}
if (o.showNonprinting && code === 127) {
out += '^?'
continue
}
if (o.showNonprinting && code > 127) {
out += 'M-' + String.fromCharCode(code - 128)
continue
}
out += ch
}
if (o.showEnds) out += '$'
return out
}
/**
* @param {string} text
* @param {{ numberLines: boolean, numberNonblank: boolean, showTabs: boolean, showEnds: boolean, showNonprinting: boolean }} opt
*/
function catFormat(text, opt) {
const t = {
showTabs: opt.showTabs,
showEnds: opt.showEnds,
showNonprinting: opt.showNonprinting
}
const useNum = opt.numberLines || opt.numberNonblank
let n = 1
let out = ''
let i = 0
while (i < text.length) {
const j = text.indexOf('\n', i)
const end = j === -1 ? text.length : j
const rawLine = text.slice(i, end)
const blank = rawLine.length === 0
const lineOut = catTransformLine(rawLine, t)
if (useNum) {
const numThis =
opt.numberNonblank && blank ? false : opt.numberLines || opt.numberNonblank
if (numThis) {
out += String(n).padStart(6, ' ') + '\t' + lineOut
n++
} else {
out += lineOut
}
} else {
out += lineOut
}
if (j === -1) break
out += '\n'
i = j + 1
}
return out
}
/** @returns {{ error?: string, opt: ReturnType<typeof defaultCatOpt>, files: string[] }} */
function parseCatArgv(argv) {
const opt = {
numberLines: false,
numberNonblank: false,
showTabs: false,
showEnds: false,
showNonprinting: false
}
const files = []
let i = 1
while (i < argv.length) {
const a = argv[i]
if (a === '--') {
files.push(...argv.slice(i + 1))
return { opt, files }
}
if (!a.startsWith('-')) {
files.push(a)
i++
continue
}
const body = a.slice(1)
if (body === '') {
files.push('-')
i++
continue
}
for (let k = 0; k < body.length; k++) {
const c = body[k]
if (c === 'n') opt.numberLines = true
else if (c === 'b') opt.numberNonblank = true
else if (c === 'A') {
opt.showTabs = true
opt.showEnds = true
opt.showNonprinting = true
} else if (c === 'e') {
opt.showEnds = true
opt.showNonprinting = true
} else if (c === 't') {
opt.showTabs = true
opt.showNonprinting = true
} else if (c === 'E') opt.showEnds = true
else if (c === 'T') opt.showTabs = true
else if (c === 'v') opt.showNonprinting = true
else return { error: 'cat: invalid option -- ' + c, opt, files: [] }
}
i++
}
if (opt.numberNonblank) opt.numberLines = false
return { opt, files }
}
async function run(ctx, argv) {
const parsed = parseCatArgv(argv)
if (parsed.error) {
ctx.console.error(parsed.error)
ctx.exitCode = 1
return
}
const { opt, files } = parsed
const vfs = ctx.vfs
const files = argv.slice(1)
const anyOpt =
opt.numberLines ||
opt.numberNonblank ||
opt.showTabs ||
opt.showEnds ||
opt.showNonprinting
async function emitOne(text) {
const s = anyOpt ? catFormat(text, opt) : text
bareCatOut(ctx, s)
}
if (!files.length) {
const s = bareStdin(ctx)
ctx.console.log(s)
await emitOne(s)
return
}
for (const f of files) {
if (f === '-') {
await emitOne(bareStdin(ctx))
continue
}
const buf = await vfs.readFile(f)
if (!buf) {
ctx.console.error('cat: ' + f + ': No such file or directory')
ctx.exitCode = 1
continue
}
ctx.console.log(ctx.b4a.toString(buf))
await emitOne(ctx.b4a.toString(buf))
}
}
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* chgrp — change file group (Hyperdrive metadata on writable paths).
*/
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
function bareChmodSymbolic(curPerm, spec) {
let m = curPerm & 0o777
const parts = spec.split(',')
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* chown — change file owner and group (Hyperdrive metadata on writable paths).
*/
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/** POSIX cksum CRC (Open Group / npm `cksum` reference). */
const BARE_CKSUM_TAB = new Uint32Array([
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
const seq = '\x1b[H\x1b[2J\x1b[3J'
if (typeof ctx.writeScreen === 'function') {
+73 -7
View File
@@ -60,16 +60,72 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function copyPath(ctx, from, to, recursive) {
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function copyPath(ctx, from, to, recursive, followSymlink) {
const st = await ctx.vfs.lstat(from)
if (!st) {
ctx.console.error('cp: ' + from + ': No such file')
return false
}
if (st.type === 'symlink') {
const t = await ctx.vfs.readlink(from)
await ctx.vfs.symlink(t, to)
return true
if (!followSymlink) {
const t = await ctx.vfs.readlink(from)
await ctx.vfs.symlink(t, to)
return true
}
const fst = await ctx.vfs.stat(from)
if (fst.type === 'file') {
const buf = await ctx.vfs.readFile(from)
if (!buf) {
ctx.console.error('cp: cannot read ' + from)
return false
}
await ctx.vfs.writeFile(to, buf)
return true
}
if (fst.type === 'directory') {
if (!recursive) {
ctx.console.error('cp: ' + from + ': Is a directory')
return false
}
await ctx.vfs.mkdir(to, { recursive: true })
const names = await ctx.vfs.readdir(from)
for (const n of names) {
if (n === '.bareos_empty') continue
const f = from.replace(/\/+$/, '') + '/' + n
const t = to.replace(/\/+$/, '') + '/' + n
if (!(await copyPath(ctx, f, t, true, followSymlink))) return false
}
return true
}
ctx.console.error('cp: cannot copy special file ' + from)
return false
}
if (st.type === 'file') {
const buf = await ctx.vfs.readFile(from)
@@ -91,7 +147,7 @@ async function copyPath(ctx, from, to, recursive) {
if (n === '.bareos_empty') continue
const f = from.replace(/\/+$/, '') + '/' + n
const t = to.replace(/\/+$/, '') + '/' + n
if (!(await copyPath(ctx, f, t, true))) return false
if (!(await copyPath(ctx, f, t, true, followSymlink))) return false
}
return true
}
@@ -100,6 +156,7 @@ async function copyPath(ctx, from, to, recursive) {
async function run(ctx, argv) {
let recursive = false
let followSymlink = false
const paths = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
@@ -107,6 +164,14 @@ async function run(ctx, argv) {
recursive = true
continue
}
if (a === '-L' || a === '--dereference') {
followSymlink = true
continue
}
if (a === '-P' || a === '--no-dereference') {
followSymlink = false
continue
}
if (a === '--') {
paths.push(...argv.slice(i + 1))
break
@@ -119,7 +184,7 @@ async function run(ctx, argv) {
paths.push(a)
}
if (paths.length < 2) {
ctx.console.error('usage: cp [-R] SOURCE... DEST')
ctx.console.error('usage: cp [-R] [-L|-P] SOURCE... DEST')
ctx.exitCode = 1
return
}
@@ -143,6 +208,7 @@ async function run(ctx, argv) {
destIsDir || sources.length > 1
? dest.replace(/\/+$/, '') + '/' + base
: dest
if (!(await copyPath(ctx, src, target, recursive))) ctx.exitCode = 1
if (!(await copyPath(ctx, src, target, recursive, followSymlink)))
ctx.exitCode = 1
}
}
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
const vfs = ctx.vfs
const home = vfs.env?.HOME || '/home/guest'
+45 -4
View File
@@ -60,9 +60,37 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
let delim = '\t'
let fieldsSpec = ''
let suppressNoDelim = false
const files = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
@@ -74,6 +102,10 @@ async function run(ctx, argv) {
fieldsSpec = argv[++i] || ''
continue
}
if (a === '-s' || a === '--only-delimited') {
suppressNoDelim = true
continue
}
if (a === '--') {
files.push(...argv.slice(i + 1))
break
@@ -86,7 +118,7 @@ async function run(ctx, argv) {
files.push(a)
}
if (!fieldsSpec) {
ctx.console.error('usage: cut -f LIST [-d DELIM] [FILE...]')
ctx.console.error('usage: cut -f LIST [-d DELIM] [-s] [FILE...]')
ctx.exitCode = 1
return
}
@@ -110,17 +142,26 @@ async function run(ctx, argv) {
return
}
const delimChar = delim === '\\t' ? '\t' : delim
async function cutLines(text) {
const lines = text.split(/\r?\n/)
for (const line of lines) {
if (line === '' && lines.length === 1) continue
const cols =
delim === '' ? [...line] : line.split(delim === '\\t' ? '\t' : delim)
if (
suppressNoDelim &&
delim !== '' &&
delimChar !== '' &&
!line.includes(delimChar)
) {
continue
}
const cols = delim === '' ? [...line] : line.split(delimChar)
const out = []
for (const n of [...want].sort((a, b) => a - b)) {
out.push(cols[n - 1] != null ? cols[n - 1] : '')
}
ctx.console.log(out.join(delim === '\\t' ? '\t' : delim))
ctx.console.log(out.join(delimChar))
}
}
+134
View File
@@ -60,9 +60,143 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* date — print current date/time; optional +FORMAT (strftime-like subset).
*/
/**
* @param {string} fmt
* @param {Date} d
* @param {boolean} utc
*/
function bareDateFormat(fmt, d, utc) {
const pad2 = (n) => String(n).padStart(2, '0')
const pad3 = (n) => String(n).padStart(3, '0')
const Y = utc ? d.getUTCFullYear() : d.getFullYear()
const m = utc ? d.getUTCMonth() + 1 : d.getMonth() + 1
const day = utc ? d.getUTCDate() : d.getDate()
const H = utc ? d.getUTCHours() : d.getHours()
const M = utc ? d.getUTCMinutes() : d.getMinutes()
const S = utc ? d.getUTCSeconds() : d.getSeconds()
const ms = utc ? d.getUTCMilliseconds() : d.getMilliseconds()
let o = ''
for (let i = 0; i < fmt.length; i++) {
if (fmt[i] !== '%') {
o += fmt[i]
continue
}
if (fmt[i + 1] === '%') {
o += '%'
i++
continue
}
const spec = fmt[i + 1] || ''
i++
switch (spec) {
case 'Y':
o += String(Y).padStart(4, '0')
break
case 'm':
o += pad2(m)
break
case 'd':
o += pad2(day)
break
case 'H':
o += pad2(H)
break
case 'M':
o += pad2(M)
break
case 'S':
o += pad2(S)
break
case 's':
o += String(Math.floor(d.getTime() / 1000))
break
case '3N':
o += pad3(ms)
break
case 'z': {
if (utc) {
o += '+0000'
break
}
const offMin = -d.getTimezoneOffset()
const sign = offMin >= 0 ? '+' : '-'
const abs = Math.abs(offMin)
const oh = Math.floor(abs / 60)
const om = abs % 60
o += sign + pad2(oh) + pad2(om)
break
}
case 'a': {
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
o += days[utc ? d.getUTCDay() : d.getDay()]
break
}
case 'b': {
const mons = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
o += mons[(utc ? d.getUTCMonth() : d.getMonth()) || 0]
break
}
default:
o += '%' + spec
}
}
return o
}
async function run(ctx, argv) {
const utc = argv.includes('-u') || argv.includes('--utc')
const rest = argv.filter((a) => a !== '-u' && a !== '--utc')
const plus = rest.find((a) => a.startsWith('+') && a.length > 1)
const d = new Date()
if (plus) {
ctx.console.log(bareDateFormat(plus.slice(1), d, utc))
return
}
ctx.console.log(
utc
? d
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* GNU-style LS_COLORS string + dircolors(5) database parsing and file classification.
* Consumed by bare-os-booter (ESM import). Prepended into /bin/ls and /bin/dircolors by bare-os-coreutils build (no import there).
+71 -10
View File
@@ -60,17 +60,78 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function run(ctx, argv) {
const parts = argv.slice(1).filter((a) => a !== '--')
if (!parts.length) {
ctx.console.error('dirname: missing operand')
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
function dirnameEmit(ctx, out, nullSep) {
if (!nullSep) {
ctx.console.log(out)
return
}
for (const path of parts) {
const cleaned = path.replace(/\/+$/, '') || '/'
const i = cleaned.lastIndexOf('/')
const out =
i <= 0 ? (cleaned[0] === '/' ? '/' : '.') : cleaned.slice(0, i) || '/'
ctx.console.log(out)
if (!bareOsEmitRaw(ctx, out + '\0')) {
ctx.console.error(
'dirname: -z requires process.stdout.write or ctx.bareOsBinWrite'
)
ctx.exitCode = 1
}
}
async function run(ctx, argv) {
let nullSep = false
let i = 1
while (i < argv.length) {
const a = argv[i]
if (a === '--') {
i++
break
}
if (a === '-z' || a === '--zero') {
nullSep = true
i++
continue
}
if (a.startsWith('-')) {
ctx.console.error('dirname: unsupported option: ' + a)
ctx.exitCode = 1
return
}
break
}
const paths = argv.slice(i).filter((x) => x !== '--')
if (!paths.length) {
ctx.console.error('dirname: missing operand')
ctx.exitCode = 1
return
}
for (const path of paths) {
const cleaned = path.replace(/\/+$/, '') || '/'
const j = cleaned.lastIndexOf('/')
const out =
j <= 0 ? (cleaned[0] === '/' ? '/' : '.') : cleaned.slice(0, j) || '/'
dirnameEmit(ctx, out, nullSep)
}
}
+117 -6
View File
@@ -60,8 +60,60 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function duPath(ctx, path, blockSize) {
const st = await ctx.vfs.lstat(path)
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function duBytes(ctx, path, followSymlinks) {
const st = followSymlinks
? await ctx.vfs.stat(path)
: await ctx.vfs.lstat(path)
if (!st) return 0
if (st.type === 'file' || st.type === 'symlink') {
return st.size || 0
}
let total = st.size || 0
let names = []
try {
names = await ctx.vfs.readdir(path)
} catch {
return total
}
for (const n of names) {
if (n === '.bareos_empty') continue
const sub = path.replace(/\/+$/, '') + '/' + n
total += await duBytes(ctx, sub, followSymlinks)
}
return total
}
async function duPath(ctx, path, blockSize, followSymlinks) {
const st = followSymlinks
? await ctx.vfs.stat(path)
: await ctx.vfs.lstat(path)
if (!st) return 0
if (st.type === 'file' || st.type === 'symlink') {
return Math.ceil((st.size || 0) / blockSize) || 1
@@ -76,24 +128,83 @@ async function duPath(ctx, path, blockSize) {
for (const n of names) {
if (n === '.bareos_empty') continue
const sub = path.replace(/\/+$/, '') + '/' + n
total += await duPath(ctx, sub, blockSize)
total += await duPath(ctx, sub, blockSize, followSymlinks)
}
return total
}
/** @param {number} bytes */
function duHuman(bytes) {
if (!Number.isFinite(bytes) || bytes < 0) bytes = 0
const units = ['K', 'M', 'G', 'T', 'P']
if (bytes < 1024) return String(bytes)
let u = -1
let v = bytes
while (v >= 1024 && u < units.length - 1) {
v /= 1024
u++
}
const rounded = v >= 10 ? Math.round(v) : Math.round(v * 10) / 10
const s = String(rounded).replace(/\.0$/, '')
return s + units[u]
}
async function duPrintAll(ctx, path, human, blockSize, followSymlinks) {
const st = followSymlinks
? await ctx.vfs.stat(path)
: await ctx.vfs.lstat(path)
if (!st) return
const bytes = st.size || 0
const blocks =
human || blockSize <= 0
? null
: Math.ceil(bytes / blockSize) || (bytes === 0 ? 0 : 1)
const label = human ? duHuman(bytes) : String(blocks)
ctx.console.log(label + '\t' + path)
if (st.type !== 'directory') return
let names = []
try {
names = await ctx.vfs.readdir(path)
} catch {
return
}
for (const n of names) {
if (n === '.bareos_empty') continue
const sub = path.replace(/\/+$/, '') + '/' + n
await duPrintAll(ctx, sub, human, blockSize, followSymlinks)
}
}
async function run(ctx, argv) {
let blockSize = 512
let human = false
let allFiles = false
let followSymlinks = false
const paths = []
for (let i = 1; i < argv.length; i++) {
if (argv[i] === '-k') blockSize = 1024
else if (argv[i] !== '--') paths.push(argv[i])
else if (argv[i] === '-h' || argv[i] === '--human-readable') human = true
else if (argv[i] === '-a' || argv[i] === '--all') allFiles = true
else if (argv[i] === '-L' || argv[i] === '--dereference') followSymlinks = true
else if (argv[i] === '-s' || argv[i] === '--summarize') {
/* Bare du already prints one total per path (GNU -s default for us). */
} else if (argv[i] !== '--') paths.push(argv[i])
}
const targets = paths.length ? paths : ['.']
for (const p of targets) {
try {
const abs = ctx.vfs.resolveLogical(p)
const kb = await duPath(ctx, abs, blockSize)
ctx.console.log(String(kb) + '\t' + p)
if (allFiles) {
await duPrintAll(ctx, abs, human, blockSize, followSymlinks)
continue
}
if (human) {
const b = await duBytes(ctx, abs, followSymlinks)
ctx.console.log(duHuman(b) + '\t' + p)
} else {
const kb = await duPath(ctx, abs, blockSize, followSymlinks)
ctx.console.log(String(kb) + '\t' + p)
}
} catch (e) {
ctx.console.error('du: ' + p + ': ' + ((e && e.message) || e))
ctx.exitCode = 1
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
const parts = argv.slice(1)
let n = false
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/** ANSI helpers for /bin/edit (preamble; no import in src). */
const EDIT_ANSI_RESET = '\x1b[0m'
+94 -3
View File
@@ -60,9 +60,100 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* Parse leading NAME=value assignments; first non-assignment starts the utility argv.
* @param {string[]} rest
* @returns {{ assigns: Record<string, string>, utility: string[] }}
*/
function parseEnvLeadingAssigns(rest) {
const assigns = {}
let i = 0
for (; i < rest.length; i++) {
const a = rest[i]
const eq = a.indexOf('=')
if (eq <= 0) break
const name = a.slice(0, eq)
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) break
assigns[name] = a.slice(eq + 1)
}
return { assigns, utility: rest.slice(i) }
}
async function run(ctx, argv) {
const e = ctx.vfs.env
for (const k of Object.keys(e).sort()) {
ctx.console.log(k + '=' + (e[k] ?? ''))
let ignore = false
let i = 1
for (; i < argv.length; i++) {
const a = argv[i]
if (a === '--') {
i++
break
}
if (a === '-i' || a === '--ignore-environment') {
ignore = true
continue
}
if (a.startsWith('-')) {
ctx.console.error('env: unsupported option ' + a)
ctx.exitCode = 1
return
}
break
}
const rest = argv.slice(i)
const { assigns, utility } = parseEnvLeadingAssigns(rest)
const base = ignore ? {} : { ...ctx.vfs.env }
const newEnv = Object.assign(base, assigns)
if (!utility.length) {
for (const k of Object.keys(newEnv).sort()) {
ctx.console.log(k + '=' + (newEnv[k] ?? ''))
}
return
}
if (typeof ctx.runBinCommand !== 'function') {
ctx.console.error(
'env: ctx.runBinCommand is not available (requires a Bare OS booter session)'
)
ctx.exitCode = 1
return
}
const oldEnv = ctx.vfs.env
ctx.vfs.env = newEnv
if (ctx.env && typeof ctx.env === 'object') ctx.env = newEnv
try {
ctx.exitCode = 0
await ctx.runBinCommand(utility)
} finally {
ctx.vfs.env = oldEnv
if (ctx.env && typeof ctx.env === 'object') ctx.env = oldEnv
}
}
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/** Drive-resident exit: ends the booter session (ctx.requestBooterExit from bare-os-booter). */
async function run(ctx, argv) {
let ec = 0
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
ctx.exitCode = 1
}
+197 -30
View File
@@ -60,21 +60,91 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* curDepth is distance from the search root directory (0 at the initial path).
* Printed paths use gnu-like depth curDepth + 1 (immediate children of the root are depth 1).
*/
async function walk(
ctx,
dir,
nameRe,
pathRe,
wantType,
maxDepth,
minDepth,
curDepth
) {
if (maxDepth >= 0 && curDepth > maxDepth) return
function findEmitLine(ctx, path, print0) {
if (!print0) {
ctx.console.log(path)
return
}
if (!bareOsEmitRaw(ctx, path + '\0')) {
ctx.console.error(
'find: -print0 requires process.stdout.write or ctx.bareOsBinWrite'
)
ctx.exitCode = 1
}
}
/** @param {string} s */
function parseMtimeSpec(s) {
const t = String(s)
let op = ''
let num = t
if (t.startsWith('+')) {
op = '+'
num = t.slice(1)
} else if (t.startsWith('-') && t.length > 1) {
op = '-'
num = t.slice(1)
}
const n = Number.parseFloat(num)
if (!Number.isFinite(n)) return null
return { op, n }
}
/** @param {{ op: string, n: number }} spec */
function findMatchMtime(st, spec) {
const days = (Date.now() - st.mtimeMs) / 86400000
if (spec.op === '+') return days > spec.n
if (spec.op === '-') return days < spec.n
return days >= spec.n && days < spec.n + 1
}
async function findIsEmpty(ctx, path, st) {
if (st.type === 'file' || st.type === 'symlink') return (st.size || 0) === 0
if (st.type !== 'directory') return false
const names = await ctx.vfs.readdir(path)
const rest = names.filter((n) => n !== '.bareos_empty')
return rest.length === 0
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} dir
* @param {Record<string, unknown>} o
* @param {number} curDepth
*/
async function walk(ctx, dir, o, curDepth) {
if (o.maxDepth >= 0 && curDepth > o.maxDepth) return
let names
try {
names = await ctx.vfs.readdir(dir)
@@ -92,30 +162,46 @@ async function walk(
}
if (!st) continue
const gnuDepth = curDepth + 1
const pathOk = !pathRe || pathRe.test(path)
const nameOk = !nameRe || nameRe.test(n)
if (pathOk && nameOk) {
if (!wantType || st.type === wantType) {
if (gnuDepth >= minDepth) ctx.console.log(path)
const pathOk = !o.pathRe || o.pathRe.test(path)
const nameOk = !o.nameRe || o.nameRe.test(n)
let match = pathOk && nameOk && (!o.wantType || st.type === o.wantType)
if (match && o.mtimeSpec) match = match && findMatchMtime(st, o.mtimeSpec)
if (match && o.newerThanMs != null) match = match && st.mtimeMs > o.newerThanMs
if (match && o.wantEmpty) {
match = match && (await findIsEmpty(ctx, path, st))
}
if (match && gnuDepth >= o.minDepth) {
if (o.doDelete) {
if (ctx.vfs.env.BARE_OS_FIND_DELETE !== '1') {
const stw = /** @type {{ warned?: boolean }} */ (o.deleteState)
if (!stw.warned) {
ctx.console.error(
'find: -delete disabled (set BARE_OS_FIND_DELETE=1 to confirm)'
)
stw.warned = true
}
ctx.exitCode = 1
} else {
try {
await ctx.vfs.rm(path, { recursive: true, force: true })
} catch (e) {
ctx.console.error('find: ' + path + ': ' + (e.message || e))
ctx.exitCode = 1
}
}
} else {
findEmitLine(ctx, path, o.print0)
}
}
if (st.type === 'directory')
await walk(
ctx,
path,
nameRe,
pathRe,
wantType,
maxDepth,
minDepth,
curDepth + 1
)
if (st.type !== 'directory') continue
const absPath = ctx.vfs.resolveLogical(path)
if (o.pruneAbs && absPath === o.pruneAbs) continue
await walk(ctx, path, o, curDepth + 1)
}
}
async function run(ctx, argv) {
let maxDepth = -1
/** Minimum path depth below the search root (1 = default; same as GNU -mindepth 1 for tree walks). */
let minDepth = 1
/** @type {string | null} */
let nameGlob = null
@@ -123,6 +209,16 @@ async function run(ctx, argv) {
let pathGlob = null
/** @type {'file' | 'directory' | 'symlink' | null} */
let wantType = null
let nameIgnoreCase = false
let print0 = false
/** @type {{ op: string, n: number } | null} */
let mtimeSpec = null
/** @type {string | null} */
let newerPath = null
/** @type {string | null} */
let prunePath = null
let wantEmpty = false
let doDelete = false
const rest = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
@@ -141,6 +237,12 @@ async function run(ctx, argv) {
}
if (a === '-name' && argv[i + 1]) {
nameGlob = argv[++i]
nameIgnoreCase = false
continue
}
if (a === '-iname' && argv[i + 1]) {
nameGlob = argv[++i]
nameIgnoreCase = true
continue
}
if (a === '-type' && argv[i + 1]) {
@@ -150,6 +252,35 @@ async function run(ctx, argv) {
else if (t === 'l') wantType = 'symlink'
continue
}
if (a === '-print0') {
print0 = true
continue
}
if (a === '-mtime' && argv[i + 1]) {
mtimeSpec = parseMtimeSpec(argv[++i])
if (!mtimeSpec) {
ctx.console.error('find: invalid -mtime')
ctx.exitCode = 1
return
}
continue
}
if (a === '-newer' && argv[i + 1]) {
newerPath = argv[++i]
continue
}
if (a === '-prune' && argv[i + 1]) {
prunePath = argv[++i]
continue
}
if (a === '-empty') {
wantEmpty = true
continue
}
if (a === '-delete') {
doDelete = true
continue
}
if (a === '--') {
rest.push(...argv.slice(i + 1))
break
@@ -168,7 +299,7 @@ async function run(ctx, argv) {
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
.replace(/\*/g, '.*')
.replace(/\?/g, '.')
nameRe = new RegExp('^' + esc + '$')
nameRe = new RegExp('^' + esc + '$', nameIgnoreCase ? 'i' : '')
}
let pathRe = null
if (pathGlob) {
@@ -180,6 +311,42 @@ async function run(ctx, argv) {
.replace(/\0GLOBSTAR\0/g, '.*')
pathRe = new RegExp('^' + esc + '$')
}
/** @type {number | null} */
let newerThanMs = null
if (newerPath) {
try {
const st = await ctx.vfs.stat(newerPath)
if (!st) {
ctx.console.error('find: ' + newerPath + ': No such file')
ctx.exitCode = 1
return
}
newerThanMs = st.mtimeMs
} catch (e) {
ctx.console.error('find: ' + newerPath + ': ' + (e.message || e))
ctx.exitCode = 1
return
}
}
/** @type {string | null} */
let pruneAbs = null
if (prunePath) {
pruneAbs = ctx.vfs.resolveLogical(prunePath).replace(/\/+$/, '') || '/'
}
const abs = ctx.vfs.resolveLogical(root)
await walk(ctx, abs, nameRe, pathRe, wantType, maxDepth, minDepth, 0)
const o = {
maxDepth,
minDepth,
nameRe,
pathRe,
wantType,
print0,
mtimeSpec,
newerThanMs,
pruneAbs,
wantEmpty,
doDelete,
deleteState: {}
}
await walk(ctx, abs, o, 0)
}
+32 -1
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* Subset of POSIX.1-2017 getconf — fixed values for Bare OS (no host sysconf).
* Unknown names exit with status 1 (matches common getconf for invalid var).
@@ -85,7 +112,11 @@ const CONF = {
_POSIX_JOB_CONTROL: '0',
_POSIX_SAVED_IDS: '0',
/** Bare shell line length (reasonable REPL limit, not a hard kernel cap). */
BARE_OS_INPUT_LINE_MAX: '8192'
BARE_OS_INPUT_LINE_MAX: '8192',
/** Defaults for simulated pipelines (override with BARE_OS_PIPELINE_* env); see handbook §3. */
BARE_OS_PIPELINE_MAX_STAGES: '32',
BARE_OS_PIPELINE_MAX_BYTES: '2097152',
BARE_OS_PIPELINE_MAX_LINES: '50000'
}
async function run(ctx, argv) {
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
const sub = argv[1]
if (sub === 'help' || sub === undefined) {
+271 -6
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* Subset of POSIX/GNU grep behavior using JavaScript RegExp / string search.
* Not bit-identical to GNU grep (no PCRE, different escaping, UTF-16 strings).
@@ -83,13 +110,18 @@ async function run(ctx, argv) {
let onlyMatching = false
/** @type {number} */
let maxMatchLines = Number.POSITIVE_INFINITY
let afterCtx = 0
let beforeCtx = 0
/** @type {'never' | 'always' | 'auto'} */
let colorMode = 'never'
let recursive = false
const args = argv.slice(1)
let i = 0
function usage() {
ctx.console.error(
'usage: grep [-E|-F] [-i] [-v] [-w] [-x] [-n] [-c] [-l] [-o] [-m NUM] [-q] [-s] [-H|-h] [-e pat] ... [-f file] ... [pattern] [file...]'
'usage: grep [-E|-F] [-r] [-i] [-v] [-w] [-x] [-n] [-c] [-l] [-o] [-m NUM] [-A|-B|-C N] [--color=never|always|auto] [-q] [-s] [-H|-h] [-e pat] ... [-f file] ... [pattern] [file...]'
)
ctx.exitCode = 2
}
@@ -103,11 +135,89 @@ async function run(ctx, argv) {
if (a === '-' || !a.startsWith('-')) break
if (a.startsWith('--')) {
if (a === '--color' || a.startsWith('--color=')) {
const v =
a === '--color'
? args[++i] || 'auto'
: a.slice('--color='.length) || 'auto'
if (v === 'never' || v === 'always' || v === 'auto') colorMode = v
else {
ctx.console.error('grep: invalid --color value')
ctx.exitCode = 2
return
}
i++
continue
}
if (a === '--after-context' || a.startsWith('--after-context=')) {
const raw =
a === '--after-context'
? args[++i]
: a.slice('--after-context='.length)
const n = Number.parseInt(raw, 10)
if (!Number.isFinite(n) || n < 0) {
usage()
return
}
afterCtx = n
i++
continue
}
if (a === '--before-context' || a.startsWith('--before-context=')) {
const raw =
a === '--before-context'
? args[++i]
: a.slice('--before-context='.length)
const n = Number.parseInt(raw, 10)
if (!Number.isFinite(n) || n < 0) {
usage()
return
}
beforeCtx = n
i++
continue
}
if (a === '--recursive') {
recursive = true
i++
continue
}
ctx.console.error('grep: unknown option ' + a)
ctx.exitCode = 2
return
}
if (a === '-A' || /^-A\d+$/.test(a)) {
const n = a === '-A' ? Number.parseInt(args[++i], 10) : Number.parseInt(a.slice(2), 10)
if (!Number.isFinite(n) || n < 0) {
usage()
return
}
afterCtx = n
i++
continue
}
if (a === '-B' || /^-B\d+$/.test(a)) {
const n = a === '-B' ? Number.parseInt(args[++i], 10) : Number.parseInt(a.slice(2), 10)
if (!Number.isFinite(n) || n < 0) {
usage()
return
}
beforeCtx = n
i++
continue
}
if (a === '-C' || /^-C\d+$/.test(a)) {
const n = a === '-C' ? Number.parseInt(args[++i], 10) : Number.parseInt(a.slice(2), 10)
if (!Number.isFinite(n) || n < 0) {
usage()
return
}
beforeCtx = afterCtx = n
i++
continue
}
if (a === '-m') {
if (i + 1 >= args.length) {
usage()
@@ -210,6 +320,10 @@ async function run(ctx, argv) {
case 'o':
onlyMatching = true
break
case 'r':
case 'R':
recursive = true
break
case 'm': {
let num = ''
let jj = j + 1
@@ -264,8 +378,35 @@ async function run(ctx, argv) {
patterns.push(rest.shift())
}
const fileArgs = rest
const useStdin = fileArgs.length === 0
let fatal = false
/** @type {string[]} */
let fileArgs = rest
if (recursive) {
if (fileArgs.length === 0) fileArgs = ['.']
const acc = []
for (const p of fileArgs) {
try {
const st = await vfs.lstat(p)
if (!st) {
if (!suppressErrors) ctx.console.error('grep: ' + p + ': No such file')
fatal = true
continue
}
if (st.type === 'directory') {
await grepWalkFiles(ctx, p, acc, suppressErrors)
} else {
acc.push(p)
}
} catch (e) {
if (!suppressErrors)
ctx.console.error('grep: ' + p + ': ' + (e.message || e))
fatal = true
}
}
fileArgs = acc
}
const useStdin = fileArgs.length === 0 && !recursive
const inputs = useStdin
? [{ label: null, path: null }]
: fileArgs.map((p) => ({ label: p, path: p }))
@@ -283,8 +424,24 @@ async function run(ctx, argv) {
const showName =
!noFilename && (multiFile || forceFilename || (useStdin && forceFilename))
const shellEnv = (ctx.vfs && ctx.vfs.env) || ctx.env || {}
const useColor =
colorMode === 'always' ||
(colorMode === 'auto' &&
globalThis.process?.stdout?.isTTY === true &&
shellEnv.NO_COLOR !== '1' &&
shellEnv.NO_COLOR !== 'true')
const C_RED = '\x1b[01;31m'
const C_RST = '\x1b[0m'
const useContext =
(afterCtx > 0 || beforeCtx > 0) &&
!countOnly &&
!listFiles &&
!quiet &&
!onlyMatching
let anyMatch = false
let fatal = false
let matchingLinesTotal = 0
for (const { label, path } of inputs) {
@@ -314,6 +471,50 @@ async function run(ctx, argv) {
let fileMatched = false
const out = []
if (useContext) {
const n = lines.length
/** @type {boolean[]} */
const isMatch = new Array(n).fill(false)
for (let li = 0; li < n; li++) {
if (matchingLinesTotal >= maxMatchLines) break
const line = stripCr(lines[li])
const matched = matchers.some((fn) => fn(line))
const hit = invert ? !matched : matched
isMatch[li] = hit
if (!hit) continue
matchingLinesTotal++
fileMatched = true
anyMatch = true
count++
}
if (quiet) continue
/** @type {number[][]} */
const iv = []
for (let li = 0; li < n; li++) {
if (!isMatch[li]) continue
iv.push([
Math.max(0, li - beforeCtx),
Math.min(n - 1, li + afterCtx)
])
}
const merged = grepMergeIntervals(iv)
for (let mi = 0; mi < merged.length; mi++) {
if (mi > 0) ctx.console.log('--')
for (let li = merged[mi][0]; li <= merged[mi][1]; li++) {
const line = stripCr(lines[li])
const lineNum = li + 1
let body = line
if (useColor && isMatch[li] && !invert) body = C_RED + line + C_RST
let chunk = numbers ? lineNum + ':' + body : body
if (showName && label != null) chunk = label + ':' + chunk
else if (showName && label == null && useStdin)
chunk = '(standard input):' + chunk
ctx.console.log(chunk)
}
}
continue
}
for (let li = 0; li < lines.length; li++) {
if (matchingLinesTotal >= maxMatchLines) break
const line = stripCr(lines[li])
@@ -337,6 +538,7 @@ async function run(ctx, argv) {
})
for (const part of parts) {
let chunk = part
if (useColor) chunk = C_RED + chunk + C_RST
if (numbers) chunk = lineNum + ':' + chunk
if (showName && label != null) chunk = label + ':' + chunk
else if (showName && label == null && useStdin)
@@ -344,8 +546,9 @@ async function run(ctx, argv) {
out.push(chunk)
}
} else {
let chunk = line
if (numbers) chunk = lineNum + ':' + chunk
let body = line
if (useColor && !invert) body = C_RED + line + C_RST
let chunk = numbers ? lineNum + ':' + body : body
if (showName && label != null) chunk = label + ':' + chunk
else if (showName && label == null && useStdin)
chunk = '(standard input):' + chunk
@@ -378,6 +581,68 @@ async function run(ctx, argv) {
else ctx.exitCode = anyMatch ? 0 : 1
}
const GREP_RECURSE_MAX_DEPTH = 64
/**
* @param {Record<string, unknown>} ctx
* @param {string} dir
* @param {string[]} acc
* @param {boolean} suppressErrors
*/
async function grepWalkFiles(ctx, dir, acc, suppressErrors) {
const vfs = ctx.vfs
/** @param {string} d @param {number} depth */
const walk = async (d, depth) => {
if (depth > GREP_RECURSE_MAX_DEPTH) return
let names = []
try {
names = await vfs.readdir(d)
} catch (e) {
if (!suppressErrors)
ctx.console.error('grep: ' + d + ': ' + (e.message || e))
return
}
for (const n of names) {
if (n === '.' || n === '..' || n === '.git') continue
const sub = d.endsWith('/') ? d + n : d + '/' + n
let st
try {
st = await vfs.lstat(sub)
} catch {
continue
}
if (!st) continue
if (st.type === 'directory') await walk(sub, depth + 1)
else if (st.type === 'file') acc.push(sub)
else if (st.type === 'symlink') {
try {
const ft = await vfs.stat(sub)
if (ft && ft.type === 'file') acc.push(sub)
} catch {
/* skip */
}
}
}
}
await walk(dir, 0)
}
/** @param {number[][]} iv */
function grepMergeIntervals(iv) {
if (!iv.length) return []
const copy = iv.map((x) => [x[0], x[1]])
copy.sort((a, b) => a[0] - b[0])
/** @type {number[][]} */
const out = [[copy[0][0], copy[0][1]]]
for (let k = 1; k < copy.length; k++) {
const cur = copy[k]
const last = out[out.length - 1]
if (cur[0] <= last[1] + 1) last[1] = Math.max(last[1], cur[1])
else out.push([cur[0], cur[1]])
}
return out
}
/**
* @param {string[]} patterns
* @param {{ fixed: boolean, icase: boolean, word?: boolean, fullLine?: boolean }} o
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
if (typeof ctx.runHdms === 'function') {
await ctx.runHdms(argv)
+122 -11
View File
@@ -60,27 +60,138 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* head — first lines or bytes of files.
*/
function bareHeadBytesSlice(u8, n) {
if (!u8 || !u8.byteLength || n <= 0) return new Uint8Array(0)
const end = Math.min(u8.byteLength, n)
return u8.subarray(0, end)
}
async function run(ctx, argv) {
const vfs = ctx.vfs
let n = 10
let start = 1
if (argv[1] === '-n' && argv[2]) {
n = parseInt(argv[2], 10) || 10
start = 3
} else if (argv[1] && /^-\d+$/.test(argv[1])) {
n = parseInt(argv[1].slice(1), 10) || 10
start = 2
/** @type {'lines' | 'bytes'} */
let mode = 'lines'
let i = 1
while (i < argv.length) {
const a = argv[i]
if (a === '--') {
i++
break
}
if (a === '-n' || a === '--lines') {
const v = argv[i + 1]
if (v == null) {
ctx.console.error('head: option requires an argument -- n')
ctx.exitCode = 1
return
}
n = Number.parseInt(v, 10) || 10
mode = 'lines'
i += 2
continue
}
if (a.startsWith('-n') && a.length > 2) {
n = Number.parseInt(a.slice(2), 10) || 10
mode = 'lines'
i++
continue
}
if (a === '-c' || a === '--bytes') {
const v = argv[i + 1]
if (v == null) {
ctx.console.error('head: option requires an argument -- c')
ctx.exitCode = 1
return
}
n = Number.parseInt(v, 10) || 0
mode = 'bytes'
i += 2
continue
}
if (a.startsWith('-c') && a.length > 2) {
n = Number.parseInt(a.slice(2), 10) || 0
mode = 'bytes'
i++
continue
}
if (a && /^-\d+$/.test(a)) {
n = Number.parseInt(a.slice(1), 10) || 10
mode = 'lines'
i++
continue
}
if (a.startsWith('-')) {
ctx.console.error('head: unrecognized option: ' + a)
ctx.exitCode = 1
return
}
break
}
const files = argv.slice(start).filter((a) => a !== '--')
const take = (s) => {
const files = argv.slice(i).filter((x) => x !== '--')
const takeString = (s) => {
if (mode === 'bytes') {
const u8 = ctx.b4a.from(s, 'utf8')
const slice = bareHeadBytesSlice(u8, n)
const out = ctx.b4a.toString(slice)
if (out) ctx.console.log(out)
return
}
const lines = s.split('\n')
const out = lines.slice(0, n).join('\n')
ctx.console.log(
out + (out && !out.endsWith('\n') && lines.length > n ? '\n' : '')
)
}
const takeU8 = (u8) => {
if (mode === 'bytes') {
const slice = bareHeadBytesSlice(
u8 instanceof Uint8Array ? u8 : ctx.b4a.from(u8),
n
)
const out = ctx.b4a.toString(slice)
if (out) ctx.console.log(out)
return
}
takeString(ctx.b4a.toString(u8))
}
if (!files.length) {
take(bareStdin(ctx))
takeString(bareStdin(ctx))
return
}
for (const f of files) {
@@ -90,6 +201,6 @@ async function run(ctx, argv) {
continue
}
if (files.length > 1) ctx.console.log('==> ' + f + ' <==')
take(ctx.b4a.toString(buf))
takeU8(buf)
}
}
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
ctx.console.log(
'Bare OS — default user: guest | shell builtins: alias, barerc, cd, command, export, exit, login, logout, readonly, type, umask, unalias, unset, : | /bin: awk basename cat chgrp chmod chown cksum clear cp crontab curl cut date dirname dircolors du edit echo env exit false find getconf grep head hdms help hostname id journalctl jq ln login logout logname ls man mkdir mkfifo mktemp nano mv nl od pathchk printenv printf pwd readlink rm rmdir savevault sed seq sleep sort stat systemctl tail tee test theme time touch tr true tty uname wc wget which whoami xargs'
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
const h =
globalThis.process?.env?.HOSTNAME ||
+40 -7
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
const e = ctx.vfs.env
const u = e.USER || e.LOGNAME || 'guest'
@@ -67,22 +94,28 @@ async function run(ctx, argv) {
const gid = e.GID || (e.BARE_OS_IDENTITY === 'guest' ? '65534' : '1000')
const g = e.GROUP || u
const rest = argv.slice(1)
const wantG = rest.includes('-g') || rest.includes('--group')
const wantU = rest.includes('-u') || rest.includes('--user')
const wantN = rest.includes('-n') || rest.includes('--name')
if (wantU && wantN) {
const wantName = rest.includes('-n') || rest.includes('--name')
const wantPrimaryGroup = rest.includes('-g') || rest.includes('--group')
const wantAllGroups = rest.includes('-G') || rest.includes('--groups')
const wantUser = rest.includes('-u') || rest.includes('--user')
if (wantUser && wantName) {
ctx.console.log(u)
return
}
if (wantG && wantN) {
if (wantPrimaryGroup && wantName) {
ctx.console.log(g)
return
}
if (wantU) {
if (wantUser) {
ctx.console.log(uid)
return
}
if (wantG) {
if (wantPrimaryGroup) {
ctx.console.log(gid)
return
}
if (wantAllGroups) {
ctx.console.log(gid)
return
}
+28
View File
@@ -60,7 +60,35 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
// Bare OS: vendored from @sscots/[email protected] (https://github.com/sscots/jqjs). ISC License. Top-level compile() and prettyPrint(); not the C jq binary (https://github.com/jqlang/jq).
// Triage: treat bug reports as jqjs semantic gaps; prefer golden tests before expanding builtins. Large JSON is bounded by host memory and stdin string caps (see handbook §3).
// jqjs - jq JSON query language in JavaScript
// Copyright (C) 2018-2023 Michael Homer
/*
+80 -2
View File
@@ -60,8 +60,63 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
function bareParentLogical(p) {
const t = p.replace(/\/+$/, '') || '/'
if (t === '/') return '/'
const i = t.lastIndexOf('/')
return i <= 0 ? '/' : t.slice(0, i) || '/'
}
function bareJoinUnder(parent, rel) {
if (rel.startsWith('/')) return rel
const base = parent.replace(/\/+$/, '') || '/'
if (base === '/') return '/' + rel.replace(/^\/+/, '')
return base + '/' + rel.replace(/^\/+/, '')
}
function bareRelativeLogical(fromDirAbs, toAbs) {
const from = fromDirAbs.replace(/\/+$/, '').split('/').filter(Boolean)
const to = toAbs.replace(/\/+$/, '').split('/').filter(Boolean)
let i = 0
while (i < from.length && i < to.length && from[i] === to[i]) i++
const up = from.length - i
const parts = []
for (let k = 0; k < up; k++) parts.push('..')
parts.push(...to.slice(i))
return parts.length ? parts.join('/') : '.'
}
async function run(ctx, argv) {
let sym = false
let force = false
let relative = false
const rest = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
@@ -69,6 +124,14 @@ async function run(ctx, argv) {
sym = true
continue
}
if (a === '-f' || a === '--force') {
force = true
continue
}
if (a === '-r' || a === '--relative') {
relative = true
continue
}
if (a === '--') {
rest.push(...argv.slice(i + 1))
break
@@ -86,12 +149,27 @@ async function run(ctx, argv) {
return
}
if (rest.length !== 2) {
ctx.console.error('usage: ln -s TARGET LINK_NAME')
ctx.console.error('usage: ln -s [-f] [-r] TARGET LINK_NAME')
ctx.exitCode = 1
return
}
let target = rest[0]
const linkName = rest[1]
try {
await ctx.vfs.symlink(rest[0], rest[1])
if (relative) {
const linkAbs = ctx.vfs.resolveLogical(linkName)
const linkDir = bareParentLogical(linkAbs)
const tgtAbs = ctx.vfs.resolveLogical(target)
target = bareRelativeLogical(linkDir, tgtAbs)
}
if (force) {
try {
await ctx.vfs.unlink(linkName)
} catch {
/* ignore */
}
}
await ctx.vfs.symlink(target, linkName)
} catch (e) {
ctx.console.error('ln: ' + ((e && e.message) || e))
ctx.exitCode = 1
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
const rest = argv.slice(1)
let createNew = false
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
const u = ctx.vfs.env.LOGNAME || ctx.vfs.env.USER || 'guest'
ctx.console.log(u)
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
const save = argv.includes('--save')
const logout = ctx.applyLogout
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* GNU-style LS_COLORS string + dircolors(5) database parsing and file classification.
* Consumed by bare-os-booter (ESM import). Prepended into /bin/ls and /bin/dircolors by bare-os-coreutils build (no import there).
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/** Plain-text manual formatter (prepended before src/man.js; no import in /bin/man). */
function bareManParseWidth(env) {
+57 -2
View File
@@ -60,8 +60,44 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
function parseModeOctal(s) {
const t = String(s).trim()
const n = Number.parseInt(t, 8)
if (!Number.isFinite(n) || n < 0) return null
return n & 0o777
}
async function run(ctx, argv) {
let parents = false
/** @type {number | null} */
let modeOpt = null
const paths = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
@@ -69,6 +105,23 @@ async function run(ctx, argv) {
parents = true
continue
}
if (a === '-m') {
const next = argv[i + 1]
if (next === undefined) {
ctx.console.error('mkdir: option requires an argument -- m')
ctx.exitCode = 1
return
}
const m = parseModeOctal(next)
if (m == null) {
ctx.console.error('mkdir: invalid mode ' + JSON.stringify(next))
ctx.exitCode = 1
return
}
modeOpt = m
i++
continue
}
if (a === '--') {
paths.push(...argv.slice(i + 1))
break
@@ -81,13 +134,15 @@ async function run(ctx, argv) {
paths.push(a)
}
if (!paths.length) {
ctx.console.error('usage: mkdir [-p] DIRECTORY...')
ctx.console.error('usage: mkdir [-p] [-m MODE] DIRECTORY...')
ctx.exitCode = 1
return
}
for (const p of paths) {
try {
await ctx.vfs.mkdir(p, { recursive: parents })
const mkOpts = { recursive: parents }
if (modeOpt != null) mkOpts.mode = modeOpt
await ctx.vfs.mkdir(p, mkOpts)
} catch (e) {
ctx.console.error('mkdir: ' + p + ': ' + ((e && e.message) || e))
ctx.exitCode = 1
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
const path = argv[1]
if (!path) {
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
let wantDir = false
/** @type {string | null} */
+84 -9
View File
@@ -60,12 +60,61 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
async function mvCopyPath(ctx, from, to, recursive) {
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function mvCopyPath(ctx, from, to, recursive, followSymlink) {
const st = await ctx.vfs.lstat(from)
if (!st) return false
if (st.type === 'symlink') {
await ctx.vfs.symlink(await ctx.vfs.readlink(from), to)
return true
if (!followSymlink) {
await ctx.vfs.symlink(await ctx.vfs.readlink(from), to)
return true
}
const fst = await ctx.vfs.stat(from)
if (fst.type === 'file') {
const buf = await ctx.vfs.readFile(from)
if (!buf) return false
await ctx.vfs.writeFile(to, buf)
return true
}
if (fst.type === 'directory') {
if (!recursive) return false
await ctx.vfs.mkdir(to, { recursive: true })
const names = await ctx.vfs.readdir(from)
for (const n of names) {
if (n === '.bareos_empty') continue
const f = from.replace(/\/+$/, '') + '/' + n
const t = to.replace(/\/+$/, '') + '/' + n
if (!(await mvCopyPath(ctx, f, t, true, followSymlink))) return false
}
return true
}
return false
}
if (st.type === 'file') {
const buf = await ctx.vfs.readFile(from)
@@ -81,7 +130,7 @@ async function mvCopyPath(ctx, from, to, recursive) {
if (n === '.bareos_empty') continue
const f = from.replace(/\/+$/, '') + '/' + n
const t = to.replace(/\/+$/, '') + '/' + n
if (!(await mvCopyPath(ctx, f, t, true))) return false
if (!(await mvCopyPath(ctx, f, t, true, followSymlink))) return false
}
return true
}
@@ -89,9 +138,31 @@ async function mvCopyPath(ctx, from, to, recursive) {
}
async function run(ctx, argv) {
const paths = argv.slice(1).filter((a) => a && a !== '--')
let followSymlink = false
const paths = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-L' || a === '--dereference') {
followSymlink = true
continue
}
if (a === '-P' || a === '--no-dereference') {
followSymlink = false
continue
}
if (a === '--') {
paths.push(...argv.slice(i + 1))
break
}
if (a.startsWith('-')) {
ctx.console.error('mv: unsupported option ' + a)
ctx.exitCode = 1
return
}
paths.push(a)
}
if (paths.length < 2) {
ctx.console.error('usage: mv SOURCE... DEST')
ctx.console.error('usage: mv [-L|-P] SOURCE... DEST')
ctx.exitCode = 1
return
}
@@ -117,8 +188,12 @@ async function run(ctx, argv) {
: dest
try {
const st = await ctx.vfs.lstat(src)
const singleFileToFile =
sources.length === 1 && !destIsDir && st && st.type === 'file'
let followReg = st && st.type === 'file'
if (!followReg && followSymlink && st && st.type === 'symlink') {
const fs = await ctx.vfs.stat(src).catch(() => null)
followReg = !!(fs && fs.type === 'file')
}
const singleFileToFile = sources.length === 1 && !destIsDir && followReg
if (singleFileToFile) {
const buf = await ctx.vfs.readFile(src)
if (!buf) throw new Error('cannot read source')
@@ -126,7 +201,7 @@ async function run(ctx, argv) {
await ctx.vfs.unlink(src)
continue
}
if (!(await mvCopyPath(ctx, src, target, true))) {
if (!(await mvCopyPath(ctx, src, target, true, followSymlink))) {
throw new Error('cannot copy')
}
await ctx.vfs.rm(src, { recursive: true, force: true })
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/** ANSI helpers for /bin/edit (preamble; no import in src). */
const EDIT_ANSI_RESET = '\x1b[0m'
+71 -4
View File
@@ -60,9 +60,71 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
const vfs = ctx.vfs
const files = argv.slice(1).filter((a) => !a.startsWith('-'))
let bodyType = 'a'
let width = 6
let sep = '\t'
let start = 1
const files = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-b' && argv[i + 1]) {
const b = argv[++i]
bodyType = b === 't' ? 't' : 'a'
continue
}
if (a === '-w' && argv[i + 1]) {
width = Number.parseInt(argv[++i], 10) || 6
continue
}
if (a === '-s' && argv[i + 1]) {
const s = argv[++i]
sep = s === '\\t' ? '\t' : s
continue
}
if (a === '-v' && argv[i + 1]) {
start = Number.parseInt(argv[++i], 10) || 1
continue
}
if (a === '--') {
files.push(...argv.slice(i + 1))
break
}
if (a.startsWith('-')) {
ctx.console.error('nl: unsupported option ' + a)
ctx.exitCode = 1
return
}
files.push(a)
}
let body = ''
if (!files.length) {
body = bareStdin(ctx)
@@ -80,11 +142,16 @@ async function run(ctx, argv) {
}
const lines = body.split('\n')
if (lines.length && lines[lines.length - 1] === '') lines.pop()
let n = 1
let n = start
for (const line of lines) {
const blank = line.trim() === ''
if (bodyType === 't' && blank) {
ctx.console.log(line)
continue
}
const num = String(n)
const pad = ' '.repeat(Math.max(0, 6 - num.length)) + num
ctx.console.log(pad + '\t' + line)
const pad = ' '.repeat(Math.max(0, width - num.length)) + num
ctx.console.log(pad + sep + line)
n++
}
}
+68 -9
View File
@@ -60,12 +60,63 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
function hexByte(b) {
return b.toString(16).padStart(2, '0')
}
async function run(ctx, argv) {
const paths = argv.slice(1).filter((a) => a && !a.startsWith('-'))
let showAddr = true
/** @type {'both' | 'hex' | 'char'} */
let style = 'both'
const paths = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-An') {
showAddr = false
continue
}
if (a === '-A' && argv[i + 1] === 'n') {
showAddr = false
i++
continue
}
if (a === '-x') {
style = 'hex'
continue
}
if (a === '-c') {
style = 'char'
continue
}
if (!a.startsWith('-')) paths.push(a)
}
let buf
if (!paths.length) {
buf = ctx.b4a.from(bareStdin(ctx))
@@ -86,14 +137,22 @@ async function run(ctx, argv) {
const asc = [...chunk]
.map((x) => (x >= 32 && x < 127 ? String.fromCharCode(x) : '.'))
.join('')
ctx.console.log(
off.toString(8).padStart(7, '0') +
' ' +
hex.padEnd(47, ' ') +
' |' +
asc +
'|'
)
let line = ''
if (showAddr) line += off.toString(8).padStart(7, '0') + ' '
if (style === 'hex') line += hex
else if (style === 'char')
line +=
[...chunk]
.map((x) => {
if (x === 9) return '\\t'
if (x === 10) return '\\n'
if (x === 13) return '\\r'
if (x >= 32 && x < 127) return String.fromCharCode(x)
return '\\' + x.toString(8).padStart(3, '0')
})
.join(' ')
else line += hex.padEnd(47, ' ') + ' |' + asc + '|'
ctx.console.log(line.trimEnd())
off += 16
}
}
+71 -1
View File
@@ -60,24 +60,94 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
const paths = argv.slice(1).filter((a) => !a.startsWith('-'))
let posix = false
let strict = false
const paths = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-p') {
posix = true
continue
}
if (a === '-P') {
strict = true
continue
}
if (a === '--') {
paths.push(...argv.slice(i + 1))
break
}
if (a.startsWith('-')) {
ctx.console.error('pathchk: unsupported option ' + a)
ctx.exitCode = 1
return
}
paths.push(a)
}
if (!paths.length) {
ctx.console.error('pathchk: missing operand')
ctx.exitCode = 1
return
}
for (const p of paths) {
if (strict && !p.length) {
ctx.console.error('pathchk: empty path name')
ctx.exitCode = 1
continue
}
if (!p.length) {
ctx.console.error('pathchk: empty path name')
continue
}
if (p.length > 4096) {
ctx.console.error('pathchk: path too long')
ctx.exitCode = 1
continue
}
if (p.includes('\0')) {
ctx.console.error('pathchk: NUL in path')
ctx.exitCode = 1
continue
}
if (strict && p.startsWith('-')) {
ctx.console.error('pathchk: leading hyphen in path')
ctx.exitCode = 1
continue
}
if (posix) {
const base = p.split('/').pop() || p
if (base !== '.' && base !== '..' && !/^[A-Za-z0-9._-]+$/.test(base)) {
ctx.console.error('pathchk: non-portable file name ' + base)
ctx.exitCode = 1
}
}
}
}
+60 -1
View File
@@ -60,9 +60,68 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
const e = ctx.vfs.env
const names = argv.slice(1).filter((a) => !a.startsWith('-'))
const nulOut =
argv.includes('-0') ||
argv.includes('--null') ||
argv.some((a) => a === '-0' || /^--null(=|$)/.test(a))
const names = argv
.slice(1)
.filter((a) => a !== '-0' && a !== '--null' && !a.startsWith('--null='))
if (nulOut) {
if (!names.length) {
for (const k of Object.keys(e).sort()) {
if (!bareOsEmitRaw(ctx, k + '=' + (e[k] ?? '') + '\0')) {
ctx.console.error(
'printenv: -0 requires process.stdout.write or ctx.bareOsBinWrite'
)
ctx.exitCode = 1
return
}
}
return
}
for (const n of names) {
if (e[n] != null) {
if (!bareOsEmitRaw(ctx, String(e[n]) + '\0')) {
ctx.console.error(
'printenv: -0 requires process.stdout.write or ctx.bareOsBinWrite'
)
ctx.exitCode = 1
return
}
} else ctx.exitCode = 1
}
return
}
if (!names.length) {
for (const k of Object.keys(e).sort()) {
ctx.console.log(k + '=' + (e[k] ?? ''))
+55
View File
@@ -60,6 +60,57 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/** @param {string} s */
function barePrintfBackslashArg(s) {
let o = ''
for (let i = 0; i < s.length; i++) {
if (s[i] !== '\\') {
o += s[i]
continue
}
const c = s[++i]
if (c === undefined) break
if (c === 'n') o += '\n'
else if (c === 't') o += '\t'
else if (c === 'r') o += '\r'
else if (c === '\\') o += '\\'
else if (c >= '0' && c <= '7') {
let oct = c
while (i + 1 < s.length && /[0-7]/.test(s[i + 1]) && oct.length < 3)
oct += s[++i]
o += String.fromCharCode(Number.parseInt(oct, 8) & 0xff)
} else o += c
}
return o
}
function barePrintfFormat(fmt, args) {
let ai = 0
let o = ''
@@ -86,6 +137,10 @@ function barePrintfFormat(fmt, args) {
else if (spec === 'o') o += (Math.trunc(Number(arg)) >>> 0).toString(8)
else if (spec === 'c') o += String.fromCharCode(Number(arg) || 0)
else if (spec === 'f') o += String(Number(arg))
else if (spec === 'e') o += Number(arg).toExponential(6)
else if (spec === 'E')
o += Number(arg).toExponential(6).replace(/e/g, 'E')
else if (spec === 'b') o += barePrintfBackslashArg(String(arg))
else o += String(arg)
i = j
}
+45
View File
@@ -60,6 +60,51 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
for (const a of argv.slice(1)) {
if (
a === '-P' ||
a === '--physical' ||
a === '-L' ||
a === '--logical'
) {
continue
}
if (a.startsWith('-')) {
ctx.console.error('pwd: unsupported option ' + a)
ctx.exitCode = 1
return
}
ctx.console.error('pwd: too many arguments')
ctx.exitCode = 1
return
}
ctx.console.log(ctx.vfs.getcwd())
}
+83 -9
View File
@@ -60,23 +60,97 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
function bareParentLogical(p) {
const t = p.replace(/\/+$/, '') || '/'
if (t === '/') return '/'
const i = t.lastIndexOf('/')
return i <= 0 ? '/' : t.slice(0, i) || '/'
}
function bareJoinUnder(parent, rel) {
if (rel.startsWith('/')) return rel
const base = parent.replace(/\/+$/, '') || '/'
if (base === '/') return '/' + rel.replace(/^\/+/, '')
return base + '/' + rel.replace(/^\/+/, '')
}
const READLINK_MAX_SYMLINKS = 48
async function bareReadlinkCanon(ctx, start) {
let cur = ctx.vfs.resolveLogical(start)
for (let d = 0; d < READLINK_MAX_SYMLINKS; d++) {
const st = await ctx.vfs.lstat(cur)
if (!st) return null
if (st.type !== 'symlink') return cur
const raw = await ctx.vfs.readlink(cur)
const par = bareParentLogical(cur)
cur = ctx.vfs.resolveLogical(bareJoinUnder(par, raw))
}
return null
}
async function run(ctx, argv) {
let noNewline = false
let canonical = false
const paths = []
for (let i = 1; i < argv.length; i++) {
if (argv[i] === '-n') noNewline = true
else if (argv[i] !== '--') paths.push(argv[i])
const a = argv[i]
if (a === '-n') noNewline = true
else if (a === '-f' || a === '--canonicalize') canonical = true
else if (a !== '--') paths.push(a)
}
if (!paths.length) {
ctx.console.error('usage: readlink [-n] FILE')
ctx.console.error('usage: readlink [-fn] FILE...')
ctx.exitCode = 1
return
}
try {
const t = await ctx.vfs.readlink(paths[0])
ctx.console.log(noNewline ? t : t)
} catch (e) {
ctx.console.error('readlink: ' + ((e && e.message) || e))
ctx.exitCode = 1
const w = globalThis.process?.stdout?.write
for (let pi = 0; pi < paths.length; pi++) {
const p = paths[pi]
try {
const out = canonical ? await bareReadlinkCanon(ctx, p) : await ctx.vfs.readlink(p)
if (canonical && out == null) {
ctx.console.error('readlink: ' + p + ': Invalid argument')
ctx.exitCode = 1
continue
}
const line = String(out)
if (noNewline && typeof w === 'function') {
w.call(globalThis.process.stdout, line)
if (pi < paths.length - 1) w.call(globalThis.process.stdout, '\n')
} else {
ctx.console.log(line)
}
} catch (e) {
ctx.console.error('readlink: ' + ((e && e.message) || e))
ctx.exitCode = 1
}
}
}
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* rm — remove files or directories.
* Flags: -r -R --recursive, -f --force, -- ; bundled e.g. -rf
+72 -3
View File
@@ -60,16 +60,85 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
function bareParentLogical(p) {
const t = p.replace(/\/+$/, '') || '/'
if (t === '/') return '/'
const i = t.lastIndexOf('/')
return i <= 0 ? '/' : t.slice(0, i) || '/'
}
async function run(ctx, argv) {
const paths = argv.slice(1).filter((a) => a && a !== '--')
let parents = false
const paths = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-p' || a === '--parents') {
parents = true
continue
}
if (a === '--') {
paths.push(...argv.slice(i + 1))
break
}
if (a.startsWith('-')) {
ctx.console.error('rmdir: unsupported option ' + a)
ctx.exitCode = 1
return
}
paths.push(a)
}
if (!paths.length) {
ctx.console.error('usage: rmdir DIRECTORY...')
ctx.console.error('usage: rmdir [-p] DIRECTORY...')
ctx.exitCode = 1
return
}
for (const p of paths) {
try {
await ctx.vfs.rmdir(p)
if (!parents) {
await ctx.vfs.rmdir(p)
continue
}
let cur = ctx.vfs.resolveLogical(p).replace(/\/+$/, '') || '/'
/** @type {string[]} */
const stack = []
while (cur !== '/' && cur !== '') {
stack.push(cur)
cur = bareParentLogical(cur)
}
for (const d of stack) {
try {
await ctx.vfs.rmdir(d)
} catch {
break
}
}
} catch (e) {
ctx.console.error('rmdir: ' + p + ': ' + ((e && e.message) || e))
ctx.exitCode = 1
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
const save = ctx.saveVault
if (typeof save !== 'function') {
+33 -2
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* POSIX-oriented sed engine for Bare OS /bin/sed (no import; concatenated before src/sed.js).
* Covers: -n -e -f -E, addresses (#,$,/re/,n,m,n~s), s///[ngp0-9], y///, d D p P n N,
@@ -841,8 +868,12 @@ async function run(ctx, argv) {
ctx.exitCode = 1
return
}
scripts.push(a)
files.push(...argv.slice(i + 1))
if (!scripts.length) {
scripts.push(a)
files.push(...argv.slice(i + 1))
} else {
files.push(...argv.slice(i))
}
break
}
if (!scripts.length) {
+63 -3
View File
@@ -60,8 +60,59 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
const args = argv.slice(1).filter((a) => !a.startsWith('-'))
let sep = '\n'
let equalWidth = false
const args = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-s' && argv[i + 1]) {
const s = argv[++i]
sep = s === '\\n' ? '\n' : s
continue
}
if (a === '-w') {
equalWidth = true
continue
}
if (a === '--') {
args.push(...argv.slice(i + 1))
break
}
if (a.startsWith('-')) {
ctx.console.error('seq: unsupported option ' + a)
ctx.exitCode = 1
return
}
args.push(a)
}
let a = 1
let step = 1
let b = 1
@@ -80,9 +131,18 @@ async function run(ctx, argv) {
ctx.exitCode = 1
return
}
/** @type {string[]} */
const out = []
if (step > 0) {
for (let x = a; x <= b; x += step) ctx.console.log(String(x))
for (let x = a; x <= b; x += step) out.push(String(x))
} else {
for (let x = a; x >= b; x += step) ctx.console.log(String(x))
for (let x = a; x >= b; x += step) out.push(String(x))
}
const w = equalWidth && out.length ? Math.max(...out.map((s) => s.length)) : 0
const fmt = (s) => (w ? s.padStart(w, '0') : s)
if (sep === '\n') {
for (const s of out) ctx.console.log(fmt(s))
} else {
ctx.console.log(out.map(fmt).join(sep))
}
}
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
const sec = parseFloat(argv[1] || '0')
if (Number.isNaN(sec) || sec < 0) {
+222 -2
View File
@@ -60,9 +60,179 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* @param {string} line
* @param {string | null} delim
* @param {number} start1
* @param {number | null} end1
*/
function sortKeySlice(line, delim, start1, end1) {
/** @type {string[]} */
let fields
if (delim != null) {
if (delim === '') fields = line.split('')
else fields = line.split(delim)
} else {
const t = line.trim()
fields = t.length ? t.split(/\s+/) : []
}
const i0 = Math.max(0, start1 - 1)
const i1 = end1 != null ? end1 : fields.length
const slice = fields.slice(i0, i1)
if (delim != null) return slice.join(delim)
return slice.join(' ')
}
async function run(ctx, argv) {
const vfs = ctx.vfs
const files = argv.slice(1).filter((a) => !a.startsWith('-'))
let numeric = false
let reverse = false
let uniq = false
let fold = false
/** @type {string | undefined} */
let delim
/** @type {number | null} */
let keyStart = null
/** @type {number | null} */
let keyEnd = null
let i = 1
while (i < argv.length) {
const a = argv[i]
if (a === '--') {
i++
break
}
if (a === '-n' || a === '--numeric-sort' || a === '-g') {
numeric = true
i++
continue
}
if (a === '-r' || a === '--reverse') {
reverse = true
i++
continue
}
if (a === '-u' || a === '--unique') {
uniq = true
i++
continue
}
if (a === '-f' || a === '--ignore-case') {
fold = true
i++
continue
}
if (a === '-t' || a === '--field-separator') {
const d = argv[++i]
if (d === undefined) {
ctx.console.error('sort: option requires an argument -- t')
ctx.exitCode = 1
return
}
delim = d === '\\t' ? '\t' : d
i++
continue
}
if (a.startsWith('-t') && a.length > 2) {
delim = a.slice(2) === 't' ? '\t' : a.slice(2)
i++
continue
}
if (a === '-k' || a === '--key') {
const spec = argv[++i]
if (spec === undefined) {
ctx.console.error('sort: option requires an argument -- k')
ctx.exitCode = 1
return
}
const parts = String(spec).split(',')
keyStart = Number.parseInt(parts[0], 10)
keyEnd =
parts[1] !== undefined && parts[1] !== ''
? Number.parseInt(parts[1], 10)
: null
if (!Number.isFinite(keyStart) || keyStart < 1) {
ctx.console.error('sort: invalid key specification')
ctx.exitCode = 1
return
}
if (
keyEnd != null &&
(!Number.isFinite(keyEnd) || keyEnd < keyStart)
) {
ctx.console.error('sort: invalid key specification')
ctx.exitCode = 1
return
}
i++
continue
}
if (/^-k\d/.test(a)) {
const spec = a.slice(2)
const parts = String(spec).split(',')
keyStart = Number.parseInt(parts[0], 10)
keyEnd =
parts[1] !== undefined && parts[1] !== ''
? Number.parseInt(parts[1], 10)
: null
if (!Number.isFinite(keyStart) || keyStart < 1) {
ctx.console.error('sort: invalid key specification')
ctx.exitCode = 1
return
}
i++
continue
}
if (a.startsWith('-') && a.length > 1) {
let j = 1
let ok = true
while (j < a.length) {
const c = a[j++]
if (c === 'n') numeric = true
else if (c === 'r') reverse = true
else if (c === 'u') uniq = true
else if (c === 'f') fold = true
else {
ok = false
break
}
}
if (ok) {
i++
continue
}
}
break
}
const files = argv.slice(i).filter((x) => x !== '--')
/** @type {string[]} */
let lines = []
if (!files.length) {
@@ -71,6 +241,13 @@ async function run(ctx, argv) {
if (lines.length && lines[lines.length - 1] === '') lines.pop()
} else {
for (const f of files) {
if (f === '-') {
const s = bareStdin(ctx)
const part = s.length ? s.split('\n') : []
if (part.length && part[part.length - 1] === '') part.pop()
lines.push(...part)
continue
}
const buf = await vfs.readFile(f)
if (!buf) {
ctx.console.error('sort: ' + f + ': No such file')
@@ -82,6 +259,49 @@ async function run(ctx, argv) {
lines.push(...part)
}
}
lines.sort((x, y) => (x < y ? -1 : x > y ? 1 : 0))
const dForKey = delim === undefined ? null : delim
/** @param {string} s */
function sortKey(s) {
const keyText =
keyStart != null
? sortKeySlice(s, dForKey, keyStart, keyEnd)
: s
if (numeric) {
const m = String(keyText).match(
/^\s*(-?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)/
)
if (m) return { n: Number(m[1]), raw: keyText }
return { n: Number.POSITIVE_INFINITY, raw: keyText }
}
if (fold) return { n: 0, raw: keyText.toLowerCase() }
return { n: 0, raw: keyText }
}
lines.sort((x, y) => {
const kx = sortKey(x)
const ky = sortKey(y)
if (numeric) {
if (kx.n !== ky.n) {
const ord = kx.n < ky.n ? -1 : 1
return reverse ? -ord : ord
}
}
const cmp =
kx.raw < ky.raw ? -1 : kx.raw > ky.raw ? 1 : x < y ? -1 : x > y ? 1 : 0
return reverse ? -cmp : cmp
})
if (uniq) {
const out = []
let prev = null
for (const ln of lines) {
if (ln !== prev) out.push(ln)
prev = ln
}
lines = out
}
if (lines.length) ctx.console.log(lines.join('\n'))
}
+92 -2
View File
@@ -60,10 +60,96 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* @param {Record<string, unknown>} st
* @param {string} displayPath
* @param {string} fmt
*/
function statApplyFormat(st, displayPath, fmt) {
let out = ''
for (let i = 0; i < fmt.length; i++) {
if (fmt[i] === '%' && i + 1 < fmt.length) {
const c = fmt[++i]
if (c === 'n')
out += displayPath.split('/').pop() || displayPath
else if (c === 'N') out += displayPath
else if (c === 's') out += String(st.size ?? 0)
else if (c === 'Y')
out += String(
Math.floor(
(typeof st.mtimeMs === 'number' ? st.mtimeMs : Date.now()) / 1000
)
)
else if (c === 'A')
out += bareFormatModeString(st.mode, st.type)
else if (c === 'U') out += String(st.user ?? '')
else if (c === 'G') out += String(st.group ?? '')
else if (c === 'u') out += String(st.uid ?? 0)
else if (c === 'g') out += String(st.gid ?? 0)
else if (c === '%') out += '%'
else out += '%' + c
} else {
out += fmt[i]
}
}
return out
}
async function run(ctx, argv) {
const paths = argv.slice(1).filter((a) => a && !a.startsWith('-'))
/** @type {string | null} */
let formatStr = null
/** @type {string[]} */
const paths = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-c' && argv[i + 1]) {
formatStr = argv[++i]
continue
}
if (a.startsWith('--format=')) {
formatStr = a.slice('--format='.length)
continue
}
if (a === '--format' && argv[i + 1]) {
formatStr = argv[++i]
continue
}
if (a.startsWith('-')) {
ctx.console.error('stat: unsupported option ' + a)
ctx.exitCode = 1
return
}
paths.push(a)
}
if (!paths.length) {
ctx.console.error('usage: stat FILE...')
ctx.console.error('usage: stat [-c FORMAT | --format=FORMAT] FILE...')
ctx.exitCode = 1
return
}
@@ -75,6 +161,10 @@ async function run(ctx, argv) {
ctx.exitCode = 1
continue
}
if (formatStr != null) {
ctx.console.log(statApplyFormat(st, p, formatStr))
continue
}
const modeStr = bareFormatModeString(st.mode, st.type)
ctx.console.log(
'File: ' +
+373 -15
View File
@@ -60,25 +60,338 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* tail — last lines/bytes of files; -f/--follow with vfs.watch or poll.
* Test/limit: BARE_OS_TAIL_F_POLL_MS, BARE_OS_TAIL_F_MAX_ROUNDS (empty = unlimited).
*/
function bareTailSleep(ms) {
return new Promise((r) => setTimeout(r, ms))
}
function bareTailEnv(ctx) {
return (ctx.vfs && ctx.vfs.env) || ctx.env || {}
}
function bareTailPollMs(env) {
const n = Number.parseInt(String(env.BARE_OS_TAIL_F_POLL_MS || '1000'), 10)
return Number.isFinite(n) && n >= 0 ? n : 1000
}
function bareTailMaxRounds(env) {
const raw = env.BARE_OS_TAIL_F_MAX_ROUNDS
if (raw == null || raw === '') return Number.POSITIVE_INFINITY
const n = Number.parseInt(String(raw), 10)
return Number.isFinite(n) && n >= 0 ? n : Number.POSITIVE_INFINITY
}
function bareTailUseWatch(ctx, vfs) {
if (typeof vfs.watch !== 'function') return false
const caps = ctx.bareOsRuntimeCaps
if (caps && caps.features && caps.features.vfsWatch === false) return false
return true
}
/** @param {string} s */
function bareTailSplitLines(s) {
const lines = s.split('\n')
if (s.endsWith('\n') && lines.length && lines[lines.length - 1] === '')
lines.pop()
return lines
}
/**
* @param {Uint8Array} u8
* @param {'end' | 'start'} which
* @param {number} n count or 1-based start (+ mode)
*/
function bareTailBytesSlice(u8, which, n, fromPlus) {
if (!u8 || !u8.byteLength) return new Uint8Array(0)
if (which === 'end') {
if (n <= 0) return new Uint8Array(0)
const start = Math.max(0, u8.byteLength - n)
return u8.subarray(start)
}
/* from start (+): GNU tail -c +K starts at byte K (1-based) */
const idx = fromPlus ? Math.max(0, n - 1) : 0
return idx >= u8.byteLength ? new Uint8Array(0) : u8.subarray(idx)
}
/**
* @param {string} s
* @param {'end' | 'start'} which
* @param {number} n
* @param {boolean} fromPlus
* @param {*} b4a
*/
function bareTailLinesText(s, which, n, fromPlus, b4a) {
const lines = bareTailSplitLines(s)
if (which === 'end') {
if (n <= 0) return ''
const slice = lines.length <= n ? lines : lines.slice(-n)
return slice.join('\n')
}
/* +N: from line N (1-based) */
const start = fromPlus ? Math.max(0, n - 1) : 0
const slice = lines.slice(start)
return slice.join('\n')
}
/**
* Emit follow chunks as full lines via console.log (tests capture log).
* @param {string} chunk
* @param {{ partial: string }} state
*/
function bareTailFollowEmit(ctx, chunk, state) {
const combined = state.partial + chunk
const lines = combined.split('\n')
state.partial = lines.pop() || ''
for (const ln of lines) ctx.console.log(ln)
}
function bareTailFollowFlush(ctx, state) {
if (state.partial !== '') {
ctx.console.log(state.partial)
state.partial = ''
}
}
/**
* @param {*} ctx
* @param {string} path
* @param {*} vfs
* @param {number} startOffset byte offset after initial read (next byte to emit)
*/
async function bareTailFollow(ctx, path, vfs, startOffset) {
const env = bareTailEnv(ctx)
const pollMs = bareTailPollMs(env)
const maxRounds = bareTailMaxRounds(env)
const state = { partial: '' }
let offset = startOffset
let rounds = 0
const pumpOnce = async () => {
const buf = await vfs.readFile(path)
if (!buf) {
offset = 0
return
}
const u8 = buf instanceof Uint8Array ? buf : ctx.b4a.from(buf)
if (u8.byteLength < offset) {
offset = 0
}
if (u8.byteLength > offset) {
const piece = u8.subarray(offset)
offset = u8.byteLength
bareTailFollowEmit(ctx, ctx.b4a.toString(piece), state)
}
}
if (
bareTailUseWatch(ctx, vfs) &&
maxRounds === Number.POSITIVE_INFINITY
) {
let handle = null
try {
handle = await vfs.watch(path)
} catch {
handle = null
}
if (handle && handle.watcher) {
try {
for await (const _ of handle.watcher) {
await pumpOnce()
}
} finally {
if (typeof handle.destroy === 'function') handle.destroy()
}
bareTailFollowFlush(ctx, state)
return
}
}
while (rounds < maxRounds) {
await bareTailSleep(pollMs)
await pumpOnce()
rounds++
}
bareTailFollowFlush(ctx, state)
}
async function run(ctx, argv) {
const vfs = ctx.vfs
let n = 10
let start = 1
if (argv[1] === '-n' && argv[2]) {
n = parseInt(argv[2], 10) || 10
start = 3
let follow = false
/** @type {'lines' | 'bytes'} */
let mode = 'lines'
let count = 10
let fromPlus = false
let i = 1
while (i < argv.length) {
const a = argv[i]
if (a === '--') {
i++
break
}
if (a === '-f' || a === '--follow') {
follow = true
i++
continue
}
if (a === '-F') {
follow = true
i++
continue
}
if (a === '-n' || a === '--lines') {
const v = argv[i + 1]
if (v == null) {
ctx.console.error('tail: option requires an argument -- n')
ctx.exitCode = 1
return
}
i++
mode = 'lines'
if (v.startsWith('+')) {
fromPlus = true
count = Number.parseInt(v.slice(1), 10) || 1
} else {
fromPlus = false
count = Number.parseInt(v, 10) || 10
}
i++
continue
}
if (a.startsWith('-n') && a.length > 2) {
const v = a.slice(2)
mode = 'lines'
if (v.startsWith('+')) {
fromPlus = true
count = Number.parseInt(v.slice(1), 10) || 1
} else {
fromPlus = false
count = Number.parseInt(v, 10) || 10
}
i++
continue
}
if (a === '-c' || a === '--bytes') {
const v = argv[i + 1]
if (v == null) {
ctx.console.error('tail: option requires an argument -- c')
ctx.exitCode = 1
return
}
i++
mode = 'bytes'
if (v.startsWith('+')) {
fromPlus = true
count = Number.parseInt(v.slice(1), 10) || 1
} else {
fromPlus = false
count = Number.parseInt(v, 10) || 0
}
i++
continue
}
if (a.startsWith('-c') && a.length > 2) {
const v = a.slice(2)
mode = 'bytes'
if (v.startsWith('+')) {
fromPlus = true
count = Number.parseInt(v.slice(1), 10) || 1
} else {
fromPlus = false
count = Number.parseInt(v, 10) || 0
}
i++
continue
}
if (a.startsWith('-') && /^-\d+$/.test(a)) {
mode = 'lines'
fromPlus = false
count = Number.parseInt(a.slice(1), 10) || 10
i++
continue
}
if (a.startsWith('-')) {
ctx.console.error('tail: unrecognized option: ' + a)
ctx.exitCode = 1
return
}
break
}
const files = argv.slice(start).filter((a) => a !== '--')
const take = (s) => {
let lines = s.split('\n')
if (s.endsWith('\n') && lines[lines.length - 1] === '') lines.pop()
const slice = lines.length <= n ? lines : lines.slice(-n)
ctx.console.log(slice.join('\n'))
}
if (!files.length) {
take(bareStdin(ctx))
const files = argv.slice(i).filter((x) => x !== '--')
if (follow && !files.length) {
ctx.console.error(
'tail: --follow is not supported for stdin on Bare OS (stdin is a captured string)'
)
ctx.exitCode = 1
return
}
if (follow && files.length > 1) {
ctx.console.error(
'tail: following multiple files is not supported (use one path)'
)
ctx.exitCode = 1
return
}
if (!files.length) {
const s = bareStdin(ctx)
if (mode === 'bytes') {
const u8 = ctx.b4a.from(s, 'utf8')
const slice = bareTailBytesSlice(
u8,
fromPlus ? 'start' : 'end',
count,
fromPlus
)
const out = ctx.b4a.toString(slice)
if (out) ctx.console.log(out)
} else {
const out = bareTailLinesText(
s,
fromPlus ? 'start' : 'end',
count,
fromPlus,
ctx.b4a
)
if (out) ctx.console.log(out)
}
return
}
for (const f of files) {
const buf = await vfs.readFile(f)
if (!buf) {
@@ -86,6 +399,51 @@ async function run(ctx, argv) {
continue
}
if (files.length > 1) ctx.console.log('==> ' + f + ' <==')
take(ctx.b4a.toString(buf))
const u8 = buf instanceof Uint8Array ? buf : ctx.b4a.from(buf)
const text = ctx.b4a.toString(u8)
if (!follow) {
if (mode === 'bytes') {
const slice = bareTailBytesSlice(
u8,
fromPlus ? 'start' : 'end',
count,
fromPlus
)
const out = ctx.b4a.toString(slice)
if (out) ctx.console.log(out)
} else {
const out = bareTailLinesText(
text,
fromPlus ? 'start' : 'end',
count,
fromPlus,
ctx.b4a
)
if (out) ctx.console.log(out)
}
continue
}
if (mode === 'bytes') {
const slice = bareTailBytesSlice(
u8,
fromPlus ? 'start' : 'end',
count,
fromPlus
)
const out = ctx.b4a.toString(slice)
if (out) ctx.console.log(out)
await bareTailFollow(ctx, f, vfs, u8.byteLength)
} else {
const out = bareTailLinesText(
text,
fromPlus ? 'start' : 'end',
count,
fromPlus,
ctx.b4a
)
if (out) ctx.console.log(out)
const startOffset = u8.byteLength
await bareTailFollow(ctx, f, vfs, startOffset)
}
}
}
+39 -3
View File
@@ -60,12 +60,42 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
let append = false
let unbuf = false
const files = []
for (let i = 1; i < argv.length; i++) {
if (argv[i] === '-a' || argv[i] === '--append') append = true
else if (argv[i] !== '--') files.push(argv[i])
const a = argv[i]
if (a === '-a' || a === '--append') append = true
else if (a === '-u') unbuf = true
else if (a !== '--') files.push(a)
}
const text = bareStdin(ctx)
const data = ctx.b4a.from(text)
@@ -83,5 +113,11 @@ async function run(ctx, argv) {
ctx.exitCode = 1
}
}
ctx.console.log(text.replace(/\n$/, ''))
const out = text.endsWith('\n') ? text : text + '\n'
const pw = globalThis.process?.stdout?.write
if (unbuf && typeof pw === 'function') {
pw.call(globalThis.process.stdout, out)
} else {
ctx.console.log(text.replace(/\n$/, ''))
}
}
+52
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function evalTest(ctx, args) {
if (!args.length) return false
if (args[0] === '!') {
@@ -68,6 +95,27 @@ async function evalTest(ctx, args) {
}
if (args.length === 3) {
const [a, op, b] = args
if (op === '-eq' || op === '-ne' || op === '-lt' || op === '-le' || op === '-gt' || op === '-ge') {
const la = Number.parseInt(String(a), 10)
const lb = Number.parseInt(String(b), 10)
if (!Number.isFinite(la) || !Number.isFinite(lb)) return false
switch (op) {
case '-eq':
return la === lb
case '-ne':
return la !== lb
case '-lt':
return la < lb
case '-le':
return la <= lb
case '-gt':
return la > lb
case '-ge':
return la >= lb
default:
return false
}
}
if (op === '=') return a === b
if (op === '!=') return a !== b
return false
@@ -75,6 +123,10 @@ async function evalTest(ctx, args) {
if (args.length === 2) {
const op = args[0]
const p = args[1]
if (op === '-h' || op === '-L') {
const st = await ctx.vfs.lstat(p)
return st != null && st.type === 'symlink'
}
const st = await ctx.vfs.stat(p)
if (op === '-e' || op === '-a') return st != null
if (op === '-f') return st != null && st.type === 'file'
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* Theme switcher: list presets, show current, set preset (updates ~/.barerc), re-apply.
* Uses ctx.bareOsListThemes / ctx.bareOsApplyTheme when provided by the booter.
+44 -3
View File
@@ -60,10 +60,43 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
const cmd = argv.slice(1)
let portable = false
let i = 1
if (argv[1] === '-p') {
portable = true
i = 2
}
const cmd = argv.slice(i)
if (!cmd.length) {
ctx.console.error('usage: time COMMAND [ARG...]')
ctx.console.error('usage: time [-p] COMMAND [ARG...]')
ctx.exitCode = 1
return
}
@@ -76,6 +109,14 @@ async function run(ctx, argv) {
}
} finally {
const ms = Date.now() - t0
ctx.console.error('real\t' + (ms / 1000).toFixed(3) + 's')
if (portable) {
ctx.console.error(
'real ' +
(ms / 1000).toFixed(2) +
'\nuser 0.00\nsys 0.00'
)
} else {
ctx.console.error('real\t' + (ms / 1000).toFixed(3) + 's')
}
}
}
+175 -3
View File
@@ -60,17 +60,189 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
function parseTouchDate(s) {
const t = String(s || '').trim()
if (t.startsWith('@')) {
const sec = Number(t.slice(1))
if (Number.isFinite(sec)) return Math.round(sec * 1000)
return null
}
const ms = Date.parse(t)
return Number.isFinite(ms) ? ms : null
}
function parseTouchArgv(argv) {
let onlyA = false
let onlyM = false
/** @type {{ kind: 'd', s: string } | { kind: 'r', path: string } | null} */
let timeSpec = null
const files = []
let i = 1
while (i < argv.length) {
const a = argv[i]
if (a === '--') {
files.push(...argv.slice(i + 1))
return { onlyA, onlyM, timeSpec, files }
}
if (a === '-a') {
onlyA = true
i++
continue
}
if (a === '-m') {
onlyM = true
i++
continue
}
if (a === '-d' || a === '--date') {
i++
if (i >= argv.length) {
return { err: 'touch: option requires an argument -- ' + a.slice(1) }
}
timeSpec = { kind: 'd', s: argv[i] }
i++
continue
}
if (a === '-r' || a === '--reference') {
i++
if (i >= argv.length) {
return { err: 'touch: option requires an argument -- reference' }
}
timeSpec = { kind: 'r', path: argv[i] }
i++
continue
}
if (a.startsWith('-')) {
return { err: 'touch: unsupported option ' + a }
}
files.push(a)
i++
}
return { onlyA, onlyM, timeSpec, files }
}
async function run(ctx, argv) {
const files = argv.slice(1).filter((a) => !a.startsWith('-'))
const p = parseTouchArgv(argv)
if (p.err) {
ctx.console.error(p.err)
ctx.exitCode = 1
return
}
const { onlyA, onlyM, timeSpec, files } = p
if (!files.length) {
ctx.console.error('touch: missing file operand')
ctx.exitCode = 1
return
}
/** @type {number | null} */
let T = null
if (timeSpec) {
if (timeSpec.kind === 'd') {
const ms = parseTouchDate(timeSpec.s)
if (ms == null) {
ctx.console.error('touch: invalid date ' + JSON.stringify(timeSpec.s))
ctx.exitCode = 1
return
}
T = ms
} else {
try {
const st = await ctx.vfs.lstat(timeSpec.path)
if (!st) {
ctx.console.error(
'touch: failed to get attributes of ' +
timeSpec.path +
': No such file'
)
ctx.exitCode = 1
return
}
T =
typeof st.mtimeMs === 'number' && Number.isFinite(st.mtimeMs)
? st.mtimeMs
: null
} catch (e) {
ctx.console.error(
'touch: ' + timeSpec.path + ': ' + ((e && e.message) || String(e))
)
ctx.exitCode = 1
return
}
}
}
if (T == null) T = Date.now()
let changeMtime = true
let changeCtime = true
if (onlyA && !onlyM) {
changeMtime = false
changeCtime = true
} else if (onlyM && !onlyA) {
changeMtime = true
changeCtime = false
}
const vfs = ctx.vfs
for (const f of files) {
try {
const existing = await ctx.vfs.readFile(f)
await ctx.vfs.writeFile(f, existing || ctx.b4a.from(''))
const existingBuf = await vfs.readFile(f)
let st = null
try {
st = await vfs.lstat(f)
} catch {
st = null
}
const buf = existingBuf || ctx.b4a.from('')
let newM
let newC
if (!st) {
newM = T
newC = T
} else {
const pm =
typeof st.mtimeMs === 'number' && Number.isFinite(st.mtimeMs)
? st.mtimeMs
: T
const pc =
typeof st.ctimeMs === 'number' && Number.isFinite(st.ctimeMs)
? st.ctimeMs
: T
newM = changeMtime ? T : pm
newC = changeCtime ? T : pc
}
await vfs.writeFile(f, buf, {
bumpMtime: false,
touchCtime: false,
mtimeMs: newM,
ctimeMs: newC
})
} catch (e) {
ctx.console.error('touch: ' + f + ': ' + (e.message || e))
ctx.exitCode = 1
+54 -3
View File
@@ -60,6 +60,57 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
const TR_CLASS = {
'[:alnum:]':
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
'[:alpha:]': 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
'[:blank:]': ' \t',
'[:cntrl:]': [...Array(32).keys()].map((i) => String.fromCharCode(i)).join('') + '\x7f',
'[:digit:]': '0123456789',
'[:lower:]': 'abcdefghijklmnopqrstuvwxyz',
'[:print:]': [...Array(95).keys()].map((i) => String.fromCharCode(i + 32)).join(''),
'[:punct:]': '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~',
'[:space:]': ' \t\n\r\v\f',
'[:upper:]': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'[:xdigit:]': '0123456789abcdefABCDEF'
}
/** @param {string} s */
function trExpandClasses(s) {
let out = s
for (const [name, chars] of Object.entries(TR_CLASS)) {
out = out.split(name).join(chars)
}
return out
}
async function run(ctx, argv) {
let del = false
const sets = []
@@ -74,14 +125,14 @@ async function run(ctx, argv) {
}
const s = bareStdin(ctx)
if (del) {
const kill = new Set(sets[0].split(''))
const kill = new Set(trExpandClasses(sets[0]).split(''))
let o = ''
for (const ch of s) if (!kill.has(ch)) o += ch
ctx.console.log(o)
return
}
const from = sets[0]
const to = sets[1]
const from = trExpandClasses(sets[0])
const to = trExpandClasses(sets[1])
const map = Object.create(null)
const n = Math.max(from.length, to.length)
for (let i = 0; i < n; i++) {
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
ctx.exitCode = 0
}
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
const tty = globalThis.process?.stdout?.isTTY
if (tty) {
+69 -10
View File
@@ -60,15 +60,74 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
const flagArgs = argv.slice(1).filter((a) => a.startsWith('-') && a !== '--')
const all = argv.includes('-a')
const noFlag = flagArgs.length === 0
const wantS = all || argv.includes('-s') || noFlag
const wantN = all || argv.includes('-n')
const wantR = all || argv.includes('-r')
const wantM = all || argv.includes('-m')
const wantV = all || argv.includes('-v')
const e = ctx.vfs.env || {}
const nodename = e.HOSTNAME || e.NAME || 'bare-os'
let all = false
let wantS = false
let wantN = false
let wantR = false
let wantM = false
let wantV = false
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '--') break
if (a === '-a' || a === '--all') {
all = true
continue
}
if (!a.startsWith('-') || a === '-') {
ctx.console.error('uname: unexpected argument ' + a)
ctx.exitCode = 1
return
}
for (let j = 1; j < a.length; j++) {
const c = a[j]
if (c === 'a') all = true
else if (c === 's') wantS = true
else if (c === 'n') wantN = true
else if (c === 'r') wantR = true
else if (c === 'm') wantM = true
else if (c === 'v') wantV = true
else {
ctx.console.error('uname: invalid option -- ' + c)
ctx.exitCode = 1
return
}
}
}
if (all) {
wantS = wantN = wantR = wantM = wantV = true
} else if (!wantS && !wantN && !wantR && !wantM && !wantV) {
wantS = true
}
let name = 'BareOS'
let version = '0.1'
@@ -85,10 +144,10 @@ async function run(ctx, argv) {
const parts = []
if (wantS) parts.push(name)
if (wantN) parts.push('bare-os')
if (wantN) parts.push(nodename)
if (wantR) parts.push(version)
if (wantV) parts.push('bare-userland')
if (wantM) parts.push('unknown')
if (wantV) parts.push('bare-userland')
ctx.console.log(parts.join(' '))
}
+127 -13
View File
@@ -60,39 +60,153 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
function count(s, b4a) {
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* wc — line, word, and byte counts.
* Default (no flags): -l -w -c. Flags are combinable.
*/
/**
* @param {string | Uint8Array} input
* @param {*} b4a
*/
function wcCount(input, b4a) {
const bytes =
input instanceof Uint8Array
? input.byteLength
: b4a.from(String(input), 'utf8').length
const s =
input instanceof Uint8Array ? b4a.toString(input) : String(input)
const lines = (s.match(/\n/g) || []).length
const words = s.trim() ? s.trim().split(/\s+/).length : 0
const bytes = b4a.from(s, 'utf8').length
const words = (s.match(/\S+/g) || []).length
return { lines, words, bytes }
}
async function run(ctx, argv) {
const vfs = ctx.vfs
const files = argv.slice(1).filter((a) => !a.startsWith('-'))
if (!files.length) {
let wantL = false
let wantW = false
let wantC = false
const paths = []
let i = 1
while (i < argv.length) {
const a = argv[i]
if (a === '--') {
i++
break
}
if (a === '-l' || a === '--lines') {
wantL = true
i++
continue
}
if (a === '-w' || a === '--words') {
wantW = true
i++
continue
}
if (a === '-c' || a === '--bytes' || a === '-m') {
wantC = true
i++
continue
}
if (a.startsWith('-') && a.length > 1) {
let j = 1
let ok = true
while (j < a.length) {
const c = a[j++]
if (c === 'l') wantL = true
else if (c === 'w') wantW = true
else if (c === 'c' || c === 'm') wantC = true
else {
ok = false
break
}
}
if (ok) {
i++
continue
}
}
paths.push(a)
i++
}
if (!wantL && !wantW && !wantC) {
wantL = true
wantW = true
wantC = true
}
function fmt(c, name) {
const parts = []
if (wantL) parts.push(String(c.lines))
if (wantW) parts.push(String(c.words))
if (wantC) parts.push(String(c.bytes))
let s = ' ' + parts.join(' ')
if (name != null) s += ' ' + name
return s
}
if (!paths.length) {
const s = bareStdin(ctx)
const c = count(s, ctx.b4a)
ctx.console.log(' ' + c.lines + ' ' + c.words + ' ' + c.bytes)
const c = wcCount(s, ctx.b4a)
ctx.console.log(fmt(c, null))
return
}
let tLines = 0
let tWords = 0
let tBytes = 0
for (const f of files) {
for (const f of paths) {
if (f === '-') {
const s = bareStdin(ctx)
const c = wcCount(s, ctx.b4a)
tLines += c.lines
tWords += c.words
tBytes += c.bytes
ctx.console.log(fmt(c, '-'))
continue
}
const buf = await vfs.readFile(f)
if (!buf) {
ctx.console.error('wc: ' + f + ': No such file')
continue
}
const s = ctx.b4a.toString(buf)
const c = count(s, ctx.b4a)
const u8 = buf instanceof Uint8Array ? buf : ctx.b4a.from(buf)
const c = wcCount(u8, ctx.b4a)
tLines += c.lines
tWords += c.words
tBytes += c.bytes
ctx.console.log(' ' + c.lines + ' ' + c.words + ' ' + c.bytes + ' ' + f)
ctx.console.log(fmt(c, f))
}
if (files.length > 1) {
ctx.console.log(' ' + tLines + ' ' + tWords + ' ' + tBytes + ' total')
if (paths.length > 1) {
ctx.console.log(
fmt({ lines: tLines, words: tWords, bytes: tBytes }, 'total')
)
}
}
+44 -1
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
function joinDirFile(dir, name) {
if (!dir || dir === '.') return name
const d = dir.endsWith('/') ? dir.slice(0, -1) : dir
@@ -67,7 +94,23 @@ function joinDirFile(dir, name) {
}
async function run(ctx, argv) {
const names = argv.slice(1).filter((a) => !a.startsWith('-'))
/** @type {string[]} */
const names = []
let i = 1
while (i < argv.length) {
const a = argv[i]
if (a === '--') {
names.push(...argv.slice(i + 1))
break
}
if (a.startsWith('-')) {
ctx.console.error('which: unsupported option ' + a)
ctx.exitCode = 1
return
}
names.push(a)
i++
}
if (!names.length) {
ctx.console.error('which: missing argument')
ctx.exitCode = 1
+27
View File
@@ -60,6 +60,33 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
async function run(ctx, argv) {
ctx.console.log(ctx.vfs.env.USER || ctx.vfs.env.LOGNAME || 'guest')
}
+68 -2
View File
@@ -60,10 +60,38 @@ function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* Bounded xargs for Bare OS: invokes ctx.runBinCommand only (no host spawn).
* Limits: stdin 256KiB, 4096 whitespace/null tokens, 128 args per invocation,
* 64 invocations per run. Exceeding limits is a fatal error (exit 125).
* Supports -0/--null, -n, -I repl (replace repl in utility argv; implies -n 1 unless -n given).
*/
const MAX_STDIN = 256 * 1024
@@ -82,6 +110,9 @@ async function run(ctx, argv) {
const args = argv.slice(1)
let nullSep = false
let maxBatch = MAX_PER_INVOCATION
/** @type {string | null} */
let repl = null
let nExplicit = false
let i = 0
while (i < args.length && args[i].startsWith('-')) {
@@ -103,20 +134,38 @@ async function run(ctx, argv) {
return
}
maxBatch = Math.min(Number(n), MAX_PER_INVOCATION)
nExplicit = true
i += 2
continue
}
if (a.startsWith('-n') && a.length > 2 && /^\d+$/.test(a.slice(2))) {
const n = Number(a.slice(2))
maxBatch = Math.min(n, MAX_PER_INVOCATION)
nExplicit = true
i++
continue
}
if (a === '-I' || a === '-i') {
repl = args[i + 1] != null ? String(args[i + 1]) : '{}'
i += 2
if (!nExplicit) maxBatch = 1
continue
}
if (
(a.startsWith('-I') || a.startsWith('-i')) &&
a.length > 2 &&
a[2] !== '-'
) {
repl = a.slice(2) || '{}'
i++
if (!nExplicit) maxBatch = 1
continue
}
ctx.console.error('xargs: unsupported option: ' + a)
ctx.console.error(
'xargs: Bare OS supports: -0/--null, -n N (max ' +
MAX_PER_INVOCATION +
' per run)'
' per run), -I repl'
)
ctx.exitCode = 1
return
@@ -151,8 +200,25 @@ async function run(ctx, argv) {
return
}
/**
* @param {string[]} batch
*/
const runOne = async (batch) => {
await ctx.runBinCommand(cmd.concat(batch))
const subst = batch.join(' ')
/** @type {string[]} */
const toRun =
repl == null
? cmd.concat(batch)
: cmd.map((c) => {
let o = c
let guard = 0
while (o.includes(repl) && guard < 4096) {
o = o.split(repl).join(subst)
guard++
}
return o
})
await ctx.runBinCommand(toRun)
}
if (pieces.length === 0) {
+182 -182
View File
@@ -1,18 +1,18 @@
{
"version": 1,
"bundles": [
{
"path": "/lib/bare/bundles/b4a.js",
"keys": [
"b4a"
]
},
{
"path": "/lib/bare/bundles/safetyCatch.js",
"keys": [
"safetyCatch"
]
},
{
"path": "/lib/bare/bundles/b4a.js",
"keys": [
"b4a"
]
},
{
"path": "/lib/bare/bundles/hypercoreIdEncoding.js",
"keys": [
@@ -25,18 +25,18 @@
"compactEncoding"
]
},
{
"path": "/lib/bare/bundles/bareUrl.js",
"keys": [
"bareUrl"
]
},
{
"path": "/lib/bare/bundles/protomux.js",
"keys": [
"protomux"
]
},
{
"path": "/lib/bare/bundles/bareUrl.js",
"keys": [
"bareUrl"
]
},
{
"path": "/lib/bare/bundles/bareEncoding.js",
"keys": [
@@ -67,12 +67,6 @@
"bareAbortController"
]
},
{
"path": "/lib/bare/bundles/bareAnsiEscapes.js",
"keys": [
"bareAnsiEscapes"
]
},
{
"path": "/lib/bare/bundles/bareAddonResolve.js",
"keys": [
@@ -80,15 +74,15 @@
]
},
{
"path": "/lib/bare/bundles/bareReadline.js",
"path": "/lib/bare/bundles/bareAnsiEscapes.js",
"keys": [
"bareReadline"
"bareAnsiEscapes"
]
},
{
"path": "/lib/bare/bundles/bareAppKit.js",
"path": "/lib/bare/bundles/bareReadline.js",
"keys": [
"bareAppKit"
"bareReadline"
]
},
{
@@ -109,6 +103,12 @@
"bareAssert"
]
},
{
"path": "/lib/bare/bundles/fetch.js",
"keys": [
"fetch"
]
},
{
"path": "/lib/bare/bundles/bareAsyncHooks.js",
"keys": [
@@ -116,15 +116,9 @@
]
},
{
"path": "/lib/bare/bundles/bareAtomics.js",
"path": "/lib/bare/bundles/bareAppKit.js",
"keys": [
"bareAtomics"
]
},
{
"path": "/lib/bare/bundles/fetch.js",
"keys": [
"fetch"
"bareAppKit"
]
},
{
@@ -134,9 +128,15 @@
]
},
{
"path": "/lib/bare/bundles/bareBundleCompile.js",
"path": "/lib/bare/bundles/bareAtomics.js",
"keys": [
"bareBundleCompile"
"bareAtomics"
]
},
{
"path": "/lib/bare/bundles/bareBuffer.js",
"keys": [
"bareBuffer"
]
},
{
@@ -146,9 +146,9 @@
]
},
{
"path": "/lib/bare/bundles/bareBuffer.js",
"path": "/lib/bare/bundles/bareBundleCompile.js",
"keys": [
"bareBuffer"
"bareBundleCompile"
]
},
{
@@ -163,6 +163,12 @@
"bareBundleEvaluate"
]
},
{
"path": "/lib/bare/bundles/bareBoot.js",
"keys": [
"bareBoot"
]
},
{
"path": "/lib/bare/bundles/bareConsole.js",
"keys": [
@@ -176,15 +182,9 @@
]
},
{
"path": "/lib/bare/bundles/bareBoot.js",
"path": "/lib/bare/bundles/bareChannel.js",
"keys": [
"bareBoot"
]
},
{
"path": "/lib/bare/bundles/bareDaemon.js",
"keys": [
"bareDaemon"
"bareChannel"
]
},
{
@@ -194,9 +194,9 @@
]
},
{
"path": "/lib/bare/bundles/bareChannel.js",
"path": "/lib/bare/bundles/bareDaemon.js",
"keys": [
"bareChannel"
"bareDaemon"
]
},
{
@@ -217,6 +217,12 @@
"bareDns"
]
},
{
"path": "/lib/bare/bundles/bareExif.js",
"keys": [
"bareExif"
]
},
{
"path": "/lib/bare/bundles/bareEnv.js",
"keys": [
@@ -229,24 +235,12 @@
"bareCov"
]
},
{
"path": "/lib/bare/bundles/bareExif.js",
"keys": [
"bareExif"
]
},
{
"path": "/lib/bare/bundles/bareDgram.js",
"keys": [
"bareDgram"
]
},
{
"path": "/lib/bare/bundles/bareFfmpeg.js",
"keys": [
"bareFfmpeg"
]
},
{
"path": "/lib/bare/bundles/bareFfmpegEncodings.js",
"keys": [
@@ -254,15 +248,15 @@
]
},
{
"path": "/lib/bare/bundles/bareFormData.js",
"path": "/lib/bare/bundles/bareFfmpeg.js",
"keys": [
"bareFormData"
"bareFfmpeg"
]
},
{
"path": "/lib/bare/bundles/bareFileLogger.js",
"path": "/lib/bare/bundles/bareFormData.js",
"keys": [
"bareFileLogger"
"bareFormData"
]
},
{
@@ -278,15 +272,15 @@
]
},
{
"path": "/lib/bare/bundles/bareHrtime.js",
"path": "/lib/bare/bundles/bareFileLogger.js",
"keys": [
"bareHrtime"
"bareFileLogger"
]
},
{
"path": "/lib/bare/bundles/bareGtk.js",
"path": "/lib/bare/bundles/bareHrtime.js",
"keys": [
"bareGtk"
"bareHrtime"
]
},
{
@@ -302,15 +296,15 @@
]
},
{
"path": "/lib/bare/bundles/bareFs.js",
"path": "/lib/bare/bundles/bareGtk.js",
"keys": [
"bareFs"
"bareGtk"
]
},
{
"path": "/lib/bare/bundles/bareImageResample.js",
"path": "/lib/bare/bundles/bareFs.js",
"keys": [
"bareImageResample"
"bareFs"
]
},
{
@@ -320,9 +314,9 @@
]
},
{
"path": "/lib/bare/bundles/bareInspect.js",
"path": "/lib/bare/bundles/bareImageResample.js",
"keys": [
"bareInspect"
"bareImageResample"
]
},
{
@@ -331,6 +325,12 @@
"bareHttp1"
]
},
{
"path": "/lib/bare/bundles/bareInspect.js",
"keys": [
"bareInspect"
]
},
{
"path": "/lib/bare/bundles/bareHttps.js",
"keys": [
@@ -349,12 +349,6 @@
"bareIntl"
]
},
{
"path": "/lib/bare/bundles/bareLogger.js",
"keys": [
"bareLogger"
]
},
{
"path": "/lib/bare/bundles/bareIpc.js",
"keys": [
@@ -367,6 +361,12 @@
"bareLief"
]
},
{
"path": "/lib/bare/bundles/bareLogger.js",
"keys": [
"bareLogger"
]
},
{
"path": "/lib/bare/bundles/bareLink.js",
"keys": [
@@ -385,18 +385,18 @@
"bareMake"
]
},
{
"path": "/lib/bare/bundles/bareModuleLexer.js",
"keys": [
"bareModuleLexer"
]
},
{
"path": "/lib/bare/bundles/bareModuleResolve.js",
"keys": [
"bareModuleResolve"
]
},
{
"path": "/lib/bare/bundles/bareModuleLexer.js",
"keys": [
"bareModuleLexer"
]
},
{
"path": "/lib/bare/bundles/bareModule.js",
"keys": [
@@ -415,12 +415,6 @@
"bareNdk"
]
},
{
"path": "/lib/bare/bundles/bareNodeFetch.js",
"keys": [
"bareNodeFetch"
]
},
{
"path": "/lib/bare/bundles/bareNative.js",
"keys": [
@@ -428,21 +422,9 @@
]
},
{
"path": "/lib/bare/bundles/bareOpen.js",
"path": "/lib/bare/bundles/bareNodeFetch.js",
"keys": [
"bareOpen"
]
},
{
"path": "/lib/bare/bundles/bareOs.js",
"keys": [
"bareOs"
]
},
{
"path": "/lib/bare/bundles/bareMedia.js",
"keys": [
"bareMedia"
"bareNodeFetch"
]
},
{
@@ -452,15 +434,21 @@
]
},
{
"path": "/lib/bare/bundles/barePerformance.js",
"path": "/lib/bare/bundles/bareMedia.js",
"keys": [
"barePerformance"
"bareMedia"
]
},
{
"path": "/lib/bare/bundles/barePack.js",
"path": "/lib/bare/bundles/bareOs.js",
"keys": [
"barePack"
"bareOs"
]
},
{
"path": "/lib/bare/bundles/bareOpen.js",
"keys": [
"bareOpen"
]
},
{
@@ -470,9 +458,15 @@
]
},
{
"path": "/lib/bare/bundles/barePipe.js",
"path": "/lib/bare/bundles/barePack.js",
"keys": [
"barePipe"
"barePack"
]
},
{
"path": "/lib/bare/bundles/barePerformance.js",
"keys": [
"barePerformance"
]
},
{
@@ -481,6 +475,12 @@
"barePng"
]
},
{
"path": "/lib/bare/bundles/barePipe.js",
"keys": [
"barePipe"
]
},
{
"path": "/lib/bare/bundles/bareDev.js",
"keys": [
@@ -517,36 +517,24 @@
"bareQueueMicrotask"
]
},
{
"path": "/lib/bare/bundles/bareRealm.js",
"keys": [
"bareRealm"
]
},
{
"path": "/lib/bare/bundles/bareProcess.js",
"keys": [
"bareProcess"
]
},
{
"path": "/lib/bare/bundles/bareRealm.js",
"keys": [
"bareRealm"
]
},
{
"path": "/lib/bare/bundles/bareRuntime.js",
"keys": [
"bareRuntime"
]
},
{
"path": "/lib/bare/bundles/bareRpc.js",
"keys": [
"bareRpc"
]
},
{
"path": "/lib/bare/bundles/barePromClient.js",
"keys": [
"barePromClient"
]
},
{
"path": "/lib/bare/bundles/bareSdl.js",
"keys": [
@@ -560,15 +548,21 @@
]
},
{
"path": "/lib/bare/bundles/bareRepl.js",
"path": "/lib/bare/bundles/bareRpc.js",
"keys": [
"bareRepl"
"bareRpc"
]
},
{
"path": "/lib/bare/bundles/bareRun.js",
"path": "/lib/bare/bundles/barePromClient.js",
"keys": [
"bareRun"
"barePromClient"
]
},
{
"path": "/lib/bare/bundles/bareRepl.js",
"keys": [
"bareRepl"
]
},
{
@@ -578,15 +572,15 @@
]
},
{
"path": "/lib/bare/bundles/bareSidecar.js",
"path": "/lib/bare/bundles/bareRun.js",
"keys": [
"bareSidecar"
"bareRun"
]
},
{
"path": "/lib/bare/bundles/bareStringDecoder.js",
"path": "/lib/bare/bundles/bareSidecar.js",
"keys": [
"bareStringDecoder"
"bareSidecar"
]
},
{
@@ -596,15 +590,9 @@
]
},
{
"path": "/lib/bare/bundles/bareSvg.js",
"path": "/lib/bare/bundles/bareStringDecoder.js",
"keys": [
"bareSvg"
]
},
{
"path": "/lib/bare/bundles/bareStream.js",
"keys": [
"bareStream"
"bareStringDecoder"
]
},
{
@@ -614,9 +602,15 @@
]
},
{
"path": "/lib/bare/bundles/bareSystemLogger.js",
"path": "/lib/bare/bundles/bareStream.js",
"keys": [
"bareSystemLogger"
"bareStream"
]
},
{
"path": "/lib/bare/bundles/bareSvg.js",
"keys": [
"bareSvg"
]
},
{
@@ -626,15 +620,15 @@
]
},
{
"path": "/lib/bare/bundles/bareTap.js",
"path": "/lib/bare/bundles/bareSystemLogger.js",
"keys": [
"bareTap"
"bareSystemLogger"
]
},
{
"path": "/lib/bare/bundles/bareTiff.js",
"path": "/lib/bare/bundles/bareTap.js",
"keys": [
"bareTiff"
"bareTap"
]
},
{
@@ -644,15 +638,9 @@
]
},
{
"path": "/lib/bare/bundles/bareThread.js",
"path": "/lib/bare/bundles/bareTiff.js",
"keys": [
"bareThread"
]
},
{
"path": "/lib/bare/bundles/bareTpl.js",
"keys": [
"bareTpl"
"bareTiff"
]
},
{
@@ -661,30 +649,30 @@
"bareTimers"
]
},
{
"path": "/lib/bare/bundles/bareThread.js",
"keys": [
"bareThread"
]
},
{
"path": "/lib/bare/bundles/bareTcp.js",
"keys": [
"bareTcp"
]
},
{
"path": "/lib/bare/bundles/bareTpl.js",
"keys": [
"bareTpl"
]
},
{
"path": "/lib/bare/bundles/bareType.js",
"keys": [
"bareType"
]
},
{
"path": "/lib/bare/bundles/bareTls.js",
"keys": [
"bareTls"
]
},
{
"path": "/lib/bare/bundles/bareTty.js",
"keys": [
"bareTty"
]
},
{
"path": "/lib/bare/bundles/bareUiKit.js",
"keys": [
@@ -697,6 +685,18 @@
"bareUnpack"
]
},
{
"path": "/lib/bare/bundles/bareTls.js",
"keys": [
"bareTls"
]
},
{
"path": "/lib/bare/bundles/bareTty.js",
"keys": [
"bareTty"
]
},
{
"path": "/lib/bare/bundles/bareV8.js",
"keys": [
@@ -721,30 +721,24 @@
"bareUnionBundle"
]
},
{
"path": "/lib/bare/bundles/bareWebp.js",
"keys": [
"bareWebp"
]
},
{
"path": "/lib/bare/bundles/bareWebKit.js",
"keys": [
"bareWebKit"
]
},
{
"path": "/lib/bare/bundles/bareWebKitGtk.js",
"keys": [
"bareWebKitGtk"
]
},
{
"path": "/lib/bare/bundles/bareWhich.js",
"keys": [
"bareWhich"
]
},
{
"path": "/lib/bare/bundles/bareWebp.js",
"keys": [
"bareWebp"
]
},
{
"path": "/lib/bare/bundles/bareUtils.js",
"keys": [
@@ -752,9 +746,9 @@
]
},
{
"path": "/lib/bare/bundles/bareV8ToIstanbul.js",
"path": "/lib/bare/bundles/bareWebKitGtk.js",
"keys": [
"bareV8ToIstanbul"
"bareWebKitGtk"
]
},
{
@@ -763,6 +757,12 @@
"bareWinUi"
]
},
{
"path": "/lib/bare/bundles/bareV8ToIstanbul.js",
"keys": [
"bareV8ToIstanbul"
]
},
{
"path": "/lib/bare/bundles/bareXdiff.js",
"keys": [
File diff suppressed because one or more lines are too long