@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.
@@ -2,6 +2,20 @@ import { Message } from "@optique/core/message";
2
2
 
3
3
  //#region src/roff.d.ts
4
4
 
5
+ /**
6
+ * Options for roff formatting of messages.
7
+ *
8
+ * @since 1.0.0
9
+ */
10
+ interface RoffFormatOptions {
11
+ /**
12
+ * Whether to wrap `value` and `values` terms in double quotes.
13
+ * Defaults to `true`.
14
+ *
15
+ * @since 1.0.0
16
+ */
17
+ readonly quotes?: boolean;
18
+ }
5
19
  /**
6
20
  * Escapes special roff characters in plain text.
7
21
  *
@@ -15,6 +29,38 @@ import { Message } from "@optique/core/message";
15
29
  * @since 0.10.0
16
30
  */
17
31
  declare function escapeRoff(text: string): string;
32
+ /**
33
+ * Escapes roff-sensitive characters inside a quoted value in body text.
34
+ * Handles backslashes and double quotes so the value can be safely
35
+ * placed between literal `"` delimiters in roff text output (e.g.,
36
+ * `value()` / `values()` message terms).
37
+ *
38
+ * This function is *not* safe for roff request arguments such as
39
+ * `.SH "..."`, where groff performs an extra level of escape
40
+ * interpretation. Use {@link escapeRequestArg} for that purpose.
41
+ *
42
+ * @param text The raw value text.
43
+ * @returns The escaped text safe for use inside roff double quotes
44
+ * in body text.
45
+ * @since 1.0.0
46
+ */
47
+ declare function escapeQuotedValue(text: string): string;
48
+ /**
49
+ * Escapes roff-sensitive characters inside a quoted roff request argument
50
+ * (e.g., `.SH "..."`). Unlike {@link escapeQuotedValue}, this function
51
+ * replaces backslashes with the `\(rs` glyph instead of `\\`, because
52
+ * groff performs an extra level of escape interpretation on request
53
+ * arguments — `\\` would still be parsed as an escape prefix.
54
+ *
55
+ * Line breaks (`\r\n`, `\r`, `\n`) are normalized to spaces because a
56
+ * raw newline would split the request line and cause the remainder to be
57
+ * parsed as new roff input.
58
+ *
59
+ * @param text The raw argument text.
60
+ * @returns The escaped text safe for use inside a quoted roff request.
61
+ * @since 1.0.0
62
+ */
63
+ declare function escapeRequestArg(text: string): string;
18
64
  /**
19
65
  * Escapes hyphens in option names to prevent line breaks.
20
66
  *
@@ -57,9 +103,10 @@ declare function escapeHyphens(text: string): string;
57
103
  * ```
58
104
  *
59
105
  * @param msg The message to format.
106
+ * @param options Optional formatting options.
60
107
  * @returns The roff-formatted string.
61
108
  * @since 0.10.0
62
109
  */
63
- declare function formatMessageAsRoff(msg: Message): string;
110
+ declare function formatMessageAsRoff(msg: Message, options?: RoffFormatOptions): string;
64
111
  //#endregion
65
- export { escapeHyphens, escapeRoff, formatMessageAsRoff };
112
+ export { RoffFormatOptions, escapeHyphens, escapeQuotedValue, escapeRequestArg, escapeRoff, formatMessageAsRoff };
@@ -2,6 +2,20 @@ import { Message } from "@optique/core/message";
2
2
 
3
3
  //#region src/roff.d.ts
4
4
 
5
+ /**
6
+ * Options for roff formatting of messages.
7
+ *
8
+ * @since 1.0.0
9
+ */
10
+ interface RoffFormatOptions {
11
+ /**
12
+ * Whether to wrap `value` and `values` terms in double quotes.
13
+ * Defaults to `true`.
14
+ *
15
+ * @since 1.0.0
16
+ */
17
+ readonly quotes?: boolean;
18
+ }
5
19
  /**
6
20
  * Escapes special roff characters in plain text.
7
21
  *
@@ -15,6 +29,38 @@ import { Message } from "@optique/core/message";
15
29
  * @since 0.10.0
16
30
  */
17
31
  declare function escapeRoff(text: string): string;
