147 lines
4.0 KiB
JavaScript
147 lines
4.0 KiB
JavaScript
/**
|
|
* Convert repo docs/ tree (.md files, recursive) into man(7) page objects (same pipeline as handbook).
|
|
*/
|
|
import { readFile, readdir } from 'fs/promises'
|
|
import { join, relative } from 'path'
|
|
|
|
import { handbookMdToDescription } from './ingest-handbook-for-man.mjs'
|
|
|
|
const SECTION = 7
|
|
|
|
function firstHeadingTitle(md) {
|
|
const m = md.match(/^#\s+(.+)$/m)
|
|
if (!m) return 'Bare OS documentation'
|
|
let t = m[1]
|
|
t = t.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
t = t.replace(/`([^`]+)`/g, '$1')
|
|
return t.trim()
|
|
}
|
|
|
|
/**
|
|
* @param {string} absDir
|
|
* @param {string} baseAbs
|
|
* @returns {Promise<string[]>}
|
|
*/
|
|
async function collectMdRelPaths(absDir, baseAbs) {
|
|
/** @type {string[]} */
|
|
const out = []
|
|
const ents = await readdir(absDir, { withFileTypes: true })
|
|
ents.sort((a, b) => a.name.localeCompare(b.name))
|
|
for (const ent of ents) {
|
|
const full = join(absDir, ent.name)
|
|
if (ent.isDirectory()) {
|
|
out.push(...(await collectMdRelPaths(full, baseAbs)))
|
|
} else if (ent.isFile() && ent.name.endsWith('.md')) {
|
|
out.push(relative(baseAbs, full).replace(/\\/g, '/'))
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
/**
|
|
* @param {string} relPosix path under docs/, e.g. reference/foo.md
|
|
*/
|
|
function relPathToManMeta(relPosix) {
|
|
if (relPosix === 'README.md') {
|
|
return {
|
|
rel: relPosix,
|
|
name: 'bare-os-docs',
|
|
title:
|
|
'Bare OS documentation (docs/) — hub, maps, and where to read next',
|
|
aliases: ['docs', 'documentation', 'bare-os-documentation']
|
|
}
|
|
}
|
|
const base = relPosix.replace(/\.md$/i, '')
|
|
const slug = base.split('/').map((s) => s.toLowerCase()).join('-')
|
|
return {
|
|
rel: relPosix,
|
|
name: 'docs-' + slug,
|
|
title: '',
|
|
aliases: []
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} repoRoot
|
|
* @returns {Promise<object[]>}
|
|
*/
|
|
export async function buildDocsManPages(repoRoot) {
|
|
const docsDir = join(repoRoot, 'docs')
|
|
let relPaths
|
|
try {
|
|
relPaths = await collectMdRelPaths(docsDir, docsDir)
|
|
} catch {
|
|
return []
|
|
}
|
|
|
|
relPaths.sort((a, b) => a.localeCompare(b))
|
|
|
|
/** @type {{ rel: string, name: string, title: string, aliases?: string[] }[]} */
|
|
const meta = relPaths.map(relPathToManMeta)
|
|
const seen = new Set()
|
|
for (const m of meta) {
|
|
if (seen.has(m.name))
|
|
throw new Error(`ingest-docs-for-man: duplicate man name "${m.name}"`)
|
|
seen.add(m.name)
|
|
}
|
|
|
|
const pages = []
|
|
for (let j = 0; j < meta.length; j++) {
|
|
const m = meta[j]
|
|
const filePath = join(docsDir, m.rel)
|
|
const md = await readFile(filePath, 'utf8')
|
|
const title = m.title || firstHeadingTitle(md)
|
|
const description = handbookMdToDescription(md, 'docs/' + m.rel)
|
|
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 })
|
|
seeAlso.push({ name: 'bare-os-handbook', section: SECTION })
|
|
|
|
const kw = new Set([
|
|
'docs',
|
|
'documentation',
|
|
'bare-os',
|
|
'reference',
|
|
'markdown'
|
|
])
|
|
for (const t of m.name.split(/[^a-z0-9]+/i)) {
|
|
if (t.length > 1) kw.add(t.toLowerCase())
|
|
}
|
|
for (const seg of m.rel.replace(/\.md$/i, '').split(/[/\\]+/)) {
|
|
for (const t of seg.toLowerCase().split(/[^a-z0-9]+/)) {
|
|
if (t.length > 2) kw.add(t)
|
|
}
|
|
}
|
|
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,
|
|
'Documentation page (plain text from docs/' + m.rel + ')'
|
|
],
|
|
description,
|
|
descriptionMode: 'preserve',
|
|
options: [],
|
|
keywords: Array.from(kw),
|
|
seeAlso,
|
|
bareOsNotes:
|
|
'Generated at build time from docs/' +
|
|
m.rel +
|
|
'. Mermaid diagrams omitted in terminal; see repo Markdown for figures.'
|
|
}
|
|
if (m.aliases && m.aliases.length) page.aliases = m.aliases
|
|
pages.push(page)
|
|
}
|
|
|
|
return pages
|
|
}
|