@optique/man 1.0.0-dev.788 → 1.0.0-dev.886

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/dist/cli.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- const require_man = require('./man-j-bcfLoE.cjs');
3
- require('./roff-EpcecLXU.cjs');
4
- const require_generator = require('./generator-CuGGdQoF.cjs');
2
+ const require_man = require('./man-DFKETOWN.cjs');
3
+ require('./roff-Cl0vekdg.cjs');
4
+ const require_generator = require('./generator-Bal_ioLr.cjs');
5
5
  const __optique_core_constructs = require_man.__toESM(require("@optique/core/constructs"));
6
6
  const __optique_core_primitives = require_man.__toESM(require("@optique/core/primitives"));
7
7
  const __optique_core_valueparser = require_man.__toESM(require("@optique/core/valueparser"));
@@ -17,7 +17,7 @@ const node_url = require_man.__toESM(require("node:url"));
17
17
 
18
18
  //#region deno.json
19
19
  var name = "@optique/man";
20
- var version = "1.0.0-dev.788+1f1c275f";
20
+ var version = "1.0.0-dev.886+4b42a9bd";
21
21
  var license = "MIT";
22
22
  var exports$1 = {
23
23
  ".": "./src/index.ts",
@@ -330,13 +330,21 @@ async function tryRegisterTsx() {
330
330
  * Checks if a value is a Program object.
331
331
  */
332
332
  function isProgram(value) {
333
- return value != null && typeof value === "object" && "parser" in value && "metadata" in value && typeof value.metadata === "object" && value.metadata != null;
333
+ try {
334
+ return value != null && typeof value === "object" && "parser" in value && "metadata" in value && typeof value.metadata === "object" && value.metadata != null && typeof value.metadata.name === "string" && isParser(value.parser);
335
+ } catch {
336
+ return false;
337
+ }
334
338
  }
335
339
  /**
336
340
  * Checks if a value is a Parser object.
337
341
  */
338
342
  function isParser(value) {
339
- return value != null && typeof value === "object" && "parse" in value && typeof value.parse === "function" && "$mode" in value && "usage" in value;
343
+ try {
344
+ return value != null && typeof value === "object" && "parse" in value && typeof value.parse === "function" && "$mode" in value && "usage" in value && "getDocFragments" in value && typeof value.getDocFragments === "function";
345
+ } catch {
346
+ return false;
347
+ }
340
348
  }
341
349
  /**
342
350
  * Infers the program name from a file path.
@@ -344,7 +352,7 @@ function isParser(value) {
344
352
  function inferNameFromPath(filePath) {
345
353
  const base = (0, node_path.basename)(filePath);
346
354
  const ext = (0, node_path.extname)(base);
347
- return base.slice(0, -ext.length);
355
+ return ext.length > 0 ? base.slice(0, -ext.length) : base;
348
356
  }
349
357
  /**
350
358
  * Gets available exports from a module.
@@ -377,19 +385,20 @@ async function main() {
377
385
  if (target === void 0) exportNotFoundError(args.file, args.exportName, getAvailableExports(mod));
378
386
  if (!isProgram(target) && !isParser(target)) notProgramOrParserError(args.file, args.exportName, describeType(target));
379
387
  const name$1 = args.name ?? (isProgram(target) ? target.metadata.name : null) ?? inferNameFromPath(args.file);
388
+ const date = args.date ?? /* @__PURE__ */ new Date();
380
389
  let manPage;
381
390
  try {
382
391
  if (isProgram(target)) manPage = await require_generator.generateManPageAsync(target, {
383
392
  section: args.section,
384
393
  name: args.name,
385
- date: args.date,
394
+ date,
386
395
  version: args.versionString,
387
396
  manual: args.manual
388
397
  });
389
398
  else manPage = await require_generator.generateManPageAsync(target, {
390
399
  name: name$1,
391
400
  section: args.section,
392
- date: args.date,
401
+ date,
393
402
  version: args.versionString,
394
403
  manual: args.manual
395
404
  });
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import "./roff-C_MiRXVS.js";
3
- import "./man-DOn-s45r.js";
4
- import { generateManPageAsync } from "./generator-kHG20XUT.js";
2
+ import "./roff-CwBRpWCo.js";
3
+ import "./man-BBDifH_Y.js";
4
+ import { generateManPageAsync } from "./generator-CdDVvD4r.js";
5
5
  import { object } from "@optique/core/constructs";
6
6
  import { argument, option } from "@optique/core/primitives";
7
7
  import { choice, string } from "@optique/core/valueparser";
@@ -17,7 +17,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
17
17
 
18
18
  //#region deno.json
19
19
  var name = "@optique/man";
20
- var version = "1.0.0-dev.788+1f1c275f";
20
+ var version = "1.0.0-dev.886+4b42a9bd";
21
21
  var license = "MIT";
22
22
  var exports = {
23
23
  ".": "./src/index.ts",
@@ -330,13 +330,21 @@ async function tryRegisterTsx() {
330
330
  * Checks if a value is a Program object.
331
331
  */
332
332
  function isProgram(value) {
333
- return value != null && typeof value === "object" && "parser" in value && "metadata" in value && typeof value.metadata === "object" && value.metadata != null;
333
+ try {
334
+ return value != null && typeof value === "object" && "parser" in value && "metadata" in value && typeof value.metadata === "object" && value.metadata != null && typeof value.metadata.name === "string" && isParser(value.parser);
335
+ } catch {
336
+ return false;
337
+ }
334
338
  }
335
339
  /**
336
340
  * Checks if a value is a Parser object.
337
341
  */
338
342
  function isParser(value) {
339
- return value != null && typeof value === "object" && "parse" in value && typeof value.parse === "function" && "$mode" in value && "usage" in value;
343
+ try {
344
+ return value != null && typeof value === "object" && "parse" in value && typeof value.parse === "function" && "$mode" in value && "usage" in value && "getDocFragments" in value && typeof value.getDocFragments === "function";
345
+ } catch {
346
+ return false;
347
+ }
340
348
  }
341
349
  /**
342
350
  * Infers the program name from a file path.
@@ -344,7 +352,7 @@ function isParser(value) {
344
352
  function inferNameFromPath(filePath) {
345
353
  const base = basename(filePath);
346
354
  const ext = extname(base);
347
- return base.slice(0, -ext.length);
355
+ return ext.length > 0 ? base.slice(0, -ext.length) : base;
348
356
  }
349
357
  /**
350
358
  * Gets available exports from a module.
@@ -377,19 +385,20 @@ async function main() {
377
385
  if (target === void 0) exportNotFoundError(args.file, args.exportName, getAvailableExports(mod));
378
386
  if (!isProgram(target) && !isParser(target)) notProgramOrParserError(args.file, args.exportName, describeType(target));
379
387
  const name$1 = args.name ?? (isProgram(target) ? target.metadata.name : null) ?? inferNameFromPath(args.file);
388
+ const date = args.date ?? /* @__PURE__ */ new Date();
380
389
  let manPage;
381
390
  try {
382
391
  if (isProgram(target)) manPage = await generateManPageAsync(target, {
383
392
  section: args.section,
384
393
  name: args.name,
385
- date: args.date,
394
+ date,
386
395
  version: args.versionString,
387
396
  manual: args.manual
388
397
  });
389
398
  else manPage = await generateManPageAsync(target, {
390
399
  name: name$1,
391
400
  section: args.section,
392
- date: args.date,
401
+ date,
393
402
  version: args.versionString,
394
403
  manual: args.manual
395
404
  });
@@ -1,4 +1,4 @@
1
- const require_man = require('./man-j-bcfLoE.cjs');
1
+ const require_man = require('./man-DFKETOWN.cjs');
2
2
  const __optique_core_parser = require_man.__toESM(require("@optique/core/parser"));
3
3
 
4
4
  //#region src/generator.ts
@@ -1,4 +1,4 @@
1
- import { formatDocPageAsMan } from "./man-DOn-s45r.js";
1
+ import { formatDocPageAsMan } from "./man-BBDifH_Y.js";
2
2
  import { getDocPageAsync, getDocPageSync } from "@optique/core/parser";
3
3
 
4
4
  //#region src/generator.ts
package/dist/index.cjs CHANGED
@@ -1,8 +1,10 @@
1
- const require_man = require('./man-j-bcfLoE.cjs');
2
- const require_roff = require('./roff-EpcecLXU.cjs');
3
- const require_generator = require('./generator-CuGGdQoF.cjs');
1
+ const require_man = require('./man-DFKETOWN.cjs');
2
+ const require_roff = require('./roff-Cl0vekdg.cjs');
3
+ const require_generator = require('./generator-Bal_ioLr.cjs');
4
4
 
5
5
  exports.escapeHyphens = require_roff.escapeHyphens;
6
+ exports.escapeQuotedValue = require_roff.escapeQuotedValue;
7
+ exports.escapeRequestArg = require_roff.escapeRequestArg;
6
8
  exports.escapeRoff = require_roff.escapeRoff;
7
9
  exports.formatDateForMan = require_man.formatDateForMan;
8
10
  exports.formatDocPageAsMan = require_man.formatDocPageAsMan;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { escapeHyphens, escapeRoff, formatMessageAsRoff } from "./roff-72Am6STB.cjs";
2
- import { ManPageOptions, formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff } from "./man-Dh4GLG_Q.cjs";
1
+ import { escapeHyphens, escapeQuotedValue, escapeRequestArg, escapeRoff, formatMessageAsRoff } from "./roff-C_87xXP6.cjs";
2
+ import { ManPageOptions, formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff } from "./man-xPX8GhXq.cjs";
3
3
  import { Mode, ModeValue, Parser } from "@optique/core/parser";
4
4
  import { Program } from "@optique/core/program";
5
5
 
@@ -156,4 +156,4 @@ declare function generateManPage(parser: Parser<"sync", unknown, unknown>, optio
156
156
  declare function generateManPage(parser: Parser<"async", unknown, unknown>, options: GenerateManPageOptions): Promise<string>;
157
157
  declare function generateManPage<M extends Mode>(parser: Parser<M, unknown, unknown>, options: GenerateManPageOptions): ModeValue<M, string>;
158
158
  //#endregion
159
- export { type GenerateManPageOptions, type GenerateManPageProgramOptions, type ManPageOptions, escapeHyphens, escapeRoff, formatDateForMan, formatDocPageAsMan, formatMessageAsRoff, formatUsageTermAsRoff, generateManPage, generateManPageAsync, generateManPageSync };
159
+ export { type GenerateManPageOptions, type GenerateManPageProgramOptions, type ManPageOptions, escapeHyphens, escapeQuotedValue, escapeRequestArg, escapeRoff, formatDateForMan, formatDocPageAsMan, formatMessageAsRoff, formatUsageTermAsRoff, generateManPage, generateManPageAsync, generateManPageSync };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { escapeHyphens, escapeRoff, formatMessageAsRoff } from "./roff-B36zMxYc.js";
2
- import { ManPageOptions, formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff } from "./man-DrdGbys8.js";
1
+ import { escapeHyphens, escapeQuotedValue, escapeRequestArg, escapeRoff, formatMessageAsRoff } from "./roff-DlmeL3KB.js";
2
+ import { ManPageOptions, formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff } from "./man-CGKLHWkS.js";
3
3
  import { Program } from "@optique/core/program";
4
4
  import { Mode, ModeValue, Parser } from "@optique/core/parser";
5
5
 
@@ -156,4 +156,4 @@ declare function generateManPage(parser: Parser<"sync", unknown, unknown>, optio
156
156
  declare function generateManPage(parser: Parser<"async", unknown, unknown>, options: GenerateManPageOptions): Promise<string>;
157
157
  declare function generateManPage<M extends Mode>(parser: Parser<M, unknown, unknown>, options: GenerateManPageOptions): ModeValue<M, string>;
158
158
  //#endregion
159
- export { type GenerateManPageOptions, type GenerateManPageProgramOptions, type ManPageOptions, escapeHyphens, escapeRoff, formatDateForMan, formatDocPageAsMan, formatMessageAsRoff, formatUsageTermAsRoff, generateManPage, generateManPageAsync, generateManPageSync };
159
+ export { type GenerateManPageOptions, type GenerateManPageProgramOptions, type ManPageOptions, escapeHyphens, escapeQuotedValue, escapeRequestArg, escapeRoff, formatDateForMan, formatDocPageAsMan, formatMessageAsRoff, formatUsageTermAsRoff, generateManPage, generateManPageAsync, generateManPageSync };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { escapeHyphens, escapeRoff, formatMessageAsRoff } from "./roff-C_MiRXVS.js";
2
- import { formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff } from "./man-DOn-s45r.js";
3
- import { generateManPage, generateManPageAsync, generateManPageSync } from "./generator-kHG20XUT.js";
1
+ import { escapeHyphens, escapeQuotedValue, escapeRequestArg, escapeRoff, formatMessageAsRoff } from "./roff-CwBRpWCo.js";
2
+ import { formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff } from "./man-BBDifH_Y.js";
3
+ import { generateManPage, generateManPageAsync, generateManPageSync } from "./generator-CdDVvD4r.js";
4
4
 
5
- export { escapeHyphens, escapeRoff, formatDateForMan, formatDocPageAsMan, formatMessageAsRoff, formatUsageTermAsRoff, generateManPage, generateManPageAsync, generateManPageSync };
5
+ export { escapeHyphens, escapeQuotedValue, escapeRequestArg, escapeRoff, formatDateForMan, formatDocPageAsMan, formatMessageAsRoff, formatUsageTermAsRoff, generateManPage, generateManPageAsync, generateManPageSync };
@@ -1,4 +1,4 @@
1
- import { escapeHyphens, escapeRoff, formatMessageAsRoff } from "./roff-C_MiRXVS.js";
1
+ import { escapeHyphens, escapeRequestArg, escapeRoff, formatMessageAsRoff } from "./roff-CwBRpWCo.js";
2
2
  import { isDocHidden, isUsageHidden } from "@optique/core/usage";
3
3
 
4
4
  //#region src/man.ts
@@ -45,10 +45,10 @@ function formatCommandNameAsRoff(name) {
45
45
  function formatUsageTermAsRoff(term) {
46
46
  if ("hidden" in term && isUsageHidden(term.hidden)) return "";
47
47
  switch (term.type) {
48
- case "argument": return `\\fI${term.metavar}\\fR`;
48
+ case "argument": return `\\fI${escapeRoff(term.metavar)}\\fR`;
49
49
  case "option": {
50
50
  const names = term.names.map((name) => `\\fB${escapeHyphens(name)}\\fR`).join(" | ");
51
- const metavarPart = term.metavar ? ` \\fI${term.metavar}\\fR` : "";
51
+ const metavarPart = term.metavar ? ` \\fI${escapeRoff(term.metavar)}\\fR` : "";
52
52
  return `[${names}${metavarPart}]`;
53
53
  }
54
54
  case "command": return formatCommandNameAsRoff(term.name);
@@ -69,7 +69,7 @@ function formatUsageTermAsRoff(term) {
69
69
  if (alternatives.length === 1) return alternatives[0];
70
70
  return `(${alternatives.join(" | ")})`;
71
71
  }
72
- case "literal": return term.value;
72
+ case "literal": return escapeRoff(term.value);
73
73
  case "passthrough": return "[...]";
74
74
  case "ellipsis": return "...";
75
75
  default: {
@@ -98,12 +98,12 @@ function formatDocEntryTerm(term) {
98
98
  switch (term.type) {
99
99
  case "option": {
100
100
  const names = term.names.map((name) => `\\fB${escapeHyphens(name)}\\fR`).join(", ");
101
- const metavarPart = term.metavar ? ` \\fI${term.metavar}\\fR` : "";
101
+ const metavarPart = term.metavar ? ` \\fI${escapeRoff(term.metavar)}\\fR` : "";
102
102
  return `${names}${metavarPart}`;
103
103
  }
104
104
  case "command": return formatCommandNameAsRoff(term.name);
105
- case "argument": return `\\fI${term.metavar}\\fR`;
106
- case "literal": return term.value;
105
+ case "argument": return `\\fI${escapeRoff(term.metavar)}\\fR`;
106
+ case "literal": return escapeRoff(term.value);
107
107
  default: return formatDocUsageTermAsRoff(term);
108
108
  }
109
109
  }
@@ -133,14 +133,14 @@ function formatDocUsageTermAsRoff(term) {
133
133
  if (alternatives.length === 1) return alternatives[0];
134
134
  return `(${alternatives.join(" | ")})`;
135
135
  }
136
- case "argument": return `\\fI${term.metavar}\\fR`;
136
+ case "argument": return `\\fI${escapeRoff(term.metavar)}\\fR`;
137
137
  case "option": {
138
138
  const names = term.names.map((name) => `\\fB${escapeHyphens(name)}\\fR`).join(", ");
139
- const metavarPart = term.metavar ? ` \\fI${term.metavar}\\fR` : "";
139
+ const metavarPart = term.metavar ? ` \\fI${escapeRoff(term.metavar)}\\fR` : "";
140
140
  return `${names}${metavarPart}`;
141
141
  }
142
142
  case "command": return formatCommandNameAsRoff(term.name);
143
- case "literal": return term.value;
143
+ case "literal": return escapeRoff(term.value);
144
144
  case "passthrough": return "[...]";
145
145
  case "ellipsis": return "...";
146
146
  default: {
@@ -204,14 +204,27 @@ function formatDocSectionEntries(section) {
204
204
  * @param page The documentation page to format.
205
205
  * @param options The man page options.
206
206
  * @returns The complete man page in roff format.
207
+ * @throws {TypeError} If the program name is empty.
208
+ * @throws {RangeError} If the section number is not a valid man page section
209
+ * (1–8).
207
210
  * @since 0.10.0
208
211
  */
209
212
  function formatDocPageAsMan(page, options) {
213
+ if (options.name === "") throw new TypeError("Program name must not be empty.");
214
+ if (!Number.isInteger(options.section) || options.section < 1 || options.section > 8) {
215
+ let repr;
216
+ try {
217
+ repr = JSON.stringify(options.section);
218
+ } catch {
219
+ repr = String(typeof options.section);
220
+ }
221
+ throw new RangeError(`Invalid man page section number (must be 1–8): ${repr}`);
222
+ }
210
223
  const lines = [];
211
224
  const thParts = [escapeHyphens(escapeThField(options.name).toUpperCase()), options.section.toString()];
212
- const hasDate = options.date != null;
213
- const hasVersion = options.version != null;
214
- const hasManual = options.manual != null;
225
+ const hasDate = options.date != null && options.date !== "";
226
+ const hasVersion = options.version != null && options.version !== "";
227
+ const hasManual = options.manual != null && options.manual !== "";
215
228
  if (hasDate) thParts.push(`"${escapeThField(formatDateForMan(options.date))}"`);
216
229
  else if (hasVersion || hasManual) thParts.push("\"\"");
217
230
  if (hasVersion) thParts.push(`"${escapeHyphens(escapeThField(options.name))} ${escapeThField(options.version)}"`);
@@ -236,7 +249,7 @@ function formatDocPageAsMan(page, options) {
236
249
  const content = formatDocSectionEntries(section);
237
250
  if (content === "") continue;
238
251
  const title = section.title?.toUpperCase() ?? "OPTIONS";
239
- lines.push(`.SH ${title}`);
252
+ lines.push(`.SH "${escapeRequestArg(title)}"`);
240
253
  lines.push(content);
241
254
  }
242
255
  if (options.environment && options.environment.entries.length > 0) {
@@ -121,6 +121,9 @@ declare function formatUsageTermAsRoff(term: UsageTerm): string;
121
121
  * @param page The documentation page to format.
122
122
  * @param options The man page options.
123
123
  * @returns The complete man page in roff format.
124
+ * @throws {TypeError} If the program name is empty.
125
+ * @throws {RangeError} If the section number is not a valid man page section
126
+ * (1–8).
124
127
  * @since 0.10.0
125
128
  */
126
129
  declare function formatDocPageAsMan(page: DocPage, options: ManPageOptions): string;
@@ -21,7 +21,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  }) : target, mod));
22
22
 
23
23
  //#endregion
24
- const require_roff = require('./roff-EpcecLXU.cjs');
24
+ const require_roff = require('./roff-Cl0vekdg.cjs');
25
25
  const __optique_core_usage = __toESM(require("@optique/core/usage"));
26
26
 
27
27
  //#region src/man.ts
@@ -68,10 +68,10 @@ function formatCommandNameAsRoff(name) {
68
68
  function formatUsageTermAsRoff(term) {
69
69
  if ("hidden" in term && (0, __optique_core_usage.isUsageHidden)(term.hidden)) return "";
70
70
  switch (term.type) {
71
- case "argument": return `\\fI${term.metavar}\\fR`;
71
+ case "argument": return `\\fI${require_roff.escapeRoff(term.metavar)}\\fR`;
72
72
  case "option": {
73
73
  const names = term.names.map((name) => `\\fB${require_roff.escapeHyphens(name)}\\fR`).join(" | ");
74
- const metavarPart = term.metavar ? ` \\fI${term.metavar}\\fR` : "";
74
+ const metavarPart = term.metavar ? ` \\fI${require_roff.escapeRoff(term.metavar)}\\fR` : "";
75
75
  return `[${names}${metavarPart}]`;
76
76
  }
77
77
  case "command": return formatCommandNameAsRoff(term.name);
@@ -92,7 +92,7 @@ function formatUsageTermAsRoff(term) {
92
92
  if (alternatives.length === 1) return alternatives[0];
93
93
  return `(${alternatives.join(" | ")})`;
94
94
  }
95
- case "literal": return term.value;
95
+ case "literal": return require_roff.escapeRoff(term.value);
96
96
  case "passthrough": return "[...]";
97
97
  case "ellipsis": return "...";
98
98
  default: {
@@ -121,12 +121,12 @@ function formatDocEntryTerm(term) {
121
121
  switch (term.type) {
122
122
  case "option": {
123
123
  const names = term.names.map((name) => `\\fB${require_roff.escapeHyphens(name)}\\fR`).join(", ");
124
- const metavarPart = term.metavar ? ` \\fI${term.metavar}\\fR` : "";
124
+ const metavarPart = term.metavar ? ` \\fI${require_roff.escapeRoff(term.metavar)}\\fR` : "";
125
125
  return `${names}${metavarPart}`;
126
126
  }
127
127
  case "command": return formatCommandNameAsRoff(term.name);
128
- case "argument": return `\\fI${term.metavar}\\fR`;
129
- case "literal": return term.value;
128
+ case "argument": return `\\fI${require_roff.escapeRoff(term.metavar)}\\fR`;
129
+ case "literal": return require_roff.escapeRoff(term.value);
130
130
  default: return formatDocUsageTermAsRoff(term);
131
131
  }
132
132
  }
@@ -156,14 +156,14 @@ function formatDocUsageTermAsRoff(term) {
156
156
  if (alternatives.length === 1) return alternatives[0];
157
157
  return `(${alternatives.join(" | ")})`;
158
158
  }
159
- case "argument": return `\\fI${term.metavar}\\fR`;
159
+ case "argument": return `\\fI${require_roff.escapeRoff(term.metavar)}\\fR`;
160
160
  case "option": {
161
161
  const names = term.names.map((name) => `\\fB${require_roff.escapeHyphens(name)}\\fR`).join(", ");
162
- const metavarPart = term.metavar ? ` \\fI${term.metavar}\\fR` : "";
162
+ const metavarPart = term.metavar ? ` \\fI${require_roff.escapeRoff(term.metavar)}\\fR` : "";
163
163
  return `${names}${metavarPart}`;
164
164
  }
165
165
  case "command": return formatCommandNameAsRoff(term.name);
166
- case "literal": return term.value;
166
+ case "literal": return require_roff.escapeRoff(term.value);
167
167
  case "passthrough": return "[...]";
168
168
  case "ellipsis": return "...";
169
169
  default: {
@@ -227,14 +227,27 @@ function formatDocSectionEntries(section) {
227
227
  * @param page The documentation page to format.
228
228
  * @param options The man page options.
229
229
  * @returns The complete man page in roff format.
230
+ * @throws {TypeError} If the program name is empty.
231
+ * @throws {RangeError} If the section number is not a valid man page section
232
+ * (1–8).
230
233
  * @since 0.10.0
231
234
  */
232
235
  function formatDocPageAsMan(page, options) {
236
+ if (options.name === "") throw new TypeError("Program name must not be empty.");
237
+ if (!Number.isInteger(options.section) || options.section < 1 || options.section > 8) {
238
+ let repr;
239
+ try {
240
+ repr = JSON.stringify(options.section);
241
+ } catch {
242
+ repr = String(typeof options.section);
243
+ }
244
+ throw new RangeError(`Invalid man page section number (must be 1–8): ${repr}`);
245
+ }
233
246
  const lines = [];
234
247
  const thParts = [require_roff.escapeHyphens(escapeThField(options.name).toUpperCase()), options.section.toString()];
235
- const hasDate = options.date != null;
236
- const hasVersion = options.version != null;
237
- const hasManual = options.manual != null;
248
+ const hasDate = options.date != null && options.date !== "";
249
+ const hasVersion = options.version != null && options.version !== "";
250
+ const hasManual = options.manual != null && options.manual !== "";
238
251
  if (hasDate) thParts.push(`"${escapeThField(formatDateForMan(options.date))}"`);
239
252
  else if (hasVersion || hasManual) thParts.push("\"\"");
240
253
  if (hasVersion) thParts.push(`"${require_roff.escapeHyphens(escapeThField(options.name))} ${escapeThField(options.version)}"`);
@@ -259,7 +272,7 @@ function formatDocPageAsMan(page, options) {
259
272
  const content = formatDocSectionEntries(section);
260
273
  if (content === "") continue;
261
274
  const title = section.title?.toUpperCase() ?? "OPTIONS";
262
- lines.push(`.SH ${title}`);
275
+ lines.push(`.SH "${require_roff.escapeRequestArg(title)}"`);
263
276
  lines.push(content);
264
277
  }
265
278
  if (options.environment && options.environment.entries.length > 0) {
@@ -121,6 +121,9 @@ declare function formatUsageTermAsRoff(term: UsageTerm): string;
121
121
  * @param page The documentation page to format.
122
122
  * @param options The man page options.
123
123
  * @returns The complete man page in roff format.
124
+ * @throws {TypeError} If the program name is empty.
125
+ * @throws {RangeError} If the section number is not a valid man page section
126
+ * (1–8).
124
127
  * @since 0.10.0
125
128
  */
126
129
  declare function formatDocPageAsMan(page: DocPage, options: ManPageOptions): string;
package/dist/man.cjs CHANGED
@@ -1,5 +1,5 @@
1
- const require_man = require('./man-j-bcfLoE.cjs');
2
- require('./roff-EpcecLXU.cjs');
1
+ const require_man = require('./man-DFKETOWN.cjs');
2
+ require('./roff-Cl0vekdg.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-Dh4GLG_Q.cjs";
1
+ import { ManPageOptions, ManPageSection, formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff } from "./man-xPX8GhXq.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-DrdGbys8.js";
1
+ import { ManPageOptions, ManPageSection, formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff } from "./man-CGKLHWkS.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-DOn-s45r.js";
1
+ import "./roff-CwBRpWCo.js";
2
+ import { formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff } from "./man-BBDifH_Y.js";
3
3
 
4
4
  export { formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff };
@@ -15,6 +15,38 @@ import { Message } from "@optique/core/message";
15
15
  * @since 0.10.0
16
16
  */
17
17
  declare function escapeRoff(text: string): string;
18
+ /**
19
+ * Escapes roff-sensitive characters inside a quoted value in body text.
20
+ * Handles backslashes and double quotes so the value can be safely
21
+ * placed between literal `"` delimiters in roff text output (e.g.,
22
+ * `value()` / `values()` message terms).
23
+ *
24
+ * This function is *not* safe for roff request arguments such as
25
+ * `.SH "..."`, where groff performs an extra level of escape
26
+ * interpretation. Use {@link escapeRequestArg} for that purpose.
27
+ *
28
+ * @param text The raw value text.
29
+ * @returns The escaped text safe for use inside roff double quotes
30
+ * in body text.
31
+ * @since 1.0.0
32
+ */
33
+ declare function escapeQuotedValue(text: string): string;
34
+ /**
35
+ * Escapes roff-sensitive characters inside a quoted roff request argument
36
+ * (e.g., `.SH "..."`). Unlike {@link escapeQuotedValue}, this function
37
+ * replaces backslashes with the `\(rs` glyph instead of `\\`, because
38
+ * groff performs an extra level of escape interpretation on request
39
+ * arguments — `\\` would still be parsed as an escape prefix.
40
+ *
41
+ * Line breaks (`\r\n`, `\r`, `\n`) are normalized to spaces because a
42
+ * raw newline would split the request line and cause the remainder to be
43
+ * parsed as new roff input.
44
+ *
45
+ * @param text The raw argument text.
46
+ * @returns The escaped text safe for use inside a quoted roff request.
47
+ * @since 1.0.0
48
+ */
49
+ declare function escapeRequestArg(text: string): string;
18
50
  /**
19
51
  * Escapes hyphens in option names to prevent line breaks.
20
52
  *
@@ -62,4 +94,4 @@ declare function escapeHyphens(text: string): string;
62
94
  */
63
95
  declare function formatMessageAsRoff(msg: Message): string;
64
96
  //#endregion
65
- export { escapeHyphens, escapeRoff, formatMessageAsRoff };
97
+ export { escapeHyphens, escapeQuotedValue, escapeRequestArg, escapeRoff, formatMessageAsRoff };
@@ -1,3 +1,4 @@
1
+
1
2
  //#region src/roff.ts
2
3
  /**
3
4
  * Escapes backslashes in text for roff.
@@ -40,6 +41,42 @@ function escapeRoff(text) {
40
41
  return escapeLineStarts(escapeBackslashes(text));
41
42
  }
42
43
  /**
44
+ * Escapes roff-sensitive characters inside a quoted value in body text.
45
+ * Handles backslashes and double quotes so the value can be safely
46
+ * placed between literal `"` delimiters in roff text output (e.g.,
47
+ * `value()` / `values()` message terms).
48
+ *
49
+ * This function is *not* safe for roff request arguments such as
50
+ * `.SH "..."`, where groff performs an extra level of escape
51
+ * interpretation. Use {@link escapeRequestArg} for that purpose.
52
+ *
53
+ * @param text The raw value text.
54
+ * @returns The escaped text safe for use inside roff double quotes
55
+ * in body text.
56
+ * @since 1.0.0
57
+ */
58
+ function escapeQuotedValue(text) {
59
+ return escapeBackslashes(text).replace(/"/g, "\\(dq");
60
+ }
61
+ /**
62
+ * Escapes roff-sensitive characters inside a quoted roff request argument
63
+ * (e.g., `.SH "..."`). Unlike {@link escapeQuotedValue}, this function
64
+ * replaces backslashes with the `\(rs` glyph instead of `\\`, because
65
+ * groff performs an extra level of escape interpretation on request
66
+ * arguments — `\\` would still be parsed as an escape prefix.
67
+ *
68
+ * Line breaks (`\r\n`, `\r`, `\n`) are normalized to spaces because a
69
+ * raw newline would split the request line and cause the remainder to be
70
+ * parsed as new roff input.
71
+ *
72
+ * @param text The raw argument text.
73
+ * @returns The escaped text safe for use inside a quoted roff request.
74
+ * @since 1.0.0
75
+ */
76
+ function escapeRequestArg(text) {
77
+ return text.replace(/\r\n|\r|\n/g, " ").replace(/\\/g, "\\(rs").replace(/"/g, "\\(dq");
78
+ }
79
+ /**
43
80
  * Escapes hyphens in option names to prevent line breaks.
44
81
  *
45
82
  * In roff, a regular hyphen (`-`) can be used as a line break point.
@@ -68,10 +105,10 @@ function formatTermAsRoff(term) {
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": return `"${escapeQuotedValue(term.value)}"`;
72
109
  case "values":
73
110
  if (term.values.length === 0) return "";
74
- return term.values.map((v) => `"${escapeBackslashes(v)}"`).join(" ");
111
+ return term.values.map((v) => `"${escapeQuotedValue(v)}"`).join(" ");
75
112
  case "envVar": return `\\fB${escapeBackslashes(term.envVar)}\\fR`;
76
113
  case "commandLine": return `\\fB${escapeHyphens(escapeBackslashes(term.commandLine))}\\fR`;
77
114
  case "lineBreak": return "\n";
@@ -122,4 +159,33 @@ function formatMessageAsRoff(msg) {
122
159
  }
123
160
 
124
161
  //#endregion
125
- export { escapeHyphens, escapeRoff, formatMessageAsRoff };
162
+ Object.defineProperty(exports, 'escapeHyphens', {
163
+ enumerable: true,
164
+ get: function () {
165
+ return escapeHyphens;
166
+ }
167
+ });
168
+ Object.defineProperty(exports, 'escapeQuotedValue', {
169
+ enumerable: true,
170
+ get: function () {
171
+ return escapeQuotedValue;
172
+ }
173
+ });
174
+ Object.defineProperty(exports, 'escapeRequestArg', {
175
+ enumerable: true,
176
+ get: function () {
177
+ return escapeRequestArg;
178
+ }
179
+ });
180
+ Object.defineProperty(exports, 'escapeRoff', {
181
+ enumerable: true,
182
+ get: function () {
183
+ return escapeRoff;
184
+ }
185
+ });
186
+ Object.defineProperty(exports, 'formatMessageAsRoff', {
187
+ enumerable: true,
188
+ get: function () {
189
+ return formatMessageAsRoff;
190
+ }
191
+ });
@@ -1,4 +1,3 @@
1
-
2
1
  //#region src/roff.ts
3
2
  /**
4
3
  * Escapes backslashes in text for roff.
@@ -41,6 +40,42 @@ function escapeRoff(text) {
41
40
  return escapeLineStarts(escapeBackslashes(text));
42
41
  }
43
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
+ /**
44
79
  * Escapes hyphens in option names to prevent line breaks.
45
80
  *
46
81
  * In roff, a regular hyphen (`-`) can be used as a line break point.
@@ -69,10 +104,10 @@ function formatTermAsRoff(term) {
69
104
  case "optionName": return `\\fB${escapeHyphens(term.optionName)}\\fR`;
70
105
  case "optionNames": return term.optionNames.map((name) => `\\fB${escapeHyphens(name)}\\fR`).join(", ");
71
106
  case "metavar": return `\\fI${escapeBackslashes(term.metavar)}\\fR`;
72
- case "value": return `"${escapeBackslashes(term.value)}"`;
107
+ case "value": return `"${escapeQuotedValue(term.value)}"`;
73
108
  case "values":
74
109
  if (term.values.length === 0) return "";
75
- return term.values.map((v) => `"${escapeBackslashes(v)}"`).join(" ");
110
+ return term.values.map((v) => `"${escapeQuotedValue(v)}"`).join(" ");
76
111
  case "envVar": return `\\fB${escapeBackslashes(term.envVar)}\\fR`;
77
112
  case "commandLine": return `\\fB${escapeHyphens(escapeBackslashes(term.commandLine))}\\fR`;
78
113
  case "lineBreak": return "\n";
@@ -123,21 +158,4 @@ function formatMessageAsRoff(msg) {
123
158
  }
124
159
 
125
160
  //#endregion
126
- Object.defineProperty(exports, 'escapeHyphens', {
127
- enumerable: true,
128
- get: function () {
129
- return escapeHyphens;
130
- }
131
- });
132
- Object.defineProperty(exports, 'escapeRoff', {
133
- enumerable: true,
134
- get: function () {
135
- return escapeRoff;
136
- }
137
- });
138
- Object.defineProperty(exports, 'formatMessageAsRoff', {
139
- enumerable: true,
140
- get: function () {
141
- return formatMessageAsRoff;
142
- }
143
- });
161
+ export { escapeHyphens, escapeQuotedValue, escapeRequestArg, escapeRoff, formatMessageAsRoff };
@@ -15,6 +15,38 @@ import { Message } from "@optique/core/message";
15
15
  * @since 0.10.0
16
16
  */
17
17
  declare function escapeRoff(text: string): string;
18
+ /**
19
+ * Escapes roff-sensitive characters inside a quoted value in body text.
20
+ * Handles backslashes and double quotes so the value can be safely
21
+ * placed between literal `"` delimiters in roff text output (e.g.,
22
+ * `value()` / `values()` message terms).
23
+ *
24
+ * This function is *not* safe for roff request arguments such as
25
+ * `.SH "..."`, where groff performs an extra level of escape
26
+ * interpretation. Use {@link escapeRequestArg} for that purpose.
27
+ *
28
+ * @param text The raw value text.
29
+ * @returns The escaped text safe for use inside roff double quotes
30
+ * in body text.
31
+ * @since 1.0.0
32
+ */
33
+ declare function escapeQuotedValue(text: string): string;
34
+ /**
35
+ * Escapes roff-sensitive characters inside a quoted roff request argument
36
+ * (e.g., `.SH "..."`). Unlike {@link escapeQuotedValue}, this function
37
+ * replaces backslashes with the `\(rs` glyph instead of `\\`, because
38
+ * groff performs an extra level of escape interpretation on request
39
+ * arguments — `\\` would still be parsed as an escape prefix.
40
+ *
41
+ * Line breaks (`\r\n`, `\r`, `\n`) are normalized to spaces because a
42
+ * raw newline would split the request line and cause the remainder to be
43
+ * parsed as new roff input.
44
+ *
45
+ * @param text The raw argument text.
46
+ * @returns The escaped text safe for use inside a quoted roff request.
47
+ * @since 1.0.0
48
+ */
49
+ declare function escapeRequestArg(text: string): string;
18
50
  /**
19
51
  * Escapes hyphens in option names to prevent line breaks.
20
52
  *
@@ -62,4 +94,4 @@ declare function escapeHyphens(text: string): string;
62
94
  */
63
95
  declare function formatMessageAsRoff(msg: Message): string;
64
96
  //#endregion
65
- export { escapeHyphens, escapeRoff, formatMessageAsRoff };
97
+ export { escapeHyphens, escapeQuotedValue, escapeRequestArg, escapeRoff, formatMessageAsRoff };
package/dist/roff.cjs CHANGED
@@ -1,5 +1,7 @@
1
- const require_roff = require('./roff-EpcecLXU.cjs');
1
+ const require_roff = require('./roff-Cl0vekdg.cjs');
2
2
 
3
3
  exports.escapeHyphens = require_roff.escapeHyphens;
4
+ exports.escapeQuotedValue = require_roff.escapeQuotedValue;
5
+ exports.escapeRequestArg = require_roff.escapeRequestArg;
4
6
  exports.escapeRoff = require_roff.escapeRoff;
5
7
  exports.formatMessageAsRoff = require_roff.formatMessageAsRoff;
package/dist/roff.d.cts CHANGED
@@ -1,2 +1,2 @@
1
- import { escapeHyphens, escapeRoff, formatMessageAsRoff } from "./roff-72Am6STB.cjs";
2
- export { escapeHyphens, escapeRoff, formatMessageAsRoff };
1
+ import { escapeHyphens, escapeQuotedValue, escapeRequestArg, escapeRoff, formatMessageAsRoff } from "./roff-C_87xXP6.cjs";
2
+ export { escapeHyphens, escapeQuotedValue, escapeRequestArg, escapeRoff, formatMessageAsRoff };
package/dist/roff.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { escapeHyphens, escapeRoff, formatMessageAsRoff } from "./roff-B36zMxYc.js";
2
- export { escapeHyphens, escapeRoff, formatMessageAsRoff };
1
+ import { escapeHyphens, escapeQuotedValue, escapeRequestArg, escapeRoff, formatMessageAsRoff } from "./roff-DlmeL3KB.js";
2
+ export { escapeHyphens, escapeQuotedValue, escapeRequestArg, escapeRoff, formatMessageAsRoff };
package/dist/roff.js CHANGED
@@ -1,3 +1,3 @@
1
- import { escapeHyphens, escapeRoff, formatMessageAsRoff } from "./roff-C_MiRXVS.js";
1
+ import { escapeHyphens, escapeQuotedValue, escapeRequestArg, escapeRoff, formatMessageAsRoff } from "./roff-CwBRpWCo.js";
2
2
 
3
- export { escapeHyphens, escapeRoff, formatMessageAsRoff };
3
+ export { escapeHyphens, escapeQuotedValue, escapeRequestArg, escapeRoff, formatMessageAsRoff };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/man",
3
- "version": "1.0.0-dev.788+1f1c275f",
3
+ "version": "1.0.0-dev.886+4b42a9bd",
4
4
  "description": "Man page generator for Optique CLI parsers",
5
5
  "keywords": [
6
6
  "CLI",
@@ -84,8 +84,8 @@
84
84
  "optique-man": "./dist/cli.js"
85
85
  },
86
86
  "dependencies": {
87
- "@optique/core": "1.0.0-dev.788+1f1c275f",
88
- "@optique/run": "1.0.0-dev.788+1f1c275f"
87
+ "@optique/core": "1.0.0-dev.886+4b42a9bd",
88
+ "@optique/run": "1.0.0-dev.886+4b42a9bd"
89
89
  },
90
90
  "devDependencies": {
91
91
  "@types/node": "^20.19.9",