32
+ /**
33
+ * Escapes roff-sensitive characters inside a quoted value in body text.
34
+ * Handles backslashes and double quotes so the value can be safely
35
+ * placed between literal `"` delimiters in roff text output (e.g.,
36
+ * `value()` / `values()` message terms).
37
+ *
38
+ * This function is *not* safe for roff request arguments such as
39
+ * `.SH "..."`, where groff performs an extra level of escape
40
+ * interpretation. Use {@link escapeRequestArg} for that purpose.
41
+ *
42
+ * @param text The raw value text.
43
+ * @returns The escaped text safe for use inside roff double quotes
44
+ * in body text.
45
+ * @since 1.0.0
46
+ */
47
+ declare function escapeQuotedValue(text: string): string;
48
+ /**
49
+ * Escapes roff-sensitive characters inside a quoted roff request argument
50
+ * (e.g., `.SH "..."`). Unlike {@link escapeQuotedValue}, this function
51
+ * replaces backslashes with the `\(rs` glyph instead of `\\`, because
52
+ * groff performs an extra level of escape interpretation on request
53
+ * arguments — `\\` would still be parsed as an escape prefix.
54
+ *
55
+ * Line breaks (`\r\n`, `\r`, `\n`) are normalized to spaces because a
56
+ * raw newline would split the request line and cause the remainder to be
57
+ * parsed as new roff input.
58
+ *
59
+ * @param text The raw argument text.
60
+ * @returns The escaped text safe for use inside a quoted roff request.
61
+ * @since 1.0.0
62
+ */
63
+ declare function escapeRequestArg(text: string): string;
18
64
  /**
19
65
  * Escapes hyphens in option names to prevent line breaks.
20
66
  *
@@ -57,9 +103,10 @@ declare function escapeHyphens(text: string): string;
57
103
  * ```
58
104
  *
59
105
  * @param msg The message to format.
106
+ * @param options Optional formatting options.
60
107
  * @returns The roff-formatted string.
61
108
  * @since 0.10.0
62
109
  */
63
- declare function formatMessageAsRoff(msg: Message): string;
110
+ declare function formatMessageAsRoff(msg: Message, options?: RoffFormatOptions): string;
64
111
  //#endregion
65
- export { escapeHyphens, escapeRoff, formatMessageAsRoff };
112
+ export { RoffFormatOptions, escapeHyphens, escapeQuotedValue, escapeRequestArg, escapeRoff, formatMessageAsRoff };
@@ -41,6 +41,42 @@ function escapeRoff(text) {
41
41
  return escapeLineStarts(escapeBackslashes(text));
42
42
  }
43
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
+ /**
44
80
  * Escapes hyphens in option names to prevent line breaks.
45
81
  *
46
82
  * In roff, a regular hyphen (`-`) can be used as a line break point.
@@ -61,18 +97,22 @@ function escapeHyphens(text) {
61
97
  * is done in formatMessageAsRoff after all terms are joined.
62
98
  *
63
99
  * @param term The message term to format.
100
+ * @param options Optional formatting options.
64
101
  * @returns The roff-formatted string.
65
102
  */
