This commit is contained in:
Raven Scott
2026-04-03 19:13:20 -04:00
parent 8002a66f6e
commit 04d903beb8
24 changed files with 564 additions and 74 deletions
+18 -7
View File
@@ -78,13 +78,14 @@ async function run(ctx, argv) {
let suppressErrors = false
let forceFilename = false
let noFilename = false
let word = false
const args = argv.slice(1)
let i = 0
function usage() {
ctx.console.error(
'usage: grep [-E|-F] [-i] [-v] [-n] [-c] [-l] [-q] [-s] [-H|-h] [-e pat] ... [-f file] ... [pattern] [file...]'
'usage: grep [-E|-F] [-i] [-v] [-w] [-n] [-c] [-l] [-q] [-s] [-H|-h] [-e pat] ... [-f file] ... [pattern] [file...]'
)
ctx.exitCode = 2
}
@@ -169,6 +170,9 @@ async function run(ctx, argv) {
case 'h':
noFilename = true
break
case 'w':
word = true
break
default:
ctx.console.error('grep: invalid option -- ' + c)
ctx.exitCode = 2
@@ -217,7 +221,7 @@ async function run(ctx, argv) {
let matchers
try {
matchers = buildMatchers(patterns, { fixed, icase })
matchers = buildMatchers(patterns, { fixed, icase, word })
} catch (e) {
ctx.console.error('grep: ' + (e.message || e))
ctx.exitCode = 2
@@ -304,7 +308,7 @@ async function run(ctx, argv) {
/**
* @param {string[]} patterns
* @param {{ fixed: boolean, icase: boolean }} o
* @param {{ fixed: boolean, icase: boolean, word?: boolean }} o
*/
function buildMatchers(patterns, o) {
if (patterns.length === 0) throw new Error('no pattern')
@@ -312,15 +316,22 @@ function buildMatchers(patterns, o) {
const pats = o.icase
? patterns.map((p) => p.toLowerCase())
: patterns.slice()
return pats.map(
(p) => (line) =>
return pats.map((p) => {
if (o.word) {
const esc = p.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&')
const flags = o.icase ? 'i' : ''
const re = new RegExp('(?:^|[^0-9A-Za-z_])' + esc + '(?:$|[^0-9A-Za-z_])', flags)
return (line) => re.test(line)
}
return (line) =>
o.icase ? line.toLowerCase().includes(p) : line.includes(p)
)
})
}
const flags = o.icase ? 'i' : ''
return patterns.map((p) => {
try {
const re = new RegExp(p, flags)
const body = o.word ? '\\b(?:' + p + ')\\b' : p
const re = new RegExp(body, flags)
return (line) => re.test(line)
} catch (e) {
throw new Error('invalid regex: ' + (e.message || e))
+38 -3
View File
@@ -274,9 +274,10 @@ async function run(ctx, argv) {
function usage() {
ctx.console.error(
'usage: man [-k keyword] [-f name] [-l] [[section] name]\n' +
'usage: man [-k keyword] [-f name] [-l] [-w] [[section] name]\n' +
' Section 1: /bin; section 7: handbook + developer guide (man handbook, man devguide).\n' +
' Data: /share/man/man.json on the system drive.'
' Data: /share/man/man.json on the system drive.\n' +
' Long pages: set PAGER=bare-slice and optional MAN_SLICE=N (default 24) for section breaks.'
)
ctx.exitCode = 2
}
@@ -318,6 +319,10 @@ async function run(ctx, argv) {
mode = 'list'
continue
}
if (a === '-w' || a === '--where' || a === '--path') {
ctx.console.log('/share/man/man.json')
return
}
if (a.startsWith('-')) {
ctx.console.error('man: unknown option: ' + a)
ctx.exitCode = 2
@@ -474,5 +479,35 @@ async function run(ctx, argv) {
ctx.exitCode = 1
return
}
ctx.console.log(bareManRenderPage(page, ctx, width).replace(/\n$/, ''))
const rendered = bareManRenderPage(page, ctx, width).replace(/\n$/, '')
if (bareManSlicePage(rendered, env, ctx.console)) return
ctx.console.log(rendered)
}
/**
* @param {string} text
* @param {Record<string, string>} env
* @param {{ log: (s: string) => void }} cons
*/
function bareManSlicePage(text, env, cons) {
const pager = env.PAGER || ''
if (pager !== 'bare-slice' && pager !== 'bare_slice') return false
const raw = env.MAN_SLICE || '24'
const n = Number.parseInt(String(raw), 10)
const sliceLines = Number.isFinite(n) && n > 0 ? n : 24
const lines = text.split('\n')
const total = lines.length
for (let i = 0; i < total; i += sliceLines) {
const chunk = lines.slice(i, i + sliceLines).join('\n')
cons.log(chunk)
if (i + sliceLines < total) {
const hi = Math.min(i + sliceLines, total)
cons.log('')
cons.log(
`--- man: lines ${i + 1}-${hi} of ${total} (PAGER=bare-slice) ---`
)
cons.log('')
}
}
return true
}
+48 -9
View File
@@ -3,10 +3,31 @@
* Loaded by the booter with an injected ctx object (trusted replication source).
*
* Boot order: /etc/os-release → /etc/motd → /etc/bare-os/rc → /etc/bare-os/rc.d/*
* (sorted by filename) → session banner → interactive loop.
* (sorted; digit-prefixed snippet names) → session banner → interactive loop.
* Use ctx.registerKernelShutdownHook(fn) for teardown before initd disposers.
*/
/**
* @param {Record<string, unknown>} ctx
*/
function wantBootTrace(ctx) {
const v = ctx.env && ctx.env.BARE_OS_BOOT_TRACE
return v === '1' || v === 'true'
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} label
* @param {() => void | Promise<void>} fn
*/
async function bootTimed(ctx, label, fn) {
const t0 = Date.now()
await fn()
if (wantBootTrace(ctx)) {
ctx.console.error(`[boot] ${label}: ${Date.now() - t0}ms`)
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} text
@@ -112,19 +133,37 @@ async function runBareOsRcDir(ctx) {
/**
* @param {Record<string, unknown>} ctx
*/
function printSessionBanner(ctx) {
ctx.console.log(
async function printSessionBanner(ctx) {
const { drive, b4a, console } = ctx
for (const p of ['/etc/bare-os/banner', '/etc/issue']) {
try {
const buf = await drive.get(p)
if (buf) {
console.log(b4a.toString(buf).trimEnd())
return
}
} catch {
/* ignore */
}
}
const defaultBanner =
'Bare operating system — guest session (login [--new] <passphrase> to unlock) | shell: cd, export, if/fi, && || ;, |, exit | services: systemctl list-units, journalctl -u UNIT | try: help, ls /bin, crontab -l'
)
if (ctx.bareOsSkipRepl) {
console.log(
'Bare operating system — non-interactive session (BARE_OS_SKIP_REPL).'
)
return
}
console.log(defaultBanner)
}
async function start(ctx) {
const { readLine, execLine, console } = ctx
await printOsRelease(ctx)
await printMotd(ctx)
await runRcFileAt(ctx, '/etc/bare-os/rc', 'rc')
await runBareOsRcDir(ctx)
printSessionBanner(ctx)
await bootTimed(ctx, 'os-release', () => printOsRelease(ctx))
await bootTimed(ctx, 'motd', () => printMotd(ctx))
await bootTimed(ctx, 'rc', () => runRcFileAt(ctx, '/etc/bare-os/rc', 'rc'))
await bootTimed(ctx, 'rc.d', () => runBareOsRcDir(ctx))
await printSessionBanner(ctx)
while (true) {
const line = await readLine('')
if (line == null) break
File diff suppressed because one or more lines are too long