Update man to add the manual guide
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
import { readFile, writeFile, mkdir } from 'fs/promises'
|
||||
import { dirname, join } from 'path'
|
||||
import { fileURLToPath, pathToFileURL } from 'url'
|
||||
|
||||
import {
|
||||
COREUTILS_COMMANDS,
|
||||
MAN_EXTRA_PAGES
|
||||
} from '../lib/commands.mjs'
|
||||
import { buildHandbookManPages } from './ingest-handbook-for-man.mjs'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const pkgRoot = join(__dirname, '..')
|
||||
const repoRoot = join(pkgRoot, '../..')
|
||||
const pagesDir = join(pkgRoot, 'man', 'pages')
|
||||
const kernelManDir = join(repoRoot, 'kernel', 'share', 'man')
|
||||
const seederManDir = join(
|
||||
repoRoot,
|
||||
'packages',
|
||||
'bare-os-seeder',
|
||||
'kernel',
|
||||
'share',
|
||||
'man'
|
||||
)
|
||||
|
||||
function isPlainObject(x) {
|
||||
return x !== null && typeof x === 'object' && !Array.isArray(x)
|
||||
}
|
||||
|
||||
function validatePage(raw, pathLabel) {
|
||||
if (!isPlainObject(raw)) throw new Error(`${pathLabel}: root must be object`)
|
||||
const allowed = new Set([
|
||||
'name',
|
||||
'section',
|
||||
'title',
|
||||
'synopsis',
|
||||
'description',
|
||||
'options',
|
||||
'environment',
|
||||
'files',
|
||||
'exitStatus',
|
||||
'diagnostics',
|
||||
'seeAlso',
|
||||
'bareOsNotes',
|
||||
'keywords',
|
||||
'aliases',
|
||||
'stub',
|
||||
'builtins',
|
||||
'examples',
|
||||
'descriptionMode'
|
||||
])
|
||||
for (const k of Object.keys(raw)) {
|
||||
if (!allowed.has(k)) throw new Error(`${pathLabel}: unknown field "${k}"`)
|
||||
}
|
||||
const {
|
||||
name,
|
||||
section,
|
||||
title,
|
||||
synopsis,
|
||||
description,
|
||||
options,
|
||||
keywords
|
||||
} = raw
|
||||
if (typeof name !== 'string' || !name)
|
||||
throw new Error(`${pathLabel}: name must be non-empty string`)
|
||||
if (typeof section !== 'number' || section < 1 || section > 8)
|
||||
throw new Error(`${pathLabel}: section must be integer 1–8`)
|
||||
if (typeof title !== 'string' || !title)
|
||||
throw new Error(`${pathLabel}: title must be non-empty string`)
|
||||
if (!Array.isArray(synopsis) || synopsis.length < 1)
|
||||
throw new Error(`${pathLabel}: synopsis must be non-empty array`)
|
||||
for (const s of synopsis) {
|
||||
if (typeof s !== 'string')
|
||||
throw new Error(`${pathLabel}: synopsis entries must be strings`)
|
||||
}
|
||||
if (typeof description !== 'string' || !description)
|
||||
throw new Error(`${pathLabel}: description must be non-empty string`)
|
||||
if (!Array.isArray(options))
|
||||
throw new Error(`${pathLabel}: options must be array`)
|
||||
for (const o of options) {
|
||||
if (!isPlainObject(o) || typeof o.flag !== 'string' || typeof o.meaning !== 'string')
|
||||
throw new Error(`${pathLabel}: each option needs { flag, meaning }`)
|
||||
}
|
||||
if (!Array.isArray(keywords))
|
||||
throw new Error(`${pathLabel}: keywords must be array`)
|
||||
for (const kw of keywords) {
|
||||
if (typeof kw !== 'string')
|
||||
throw new Error(`${pathLabel}: keywords entries must be strings`)
|
||||
}
|
||||
if (raw.environment !== undefined) {
|
||||
if (!Array.isArray(raw.environment))
|
||||
throw new Error(`${pathLabel}: environment must be array of strings`)
|
||||
for (const e of raw.environment) {
|
||||
if (typeof e !== 'string') throw new Error(`${pathLabel}: environment must be strings`)
|
||||
}
|
||||
}
|
||||
if (raw.files !== undefined) {
|
||||
if (!Array.isArray(raw.files))
|
||||
throw new Error(`${pathLabel}: files must be array of strings`)
|
||||
for (const f of raw.files) {
|
||||
if (typeof f !== 'string') throw new Error(`${pathLabel}: files must be strings`)
|
||||
}
|
||||
}
|
||||
if (raw.exitStatus !== undefined) {
|
||||
if (!Array.isArray(raw.exitStatus))
|
||||
throw new Error(`${pathLabel}: exitStatus must be array of strings`)
|
||||
for (const e of raw.exitStatus) {
|
||||
if (typeof e !== 'string') throw new Error(`${pathLabel}: exitStatus must be strings`)
|
||||
}
|
||||
}
|
||||
if (raw.diagnostics !== undefined) {
|
||||
if (!Array.isArray(raw.diagnostics))
|
||||
throw new Error(`${pathLabel}: diagnostics must be array of strings`)
|
||||
for (const d of raw.diagnostics) {
|
||||
if (typeof d !== 'string') throw new Error(`${pathLabel}: diagnostics must be strings`)
|
||||
}
|
||||
}
|
||||
if (raw.seeAlso !== undefined) {
|
||||
if (!Array.isArray(raw.seeAlso))
|
||||
throw new Error(`${pathLabel}: seeAlso must be array`)
|
||||
for (const ref of raw.seeAlso) {
|
||||
if (
|
||||
!isPlainObject(ref) ||
|
||||
typeof ref.name !== 'string' ||
|
||||
typeof ref.section !== 'number'
|
||||
)
|
||||
throw new Error(`${pathLabel}: seeAlso entries need { name, section }`)
|
||||
}
|
||||
}
|
||||
if (raw.bareOsNotes !== undefined && typeof raw.bareOsNotes !== 'string')
|
||||
throw new Error(`${pathLabel}: bareOsNotes must be string`)
|
||||
if (raw.descriptionMode !== undefined) {
|
||||
if (raw.descriptionMode !== 'wrap' && raw.descriptionMode !== 'preserve')
|
||||
throw new Error(`${pathLabel}: descriptionMode must be "wrap" or "preserve"`)
|
||||
}
|
||||
if (raw.aliases !== undefined) {
|
||||
if (!Array.isArray(raw.aliases))
|
||||
throw new Error(`${pathLabel}: aliases must be array of strings`)
|
||||
for (const a of raw.aliases) {
|
||||
if (typeof a !== 'string') throw new Error(`${pathLabel}: aliases must be strings`)
|
||||
}
|
||||
}
|
||||
if (raw.stub !== undefined && typeof raw.stub !== 'boolean')
|
||||
throw new Error(`${pathLabel}: stub must be boolean`)
|
||||
if (raw.examples !== undefined) {
|
||||
if (!Array.isArray(raw.examples))
|
||||
throw new Error(`${pathLabel}: examples must be array`)
|
||||
for (const ex of raw.examples) {
|
||||
if (!isPlainObject(ex) || typeof ex.code !== 'string' || !ex.code.trim())
|
||||
throw new Error(`${pathLabel}: each example needs non-empty { code }`)
|
||||
if (ex.caption !== undefined && typeof ex.caption !== 'string')
|
||||
throw new Error(`${pathLabel}: example caption must be string`)
|
||||
}
|
||||
}
|
||||
if (raw.builtins !== undefined) {
|
||||
if (!Array.isArray(raw.builtins))
|
||||
throw new Error(`${pathLabel}: builtins must be array`)
|
||||
for (const b of raw.builtins) {
|
||||
if (!isPlainObject(b) || typeof b.name !== 'string' || typeof b.description !== 'string')
|
||||
throw new Error(`${pathLabel}: each builtin needs { name, description }`)
|
||||
if (b.synopsis !== undefined) {
|
||||
if (!Array.isArray(b.synopsis))
|
||||
throw new Error(`${pathLabel}: builtin synopsis must be array`)
|
||||
for (const s of b.synopsis) {
|
||||
if (typeof s !== 'string') throw new Error(`${pathLabel}: builtin synopsis strings`)
|
||||
}
|
||||
}
|
||||
if (b.options !== undefined) {
|
||||
if (!Array.isArray(b.options))
|
||||
throw new Error(`${pathLabel}: builtin options must be array`)
|
||||
for (const o of b.options) {
|
||||
if (!isPlainObject(o) || typeof o.flag !== 'string' || typeof o.meaning !== 'string')
|
||||
throw new Error(`${pathLabel}: builtin option { flag, meaning }`)
|
||||
}
|
||||
}
|
||||
if (b.examples !== undefined) {
|
||||
if (!Array.isArray(b.examples))
|
||||
throw new Error(`${pathLabel}: builtin examples must be array`)
|
||||
for (const ex of b.examples) {
|
||||
if (!isPlainObject(ex) || typeof ex.code !== 'string' || !ex.code.trim())
|
||||
throw new Error(`${pathLabel}: builtin example needs { code }`)
|
||||
if (ex.caption !== undefined && typeof ex.caption !== 'string')
|
||||
throw new Error(`${pathLabel}: builtin example caption must be string`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tokenizeForApropos(text) {
|
||||
return String(text)
|
||||
.toLowerCase()
|
||||
.split(/[^a-z0-9_-]+/)
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
export async function buildManDb() {
|
||||
const required = [...COREUTILS_COMMANDS, ...MAN_EXTRA_PAGES]
|
||||
const pages = []
|
||||
const index = Object.create(null)
|
||||
const apropos = []
|
||||
|
||||
for (let i = 0; i < required.length; i++) {
|
||||
const cmd = required[i]
|
||||
const filePath = join(pagesDir, `${cmd}.json`)
|
||||
let raw
|
||||
try {
|
||||
raw = JSON.parse(await readFile(filePath, 'utf8'))
|
||||
} catch (e) {
|
||||
throw new Error(`man: missing or invalid JSON for "${cmd}": ${filePath} (${e.message})`)
|
||||
}
|
||||
validatePage(raw, cmd)
|
||||
if (raw.name !== cmd)
|
||||
throw new Error(`man/pages/${cmd}.json: "name" must be "${cmd}", got "${raw.name}"`)
|
||||
pages.push(raw)
|
||||
const pageIdx = pages.length - 1
|
||||
const addIndex = (key) => {
|
||||
const k = String(key).toLowerCase()
|
||||
if (index[k] !== undefined && index[k] !== pageIdx)
|
||||
throw new Error(`man: duplicate index key "${k}"`)
|
||||
index[k] = pageIdx
|
||||
}
|
||||
addIndex(raw.name)
|
||||
if (Array.isArray(raw.aliases)) {
|
||||
for (const a of raw.aliases) addIndex(a)
|
||||
}
|
||||
const kwSet = new Set()
|
||||
for (const k of raw.keywords) kwSet.add(k.toLowerCase())
|
||||
kwSet.add(raw.name.toLowerCase())
|
||||
for (const t of tokenizeForApropos(raw.title)) kwSet.add(t)
|
||||
if (Array.isArray(raw.examples)) {
|
||||
for (const ex of raw.examples) {
|
||||
if (ex.caption) {
|
||||
for (const t of tokenizeForApropos(ex.caption)) kwSet.add(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const kw of kwSet) {
|
||||
apropos.push({ kw, pageRef: pageIdx })
|
||||
}
|
||||
}
|
||||
|
||||
const handbookPages = await buildHandbookManPages(repoRoot)
|
||||
for (let hi = 0; hi < handbookPages.length; hi++) {
|
||||
const raw = handbookPages[hi]
|
||||
validatePage(raw, raw.name)
|
||||
pages.push(raw)
|
||||
const pageIdx = pages.length - 1
|
||||
const addIndex = (key) => {
|
||||
const k = String(key).toLowerCase()
|
||||
if (index[k] !== undefined && index[k] !== pageIdx)
|
||||
throw new Error(`man: duplicate index key "${k}"`)
|
||||
index[k] = pageIdx
|
||||
}
|
||||
addIndex(raw.name)
|
||||
if (Array.isArray(raw.aliases)) {
|
||||
for (const a of raw.aliases) addIndex(a)
|
||||
}
|
||||
const kwSet = new Set()
|
||||
for (const k of raw.keywords) kwSet.add(k.toLowerCase())
|
||||
kwSet.add(raw.name.toLowerCase())
|
||||
for (const t of tokenizeForApropos(raw.title)) kwSet.add(t)
|
||||
if (Array.isArray(raw.examples)) {
|
||||
for (const ex of raw.examples) {
|
||||
if (ex.caption) {
|
||||
for (const t of tokenizeForApropos(ex.caption)) kwSet.add(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const kw of kwSet) {
|
||||
apropos.push({ kw, pageRef: pageIdx })
|
||||
}
|
||||
}
|
||||
|
||||
const out = {
|
||||
schemaVersion: 1,
|
||||
generatedAt: new Date().toISOString(),
|
||||
pages,
|
||||
index,
|
||||
apropos
|
||||
}
|
||||
|
||||
const json = JSON.stringify(out, null, 0) + '\n'
|
||||
await mkdir(kernelManDir, { recursive: true })
|
||||
await mkdir(seederManDir, { recursive: true })
|
||||
await writeFile(join(kernelManDir, 'man.json'), json)
|
||||
await writeFile(join(seederManDir, 'man.json'), json)
|
||||
}
|
||||
|
||||
const isMain =
|
||||
process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href
|
||||
|
||||
if (isMain) {
|
||||
await buildManDb()
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
/**
|
||||
* Convert repo handbook/*.md into man(7) page objects merged at build time.
|
||||
*/
|
||||
import { readFile, readdir } from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
|
||||
const SECTION = 7
|
||||
|
||||
function stripMdInline(s) {
|
||||
let t = String(s)
|
||||
t = t.replace(/\[([^\]]*)\]\(([^)]+)\)/g, '$1 <$2>')
|
||||
t = t.replace(/\*\*([^*]+)\*\*/g, '$1')
|
||||
t = t.replace(/__([^_]+)__/g, '$1')
|
||||
t = t.replace(/\*([^*]+)\*/g, '$1')
|
||||
t = t.replace(/`([^`]+)`/g, '$1')
|
||||
return t.trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} md
|
||||
* @param {string} sourceFile
|
||||
*/
|
||||
export function handbookMdToDescription(md, sourceFile) {
|
||||
const lines = md.split('\n')
|
||||
const out = []
|
||||
let i = 0
|
||||
let inFence = false
|
||||
/** @type {string} */
|
||||
let fenceKind = ''
|
||||
|
||||
while (i < lines.length) {
|
||||
const line = lines[i]
|
||||
const fenceM = line.match(/^(\s*)```(\w*)\s*$/)
|
||||
if (fenceM) {
|
||||
if (!inFence) {
|
||||
inFence = true
|
||||
fenceKind = fenceM[2] || ''
|
||||
i++
|
||||
continue
|
||||
}
|
||||
inFence = false
|
||||
fenceKind = ''
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if (inFence) {
|
||||
if (fenceKind === 'mermaid') {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
out.push(' ' + line)
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
const t = line.trim()
|
||||
if (/^---+$/.test(t)) {
|
||||
out.push('')
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
const h = line.match(/^(#{1,6})\s+(.*)$/)
|
||||
if (h) {
|
||||
const text = stripMdInline(h[2])
|
||||
if (text) {
|
||||
out.push('')
|
||||
out.push(text.toUpperCase())
|
||||
out.push('')
|
||||
}
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
if (/^\s*[-*]\s+/.test(line)) {
|
||||
out.push(stripMdInline(line.replace(/^\s+/, '')))
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
if (/^\s*\d+\.\s+/.test(line)) {
|
||||
out.push(stripMdInline(line.trim()))
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
if (t.startsWith('|')) {
|
||||
if (/^\|[\s-:|]+\|$/.test(t)) {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
out.push(stripMdInline(t))
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
if (!t) {
|
||||
out.push('')
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
out.push(stripMdInline(line.trim()))
|
||||
i++
|
||||
}
|
||||
|
||||
let body = out.join('\n').replace(/\n{3,}/g, '\n\n').trim()
|
||||
if (!body) body = '(empty chapter — see ' + sourceFile + ' in the repo)'
|
||||
return body
|
||||
}
|
||||
|
||||
function firstHeadingTitle(md) {
|
||||
const m = md.match(/^#\s+(.+)$/m)
|
||||
return m ? stripMdInline(m[1]) : 'Bare OS handbook'
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} repoRoot
|
||||
* @returns {Promise<object[]>}
|
||||
*/
|
||||
export async function buildHandbookManPages(repoRoot) {
|
||||
const handbookDir = join(repoRoot, 'handbook')
|
||||
let names
|
||||
try {
|
||||
names = (await readdir(handbookDir)).filter((f) => f.endsWith('.md'))
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
|
||||
names.sort((a, b) => {
|
||||
if (a === 'README.md') return -1
|
||||
if (b === 'README.md') return 1
|
||||
return a.localeCompare(b)
|
||||
})
|
||||
|
||||
/** @type {{ file: string, name: string, title: string, aliases?: string[] }[]} */
|
||||
const meta = []
|
||||
for (const file of names) {
|
||||
if (file === 'README.md') {
|
||||
meta.push({
|
||||
file,
|
||||
name: 'bare-os-handbook',
|
||||
title: 'Bare OS handbook — table of contents and reading order',
|
||||
aliases: ['handbook', 'bare-os-handbook-index']
|
||||
})
|
||||
} else {
|
||||
const base = file.replace(/\.md$/i, '')
|
||||
meta.push({
|
||||
file,
|
||||
name: 'handbook-' + base,
|
||||
title: '',
|
||||
aliases: []
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const pages = []
|
||||
for (let j = 0; j < meta.length; j++) {
|
||||
const m = meta[j]
|
||||
const path = join(handbookDir, m.file)
|
||||
const md = await readFile(path, 'utf8')
|
||||
const title = m.title || firstHeadingTitle(md)
|
||||
const description = handbookMdToDescription(md, 'handbook/' + m.file)
|
||||
const next = meta[j + 1]
|
||||
const prev = meta[j - 1]
|
||||
/** @type {{ name: string, section: number }[]} */
|
||||
const seeAlso = []
|
||||
if (next) seeAlso.push({ name: next.name, section: SECTION })
|
||||
if (prev) seeAlso.push({ name: prev.name, section: SECTION })
|
||||
seeAlso.push({ name: 'man', section: 1 })
|
||||
|
||||
const kw = new Set([
|
||||
'handbook',
|
||||
'bare-os',
|
||||
'documentation',
|
||||
'narrative',
|
||||
'chapter'
|
||||
])
|
||||
for (const t of m.name.split(/[^a-z0-9]+/i)) {
|
||||
if (t.length > 1) kw.add(t.toLowerCase())
|
||||
}
|
||||
for (const t of title.toLowerCase().split(/[^a-z0-9]+/)) {
|
||||
if (t.length > 2) kw.add(t)
|
||||
}
|
||||
|
||||
const page = {
|
||||
name: m.name,
|
||||
section: SECTION,
|
||||
title,
|
||||
synopsis: ['man 7 ' + m.name, 'Handbook chapter (plain text from handbook/' + m.file + ')'],
|
||||
description,
|
||||
descriptionMode: 'preserve',
|
||||
options: [],
|
||||
keywords: Array.from(kw),
|
||||
seeAlso,
|
||||
bareOsNotes:
|
||||
'Generated at build time from handbook/' +
|
||||
m.file +
|
||||
'. Diagrams in ```mermaid blocks are omitted; open the Markdown in the repo for figures.'
|
||||
}
|
||||
if (m.aliases && m.aliases.length) page.aliases = m.aliases
|
||||
pages.push(page)
|
||||
}
|
||||
|
||||
return pages
|
||||
}
|
||||
@@ -0,0 +1,667 @@
|
||||
/**
|
||||
* One-shot generator for man/pages/*.json (run after changing commands list).
|
||||
* node packages/bare-os-coreutils/scripts/seed-man-pages.mjs
|
||||
*/
|
||||
import { mkdir, writeFile } from 'fs/promises'
|
||||
import { dirname, join } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import {
|
||||
COREUTILS_COMMANDS,
|
||||
MAN_EXTRA_PAGES
|
||||
} from '../lib/commands.mjs'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const pagesDir = join(__dirname, '../man/pages')
|
||||
|
||||
const STUB = new Set(['chgrp', 'chown', 'xargs', 'getconf', 'mkfifo'])
|
||||
|
||||
const POSIX_TITLE = {
|
||||
awk: 'pattern scanning and processing language',
|
||||
basename: 'strip directory and suffix from pathnames',
|
||||
cat: 'concatenate and print files',
|
||||
chgrp: 'change file group ownership',
|
||||
chmod: 'change file mode bits',
|
||||
chown: 'change file owner and group',
|
||||
cksum: 'write file checksums and sizes',
|
||||
clear: 'clear the terminal screen',
|
||||
cp: 'copy files',
|
||||
crontab: 'user crontab manipulation',
|
||||
cut: 'cut out selected fields of each line',
|
||||
date: 'display or set date and time',
|
||||
dirname: 'return directory portion of a pathname',
|
||||
du: 'estimate file space usage',
|
||||
echo: 'write arguments to standard output',
|
||||
env: 'set the environment for command invocation',
|
||||
exit: 'exit the shell or booter session',
|
||||
false: 'return false value',
|
||||
find: 'find files',
|
||||
getconf: 'get configuration values',
|
||||
grep: 'pattern matching utility',
|
||||
head: 'copy the first part of files',
|
||||
hdms: 'Hyperswarm distributed map store',
|
||||
help: 'Bare OS help summary',
|
||||
hostname: 'set or print hostname',
|
||||
id: 'return user identity',
|
||||
ln: 'link files',
|
||||
login: 'begin a session on the system',
|
||||
logout: 'end session (save vault)',
|
||||
logname: "return the user's login name",
|
||||
ls: 'list directory contents',
|
||||
man: 'display on-line manual pages',
|
||||
mkdir: 'make directories',
|
||||
mkfifo: 'make FIFO special files',
|
||||
mv: 'move or rename files',
|
||||
nl: 'line numbering utility',
|
||||
od: 'octal dump',
|
||||
pathchk: 'check pathname portability',
|
||||
printenv: 'print environment variables',
|
||||
printf: 'format and print',
|
||||
pwd: 'return working directory name',
|
||||
readlink: 'print symbolic link targets',
|
||||
rm: 'remove files',
|
||||
rmdir: 'remove empty directories',
|
||||
savevault: 'encrypt snapshot of personal drive',
|
||||
sed: 'stream editor',
|
||||
seq: 'print sequences of numbers',
|
||||
sleep: 'suspend execution for an interval',
|
||||
sort: 'sort lines',
|
||||
stat: 'display file status',
|
||||
tail: 'copy the last part of a file',
|
||||
tee: 'duplicate standard input',
|
||||
test: 'evaluate a condition',
|
||||
time: 'time a simple command',
|
||||
touch: 'change file timestamps or create files',
|
||||
tr: 'translate or delete characters',
|
||||
true: 'return true value',
|
||||
tty: "return user's terminal name",
|
||||
uname: 'return operating system name',
|
||||
wc: 'word, line, and byte or character count',
|
||||
which: 'locate a command',
|
||||
whoami: 'display effective user ID',
|
||||
xargs: 'construct argument lists and invoke utility'
|
||||
}
|
||||
|
||||
/**
|
||||
* cheat.sh-style snippets: { caption?, code } — shown under EXAMPLES in man output.
|
||||
* @type {Record<string, Array<{ caption?: string, code: string }>>}
|
||||
*/
|
||||
const EXAMPLES = {}
|
||||
|
||||
EXAMPLES.awk = [
|
||||
{ caption: 'print column 1', code: "awk '{print $1}' file.txt" },
|
||||
{ caption: 'field separator', code: "awk -F: '{print $1}' /etc/passwd" },
|
||||
{ caption: 'sum numbers in first column', code: "awk '{s+=$1} END{print s}' nums.txt" },
|
||||
{ caption: 'lines matching /re/', code: "awk '/error/{print NR\": \"$0}' log.txt" }
|
||||
]
|
||||
EXAMPLES.basename = [
|
||||
{ caption: 'strip directory', code: 'basename /home/user/docs/readme.md' },
|
||||
{ caption: 'strip suffix', code: 'basename -s .md /path/readme.md' }
|
||||
]
|
||||
EXAMPLES.cat = [
|
||||
{ caption: 'stdout several files', code: 'cat a.txt b.txt' },
|
||||
{ caption: 'number lines (use nl)', code: 'cat -n file.txt # if supported; else nl file' },
|
||||
{ caption: 'here-string via echo pipe', code: 'echo hello | cat' }
|
||||
]
|
||||
EXAMPLES.chgrp = [
|
||||
{
|
||||
caption: 'not supported — use identity model',
|
||||
code: '# chgrp is a stub; group is display metadata only'
|
||||
}
|
||||
]
|
||||
EXAMPLES.chmod = [
|
||||
{ caption: 'octal', code: 'chmod 644 ~/.profile' },
|
||||
{ caption: 'recursive-ish (run find + chmod per file)', code: 'find . -type f -name "*.sh" -print' },
|
||||
{ caption: 'symbolic user bits', code: 'chmod u+x script.sh' },
|
||||
{ caption: 'all read, owner write', code: 'chmod a+r,u+w shared.txt' }
|
||||
]
|
||||
EXAMPLES.chown = [
|
||||
{ caption: 'not supported', code: '# chown stub — see man identity / login' }
|
||||
]
|
||||
EXAMPLES.cksum = [
|
||||
{ caption: 'checksum file', code: 'cksum iso.img' },
|
||||
{ caption: 'verify pipeline', code: 'cat f | cksum' }
|
||||
]
|
||||
EXAMPLES.clear = [
|
||||
{ caption: 'wipe screen', code: 'clear' }
|
||||
]
|
||||
EXAMPLES.cp = [
|
||||
{ caption: 'copy file', code: 'cp src.txt dest.txt' },
|
||||
{ caption: 'into directory', code: 'cp a b c ~/backup/' },
|
||||
{ caption: 'preserve implied (if implemented)', code: 'cp -R proj proj.bak' }
|
||||
]
|
||||
EXAMPLES.crontab = [
|
||||
{ caption: 'list jobs', code: 'crontab -l' },
|
||||
{ caption: 'install from file', code: 'crontab ~/.crontab' },
|
||||
{ caption: 'remove all', code: 'crontab -r' }
|
||||
]
|
||||
EXAMPLES.cut = [
|
||||
{ caption: 'fields by delimiter', code: "cut -d: -f1,3 /etc/passwd" },
|
||||
{ caption: 'characters', code: 'cut -c1-16 file.txt' }
|
||||
]
|
||||
EXAMPLES.date = [
|
||||
{ caption: 'RFC-ish output', code: 'date' },
|
||||
{ caption: 'epoch seconds', code: 'date +%s' }
|
||||
]
|
||||
EXAMPLES.dirname = [
|
||||
{ caption: 'parent path', code: 'dirname /a/b/c.txt' },
|
||||
{ caption: 'compose with basename', code: 'p=/x/y/z; echo $(dirname $p)/$(basename $p)' }
|
||||
]
|
||||
EXAMPLES.du = [
|
||||
{ caption: 'sizes under cwd', code: 'du .' },
|
||||
{ caption: 'human (if supported)', code: 'du -h ~' }
|
||||
]
|
||||
EXAMPLES.echo = [
|
||||
{ caption: 'literal', code: 'echo hello world' },
|
||||
{ caption: 'no newline (if -n supported)', code: 'echo -n OK' }
|
||||
]
|
||||
EXAMPLES.env = [
|
||||
{ caption: 'print environment', code: 'env' },
|
||||
{ caption: 'run with override', code: 'env PATH=/bin:/usr/bin man ls' }
|
||||
]
|
||||
EXAMPLES.exit = [
|
||||
{ caption: 'leave session with status', code: 'exit 0' },
|
||||
{ caption: 'from script', code: '/bin/exit 42' }
|
||||
]
|
||||
EXAMPLES.false = [
|
||||
{ caption: 'force failure in pipeline tests', code: 'false; echo $?' }
|
||||
]
|
||||
EXAMPLES.find = [
|
||||
{ caption: 'files by name glob', code: 'find . -name "*.js"' },
|
||||
{ caption: 'directories only', code: 'find . -type d' },
|
||||
{ caption: 'max depth', code: 'find . -maxdepth 2 -type f' },
|
||||
{ caption: 'OR names', code: 'find . \\( -name "*.c" -o -name "*.h" \\)' }
|
||||
]
|
||||
EXAMPLES.getconf = [
|
||||
{ caption: 'stub', code: '# getconf PATH_MAX — not available on Bare OS' }
|
||||
]
|
||||
EXAMPLES.grep = [
|
||||
{ caption: 'recursive feel (grep each file)', code: 'grep -n error *.log' },
|
||||
{ caption: 'case insensitive', code: 'grep -i todo NOTES.md' },
|
||||
{ caption: 'invert (lines without)', code: "grep -v '^#' config" },
|
||||
{ caption: 'fixed string (no regex)', code: 'grep -F "v1.0" CHANGES' },
|
||||
{ caption: 'count matches', code: 'grep -c FAIL build.log' },
|
||||
{ caption: 'only filenames', code: 'grep -l main *.js' },
|
||||
{ caption: 'multiple patterns', code: 'grep -e foo -e bar file.txt' }
|
||||
]
|
||||
EXAMPLES.head = [
|
||||
{ caption: 'first 10 lines', code: 'head /etc/os-release' },
|
||||
{ caption: 'first N', code: 'head -n 50 big.log' },
|
||||
{ caption: 'stdin', code: 'cat long.txt | head' }
|
||||
]
|
||||
EXAMPLES.hdms = [
|
||||
{ caption: 'when booter wires HDMS', code: 'hdms ls /mnt' },
|
||||
{ caption: 'otherwise', code: '# prints unavailable without ctx.runHdms' }
|
||||
]
|
||||
EXAMPLES.help = [
|
||||
{ caption: 'quick index', code: 'help' },
|
||||
{ caption: 'then deep dive', code: 'man grep' }
|
||||
]
|
||||
EXAMPLES.hostname = [
|
||||
{ caption: 'show host', code: 'hostname' }
|
||||
]
|
||||
EXAMPLES.id = [
|
||||
{ caption: 'who am I numerically', code: 'id' }
|
||||
]
|
||||
EXAMPLES.ln = [
|
||||
{ caption: 'symlink', code: 'ln -s target name' },
|
||||
{ caption: 'hard link (if supported)', code: 'ln file linkname' }
|
||||
]
|
||||
EXAMPLES.login = [
|
||||
{ caption: 'unlock existing identity', code: 'login my passphrase words here' },
|
||||
{ caption: 'register new', code: 'login --new first time passphrase' }
|
||||
]
|
||||
EXAMPLES.logout = [
|
||||
{ caption: 'end session', code: 'logout' },
|
||||
{ caption: 'save vault hint', code: 'logout --save' }
|
||||
]
|
||||
EXAMPLES.logname = [
|
||||
{ caption: 'login name', code: 'logname' }
|
||||
]
|
||||
EXAMPLES.ls = [
|
||||
{ caption: 'long + hidden', code: 'ls -la ~' },
|
||||
{ caption: 'one per line', code: 'ls -1 /bin | head' },
|
||||
{ caption: 'multiple paths', code: 'ls /bin /etc' }
|
||||
]
|
||||
EXAMPLES.man = [
|
||||
{ caption: 'open page', code: 'man sed' },
|
||||
{ caption: 'handbook TOC (section 7)', code: 'man handbook' },
|
||||
{ caption: 'handbook chapter by section', code: 'man 7 handbook-01-introduction' },
|
||||
{ caption: 'apropos', code: 'man -k copy' },
|
||||
{ caption: 'whatis', code: 'man -f grep' },
|
||||
{ caption: 'all pages', code: 'man -l' },
|
||||
{ caption: 'narrow terminal', code: 'MANWIDTH=64 man awk' }
|
||||
]
|
||||
EXAMPLES.mkdir = [
|
||||
{ caption: 'one dir', code: 'mkdir proj' },
|
||||
{ caption: 'parents', code: 'mkdir -p a/b/c' }
|
||||
]
|
||||
EXAMPLES.mkfifo = [
|
||||
{ caption: 'stub', code: '# FIFOs not on Hyperdrive — use shell pipelines' }
|
||||
]
|
||||
EXAMPLES.mv = [
|
||||
{ caption: 'rename', code: 'mv old.txt new.txt' },
|
||||
{ caption: 'into dir', code: 'mv *.txt ~/inbox/' }
|
||||
]
|
||||
EXAMPLES.nl = [
|
||||
{ caption: 'number all lines', code: 'nl README.md' }
|
||||
]
|
||||
EXAMPLES.od = [
|
||||
{ caption: 'hex dump vibe', code: 'od -c file.bin | head' }
|
||||
]
|
||||
EXAMPLES.pathchk = [
|
||||
{ caption: 'portable path check', code: 'pathchk -p "$HOME/file name"' }
|
||||
]
|
||||
EXAMPLES.printenv = [
|
||||
{ caption: 'one variable', code: 'printenv HOME' },
|
||||
{ caption: 'all', code: 'printenv' }
|
||||
]
|
||||
EXAMPLES.printf = [
|
||||
{ caption: 'format', code: 'printf "hex=%x dec=%d\\n" 255 255' },
|
||||
{ caption: 'no newline', code: 'printf "%s" OK' }
|
||||
]
|
||||
EXAMPLES.pwd = [
|
||||
{ caption: 'where am I', code: 'pwd' }
|
||||
]
|
||||
EXAMPLES.readlink = [
|
||||
{ caption: 'symlink target', code: 'readlink ~/.config' }
|
||||
]
|
||||
EXAMPLES.rm = [
|
||||
{ caption: 'file', code: 'rm tmp.txt' },
|
||||
{ caption: 'tree', code: 'rm -rf build/' }
|
||||
]
|
||||
EXAMPLES.rmdir = [
|
||||
{ caption: 'empty dir', code: 'rmdir olddir' }
|
||||
]
|
||||
EXAMPLES.savevault = [
|
||||
{ caption: 'snapshot encrypted vault', code: 'savevault' }
|
||||
]
|
||||
EXAMPLES.sed = [
|
||||
{ caption: 'substitute first per line', code: "sed 's/foo/bar/' file.txt" },
|
||||
{ caption: 'global per line', code: "sed 's/ //g' spaced.txt" },
|
||||
{ caption: 'in-place (if supported)', code: "sed -i.bak 's/^/# /' f.cfg" },
|
||||
{ caption: 'print line 5 only', code: "sed -n '5p' file" },
|
||||
{ caption: 'delete blank lines', code: "sed '/^$/d' file" }
|
||||
]
|
||||
EXAMPLES.seq = [
|
||||
{ caption: '1..10', code: 'seq 1 10' },
|
||||
{ caption: 'step', code: 'seq 0 2 20' }
|
||||
]
|
||||
EXAMPLES.sleep = [
|
||||
{ caption: 'pause seconds', code: 'sleep 2' }
|
||||
]
|
||||
EXAMPLES.sort = [
|
||||
{ caption: 'lexicographic', code: 'sort names.txt' },
|
||||
{ caption: 'numeric', code: 'sort -n scores.txt' },
|
||||
{ caption: 'unique', code: 'sort -u tags.txt' }
|
||||
]
|
||||
EXAMPLES.stat = [
|
||||
{ caption: 'metadata', code: 'stat ~/README.md' }
|
||||
]
|
||||
EXAMPLES.tail = [
|
||||
{ caption: 'last lines', code: 'tail -n 20 app.log' },
|
||||
{ caption: 'follow vibe (Bare: poll manually)', code: 'tail error.log' }
|
||||
]
|
||||
EXAMPLES.tee = [
|
||||
{ caption: 'copy stdout to file', code: 'cat x | tee copy.txt | wc -l' }
|
||||
]
|
||||
EXAMPLES.test = [
|
||||
{ caption: 'file exists', code: 'test -f ~/.barerc && echo yes' },
|
||||
{ caption: 'directory', code: 'test -d /home/user' },
|
||||
{ caption: 'string equal', code: 'test "$USER" = guest' }
|
||||
]
|
||||
EXAMPLES.time = [
|
||||
{ caption: 'wall time a command', code: 'time sort big.txt' }
|
||||
]
|
||||
EXAMPLES.touch = [
|
||||
{ caption: 'create empty', code: 'touch newfile' },
|
||||
{ caption: 'refresh mtime', code: 'touch -c existing' }
|
||||
]
|
||||
EXAMPLES.tr = [
|
||||
{ caption: 'uppercase', code: "echo hi | tr 'a-z' 'A-Z'" },
|
||||
{ caption: 'delete chars', code: "tr -d '\\r' < win.txt" }
|
||||
]
|
||||
EXAMPLES.true = [
|
||||
{ caption: 'always success', code: 'true && echo ok' }
|
||||
]
|
||||
EXAMPLES.tty = [
|
||||
{ caption: 'am I a tty', code: 'tty' }
|
||||
]
|
||||
EXAMPLES.uname = [
|
||||
{ caption: 'kernel-ish info', code: 'uname -a' }
|
||||
]
|
||||
EXAMPLES.wc = [
|
||||
{ caption: 'lines words bytes', code: 'wc README.md' },
|
||||
{ caption: 'stdin only', code: 'cat f | wc -l' }
|
||||
]
|
||||
EXAMPLES.which = [
|
||||
{ caption: 'resolve on PATH', code: 'which ls' }
|
||||
]
|
||||
EXAMPLES.whoami = [
|
||||
{ caption: 'effective user', code: 'whoami' }
|
||||
]
|
||||
EXAMPLES.xargs = [
|
||||
{ caption: 'workaround: shell word split', code: '# for f in *.txt; do grep -l foo $f; done' }
|
||||
]
|
||||
|
||||
/** @type {Record<string, Record<string, unknown>>} */
|
||||
const EXTRA = {}
|
||||
|
||||
EXTRA.chgrp = {
|
||||
description:
|
||||
'Changing group ownership is not supported on Bare OS: Hyperdrive metadata is single-session oriented.',
|
||||
diagnostics: ['chgrp: changing group is not supported on Bare OS'],
|
||||
bareOsNotes: 'Single-user identity; gid fields exist for display only.'
|
||||
}
|
||||
EXTRA.chown = {
|
||||
description:
|
||||
'Changing file owner is not supported on Bare OS (single-user Hyperdrive metadata).',
|
||||
diagnostics: ['chown: changing owner is not supported on Bare OS'],
|
||||
bareOsNotes: 'Use identity login/logout instead of POSIX ownership changes.'
|
||||
}
|
||||
EXTRA.xargs = {
|
||||
description:
|
||||
'xargs does not spawn arbitrary /bin utilities on Bare OS. Use shell word splitting or pipelines.',
|
||||
bareOsNotes: 'No process fork model; see handbook ch.9.'
|
||||
}
|
||||
EXTRA.getconf = {
|
||||
description:
|
||||
'Host sysconf-style values are not exposed. The command prints an error.',
|
||||
bareOsNotes: 'Stub only; no kernel sysconf surface.'
|
||||
}
|
||||
EXTRA.mkfifo = {
|
||||
description:
|
||||
'FIFO special files are not implemented on Hyperdrive. The command reports failure.',
|
||||
bareOsNotes: 'Documented stub; no real pipes as kernel objects.'
|
||||
}
|
||||
EXTRA.chmod = {
|
||||
synopsis: ['chmod MODE FILE...', 'MODE is octal (e.g. 644) or symbolic (e.g. u+rw)'],
|
||||
description:
|
||||
'Sets file mode bits on the VFS. Supports POSIX-style symbolic modes (u/g/o/a, +/-/=, rwxX) and octal modes.',
|
||||
options: [],
|
||||
keywords: ['chmod', 'mode', 'permission', 'octal', 'symbolic'],
|
||||
diagnostics: ['chmod: No such file', 'chmod: invalid mode'],
|
||||
bareOsNotes: 'Applies to Hyperdrive metadata; not a host inode.'
|
||||
}
|
||||
EXTRA.grep = {
|
||||
synopsis: [
|
||||
'grep [-E|-F] [-i] [-v] [-n] [-c] [-l] [-q] [-s] [-H|-h] [-e pat] ... [-f file] ... [pattern] [file...]'
|
||||
],
|
||||
description:
|
||||
'Searches input or files for lines matching a pattern. Uses JavaScript RegExp unless -F (fixed string). Not bit-identical to GNU grep.',
|
||||
options: [
|
||||
{ flag: '-E', meaning: 'Extended regex (accepted; patterns use JS RegExp)' },
|
||||
{ flag: '-F', meaning: 'Fixed string match' },
|
||||
{ flag: '-i', meaning: 'Ignore case' },
|
||||
{ flag: '-v', meaning: 'Invert match' },
|
||||
{ flag: '-n', meaning: 'Prefix lines with line number' },
|
||||
{ flag: '-c', meaning: 'Count matching lines only' },
|
||||
{ flag: '-l', meaning: 'List files with matches' },
|
||||
{ flag: '-q', meaning: 'Quiet (exit status only)' },
|
||||
{ flag: '-s', meaning: 'Suppress error messages' },
|
||||
{ flag: '-H / -h', meaning: 'Force / suppress filename prefix' },
|
||||
{ flag: '-e pat', meaning: 'Specify pattern' },
|
||||
{ flag: '-f file', meaning: 'Read patterns from file' }
|
||||
],
|
||||
keywords: ['grep', 'search', 'regex', 'pattern', 'filter'],
|
||||
seeAlso: [
|
||||
{ name: 'sed', section: 1 },
|
||||
{ name: 'awk', section: 1 }
|
||||
],
|
||||
bareOsNotes: 'UTF-16 strings and JS regex differ from strict POSIX/GNU.'
|
||||
}
|
||||
EXTRA.sed = {
|
||||
description:
|
||||
'Stream editor with a subset of POSIX sed. Large engine is vendored in lib/sed-engine.js.',
|
||||
keywords: ['sed', 'stream', 'edit', 'substitute'],
|
||||
seeAlso: [
|
||||
{ name: 'awk', section: 1 },
|
||||
{ name: 'grep', section: 1 }
|
||||
],
|
||||
bareOsNotes: 'JavaScript implementation; edge cases differ from GNU sed.'
|
||||
}
|
||||
EXTRA.awk = {
|
||||
description:
|
||||
'Pattern-directed scanning and processing. Engine in lib/awk-engine.js; not full POSIX awk.',
|
||||
keywords: ['awk', 'pattern', 'field', 'script'],
|
||||
seeAlso: [
|
||||
{ name: 'sed', section: 1 },
|
||||
{ name: 'grep', section: 1 }
|
||||
],
|
||||
bareOsNotes: 'See handbook ch.9 for divergence from Issue 7.'
|
||||
}
|
||||
EXTRA.ls = {
|
||||
synopsis: ['ls [-1al] [FILE...]'],
|
||||
description:
|
||||
'Lists directory contents. Long format shows mode, links, owner, group, size, mtime, and symlink targets.',
|
||||
options: [
|
||||
{ flag: '-a', meaning: 'Include names starting with .' },
|
||||
{ flag: '-l', meaning: 'Long listing' },
|
||||
{ flag: '-1', meaning: 'One name per line (short format)' }
|
||||
],
|
||||
keywords: ['ls', 'list', 'directory', 'dir'],
|
||||
bareOsNotes: 'Hides .bareos_empty marker like other tools.'
|
||||
}
|
||||
EXTRA.man = {
|
||||
synopsis: [
|
||||
'man [-k keyword] [-f name] [-l] [[section] name]',
|
||||
'man reads /share/man/man.json on the system drive.'
|
||||
],
|
||||
description:
|
||||
'Displays manual pages from the merged JSON database. Section 1 only in this release.',
|
||||
options: [
|
||||
{ flag: '-k, --apropos', meaning: 'Search keywords and titles (substring)' },
|
||||
{ flag: '-f, --whatis', meaning: 'One-line description for exact name' },
|
||||
{ flag: '-l, --list', meaning: 'List all manual page names' }
|
||||
],
|
||||
environment: ['MANWIDTH — wrap width (default 72, min 40)', 'NO_COLOR — disable bold headings on TTY'],
|
||||
keywords: ['man', 'manual', 'help', 'documentation', 'apropos', 'whatis', 'cheat', 'examples'],
|
||||
seeAlso: [{ name: 'help', section: 1 }],
|
||||
bareOsNotes: 'No troff; no embedded DB fallback in v1.'
|
||||
}
|
||||
EXTRA.help = {
|
||||
synopsis: ['help'],
|
||||
description:
|
||||
'Prints a one-screen summary of shell builtins and /bin command names. Use man for long-form documentation.',
|
||||
keywords: ['help', 'summary', 'builtins', 'commands'],
|
||||
seeAlso: [
|
||||
{ name: 'man', section: 1 },
|
||||
{ name: 'bare-os-shell', section: 1 }
|
||||
]
|
||||
}
|
||||
EXTRA.exit = {
|
||||
synopsis: ['exit [status]'],
|
||||
description:
|
||||
'When run as /bin/exit, requests the booter to end the session via ctx.requestBooterExit. Status defaults to 0.',
|
||||
bareOsNotes: 'Also available as a shell builtin with different wiring.'
|
||||
}
|
||||
EXTRA.hdms = {
|
||||
description:
|
||||
'Invokes ctx.runHdms when the booter provides HDMS integration; otherwise prints unavailable.',
|
||||
keywords: ['hdms', 'hyperswarm', 'map'],
|
||||
bareOsNotes: 'Optional booter capability.'
|
||||
}
|
||||
EXTRA.find = {
|
||||
synopsis: ['find [PATH...] [EXPRESSION]'],
|
||||
description:
|
||||
'Walks directories and applies expressions (-name, -type, -print, -maxdepth, logical -and/-or/-not).',
|
||||
keywords: ['find', 'directory', 'walk', 'search'],
|
||||
bareOsNotes: 'Expression syntax is a simplified subset.'
|
||||
}
|
||||
EXTRA.login = {
|
||||
description:
|
||||
'When invoked from /bin, behavior aligns with session identity hooks (see booter). Prefer the shell builtin for passphrase entry.',
|
||||
keywords: ['login', 'identity', 'passphrase'],
|
||||
seeAlso: [{ name: 'logout', section: 1 }]
|
||||
}
|
||||
EXTRA.logout = {
|
||||
description: 'Ends session; may persist vault depending on booter and flags.',
|
||||
keywords: ['logout', 'session'],
|
||||
seeAlso: [{ name: 'login', section: 1 }]
|
||||
}
|
||||
EXTRA.savevault = {
|
||||
description: 'Encrypts a copy of the personal drive under /.bare/vault/ when identity services are available.',
|
||||
keywords: ['savevault', 'vault', 'encrypt', 'backup'],
|
||||
seeAlso: [{ name: 'login', section: 1 }]
|
||||
}
|
||||
|
||||
function basePage(name) {
|
||||
const title = POSIX_TITLE[name] || name
|
||||
const p = {
|
||||
name,
|
||||
section: 1,
|
||||
title,
|
||||
synopsis: [`${name} [OPTION]... [OPERAND]...`],
|
||||
description: `Bare OS implementation of ${title}. Full behavior is defined in packages/bare-os-coreutils/src/${name}.js.`,
|
||||
options: [],
|
||||
keywords: [name, 'bare-os', 'coreutils']
|
||||
}
|
||||
if (STUB.has(name)) {
|
||||
p.stub = true
|
||||
p.keywords.push('stub')
|
||||
}
|
||||
const ex = EXTRA[name]
|
||||
if (ex) Object.assign(p, ex)
|
||||
if (EXAMPLES[name]) p.examples = EXAMPLES[name]
|
||||
return p
|
||||
}
|
||||
|
||||
function gitPage() {
|
||||
return {
|
||||
name: 'git',
|
||||
section: 1,
|
||||
title: 'Bare OS git front-end (isomorphic-git)',
|
||||
synopsis: ['git [-C dir] <subcommand> [ARGUMENTS...]'],
|
||||
description:
|
||||
'Runs isomorphic-git against the VFS-backed adapter. Remote HTTP(S) uses BARE_OS_GIT_HTTP when set; otherwise Pear bare module fetch.',
|
||||
options: [
|
||||
{ flag: '-C dir', meaning: 'Run as if git was started in dir' }
|
||||
],
|
||||
environment: [
|
||||
'BARE_OS_GIT_HTTP — optional fetch implementation for remotes',
|
||||
'GIT_* — standard hints where supported'
|
||||
],
|
||||
keywords: ['git', 'version control', 'repository', 'clone', 'commit', 'isomorphic-git'],
|
||||
bareOsNotes: 'Not a separate /bin script; booter delegates argv[0]=git to git-cli.js.',
|
||||
seeAlso: [{ name: 'bare-os-shell', section: 1 }],
|
||||
examples: [
|
||||
{ caption: 'new repo', code: 'git init -C ~/myrepo' },
|
||||
{ caption: 'status', code: 'git -C ~/myrepo status' },
|
||||
{ caption: 'clone over HTTP (needs remote + fetch)', code: 'git clone https://example.com/repo.git ~/work/repo' },
|
||||
{ caption: 'config local', code: 'git -C ~/myrepo config user.email "[email protected]"' },
|
||||
{ caption: 'log one line', code: 'git -C ~/myrepo log --oneline -5' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function shellPage() {
|
||||
return {
|
||||
name: 'bare-os-shell',
|
||||
section: 1,
|
||||
title: 'Bare OS interactive shell builtins',
|
||||
synopsis: ['# builtins only — no full POSIX sh grammar'],
|
||||
description:
|
||||
'The line-at-a-time shell supports aliases, simple pipelines (simulated), redirection, and the builtins below. Compound commands (if, for, while) are not available.',
|
||||
options: [],
|
||||
aliases: ['sh-builtins'],
|
||||
keywords: [
|
||||
'shell',
|
||||
'builtin',
|
||||
'cd',
|
||||
'export',
|
||||
'alias',
|
||||
'bare-os-shell',
|
||||
'sh-builtins'
|
||||
],
|
||||
builtins: [
|
||||
{
|
||||
name: 'alias',
|
||||
synopsis: ['alias', 'alias name=value ...', 'unalias name ...'],
|
||||
description: 'Define or list command aliases. unalias removes definitions.'
|
||||
},
|
||||
{
|
||||
name: 'cd',
|
||||
synopsis: ['cd [DIR]'],
|
||||
description: 'Change working directory via vfs.chdir; default is HOME.'
|
||||
},
|
||||
{
|
||||
name: 'export',
|
||||
synopsis: ['export NAME=value ...'],
|
||||
description: 'Set environment variables visible to child /bin invocations.'
|
||||
},
|
||||
{
|
||||
name: 'unset',
|
||||
synopsis: ['unset NAME ...'],
|
||||
description: 'Remove variables; readonly names cannot be unset.'
|
||||
},
|
||||
{
|
||||
name: 'readonly',
|
||||
synopsis: ['readonly NAME[=value] ...'],
|
||||
description: 'Mark variables read-only.'
|
||||
},
|
||||
{
|
||||
name: 'umask',
|
||||
synopsis: ['umask [octal]'],
|
||||
description: 'Show or set shell file creation mask (stored in env UMASK).'
|
||||
},
|
||||
{
|
||||
name: 'command',
|
||||
synopsis: ['command -v|-V NAME', 'command ARGV...'],
|
||||
description: 'Resolve or run a command without using shell functions (none) or aliases for -v/-V.'
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
synopsis: ['type NAME'],
|
||||
description: 'Report whether NAME is a builtin or a path under PATH.'
|
||||
},
|
||||
{
|
||||
name: 'login / logout',
|
||||
synopsis: ['login [--new] passphrase...', 'logout [--save]'],
|
||||
description: 'Identity unlock/register and session teardown; require booter hooks.'
|
||||
},
|
||||
{
|
||||
name: ':',
|
||||
synopsis: [':'],
|
||||
description: 'No-op builtin.'
|
||||
},
|
||||
{
|
||||
name: 'exit',
|
||||
synopsis: ['exit [n]'],
|
||||
description: 'Request booter exit with status n (builtin path).'
|
||||
}
|
||||
],
|
||||
seeAlso: [
|
||||
{ name: 'help', section: 1 },
|
||||
{ name: 'man', section: 1 }
|
||||
],
|
||||
bareOsNotes: 'Pipelines do not use OS pipes; see handbook ch.4 and ch.9.',
|
||||
examples: [
|
||||
{ caption: 'pipeline (simulated)', code: 'ls -1 /bin | grep man' },
|
||||
{ caption: 'redirect out', code: 'echo hi > ~/hello.txt' },
|
||||
{ caption: 'append', code: 'date >> ~/log.txt' },
|
||||
{ caption: 'alias + use', code: "alias ll='ls -la'\nll ~" },
|
||||
{ caption: 'export for children', code: 'export EDITOR=ed\nman ls' },
|
||||
{ caption: 'temp var for one command', code: 'PATH=/bin man which' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await mkdir(pagesDir, { recursive: true })
|
||||
for (const name of COREUTILS_COMMANDS) {
|
||||
const p = basePage(name)
|
||||
await writeFile(
|
||||
join(pagesDir, `${name}.json`),
|
||||
JSON.stringify(p, null, 2) + '\n'
|
||||
)
|
||||
}
|
||||
for (const name of MAN_EXTRA_PAGES) {
|
||||
const p = name === 'git' ? gitPage() : shellPage()
|
||||
await writeFile(
|
||||
join(pagesDir, `${name}.json`),
|
||||
JSON.stringify(p, null, 2) + '\n'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
await main()
|
||||
Reference in New Issue
Block a user