66
- function formatTermAsRoff(term) {
103
+ function formatTermAsRoff(term, options) {
67
104
  switch (term.type) {
68
105
  case "text": return escapeBackslashes(term.text);
69
106
  case "optionName": return `\\fB${escapeHyphens(term.optionName)}\\fR`;
70
107
  case "optionNames": return term.optionNames.map((name) => `\\fB${escapeHyphens(name)}\\fR`).join(", ");
71
108
  case "metavar": return `\\fI${escapeBackslashes(term.metavar)}\\fR`;
72
- case "value": return `"${escapeBackslashes(term.value)}"`;
109
+ case "value":
110
+ if (options?.quotes === false) return escapeBackslashes(term.value);
111
+ return `"${escapeQuotedValue(term.value)}"`;
73
112
  case "values":
74
113
  if (term.values.length === 0) return "";
75
- return term.values.map((v) => `"${escapeBackslashes(v)}"`).join(" ");
114
+ if (options?.quotes === false) return term.values.map((v) => escapeBackslashes(v)).join(" ");
115
+ return term.values.map((v) => `"${escapeQuotedValue(v)}"`).join(" ");
76
116
  case "envVar": return `\\fB${escapeBackslashes(term.envVar)}\\fR`;
77
117
  case "commandLine": return `\\fB${escapeHyphens(escapeBackslashes(term.commandLine))}\\fR`;
78
118
  case "lineBreak": return "\n";
@@ -113,11 +153,12 @@ function formatTermAsRoff(term) {
113
153
  * ```
114
154
  *
115
155
  * @param msg The message to format.
156
+ * @param options Optional formatting options.
116
157
  * @returns The roff-formatted string.
117
158
  * @since 0.10.0
118
159
  */
119
- function formatMessageAsRoff(msg) {
120
- const joined = msg.map(formatTermAsRoff).join("");
160
+ function formatMessageAsRoff(msg, options) {
161
+ const joined = msg.map((t) => formatTermAsRoff(t, options)).join("");
121
162
  const escaped = escapeLineStarts(joined);
122
163
  return escaped.replace(/\n\n+/g, "\n.PP\n");
123
164
  }
@@ -129,6 +170,18 @@ Object.defineProperty(exports, 'escapeHyphens', {
129
170
  return escapeHyphens;
130
171
  }
131
172
  });
173
+ Object.defineProperty(exports, 'escapeQuotedValue', {
174
+ enumerable: true,
175
+ get: function () {
176
+ return escapeQuotedValue;
177
+ }
178
+ });
179
+ Object.defineProperty(exports, 'escapeRequestArg', {
180
+ enumerable: true,
181
+ get: function () {
182
+ return escapeRequestArg;
183
+ }
184
+ });
132
185
  Object.defineProperty(exports, 'escapeRoff', {
133
186
  enumerable: true,
134
187
  get: function () {
package/dist/roff.cjs CHANGED
@@ -1,5 +1,7 @@
1
- const require_roff = require('./roff-EpcecLXU.cjs');
1
+ const require_roff = require('./roff-DJ-LkRXl.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 { RoffFormatOptions, escapeHyphens, escapeQuotedValue, escapeRequestArg, escapeRoff, formatMessageAsRoff } from "./roff-COeDIWWF.cjs";
2
+ export { RoffFormatOptions, 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 { RoffFormatOptions, escapeHyphens, escapeQuotedValue, escapeRequestArg, escapeRoff, formatMessageAsRoff } from "./roff-CgYQDHBN.js";
2
+ export { RoffFormatOptions, 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-CCdIQO7B.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": "0.10.7",
3
+ "version": "1.0.0-dev.1109+fa132665",
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": "0.10.7",
88
- "@optique/run": "0.10.7"
87
+ "@optique/core": "1.0.0-dev.1109+fa132665",
88
+ "@optique/run": "1.0.0-dev.1109+fa132665"
89
89
  },
90
90
  "devDependencies": {
91
91
  "@types/node": "^20.19.9",
@@ -1,235 +0,0 @@
1
- const require_roff = require('./roff-EpcecLXU.cjs');
2
-
3
- //#region src/man.ts
4
- /**
5
- * Formats a date for use in man pages.
6
- *
7
- * @param date The date to format, or undefined.
8
- * @returns The formatted date string, or undefined.
9
- * @since 0.10.0
10
- */
11
- function formatDateForMan(date) {
12
- if (date === void 0) return void 0;
13
- if (typeof date === "string") return date;
14
- const months = [
15
- "January",
16
- "February",
17
- "March",
18
- "April",
19
- "May",
20
- "June",
21
- "July",
22
- "August",
23
- "September",
24
- "October",
25
- "November",
26
- "December"
27
- ];
28
- return `${months[date.getMonth()]} ${date.getFullYear()}`;
29
- }
30
- function escapeThField(value) {
31
- return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
32
- }
33
- /**
34
- * Formats a single {@link UsageTerm} as roff markup for the SYNOPSIS section.
35
- *
36
- * @param term The usage term to format.
37
- * @returns The roff-formatted string.
38
- * @since 0.10.0
39
- */
40
- function formatUsageTermAsRoff(term) {
41
- if ("hidden" in term && term.hidden) return "";
42
- switch (term.type) {
43
- case "argument": return `\\fI${term.metavar}\\fR`;
44
- case "option": {
45
- const names = term.names.map((name) => `\\fB${require_roff.escapeHyphens(name)}\\fR`).join(" | ");
46
- const metavarPart = term.metavar ? ` \\fI${term.metavar}\\fR` : "";
47
- return `[${names}${metavarPart}]`;
48
- }
49
- case "command": return `\\fB${term.name}\\fR`;
50
- case "optional": {
51
- const inner = formatUsageAsRoff(term.terms);
52
- return `[${inner}]`;
53
- }
54
- case "multiple": {
55
- const inner = formatUsageAsRoff(term.terms);
56
- if (term.min < 1) return `[${inner} ...]`;
57
- return `${inner} ...`;
58
- }
59
- case "exclusive": {
60
- const alternatives = term.terms.map((t) => formatUsageAsRoff(t)).join(" | ");
61
- return `(${alternatives})`;
62
- }
63
- case "literal": return term.value;
64
- case "passthrough": return "[...]";
65
- default: {
66
- const _exhaustive = term;
67
- throw new TypeError(`Unknown usage term type: ${_exhaustive.type}.`);
68
- }
69
- }
70
- }
71
- /**
72
- * Formats a {@link Usage} array as roff markup.
73
- *
74
- * @param usage The usage terms to format.
75
- * @returns The roff-formatted string.
76
- */
77
- function formatUsageAsRoff(usage) {
78
- return usage.map(formatUsageTermAsRoff).filter((s) => s !== "").join(" ");
79
- }
80
- /**
81
- * Formats a {@link DocEntry}'s term for man page output.
82
- *
83
- * @param term The usage term from the entry.
84
- * @returns The roff-formatted term string.
85
- */
86
- function formatDocEntryTerm(term) {
87
- switch (term.type) {
88
- case "option": {
89
- const names = term.names.map((name) => `\\fB${require_roff.escapeHyphens(name)}\\fR`).join(", ");
90
- const metavarPart = term.metavar ? ` \\fI${term.metavar}\\fR` : "";
91
- return `${names}${metavarPart}`;
92
- }
93
- case "command": return `\\fB${term.name}\\fR`;
94
- case "argument": return `\\fI${term.metavar}\\fR`;
95
- case "literal": return term.value;
96
- default: return formatUsageTermAsRoff(term);
97
- }
98
- }
99
- /**
100
- * Formats a {@link DocSection} as roff markup with .TP macros.
101
- *
102
- * @param section The section to format.
103
- * @returns The roff-formatted section content.
104
- */
105
- function formatDocSectionEntries(section) {
106
- const lines = [];
107
- for (const entry of section.entries) {
108
- lines.push(".TP");
109
- lines.push(formatDocEntryTerm(entry.term));
110
- if (entry.description) {
111
- let desc = require_roff.formatMessageAsRoff(entry.description);
112
- if (entry.default) desc += ` [${require_roff.formatMessageAsRoff(entry.default)}]`;
113
- lines.push(desc);
114
- } else if (entry.default) lines.push(`[${require_roff.formatMessageAsRoff(entry.default)}]`);
115
- }
116
- return lines.join("\n");
117
- }
118
- /**
119
- * Formats a {@link DocPage} as a complete man page in roff format.
120
- *
121
- * This function generates a man page following the standard man(7) format,
122
- * including sections for NAME, SYNOPSIS, DESCRIPTION, OPTIONS, and more.
123
- *
124
- * @example
125
- * ```typescript
126
- * import { formatDocPageAsMan } from "@optique/man/man";
127
- * import type { DocPage } from "@optique/core/doc";
128
- *
129
- * const page: DocPage = {
130
- * brief: message`A sample CLI application`,
131
- * usage: [{ type: "argument", metavar: "FILE" }],
132
- * sections: [],
133
- * };
134
- *
135
- * const manPage = formatDocPageAsMan(page, {
136
- * name: "myapp",
137
- * section: 1,
138
- * version: "1.0.0",
139
- * });
140
- * ```
141
- *
142
- * @param page The documentation page to format.
143
- * @param options The man page options.
144
- * @returns The complete man page in roff format.
145
- * @since 0.10.0
146
- */
147
- function formatDocPageAsMan(page, options) {
148
- const lines = [];
149
- const thParts = [escapeThField(options.name.toUpperCase()), options.section.toString()];
150
- const hasDate = options.date != null;
151
- const hasVersion = options.version != null;
152
- const hasManual = options.manual != null;
153
- if (hasDate) thParts.push(`"${escapeThField(formatDateForMan(options.date))}"`);
154
- else if (hasVersion || hasManual) thParts.push("\"\"");
155
- if (hasVersion) thParts.push(`"${escapeThField(`${options.name} ${options.version}`)}"`);
156
- else if (hasManual) thParts.push("\"\"");
157
- if (hasManual) thParts.push(`"${escapeThField(options.manual)}"`);
158
- lines.push(`.TH ${thParts.join(" ")}`);
159
- lines.push(".SH NAME");
160
- if (page.brief) lines.push(`${options.name} \\- ${require_roff.formatMessageAsRoff(page.brief)}`);
161
- else lines.push(options.name);
162
- if (page.usage) {
163
- lines.push(".SH SYNOPSIS");
164
- lines.push(`.B ${options.name}`);
165
- const usageStr = formatUsageAsRoff(page.usage);
166
- if (usageStr) lines.push(usageStr);
167
- }
168
- if (page.description) {
169
- lines.push(".SH DESCRIPTION");
170
- lines.push(require_roff.formatMessageAsRoff(page.description));
171
- }
172
- for (const section of page.sections) {
173
- if (section.entries.length === 0) continue;
174
- const title = section.title?.toUpperCase() ?? "OPTIONS";
175
- lines.push(`.SH ${title}`);
176
- lines.push(formatDocSectionEntries(section));
177
- }
178
- if (options.environment && options.environment.entries.length > 0) {
179
- lines.push(".SH ENVIRONMENT");
180
- lines.push(formatDocSectionEntries(options.environment));
181
- }
182
- if (options.files && options.files.entries.length > 0) {
183
- lines.push(".SH FILES");
184
- lines.push(formatDocSectionEntries(options.files));
185
- }
186
- if (options.exitStatus && options.exitStatus.entries.length > 0) {
187
- lines.push(".SH EXIT STATUS");
188
- lines.push(formatDocSectionEntries(options.exitStatus));
189
- }
190
- if (options.examples) {
191
- lines.push(".SH EXAMPLES");
192
- lines.push(require_roff.formatMessageAsRoff(options.examples));
193
- }
194
- if (options.bugs) {
195
- lines.push(".SH BUGS");
196
- lines.push(require_roff.formatMessageAsRoff(options.bugs));
197
- }
198
- if (options.seeAlso && options.seeAlso.length > 0) {
199
- lines.push(".SH SEE ALSO");
200
- const refs = options.seeAlso.map((ref, i) => {
201
- const suffix = i < options.seeAlso.length - 1 ? "," : "";
202
- return `.BR ${ref.name} (${ref.section})${suffix}`;
203
- });
204
- lines.push(refs.join("\n"));
205
- }
206
- if (options.author) {
207
- lines.push(".SH AUTHOR");
208
- lines.push(require_roff.formatMessageAsRoff(options.author));
209
- }
210
- if (page.footer) {
211
- lines.push(".PP");
212
- lines.push(require_roff.formatMessageAsRoff(page.footer));
213
- }
214
- return lines.join("\n");
215
- }
216
-
217
- //#endregion
218
- Object.defineProperty(exports, 'formatDateForMan', {
219
- enumerable: true,
220
- get: function () {
221
- return formatDateForMan;
222
- }
223
- });
224
- Object.defineProperty(exports, 'formatDocPageAsMan', {
225
- enumerable: true,
226
- get: function () {
227
- return formatDocPageAsMan;
228
- }
229
- });
230
- Object.defineProperty(exports, 'formatUsageTermAsRoff', {
231
- enumerable: true,
232
- get: function () {
233
- return formatUsageTermAsRoff;
234
- }
235
- });