@optique/man 0.10.7 → 1.0.0-dev.1116
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -3
- package/dist/cli.cjs +136 -36
- package/dist/cli.js +124 -24
- package/dist/{generator-zoVk04OH.cjs → generator-RyVL5b_V.cjs} +32 -32
- package/dist/{generator-CCa36YC2.js → generator-YM0drazb.js} +31 -2
- package/dist/index.cjs +5 -3
- package/dist/index.d.cts +12 -9
- package/dist/index.d.ts +12 -9
- package/dist/index.js +4 -4
- package/dist/{man-BMb0Vyt9.d.cts → man-B1Q2mhit.d.ts} +34 -3
- package/dist/man-BE1OY_lJ.js +386 -0
- package/dist/man-CkvscTlM.cjs +432 -0
- package/dist/{man-tclTvdWs.d.ts → man-DKwa47XV.d.cts} +33 -2
- package/dist/man.cjs +2 -2
- package/dist/man.d.cts +1 -1
- package/dist/man.d.ts +1 -1
- package/dist/man.js +2 -2
- package/dist/{roff-C_MiRXVS.js → roff-CCdIQO7B.js} +47 -6
- package/dist/{roff-72Am6STB.d.cts → roff-COeDIWWF.d.cts} +49 -2
- package/dist/{roff-B36zMxYc.d.ts → roff-CgYQDHBN.d.ts} +49 -2
- package/dist/{roff-EpcecLXU.cjs → roff-DJ-LkRXl.cjs} +58 -5
- package/dist/roff.cjs +3 -1
- package/dist/roff.d.cts +2 -2
- package/dist/roff.d.ts +2 -2
- package/dist/roff.js +2 -2
- package/package.json +3 -3
- package/dist/man-DGnow1Jr.cjs +0 -235
- package/dist/man-Leuf7kOn.js +0 -218
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { escapeHyphens, escapeRequestArg, escapeRoff, formatMessageAsRoff } from "./roff-CCdIQO7B.js";
|
|
2
|
+
import { isDocHidden, isUsageHidden } from "@optique/core/usage";
|
|
3
|
+
|
|
4
|
+
//#region src/man.ts
|
|
5
|
+
/**
|
|
6
|
+
* Formats a date for use in man pages.
|
|
7
|
+
*
|
|
8
|
+
* When a `Date` object is given, the month and year are extracted using
|
|
9
|
+
* the host's local timezone (`getMonth()` / `getFullYear()`). This means
|
|
10
|
+
* the same `Date` instant may produce different output on machines in
|
|
11
|
+
* different timezones. Pass a pre-formatted string (e.g., `"January 2026"`)
|
|
12
|
+
* if you need timezone-independent output.
|
|
13
|
+
*
|
|
14
|
+
* @param date The date to format, or undefined.
|
|
15
|
+
* @returns The formatted date string, or undefined.
|
|
16
|
+
* @throws {RangeError} If the given `Date` object is invalid.
|
|
17
|
+
* @since 0.10.0
|
|
18
|
+
*/
|
|
19
|
+
function formatDateForMan(date) {
|
|
20
|
+
if (date === void 0) return void 0;
|
|
21
|
+
if (typeof date === "string") return date;
|
|
22
|
+
if (Number.isNaN(date.getTime())) throw new RangeError("Invalid Date object.");
|
|
23
|
+
const months = [
|
|
24
|
+
"January",
|
|
25
|
+
"February",
|
|
26
|
+
"March",
|
|
27
|
+
"April",
|
|
28
|
+
"May",
|
|
29
|
+
"June",
|
|
30
|
+
"July",
|
|
31
|
+
"August",
|
|
32
|
+
"September",
|
|
33
|
+
"October",
|
|
34
|
+
"November",
|
|
35
|
+
"December"
|
|
36
|
+
];
|
|
37
|
+
return `${months[date.getMonth()]} ${date.getFullYear()}`;
|
|
38
|
+
}
|
|
39
|
+
function formatCommandNameAsRoff(name) {
|
|
40
|
+
return `\\fB${escapeHyphens(escapeRoff(name))}\\fR`;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Formats a single {@link UsageTerm} as roff markup for the SYNOPSIS section.
|
|
44
|
+
*
|
|
45
|
+
* @param term The usage term to format.
|
|
46
|
+
* @returns The roff-formatted string.
|
|
47
|
+
* @throws {TypeError} If the term has an unknown type.
|
|
48
|
+
* @since 0.10.0
|
|
49
|
+
*/
|
|
50
|
+
function formatUsageTermAsRoff(term) {
|
|
51
|
+
return formatUsageTermAsRoffInternal(term, false);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Returns whether a usage list contains exactly one visible term that
|
|
55
|
+
* produces its own brackets. When true, a parent wrapper can safely elide
|
|
56
|
+
* its own brackets to avoid redundant nesting. Multiple bracket-producing
|
|
57
|
+
* siblings must keep their individual brackets for disambiguation.
|
|
58
|
+
*/
|
|
59
|
+
function hasSingleBracketedTerm(terms) {
|
|
60
|
+
const visible = terms.filter((t$1) => !("hidden" in t$1 && isUsageHidden(t$1.hidden)));
|
|
61
|
+
if (visible.length !== 1) return false;
|
|
62
|
+
const t = visible[0];
|
|
63
|
+
return t.type === "option" || t.type === "optional" || t.type === "multiple" && t.min < 1 || t.type === "passthrough";
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Returns whether a usage list's single visible term has the given type.
|
|
67
|
+
*/
|
|
68
|
+
function hasSingleVisibleTermOfType(terms, type) {
|
|
69
|
+
const visible = terms.filter((t) => !("hidden" in t && isUsageHidden(t.hidden)));
|
|
70
|
+
return visible.length === 1 && visible[0].type === type;
|
|
71
|
+
}
|
|
72
|
+
function formatUsageTermAsRoffInternal(term, insideBrackets) {
|
|
73
|
+
if ("hidden" in term && isUsageHidden(term.hidden)) return "";
|
|
74
|
+
switch (term.type) {
|
|
75
|
+
case "argument": return `\\fI${escapeRoff(term.metavar)}\\fR`;
|
|
76
|
+
case "option": {
|
|
77
|
+
const names = term.names.map((name) => `\\fB${escapeHyphens(name)}\\fR`).join(" | ");
|
|
78
|
+
const metavarPart = term.metavar ? ` \\fI${escapeRoff(term.metavar)}\\fR` : "";
|
|
79
|
+
if (insideBrackets) return `${names}${metavarPart}`;
|
|
80
|
+
return `[${names}${metavarPart}]`;
|
|
81
|
+
}
|
|
82
|
+
case "command": return formatCommandNameAsRoff(term.name);
|
|
83
|
+
case "optional": {
|
|
84
|
+
const childrenBracketed = hasSingleBracketedTerm(term.terms);
|
|
85
|
+
const childIsMultiple = childrenBracketed && hasSingleVisibleTermOfType(term.terms, "multiple");
|
|
86
|
+
const inner = formatUsageAsRoffInternal(term.terms, childrenBracketed);
|
|
87
|
+
if (inner === "") return "";
|
|
88
|
+
if (insideBrackets && childrenBracketed && !childIsMultiple) return inner;
|
|
89
|
+
return `[${inner}]`;
|
|
90
|
+
}
|
|
91
|
+
case "multiple": {
|
|
92
|
+
const wrapInBrackets = term.min < 1;
|
|
93
|
+
const childrenBracketed = hasSingleBracketedTerm(term.terms);
|
|
94
|
+
const childIsMultiple = childrenBracketed && hasSingleVisibleTermOfType(term.terms, "multiple");
|
|
95
|
+
const passInsideBrackets = wrapInBrackets && childrenBracketed && !childIsMultiple;
|
|
96
|
+
const inner = formatUsageAsRoffInternal(term.terms, passInsideBrackets);
|
|
97
|
+
if (inner === "") return "";
|
|
98
|
+
if (wrapInBrackets) {
|
|
99
|
+
if (insideBrackets && passInsideBrackets) return `${inner} ...`;
|
|
100
|
+
return `[${inner} ...]`;
|
|
101
|
+
}
|
|
102
|
+
return `${inner} ...`;
|
|
103
|
+
}
|
|
104
|
+
case "exclusive": {
|
|
105
|
+
const alternatives = term.terms.map((t) => formatUsageAsRoffInternal(t, false)).filter((s) => s !== "");
|
|
106
|
+
if (alternatives.length === 0) return "";
|
|
107
|
+
if (alternatives.length === 1) return alternatives[0];
|
|
108
|
+
return `(${alternatives.join(" | ")})`;
|
|
109
|
+
}
|
|
110
|
+
case "literal": return escapeRoff(term.value);
|
|
111
|
+
case "passthrough": return "[...]";
|
|
112
|
+
case "ellipsis": return "...";
|
|
113
|
+
default: {
|
|
114
|
+
const _exhaustive = term;
|
|
115
|
+
throw new TypeError(`Unknown usage term type: ${_exhaustive.type}.`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Formats a {@link Usage} array as roff markup.
|
|
121
|
+
*
|
|
122
|
+
* @param usage The usage terms to format.
|
|
123
|
+
* @returns The roff-formatted string.
|
|
124
|
+
*/
|
|
125
|
+
function formatUsageAsRoff(usage) {
|
|
126
|
+
return formatUsageAsRoffInternal(usage, false);
|
|
127
|
+
}
|
|
128
|
+
function formatUsageAsRoffInternal(usage, insideBrackets) {
|
|
129
|
+
return usage.map((term) => formatUsageTermAsRoffInternal(term, insideBrackets)).filter((s) => s !== "").join(" ");
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Formats a {@link DocEntry}'s term for man page output.
|
|
133
|
+
*
|
|
134
|
+
* @param term The usage term from the entry.
|
|
135
|
+
* @returns The roff-formatted term string.
|
|
136
|
+
*/
|
|
137
|
+
function formatDocEntryTerm(term) {
|
|
138
|
+
if ("hidden" in term && isDocHidden(term.hidden)) return "";
|
|
139
|
+
switch (term.type) {
|
|
140
|
+
case "option": {
|
|
141
|
+
const names = term.names.map((name) => `\\fB${escapeHyphens(name)}\\fR`).join(", ");
|
|
142
|
+
const metavarPart = term.metavar ? ` \\fI${escapeRoff(term.metavar)}\\fR` : "";
|
|
143
|
+
return `${names}${metavarPart}`;
|
|
144
|
+
}
|
|
145
|
+
case "command": return formatCommandNameAsRoff(term.name);
|
|
146
|
+
case "argument": return `\\fI${escapeRoff(term.metavar)}\\fR`;
|
|
147
|
+
case "literal": return escapeRoff(term.value);
|
|
148
|
+
default: return formatDocUsageTermAsRoff(term);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Formats a {@link UsageTerm} as roff markup for doc rendering, filtering
|
|
153
|
+
* doc-hidden terms instead of usage-hidden terms.
|
|
154
|
+
*
|
|
155
|
+
* @throws {TypeError} If the term has an unknown type.
|
|
156
|
+
*/
|
|
157
|
+
function formatDocUsageTermAsRoff(term) {
|
|
158
|
+
if ("hidden" in term && isDocHidden(term.hidden)) return "";
|
|
159
|
+
switch (term.type) {
|
|
160
|
+
case "optional": {
|
|
161
|
+
const inner = formatDocUsageAsRoff(term.terms);
|
|
162
|
+
if (inner === "") return "";
|
|
163
|
+
return `[${inner}]`;
|
|
164
|
+
}
|
|
165
|
+
case "multiple": {
|
|
166
|
+
const inner = formatDocUsageAsRoff(term.terms);
|
|
167
|
+
if (inner === "") return "";
|
|
168
|
+
if (term.min < 1) return `[${inner} ...]`;
|
|
169
|
+
return `${inner} ...`;
|
|
170
|
+
}
|
|
171
|
+
case "exclusive": {
|
|
172
|
+
const alternatives = term.terms.map((t) => formatDocUsageAsRoff(t)).filter((s) => s !== "");
|
|
173
|
+
if (alternatives.length === 0) return "";
|
|
174
|
+
if (alternatives.length === 1) return alternatives[0];
|
|
175
|
+
return `(${alternatives.join(" | ")})`;
|
|
176
|
+
}
|
|
177
|
+
case "argument": return `\\fI${escapeRoff(term.metavar)}\\fR`;
|
|
178
|
+
case "option": {
|
|
179
|
+
const names = term.names.map((name) => `\\fB${escapeHyphens(name)}\\fR`).join(", ");
|
|
180
|
+
const metavarPart = term.metavar ? ` \\fI${escapeRoff(term.metavar)}\\fR` : "";
|
|
181
|
+
return `${names}${metavarPart}`;
|
|
182
|
+
}
|
|
183
|
+
case "command": return formatCommandNameAsRoff(term.name);
|
|
184
|
+
case "literal": return escapeRoff(term.value);
|
|
185
|
+
case "passthrough": return "[...]";
|
|
186
|
+
case "ellipsis": return "...";
|
|
187
|
+
default: {
|
|
188
|
+
const _exhaustive = term;
|
|
189
|
+
throw new TypeError(`Unknown usage term type: ${_exhaustive.type}.`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Formats a {@link Usage} array as roff markup for doc rendering,
|
|
195
|
+
* filtering doc-hidden terms.
|
|
196
|
+
*/
|
|
197
|
+
function formatDocUsageAsRoff(usage) {
|
|
198
|
+
return usage.map(formatDocUsageTermAsRoff).filter((s) => s !== "").join(" ");
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Infers the section title from entry kinds when no explicit title is given.
|
|
202
|
+
* Returns `"COMMANDS"` for command-only sections, `"ARGUMENTS"` for
|
|
203
|
+
* argument-only sections, and `"OPTIONS"` otherwise.
|
|
204
|
+
*
|
|
205
|
+
* @param entries The entries in the section.
|
|
206
|
+
* @returns The inferred section title in uppercase.
|
|
207
|
+
*/
|
|
208
|
+
function inferSectionTitle(entries) {
|
|
209
|
+
const kinds = /* @__PURE__ */ new Set();
|
|
210
|
+
for (const entry of entries) {
|
|
211
|
+
if ("hidden" in entry.term && isDocHidden(entry.term.hidden)) continue;
|
|
212
|
+
kinds.add(entry.term.type);
|
|
213
|
+
}
|
|
214
|
+
if (kinds.size === 1) {
|
|
215
|
+
if (kinds.has("command")) return "COMMANDS";
|
|
216
|
+
if (kinds.has("argument")) return "ARGUMENTS";
|
|
217
|
+
}
|
|
218
|
+
return "OPTIONS";
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Formats a {@link DocSection} as roff markup with .TP macros.
|
|
222
|
+
*
|
|
223
|
+
* @param section The section to format.
|
|
224
|
+
* @returns The roff-formatted section content.
|
|
225
|
+
*/
|
|
226
|
+
function formatDocSectionEntries(section) {
|
|
227
|
+
const lines = [];
|
|
228
|
+
for (const entry of section.entries) {
|
|
229
|
+
const termStr = formatDocEntryTerm(entry.term);
|
|
230
|
+
if (termStr === "") continue;
|
|
231
|
+
lines.push(".TP");
|
|
232
|
+
lines.push(termStr);
|
|
233
|
+
if (entry.description) {
|
|
234
|
+
let desc = formatMessageAsRoff(entry.description);
|
|
235
|
+
if (entry.default) desc += ` [${formatMessageAsRoff(entry.default)}]`;
|
|
236
|
+
if (entry.choices) desc += ` (choices: ${formatMessageAsRoff(entry.choices, { quotes: false })})`;
|
|
237
|
+
lines.push(desc);
|
|
238
|
+
} else if (entry.default || entry.choices) {
|
|
239
|
+
const parts = [];
|
|
240
|
+
if (entry.default) parts.push(`[${formatMessageAsRoff(entry.default)}]`);
|
|
241
|
+
if (entry.choices) parts.push(`(choices: ${formatMessageAsRoff(entry.choices, { quotes: false })})`);
|
|
242
|
+
lines.push(parts.join(" "));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return lines.join("\n");
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Formats a {@link DocPage} as a complete man page in roff format.
|
|
249
|
+
*
|
|
250
|
+
* This function generates a man page following the standard man(7) format,
|
|
251
|
+
* including sections for NAME, SYNOPSIS, DESCRIPTION, OPTIONS, and more.
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* ```typescript
|
|
255
|
+
* import { formatDocPageAsMan } from "@optique/man/man";
|
|
256
|
+
* import type { DocPage } from "@optique/core/doc";
|
|
257
|
+
*
|
|
258
|
+
* const page: DocPage = {
|
|
259
|
+
* brief: message`A sample CLI application`,
|
|
260
|
+
* usage: [{ type: "argument", metavar: "FILE" }],
|
|
261
|
+
* sections: [],
|
|
262
|
+
* };
|
|
263
|
+
*
|
|
264
|
+
* const manPage = formatDocPageAsMan(page, {
|
|
265
|
+
* name: "myapp",
|
|
266
|
+
* section: 1,
|
|
267
|
+
* version: "1.0.0",
|
|
268
|
+
* });
|
|
269
|
+
* ```
|
|
270
|
+
*
|
|
271
|
+
* @param page The documentation page to format.
|
|
272
|
+
* @param options The man page options.
|
|
273
|
+
* @returns The complete man page in roff format.
|
|
274
|
+
* @throws {TypeError} If the program name is empty.
|
|
275
|
+
* @throws {RangeError} If the section number or any `seeAlso` entry's section
|
|
276
|
+
* number is not a valid man page section (1–8).
|
|
277
|
+
* @since 0.10.0
|
|
278
|
+
*/
|
|
279
|
+
function formatDocPageAsMan(page, options) {
|
|
280
|
+
if (options.name === "") throw new TypeError("Program name must not be empty.");
|
|
281
|
+
if (!Number.isInteger(options.section) || options.section < 1 || options.section > 8) {
|
|
282
|
+
let repr;
|
|
283
|
+
try {
|
|
284
|
+
repr = JSON.stringify(options.section);
|
|
285
|
+
} catch {
|
|
286
|
+
repr = String(typeof options.section);
|
|
287
|
+
}
|
|
288
|
+
throw new RangeError(`Invalid man page section number (must be 1–8): ${repr}`);
|
|
289
|
+
}
|
|
290
|
+
const lines = [];
|
|
291
|
+
const thParts = [`"${escapeHyphens(escapeRequestArg(options.name.toUpperCase()))}"`, options.section.toString()];
|
|
292
|
+
const hasDate = options.date != null && options.date !== "";
|
|
293
|
+
const hasVersion = options.version != null && options.version !== "";
|
|
294
|
+
const hasManual = options.manual != null && options.manual !== "";
|
|
295
|
+
if (hasDate) thParts.push(`"${escapeRequestArg(formatDateForMan(options.date))}"`);
|
|
296
|
+
else if (hasVersion || hasManual) thParts.push("\"\"");
|
|
297
|
+
if (hasVersion) thParts.push(`"${escapeHyphens(escapeRequestArg(options.name))} ${escapeRequestArg(options.version)}"`);
|
|
298
|
+
else if (hasManual) thParts.push("\"\"");
|
|
299
|
+
if (hasManual) thParts.push(`"${escapeRequestArg(options.manual)}"`);
|
|
300
|
+
lines.push(`.TH ${thParts.join(" ")}`);
|
|
301
|
+
lines.push(".SH NAME");
|
|
302
|
+
const brief = options.brief ?? page.brief;
|
|
303
|
+
if (brief) lines.push(`${escapeHyphens(escapeRoff(options.name))} \\- ${formatMessageAsRoff(brief)}`);
|
|
304
|
+
else lines.push(escapeHyphens(escapeRoff(options.name)));
|
|
305
|
+
if (page.usage) {
|
|
306
|
+
lines.push(".SH SYNOPSIS");
|
|
307
|
+
lines.push(`.B "${escapeHyphens(escapeRequestArg(options.name))}"`);
|
|
308
|
+
const usageStr = formatUsageAsRoff(page.usage);
|
|
309
|
+
if (usageStr) lines.push(usageStr);
|
|
310
|
+
}
|
|
311
|
+
const description = options.description ?? page.description;
|
|
312
|
+
if (description) {
|
|
313
|
+
lines.push(".SH DESCRIPTION");
|
|
314
|
+
lines.push(formatMessageAsRoff(description));
|
|
315
|
+
}
|
|
316
|
+
for (const section of page.sections) {
|
|
317
|
+
if (section.entries.length === 0) continue;
|
|
318
|
+
const content = formatDocSectionEntries(section);
|
|
319
|
+
if (content === "") continue;
|
|
320
|
+
const title = section.title?.toUpperCase() ?? inferSectionTitle(section.entries);
|
|
321
|
+
lines.push(`.SH "${escapeRequestArg(title)}"`);
|
|
322
|
+
lines.push(content);
|
|
323
|
+
}
|
|
324
|
+
if (options.environment && options.environment.entries.length > 0) {
|
|
325
|
+
const content = formatDocSectionEntries(options.environment);
|
|
326
|
+
if (content !== "") {
|
|
327
|
+
lines.push(".SH ENVIRONMENT");
|
|
328
|
+
lines.push(content);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (options.files && options.files.entries.length > 0) {
|
|
332
|
+
const content = formatDocSectionEntries(options.files);
|
|
333
|
+
if (content !== "") {
|
|
334
|
+
lines.push(".SH FILES");
|
|
335
|
+
lines.push(content);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (options.exitStatus && options.exitStatus.entries.length > 0) {
|
|
339
|
+
const content = formatDocSectionEntries(options.exitStatus);
|
|
340
|
+
if (content !== "") {
|
|
341
|
+
lines.push(".SH EXIT STATUS");
|
|
342
|
+
lines.push(content);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const examples = options.examples ?? page.examples;
|
|
346
|
+
if (examples) {
|
|
347
|
+
lines.push(".SH EXAMPLES");
|
|
348
|
+
lines.push(formatMessageAsRoff(examples));
|
|
349
|
+
}
|
|
350
|
+
const bugs = options.bugs ?? page.bugs;
|
|
351
|
+
if (bugs) {
|
|
352
|
+
lines.push(".SH BUGS");
|
|
353
|
+
lines.push(formatMessageAsRoff(bugs));
|
|
354
|
+
}
|
|
355
|
+
if (options.seeAlso && options.seeAlso.length > 0) {
|
|
356
|
+
for (const ref of options.seeAlso) if (!Number.isInteger(ref.section) || ref.section < 1 || ref.section > 8) {
|
|
357
|
+
let repr;
|
|
358
|
+
try {
|
|
359
|
+
repr = JSON.stringify(ref.section);
|
|
360
|
+
} catch {
|
|
361
|
+
repr = String(typeof ref.section);
|
|
362
|
+
}
|
|
363
|
+
throw new RangeError(`Invalid man page section number for seeAlso entry ${JSON.stringify(ref.name)} (must be 1–8): ${repr}`);
|
|
364
|
+
}
|
|
365
|
+
lines.push(".SH SEE ALSO");
|
|
366
|
+
const refs = options.seeAlso.map((ref, i) => {
|
|
367
|
+
const suffix = i < options.seeAlso.length - 1 ? "," : "";
|
|
368
|
+
return `.BR "${escapeHyphens(escapeRequestArg(ref.name))}" (${ref.section})${suffix}`;
|
|
369
|
+
});
|
|
370
|
+
lines.push(refs.join("\n"));
|
|
371
|
+
}
|
|
372
|
+
const author = options.author ?? page.author;
|
|
373
|
+
if (author) {
|
|
374
|
+
lines.push(".SH AUTHOR");
|
|
375
|
+
lines.push(formatMessageAsRoff(author));
|
|
376
|
+
}
|
|
377
|
+
const footer = options.footer ?? page.footer;
|
|
378
|
+
if (footer) {
|
|
379
|
+
lines.push(".PP");
|
|
380
|
+
lines.push(formatMessageAsRoff(footer));
|
|
381
|
+
}
|
|
382
|
+
return lines.join("\n");
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
//#endregion
|
|
386
|
+
export { formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff };
|