Files
bare-operating-system/packages/bare-os-coreutils/lib/man-render.js
T

207 lines
6.0 KiB
JavaScript

/** Plain-text manual formatter (prepended before src/man.js; no import in /bin/man). */
function bareManParseWidth(env) {
const raw = env && env.MANWIDTH != null ? String(env.MANWIDTH).trim() : ''
const n = raw ? Number.parseInt(raw, 10) : 72
if (!Number.isFinite(n)) return 72
return Math.max(40, Math.min(n, 200))
}
function bareManUseAnsi(ctx) {
const env = ctx.env || {}
if (env.NO_COLOR != null && String(env.NO_COLOR) !== '') return false
const out = ctx.stdout
return Boolean(out && out.isTTY)
}
function bareManBold(s, on) {
if (!on) return s
return '\x1b[1m' + s + '\x1b[0m'
}
function bareManWrap(text, width) {
const words = String(text).replace(/\s+/g, ' ').trim().split(' ')
const lines = []
let cur = ''
for (const w of words) {
const next = cur ? cur + ' ' + w : w
if (next.length <= width) cur = next
else {
if (cur) lines.push(cur)
cur = w.length > width ? w.slice(0, width) : w
while (cur.length > width) {
lines.push(cur.slice(0, width))
cur = cur.slice(width)
}
}
}
if (cur) lines.push(cur)
return lines
}
/** Indent fixed-width command lines; hard-wrap only when longer than width. */
function bareManRenderExampleCode(code, width) {
const indent = ' '
const max = Math.max(20, width - indent.length)
const out = []
for (const line of String(code).split('\n')) {
if (line.length <= max) {
out.push(indent + line)
continue
}
let rest = line
while (rest.length > max) {
out.push(indent + rest.slice(0, max))
rest = rest.slice(max)
}
if (rest) out.push(indent + rest)
}
return out.join('\n')
}
/** Keep newlines; hard-wrap long lines only (for handbook / preformatted text). */
function bareManRenderPreserve(text, width) {
const indent = ' '
const max = Math.max(20, width - indent.length)
const out = []
for (const line of String(text).split('\n')) {
if (line === '') {
out.push('')
continue
}
let rest = line
while (rest.length > max) {
out.push(indent + rest.slice(0, max))
rest = rest.slice(max)
}
out.push(indent + rest)
}
return out.join('\n') + '\n'
}
function bareManFlushBlock(lines, width, prefixFirst, prefixRest) {
const out = []
let first = true
for (const line of lines) {
const wrapped = bareManWrap(line, width - (first ? prefixFirst.length : prefixRest.length))
for (let i = 0; i < wrapped.length; i++) {
const p = i === 0 && first ? prefixFirst : prefixRest
out.push(p + wrapped[i])
first = false
}
}
return out.join('\n') + '\n'
}
function bareManRenderPage(page, ctx, width) {
const ansi = bareManUseAnsi(ctx)
const H = (s) => bareManBold(s, ansi) + '\n'
let s = ''
s += H('NAME')
s += page.name + '(' + page.section + ') - ' + page.title + '\n\n'
s += H('SYNOPSIS')
for (const line of page.synopsis) {
s += ' ' + line + '\n'
}
s += '\n'
s += H('DESCRIPTION')
if (page.descriptionMode === 'preserve') {
s += bareManRenderPreserve(page.description, width)
} else {
s += bareManFlushBlock([page.description], width, '', ' ')
}
if (page.options && page.options.length) {
s += '\n' + H('OPTIONS')
for (const o of page.options) {
const head = o.flag + '\t'
const rest = o.meaning
s += bareManFlushBlock([rest], width, ' ' + head, ' ')
}
}
if (page.examples && page.examples.length) {
s += '\n' + H('EXAMPLES')
s +=
bareManFlushBlock(
[
'tl;dr-style snippets (like cheat.sh). Copy, adapt paths; pipelines are shell-simulated on Bare OS.'
],
width,
'',
' '
) + '\n'
for (const ex of page.examples) {
if (ex.caption) {
s +=
bareManFlushBlock(
['# ' + ex.caption],
width,
'',
' '
) + '\n'
}
s += bareManRenderExampleCode(ex.code, width) + '\n\n'
}
}
if (page.environment && page.environment.length) {
s += '\n' + H('ENVIRONMENT')
for (const e of page.environment) s += ' ' + e + '\n'
}
if (page.files && page.files.length) {
s += '\n' + H('FILES')
for (const f of page.files) s += ' ' + f + '\n'
}
if (page.exitStatus && page.exitStatus.length) {
s += '\n' + H('EXIT STATUS')
for (const e of page.exitStatus) s += ' ' + e + '\n'
}
if (page.diagnostics && page.diagnostics.length) {
s += '\n' + H('DIAGNOSTICS')
for (const d of page.diagnostics) s += ' ' + d + '\n'
}
if (page.builtins && page.builtins.length) {
s += '\n' + H('SHELL BUILTINS')
for (const b of page.builtins) {
s += '\n' + bareManBold(b.name, ansi) + '\n'
if (b.synopsis && b.synopsis.length) {
for (const line of b.synopsis) s += ' ' + line + '\n'
}
s += bareManFlushBlock([b.description], width, ' ', ' ')
if (b.options && b.options.length) {
for (const o of b.options) {
const head = o.flag + '\t'
s += bareManFlushBlock([o.meaning], width, ' ' + head, ' ')
}
}
if (b.examples && b.examples.length) {
s += '\n' + bareManBold(' Examples', ansi) + '\n'
for (const ex of b.examples) {
if (ex.caption) {
s +=
bareManFlushBlock(
['# ' + ex.caption],
width,
' ',
' '
) + '\n'
}
s += bareManRenderExampleCode(ex.code, width) + '\n\n'
}
}
}
}
if (page.bareOsNotes) {
s += '\n' + H('BARE OS NOTES')
s += bareManFlushBlock([page.bareOsNotes], width, '', ' ')
}
if (page.seeAlso && page.seeAlso.length) {
s += '\n' + H('SEE ALSO')
const parts = page.seeAlso.map((r) => r.name + '(' + r.section + ')')
s += ' ' + parts.join(', ') + '\n'
}
if (page.stub) {
s += '\n' + H('STATUS')
s += ' This command is a stub or intentionally limited on Bare OS.\n'
}
return s
}