@optique/man 0.10.7 → 1.0.0-dev.1109

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.
@@ -0,0 +1,432 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+ const require_roff = require('./roff-DJ-LkRXl.cjs');
25
+ const __optique_core_usage = __toESM(require("@optique/core/usage"));
26
+
27
+ //#region src/man.ts
28
+ /**
29
+ * Formats a date for use in man pages.
30
+ *
31
+ * When a `Date` object is given, the month and year are extracted using
32
+ * the host's local timezone (`getMonth()` / `getFullYear()`). This means
33
+ * the same `Date` instant may produce different output on machines in
34
+ * different timezones. Pass a pre-formatted string (e.g., `"January 2026"`)
35
+ * if you need timezone-independent output.
36
+ *
37
+ * @param date The date to format, or undefined.
38
+ * @returns The formatted date string, or undefined.
39
+ * @throws {RangeError} If the given `Date` object is invalid.
40
+ * @since 0.10.0
41
+ */
42
+ function formatDateForMan(date) {
43
+ if (date === void 0) return void 0;
44
+ if (typeof date === "string") return date;
45
+ if (Number.isNaN(date.getTime())) throw new RangeError("Invalid Date object.");
46
+ const months = [
47
+ "January",
48
+ "February",
49
+ "March",
50
+ "April",
51
+ "May",
52
+ "June",
53
+ "July",
54
+ "August",
55
+ "September",
56
+ "October",
57
+ "November",
58
+ "December"
59
+ ];
60
+ return `${months[date.getMonth()]} ${date.getFullYear()}`;
61
+ }
62
+ function formatCommandNameAsRoff(name) {
63
+ return `\\fB${require_roff.escapeHyphens(require_roff.escapeRoff(name))}\\fR`;
64
+ }
65
+ /**
66
+ * Formats a single {@link UsageTerm} as roff markup for the SYNOPSIS section.
67
+ *
68
+ * @param term The usage term to format.
69
+ * @returns The roff-formatted string.
70
+ * @throws {TypeError} If the term has an unknown type.
71
+ * @since 0.10.0
72
+ */
73
+ function formatUsageTermAsRoff(term) {
74
+ return formatUsageTermAsRoffInternal(term, false);
75
+ }
76
+ /**
77
+ * Returns whether a usage list contains exactly one visible term that
78
+ * produces its own brackets. When true, a parent wrapper can safely elide
79
+ * its own brackets to avoid redundant nesting. Multiple bracket-producing
80
+ * siblings must keep their individual brackets for disambiguation.
81
+ */
82
+ function hasSingleBracketedTerm(terms) {
83
+ const visible = terms.filter((t$1) => !("hidden" in t$1 && (0, __optique_core_usage.isUsageHidden)(t$1.hidden)));
84
+ if (visible.length !== 1) return false;
85
+ const t = visible[0];
86
+ return t.type === "option" || t.type === "optional" || t.type === "multiple" && t.min < 1 || t.type === "passthrough";
87
+ }
88
+ /**
89
+ * Returns whether a usage list's single visible term has the given type.
90
+ */
91
+ function hasSingleVisibleTermOfType(terms, type) {
92
+ const visible = terms.filter((t) => !("hidden" in t && (0, __optique_core_usage.isUsageHidden)(t.hidden)));
93
+ return visible.length === 1 && visible[0].type === type;
94
+ }
95
+ function formatUsageTermAsRoffInternal(term, insideBrackets) {
96
+ if ("hidden" in term && (0, __optique_core_usage.isUsageHidden)(term.hidden)) return "";
97
+ switch (term.type) {
98
+ case "argument": return `\\fI${require_roff.escapeRoff(term.metavar)}\\fR`;
99
+ case "option": {
100
+ const names = term.names.map((name) => `\\fB${require_roff.escapeHyphens(name)}\\fR`).join(" | ");
101
+ const metavarPart = term.metavar ? ` \\fI${require_roff.escapeRoff(term.metavar)}\\fR` : "";
102
+ if (insideBrackets) return `${names}${metavarPart}`;
103
+ return `[${names}${metavarPart}]`;
104
+ }
105
+ case "command": return formatCommandNameAsRoff(term.name);
106
+ case "optional": {
107
+ const childrenBracketed = hasSingleBracketedTerm(term.terms);
108
+ const childIsMultiple = childrenBracketed && hasSingleVisibleTermOfType(term.terms, "multiple");
109
+ const inner = formatUsageAsRoffInternal(term.terms, childrenBracketed);
110
+ if (inner === "") return "";
111
+ if (insideBrackets && childrenBracketed && !childIsMultiple) return inner;
112
+ return `[${inner}]`;
113
+ }
114
+ case "multiple": {
115
+ const wrapInBrackets = term.min < 1;
116
+ const childrenBracketed = hasSingleBracketedTerm(term.terms);
117
+ const childIsMultiple = childrenBracketed && hasSingleVisibleTermOfType(term.terms, "multiple");
118
+ const passInsideBrackets = wrapInBrackets && childrenBracketed && !childIsMultiple;
119
+ const inner = formatUsageAsRoffInternal(term.terms, passInsideBrackets);
120
+ if (inner === "") return "";
121
+ if (wrapInBrackets) {
122
+ if (insideBrackets && passInsideBrackets) return `${inner} ...`;
123
+ return `[${inner} ...]`;
124
+ }
125
+ return `${inner} ...`;
126
+ }
127
+ case "exclusive": {
128
+ const alternatives = term.terms.map((t) => formatUsageAsRoffInternal(t, false)).filter((s) => s !== "");
129
+ if (alternatives.length === 0) return "";
130
+ if (alternatives.length === 1) return alternatives[0];
131
+ return `(${alternatives.join(" | ")})`;
132
+ }
133
+ case "literal": return require_roff.escapeRoff(term.value);
134
+ case "passthrough": return "[...]";
135
+ case "ellipsis": return "...";
136
+ default: {
137
+ const _exhaustive = term;
138
+ throw new TypeError(`Unknown usage term type: ${_exhaustive.type}.`);
139
+ }
140
+ }
141
+ }
142
+ /**
143
+ * Formats a {@link Usage} array as roff markup.
144
+ *
145
+ * @param usage The usage terms to format.
146
+ * @returns The roff-formatted string.
147
+ */
148
+ function formatUsageAsRoff(usage) {
149
+ return formatUsageAsRoffInternal(usage, false);
150
+ }
151
+ function formatUsageAsRoffInternal(usage, insideBrackets) {
152
+ return usage.map((term) => formatUsageTermAsRoffInternal(term, insideBrackets)).filter((s) => s !== "").join(" ");
153
+ }
154
+ /**
155
+ * Formats a {@link DocEntry}'s term for man page output.
156
+ *
157
+ * @param term The usage term from the entry.
158
+ * @returns The roff-formatted term string.
159
+ */
160
+ function formatDocEntryTerm(term) {
161
+ if ("hidden" in term && (0, __optique_core_usage.isDocHidden)(term.hidden)) return "";
162
+ switch (term.type) {
163
+ case "option": {
164
+ const names = term.names.map((name) => `\\fB${require_roff.escapeHyphens(name)}\\fR`).join(", ");
165
+ const metavarPart = term.metavar ? ` \\fI${require_roff.escapeRoff(term.metavar)}\\fR` : "";
166
+ return `${names}${metavarPart}`;
167
+ }
168
+ case "command": return formatCommandNameAsRoff(term.name);
169
+ case "argument": return `\\fI${require_roff.escapeRoff(term.metavar)}\\fR`;
170
+ case "literal": return require_roff.escapeRoff(term.value);
171
+ default: return formatDocUsageTermAsRoff(term);
172
+ }
173
+ }
174
+ /**
175
+ * Formats a {@link UsageTerm} as roff markup for doc rendering, filtering
176
+ * doc-hidden terms instead of usage-hidden terms.
177
+ *
178
+ * @throws {TypeError} If the term has an unknown type.
179
+ */
180
+ function formatDocUsageTermAsRoff(term) {
181
+ if ("hidden" in term && (0, __optique_core_usage.isDocHidden)(term.hidden)) return "";
182
+ switch (term.type) {
183
+ case "optional": {
184
+ const inner = formatDocUsageAsRoff(term.terms);
185
+ if (inner === "") return "";
186
+ return `[${inner}]`;
187
+ }
188
+ case "multiple": {
189
+ const inner = formatDocUsageAsRoff(term.terms);
190
+ if (inner === "") return "";
191
+ if (term.min < 1) return `[${inner} ...]`;
192
+ return `${inner} ...`;
193
+ }
194
+ case "exclusive": {
195
+ const alternatives = term.terms.map((t) => formatDocUsageAsRoff(t)).filter((s) => s !== "");
196
+ if (alternatives.length === 0) return "";
197
+ if (alternatives.length === 1) return alternatives[0];
198
+ return `(${alternatives.join(" | ")})`;
199
+ }
200
+ case "argument": return `\\fI${require_roff.escapeRoff(term.metavar)}\\fR`;
201
+ case "option": {
202
+ const names = term.names.map((name) => `\\fB${require_roff.escapeHyphens(name)}\\fR`).join(", ");
203
+ const metavarPart = term.metavar ? ` \\fI${require_roff.escapeRoff(term.metavar)}\\fR` : "";
204
+ return `${names}${metavarPart}`;
205
+ }
206
+ case "command": return formatCommandNameAsRoff(term.name);
207
+ case "literal": return require_roff.escapeRoff(term.value);
208
+ case "passthrough": return "[...]";
209
+ case "ellipsis": return "...";
210
+ default: {
211
+ const _exhaustive = term;
212
+ throw new TypeError(`Unknown usage term type: ${_exhaustive.type}.`);
213
+ }
214
+ }
215
+ }
216
+ /**
217
+ * Formats a {@link Usage} array as roff markup for doc rendering,
218
+ * filtering doc-hidden terms.
219
+ */
220
+ function formatDocUsageAsRoff(usage) {
221
+ return usage.map(formatDocUsageTermAsRoff).filter((s) => s !== "").join(" ");
222
+ }
223
+ /**
224
+ * Infers the section title from entry kinds when no explicit title is given.
225
+ * Returns `"COMMANDS"` for command-only sections, `"ARGUMENTS"` for
226
+ * argument-only sections, and `"OPTIONS"` otherwise.
227
+ *
228
+ * @param entries The entries in the section.
229
+ * @returns The inferred section title in uppercase.
230
+ */
231
+ function inferSectionTitle(entries) {
232
+ const kinds = /* @__PURE__ */ new Set();
233
+ for (const entry of entries) {
234
+ if ("hidden" in entry.term && (0, __optique_core_usage.isDocHidden)(entry.term.hidden)) continue;
235
+ kinds.add(entry.term.type);
236
+ }
237
+ if (kinds.size === 1) {
238
+ if (kinds.has("command")) return "COMMANDS";
239
+ if (kinds.has("argument")) return "ARGUMENTS";
240
+ }
241
+ return "OPTIONS";
242
+ }
243
+ /**
244
+ * Formats a {@link DocSection} as roff markup with .TP macros.
245
+ *
246
+ * @param section The section to format.
247
+ * @returns The roff-formatted section content.
248
+ */
249
+ function formatDocSectionEntries(section) {
250
+ const lines = [];
251
+ for (const entry of section.entries) {
252
+ const termStr = formatDocEntryTerm(entry.term);
253
+ if (termStr === "") continue;
254
+ lines.push(".TP");
255
+ lines.push(termStr);
256
+ if (entry.description) {
257
+ let desc = require_roff.formatMessageAsRoff(entry.description);
258
+ if (entry.default) desc += ` [${require_roff.formatMessageAsRoff(entry.default)}]`;
259
+ if (entry.choices) desc += ` (choices: ${require_roff.formatMessageAsRoff(entry.choices, { quotes: false })})`;
260
+ lines.push(desc);
261
+ } else if (entry.default || entry.choices) {
262
+ const parts = [];
263
+ if (entry.default) parts.push(`[${require_roff.formatMessageAsRoff(entry.default)}]`);
264
+ if (entry.choices) parts.push(`(choices: ${require_roff.formatMessageAsRoff(entry.choices, { quotes: false })})`);
265
+ lines.push(parts.join(" "));
266
+ }
267
+ }
268
+ return lines.join("\n");
269
+ }
270
+ /**
271
+ * Formats a {@link DocPage} as a complete man page in roff format.
272
+ *
273
+ * This function generates a man page following the standard man(7) format,
274
+ * including sections for NAME, SYNOPSIS, DESCRIPTION, OPTIONS, and more.
275
+ *
276
+ * @example
277
+ * ```typescript
278
+ * import { formatDocPageAsMan } from "@optique/man/man";
279
+ * import type { DocPage } from "@optique/core/doc";
280
+ *
281
+ * const page: DocPage = {
282
+ * brief: message`A sample CLI application`,
283
+ * usage: [{ type: "argument", metavar: "FILE" }],
284
+ * sections: [],
285
+ * };
286
+ *
287
+ * const manPage = formatDocPageAsMan(page, {
288
+ * name: "myapp",
289
+ * section: 1,
290
+ * version: "1.0.0",
291
+ * });
292
+ * ```
293
+ *
294
+ * @param page The documentation page to format.
295
+ * @param options The man page options.
296
+ * @returns The complete man page in roff format.
297
+ * @throws {TypeError} If the program name is empty.
298
+ * @throws {RangeError} If the section number or any `seeAlso` entry's section
299
+ * number is not a valid man page section (1–8).
300
+ * @since 0.10.0
301
+ */
302
+ function formatDocPageAsMan(page, options) {
303
+ if (options.name === "") throw new TypeError("Program name must not be empty.");
304
+ if (!Number.isInteger(options.section) || options.section < 1 || options.section > 8) {
305
+ let repr;
306
+ try {
307
+ repr = JSON.stringify(options.section);
308
+ } catch {
309
+ repr = String(typeof options.section);
310
+ }
311
+ throw new RangeError(`Invalid man page section number (must be 1–8): ${repr}`);
312
+ }
313
+ const lines = [];
314
+ const thParts = [`"${require_roff.escapeHyphens(require_roff.escapeRequestArg(options.name.toUpperCase()))}"`, options.section.toString()];
315
+ const hasDate = options.date != null && options.date !== "";
316
+ const hasVersion = options.version != null && options.version !== "";
317
+ const hasManual = options.manual != null && options.manual !== "";
318
+ if (hasDate) thParts.push(`"${require_roff.escapeRequestArg(formatDateForMan(options.date))}"`);
319
+ else if (hasVersion || hasManual) thParts.push("\"\"");
320
+ if (hasVersion) thParts.push(`"${require_roff.escapeHyphens(require_roff.escapeRequestArg(options.name))} ${require_roff.escapeRequestArg(options.version)}"`);
321
+ else if (hasManual) thParts.push("\"\"");
322
+ if (hasManual) thParts.push(`"${require_roff.escapeRequestArg(options.manual)}"`);
323
+ lines.push(`.TH ${thParts.join(" ")}`);
324
+ lines.push(".SH NAME");
325
+ const brief = options.brief ?? page.brief;
326
+ if (brief) lines.push(`${require_roff.escapeHyphens(require_roff.escapeRoff(options.name))} \\- ${require_roff.formatMessageAsRoff(brief)}`);
327
+ else lines.push(require_roff.escapeHyphens(require_roff.escapeRoff(options.name)));
328
+ if (page.usage) {
329
+ lines.push(".SH SYNOPSIS");
330
+ lines.push(`.B "${require_roff.escapeHyphens(require_roff.escapeRequestArg(options.name))}"`);
331
+ const usageStr = formatUsageAsRoff(page.usage);
332
+ if (usageStr) lines.push(usageStr);
333
+ }
334
+ const description = options.description ?? page.description;
335
+ if (description) {
336
+ lines.push(".SH DESCRIPTION");
337
+ lines.push(require_roff.formatMessageAsRoff(description));
338
+ }
339
+ for (const section of page.sections) {
340
+ if (section.entries.length === 0) continue;
341
+ const content = formatDocSectionEntries(section);
342
+ if (content === "") continue;
343
+ const title = section.title?.toUpperCase() ?? inferSectionTitle(section.entries);
344
+ lines.push(`.SH "${require_roff.escapeRequestArg(title)}"`);
345
+ lines.push(content);
346
+ }
347
+ if (options.environment && options.environment.entries.length > 0) {
348
+ const content = formatDocSectionEntries(options.environment);
349
+ if (content !== "") {
350
+ lines.push(".SH ENVIRONMENT");
351
+ lines.push(content);
352
+ }
353
+ }
354
+ if (options.files && options.files.entries.length > 0) {
355
+ const content = formatDocSectionEntries(options.files);
356
+ if (content !== "") {
357
+ lines.push(".SH FILES");
358
+ lines.push(content);
359
+ }
360
+ }
361
+ if (options.exitStatus && options.exitStatus.entries.length > 0) {
362
+ const content = formatDocSectionEntries(options.exitStatus);
363
+ if (content !== "") {
364
+ lines.push(".SH EXIT STATUS");
365
+ lines.push(content);
366
+ }
367
+ }
368
+ const examples = options.examples ?? page.examples;
369
+ if (examples) {
370
+ lines.push(".SH EXAMPLES");
371
+ lines.push(require_roff.formatMessageAsRoff(examples));
372
+ }
373
+ const bugs = options.bugs ?? page.bugs;
374
+ if (bugs) {
375
+ lines.push(".SH BUGS");
376
+ lines.push(require_roff.formatMessageAsRoff(bugs));
377
+ }
378
+ if (options.seeAlso && options.seeAlso.length > 0) {
379
+ for (const ref of options.seeAlso) if (!Number.isInteger(ref.section) || ref.section < 1 || ref.section > 8) {
380
+ let repr;
381
+ try {
382
+ repr = JSON.stringify(ref.section);
383
+ } catch {
384
+ repr = String(typeof ref.section);
385
+ }
386
+ throw new RangeError(`Invalid man page section number for seeAlso entry ${JSON.stringify(ref.name)} (must be 1–8): ${repr}`);
387
+ }
388
+ lines.push(".SH SEE ALSO");
389
+ const refs = options.seeAlso.map((ref, i) => {
390
+ const suffix = i < options.seeAlso.length - 1 ? "," : "";
391
+ return `.BR "${require_roff.escapeHyphens(require_roff.escapeRequestArg(ref.name))}" (${ref.section})${suffix}`;
392
+ });
393
+ lines.push(refs.join("\n"));
394
+ }
395
+ const author = options.author ?? page.author;
396
+ if (author) {
397
+ lines.push(".SH AUTHOR");
398
+ lines.push(require_roff.formatMessageAsRoff(author));
399
+ }
400
+ const footer = options.footer ?? page.footer;
401
+ if (footer) {
402
+ lines.push(".PP");
403
+ lines.push(require_roff.formatMessageAsRoff(footer));
404
+ }
405
+ return lines.join("\n");
406
+ }
407
+
408
+ //#endregion
409
+ Object.defineProperty(exports, '__toESM', {
410
+ enumerable: true,
411
+ get: function () {
412
+ return __toESM;
413
+ }
414
+ });
415
+ Object.defineProperty(exports, 'formatDateForMan', {
416
+ enumerable: true,
417
+ get: function () {
418
+ return formatDateForMan;
419
+ }
420
+ });
421
+ Object.defineProperty(exports, 'formatDocPageAsMan', {
422
+ enumerable: true,
423
+ get: function () {
424
+ return formatDocPageAsMan;
425
+ }
426
+ });
427
+ Object.defineProperty(exports, 'formatUsageTermAsRoff', {
428
+ enumerable: true,
429
+ get: function () {
430
+ return formatUsageTermAsRoff;
431
+ }
432
+ });
@@ -32,7 +32,9 @@ interface ManPageOptions {
32
32
  readonly section: ManPageSection;
33
33
  /**
34
34
  * The date to display in the man page footer.
35
- * If a Date object is provided, it will be formatted as "Month Year".
35
+ * If a `Date` object is provided, it will be formatted as `"Month Year"`
36
+ * using the host's local timezone. For timezone-independent output (e.g.,
37
+ * in CI), pass a pre-formatted string instead.
36
38
  * If a string is provided, it will be used as-is.
37
39
  */
38
40
  readonly date?: string | Date;
@@ -62,7 +64,7 @@ interface ManPageOptions {
62
64
  */
63
65
  readonly seeAlso?: ReadonlyArray<{
64
66
  readonly name: string;
65
- readonly section: number;
67
+ readonly section: ManPageSection;
66
68
  }>;
67
69
  /**
68
70
  * Environment variables to document in the ENVIRONMENT section.
@@ -76,12 +78,37 @@ interface ManPageOptions {
76
78
  * Exit status codes to document in the EXIT STATUS section.
77
79
  */
78
80
  readonly exitStatus?: DocSection;
81
+ /**
82
+ * A brief description of the program for the NAME section.
83
+ * Overrides the brief from the {@link DocPage} if both are present.
84
+ * @since 1.0.0
85
+ */
86
+ readonly brief?: Message;
87
+ /**
88
+ * A detailed description of the program for the DESCRIPTION section.
89
+ * Overrides the description from the {@link DocPage} if both are present.
90
+ * @since 1.0.0
91
+ */
92
+ readonly description?: Message;
93
+ /**
94
+ * Footer text appended at the end of the man page.
95
+ * Overrides the footer from the {@link DocPage} if both are present.
96
+ * @since 1.0.0
97
+ */
98
+ readonly footer?: Message;
79
99
  }
80
100
  /**
81
101
  * Formats a date for use in man pages.
82
102
  *
103
+ * When a `Date` object is given, the month and year are extracted using
104
+ * the host's local timezone (`getMonth()` / `getFullYear()`). This means
105
+ * the same `Date` instant may produce different output on machines in
106
+ * different timezones. Pass a pre-formatted string (e.g., `"January 2026"`)
107
+ * if you need timezone-independent output.
108
+ *
83
109
  * @param date The date to format, or undefined.
84
110
  * @returns The formatted date string, or undefined.
111
+ * @throws {RangeError} If the given `Date` object is invalid.
85
112
  * @since 0.10.0
86
113
  */
87
114
  declare function formatDateForMan(date: string | Date | undefined): string | undefined;
@@ -90,6 +117,7 @@ declare function formatDateForMan(date: string | Date | undefined): string | und
90
117
  *
91
118
  * @param term The usage term to format.
92
119
  * @returns The roff-formatted string.
120
+ * @throws {TypeError} If the term has an unknown type.
93
121
  * @since 0.10.0
94
122
  */
95
123
  declare function formatUsageTermAsRoff(term: UsageTerm): string;
@@ -120,6 +148,9 @@ declare function formatUsageTermAsRoff(term: UsageTerm): string;
120
148
  * @param page The documentation page to format.
121
149
  * @param options The man page options.
122
150
  * @returns The complete man page in roff format.
151
+ * @throws {TypeError} If the program name is empty.
152
+ * @throws {RangeError} If the section number or any `seeAlso` entry's section
153
+ * number is not a valid man page section (1–8).
123
154
  * @since 0.10.0
124
155
  */
125
156
  declare function formatDocPageAsMan(page: DocPage, options: ManPageOptions): string;
package/dist/man.cjs CHANGED
@@ -1,5 +1,5 @@
1
- require('./roff-EpcecLXU.cjs');
2
- const require_man = require('./man-DGnow1Jr.cjs');
1
+ const require_man = require('./man-CkvscTlM.cjs');
2
+ require('./roff-DJ-LkRXl.cjs');
3
3
 
4
4
  exports.formatDateForMan = require_man.formatDateForMan;
5
5
  exports.formatDocPageAsMan = require_man.formatDocPageAsMan;
package/dist/man.d.cts CHANGED
@@ -1,2 +1,2 @@
1
- import { ManPageOptions, ManPageSection, formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff } from "./man-BMb0Vyt9.cjs";
1
+ import { ManPageOptions, ManPageSection, formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff } from "./man-DKwa47XV.cjs";
2
2
  export { ManPageOptions, ManPageSection, formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff };
package/dist/man.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { ManPageOptions, ManPageSection, formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff } from "./man-tclTvdWs.js";
1
+ import { ManPageOptions, ManPageSection, formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff } from "./man-B1Q2mhit.js";
2
2
  export { ManPageOptions, ManPageSection, formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff };
package/dist/man.js CHANGED
@@ -1,4 +1,4 @@
1
- import "./roff-C_MiRXVS.js";
2
- import { formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff } from "./man-Leuf7kOn.js";
1
+ import "./roff-CCdIQO7B.js";
2
+ import { formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff } from "./man-BE1OY_lJ.js";
3
3
 
4
4
  export { formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff };
@@ -40,6 +40,42 @@ function escapeRoff(text) {
40
40
  return escapeLineStarts(escapeBackslashes(text));
41
41
  }
42
42
  /**
43
+ * Escapes roff-sensitive characters inside a quoted value in body text.
44
+ * Handles backslashes and double quotes so the value can be safely
45
+ * placed between literal `"` delimiters in roff text output (e.g.,
46
+ * `value()` / `values()` message terms).
47
+ *
48
+ * This function is *not* safe for roff request arguments such as
49
+ * `.SH "..."`, where groff performs an extra level of escape
50
+ * interpretation. Use {@link escapeRequestArg} for that purpose.
51
+ *
52
+ * @param text The raw value text.
53
+ * @returns The escaped text safe for use inside roff double quotes
54
+ * in body text.
55
+ * @since 1.0.0
56
+ */
57
+ function escapeQuotedValue(text) {
58
+ return escapeBackslashes(text).replace(/"/g, "\\(dq");
59
+ }
60
+ /**
61
+ * Escapes roff-sensitive characters inside a quoted roff request argument
62
+ * (e.g., `.SH "..."`). Unlike {@link escapeQuotedValue}, this function
63
+ * replaces backslashes with the `\(rs` glyph instead of `\\`, because
64
+ * groff performs an extra level of escape interpretation on request
65
+ * arguments — `\\` would still be parsed as an escape prefix.
66
+ *
67
+ * Line breaks (`\r\n`, `\r`, `\n`) are normalized to spaces because a
68
+ * raw newline would split the request line and cause the remainder to be
69
+ * parsed as new roff input.
70
+ *
71
+ * @param text The raw argument text.
72
+ * @returns The escaped text safe for use inside a quoted roff request.
73
+ * @since 1.0.0
74
+ */
75
+ function escapeRequestArg(text) {
76
+ return text.replace(/\r\n|\r|\n/g, " ").replace(/\\/g, "\\(rs").replace(/"/g, "\\(dq");
77
+ }
78
+ /**
43
79
  * Escapes hyphens in option names to prevent line breaks.
44
80
  *
45
81
  * In roff, a regular hyphen (`-`) can be used as a line break point.
@@ -60,18 +96,22 @@ function escapeHyphens(text) {
60
96
  * is done in formatMessageAsRoff after all terms are joined.
61
97
  *
62
98
  * @param term The message term to format.
99
+ * @param options Optional formatting options.
63
100
  * @returns The roff-formatted string.
64
101
  */
65
- function formatTermAsRoff(term) {
102
+ function formatTermAsRoff(term, options) {
66
103
  switch (term.type) {
67
104
  case "text": return escapeBackslashes(term.text);
68
105
  case "optionName": return `\\fB${escapeHyphens(term.optionName)}\\fR`;
69
106
  case "optionNames": return term.optionNames.map((name) => `\\fB${escapeHyphens(name)}\\fR`).join(", ");
70
107
  case "metavar": return `\\fI${escapeBackslashes(term.metavar)}\\fR`;
71
- case "value": return `"${escapeBackslashes(term.value)}"`;
108
+ case "value":
109
+ if (options?.quotes === false) return escapeBackslashes(term.value);
110
+ return `"${escapeQuotedValue(term.value)}"`;
72
111
  case "values":
73
112
  if (term.values.length === 0) return "";
74
- return term.values.map((v) => `"${escapeBackslashes(v)}"`).join(" ");
113
+ if (options?.quotes === false) return term.values.map((v) => escapeBackslashes(v)).join(" ");
114
+ return term.values.map((v) => `"${escapeQuotedValue(v)}"`).join(" ");
75
115
  case "envVar": return `\\fB${escapeBackslashes(term.envVar)}\\fR`;
76
116
  case "commandLine": return `\\fB${escapeHyphens(escapeBackslashes(term.commandLine))}\\fR`;
77
117
  case "lineBreak": return "\n";
@@ -112,14 +152,15 @@ function formatTermAsRoff(term) {
112
152
  * ```
113
153
  *
114
154
  * @param msg The message to format.
155
+ * @param options Optional formatting options.
115
156
  * @returns The roff-formatted string.
116
157
  * @since 0.10.0
117
158
  */
118
- function formatMessageAsRoff(msg) {
119
- const joined = msg.map(formatTermAsRoff).join("");
159
+ function formatMessageAsRoff(msg, options) {
160
+ const joined = msg.map((t) => formatTermAsRoff(t, options)).join("");
120
161
  const escaped = escapeLineStarts(joined);
121
162
  return escaped.replace(/\n\n+/g, "\n.PP\n");
122
163
  }
123
164
 
124
165
  //#endregion
125
- export { escapeHyphens, escapeRoff, formatMessageAsRoff };
166
+ export { escapeHyphens, escapeQuotedValue, escapeRequestArg, escapeRoff, formatMessageAsRoff };