@optique/man 0.10.0-dev.0
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/LICENSE +20 -0
- package/README.md +113 -0
- package/dist/cli.cjs +319 -0
- package/dist/cli.d.cts +7 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +319 -0
- package/dist/generator-DFTQgmHv.cjs +104 -0
- package/dist/generator-DQks_OLj.js +58 -0
- package/dist/index.cjs +13 -0
- package/dist/index.d.cts +162 -0
- package/dist/index.d.ts +162 -0
- package/dist/index.js +5 -0
- package/dist/man-BMb0Vyt9.d.cts +127 -0
- package/dist/man-BmhJz56F.js +218 -0
- package/dist/man-K9LPAprq.cjs +235 -0
- package/dist/man-tclTvdWs.d.ts +127 -0
- package/dist/man.cjs +6 -0
- package/dist/man.d.cts +2 -0
- package/dist/man.d.ts +2 -0
- package/dist/man.js +4 -0
- package/dist/roff-CK3-yKaY.d.ts +64 -0
- package/dist/roff-Cs3_UBG5.d.cts +64 -0
- package/dist/roff-DzxoXJIL.cjs +140 -0
- package/dist/roff-i3JSYqke.js +122 -0
- package/dist/roff.cjs +5 -0
- package/dist/roff.d.cts +2 -0
- package/dist/roff.d.ts +2 -0
- package/dist/roff.js +3 -0
- package/package.json +104 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
const require_roff = require('./roff-DzxoXJIL.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
|
+
/**
|
|
31
|
+
* Formats a single {@link UsageTerm} as roff markup for the SYNOPSIS section.
|
|
32
|
+
*
|
|
33
|
+
* @param term The usage term to format.
|
|
34
|
+
* @returns The roff-formatted string.
|
|
35
|
+
* @since 0.10.0
|
|
36
|
+
*/
|
|
37
|
+
function formatUsageTermAsRoff(term) {
|
|
38
|
+
if ("hidden" in term && term.hidden) return "";
|
|
39
|
+
switch (term.type) {
|
|
40
|
+
case "argument": return `\\fI${term.metavar}\\fR`;
|
|
41
|
+
case "option": {
|
|
42
|
+
const names = term.names.map((name) => `\\fB${require_roff.escapeHyphens(name)}\\fR`).join(" | ");
|
|
43
|
+
const metavarPart = term.metavar ? ` \\fI${term.metavar}\\fR` : "";
|
|
44
|
+
return `[${names}${metavarPart}]`;
|
|
45
|
+
}
|
|
46
|
+
case "command": return `\\fB${term.name}\\fR`;
|
|
47
|
+
case "optional": {
|
|
48
|
+
const inner = formatUsageAsRoff(term.terms);
|
|
49
|
+
return `[${inner}]`;
|
|
50
|
+
}
|
|
51
|
+
case "multiple": {
|
|
52
|
+
const inner = formatUsageAsRoff(term.terms);
|
|
53
|
+
if (term.min < 1) return `[${inner} ...]`;
|
|
54
|
+
return `${inner} ...`;
|
|
55
|
+
}
|
|
56
|
+
case "exclusive": {
|
|
57
|
+
const alternatives = term.terms.map((t) => formatUsageAsRoff(t)).join(" | ");
|
|
58
|
+
return `(${alternatives})`;
|
|
59
|
+
}
|
|
60
|
+
case "literal": return term.value;
|
|
61
|
+
case "passthrough": return "[...]";
|
|
62
|
+
default: {
|
|
63
|
+
const _exhaustive = term;
|
|
64
|
+
throw new TypeError(`Unknown usage term type: ${_exhaustive.type}.`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Formats a {@link Usage} array as roff markup.
|
|
70
|
+
*
|
|
71
|
+
* @param usage The usage terms to format.
|
|
72
|
+
* @returns The roff-formatted string.
|
|
73
|
+
*/
|
|
74
|
+
function formatUsageAsRoff(usage) {
|
|
75
|
+
return usage.map(formatUsageTermAsRoff).filter((s) => s !== "").join(" ");
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Formats a {@link DocEntry}'s term for man page output.
|
|
79
|
+
*
|
|
80
|
+
* @param term The usage term from the entry.
|
|
81
|
+
* @returns The roff-formatted term string.
|
|
82
|
+
*/
|
|
83
|
+
function formatDocEntryTerm(term) {
|
|
84
|
+
switch (term.type) {
|
|
85
|
+
case "option": {
|
|
86
|
+
const names = term.names.map((name) => `\\fB${require_roff.escapeHyphens(name)}\\fR`).join(", ");
|
|
87
|
+
const metavarPart = term.metavar ? ` \\fI${term.metavar}\\fR` : "";
|
|
88
|
+
return `${names}${metavarPart}`;
|
|
89
|
+
}
|
|
90
|
+
case "command": return `\\fB${term.name}\\fR`;
|
|
91
|
+
case "argument": return `\\fI${term.metavar}\\fR`;
|
|
92
|
+
case "literal": return term.value;
|
|
93
|
+
default: return formatUsageTermAsRoff(term);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Formats a {@link DocSection} as roff markup with .TP macros.
|
|
98
|
+
*
|
|
99
|
+
* @param section The section to format.
|
|
100
|
+
* @returns The roff-formatted section content.
|
|
101
|
+
*/
|
|
102
|
+
function formatDocSectionEntries(section) {
|
|
103
|
+
const lines = [];
|
|
104
|
+
for (const entry of section.entries) {
|
|
105
|
+
lines.push(".TP");
|
|
106
|
+
lines.push(formatDocEntryTerm(entry.term));
|
|
107
|
+
if (entry.description) {
|
|
108
|
+
let desc = require_roff.formatMessageAsRoff(entry.description);
|
|
109
|
+
if (entry.default) desc += ` [${require_roff.formatMessageAsRoff(entry.default)}]`;
|
|
110
|
+
lines.push(desc);
|
|
111
|
+
} else if (entry.default) lines.push(`[${require_roff.formatMessageAsRoff(entry.default)}]`);
|
|
112
|
+
}
|
|
113
|
+
return lines.join("\n");
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Formats a {@link DocPage} as a complete man page in roff format.
|
|
117
|
+
*
|
|
118
|
+
* This function generates a man page following the standard man(7) format,
|
|
119
|
+
* including sections for NAME, SYNOPSIS, DESCRIPTION, OPTIONS, and more.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* import { formatDocPageAsMan } from "@optique/man/man";
|
|
124
|
+
* import type { DocPage } from "@optique/core/doc";
|
|
125
|
+
*
|
|
126
|
+
* const page: DocPage = {
|
|
127
|
+
* brief: message`A sample CLI application`,
|
|
128
|
+
* usage: [{ type: "argument", metavar: "FILE" }],
|
|
129
|
+
* sections: [],
|
|
130
|
+
* };
|
|
131
|
+
*
|
|
132
|
+
* const manPage = formatDocPageAsMan(page, {
|
|
133
|
+
* name: "myapp",
|
|
134
|
+
* section: 1,
|
|
135
|
+
* version: "1.0.0",
|
|
136
|
+
* });
|
|
137
|
+
* ```
|
|
138
|
+
*
|
|
139
|
+
* @param page The documentation page to format.
|
|
140
|
+
* @param options The man page options.
|
|
141
|
+
* @returns The complete man page in roff format.
|
|
142
|
+
* @since 0.10.0
|
|
143
|
+
*/
|
|
144
|
+
function formatDocPageAsMan(page, options) {
|
|
145
|
+
const lines = [];
|
|
146
|
+
const thParts = [options.name.toUpperCase(), options.section.toString()];
|
|
147
|
+
if (options.date != null) thParts.push(`"${formatDateForMan(options.date)}"`);
|
|
148
|
+
if (options.version != null) {
|
|
149
|
+
const dateStr = options.date != null ? "" : "\"\"";
|
|
150
|
+
if (dateStr) thParts.push(dateStr);
|
|
151
|
+
thParts.push(`"${options.name} ${options.version}"`);
|
|
152
|
+
}
|
|
153
|
+
if (options.manual != null) {
|
|
154
|
+
if (options.date == null) thParts.push("\"\"");
|
|
155
|
+
if (options.version == null) thParts.push("\"\"");
|
|
156
|
+
thParts.push(`"${options.manual}"`);
|
|
157
|
+
}
|
|
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
|
+
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Message } from "@optique/core/message";
|
|
2
|
+
import { DocPage, DocSection } from "@optique/core/doc";
|
|
3
|
+
import { UsageTerm } from "@optique/core/usage";
|
|
4
|
+
|
|
5
|
+
//#region src/man.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Valid man page section numbers.
|
|
9
|
+
* @since 0.10.0
|
|
10
|
+
*/
|
|
11
|
+
type ManPageSection = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
|
12
|
+
/**
|
|
13
|
+
* Options for generating a man page.
|
|
14
|
+
* @since 0.10.0
|
|
15
|
+
*/
|
|
16
|
+
interface ManPageOptions {
|
|
17
|
+
/**
|
|
18
|
+
* The name of the program. This appears in the NAME section and header.
|
|
19
|
+
*/
|
|
20
|
+
readonly name: string;
|
|
21
|
+
/**
|
|
22
|
+
* The manual section number. Common sections:
|
|
23
|
+
* - 1: User commands
|
|
24
|
+
* - 2: System calls
|
|
25
|
+
* - 3: Library functions
|
|
26
|
+
* - 4: Special files
|
|
27
|
+
* - 5: File formats
|
|
28
|
+
* - 6: Games
|
|
29
|
+
* - 7: Miscellaneous
|
|
30
|
+
* - 8: System administration
|
|
31
|
+
*/
|
|
32
|
+
readonly section: ManPageSection;
|
|
33
|
+
/**
|
|
34
|
+
* The date to display in the man page footer.
|
|
35
|
+
* If a Date object is provided, it will be formatted as "Month Year".
|
|
36
|
+
* If a string is provided, it will be used as-is.
|
|
37
|
+
*/
|
|
38
|
+
readonly date?: string | Date;
|
|
39
|
+
/**
|
|
40
|
+
* The version string to display in the man page footer.
|
|
41
|
+
*/
|
|
42
|
+
readonly version?: string;
|
|
43
|
+
/**
|
|
44
|
+
* The manual title (e.g., "User Commands", "System Calls").
|
|
45
|
+
* This appears in the header.
|
|
46
|
+
*/
|
|
47
|
+
readonly manual?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Author information to include in the AUTHOR section.
|
|
50
|
+
*/
|
|
51
|
+
readonly author?: Message;
|
|
52
|
+
/**
|
|
53
|
+
* Bug reporting information to include in the BUGS section.
|
|
54
|
+
*/
|
|
55
|
+
readonly bugs?: Message;
|
|
56
|
+
/**
|
|
57
|
+
* Examples to include in the EXAMPLES section.
|
|
58
|
+
*/
|
|
59
|
+
readonly examples?: Message;
|
|
60
|
+
/**
|
|
61
|
+
* Cross-references to include in the SEE ALSO section.
|
|
62
|
+
*/
|
|
63
|
+
readonly seeAlso?: ReadonlyArray<{
|
|
64
|
+
readonly name: string;
|
|
65
|
+
readonly section: number;
|
|
66
|
+
}>;
|
|
67
|
+
/**
|
|
68
|
+
* Environment variables to document in the ENVIRONMENT section.
|
|
69
|
+
*/
|
|
70
|
+
readonly environment?: DocSection;
|
|
71
|
+
/**
|
|
72
|
+
* File paths to document in the FILES section.
|
|
73
|
+
*/
|
|
74
|
+
readonly files?: DocSection;
|
|
75
|
+
/**
|
|
76
|
+
* Exit status codes to document in the EXIT STATUS section.
|
|
77
|
+
*/
|
|
78
|
+
readonly exitStatus?: DocSection;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Formats a date for use in man pages.
|
|
82
|
+
*
|
|
83
|
+
* @param date The date to format, or undefined.
|
|
84
|
+
* @returns The formatted date string, or undefined.
|
|
85
|
+
* @since 0.10.0
|
|
86
|
+
*/
|
|
87
|
+
declare function formatDateForMan(date: string | Date | undefined): string | undefined;
|
|
88
|
+
/**
|
|
89
|
+
* Formats a single {@link UsageTerm} as roff markup for the SYNOPSIS section.
|
|
90
|
+
*
|
|
91
|
+
* @param term The usage term to format.
|
|
92
|
+
* @returns The roff-formatted string.
|
|
93
|
+
* @since 0.10.0
|
|
94
|
+
*/
|
|
95
|
+
declare function formatUsageTermAsRoff(term: UsageTerm): string;
|
|
96
|
+
/**
|
|
97
|
+
* Formats a {@link DocPage} as a complete man page in roff format.
|
|
98
|
+
*
|
|
99
|
+
* This function generates a man page following the standard man(7) format,
|
|
100
|
+
* including sections for NAME, SYNOPSIS, DESCRIPTION, OPTIONS, and more.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* import { formatDocPageAsMan } from "@optique/man/man";
|
|
105
|
+
* import type { DocPage } from "@optique/core/doc";
|
|
106
|
+
*
|
|
107
|
+
* const page: DocPage = {
|
|
108
|
+
* brief: message`A sample CLI application`,
|
|
109
|
+
* usage: [{ type: "argument", metavar: "FILE" }],
|
|
110
|
+
* sections: [],
|
|
111
|
+
* };
|
|
112
|
+
*
|
|
113
|
+
* const manPage = formatDocPageAsMan(page, {
|
|
114
|
+
* name: "myapp",
|
|
115
|
+
* section: 1,
|
|
116
|
+
* version: "1.0.0",
|
|
117
|
+
* });
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* @param page The documentation page to format.
|
|
121
|
+
* @param options The man page options.
|
|
122
|
+
* @returns The complete man page in roff format.
|
|
123
|
+
* @since 0.10.0
|
|
124
|
+
*/
|
|
125
|
+
declare function formatDocPageAsMan(page: DocPage, options: ManPageOptions): string;
|
|
126
|
+
//#endregion
|
|
127
|
+
export { ManPageOptions, ManPageSection, formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff };
|
package/dist/man.cjs
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
require('./roff-DzxoXJIL.cjs');
|
|
2
|
+
const require_man = require('./man-K9LPAprq.cjs');
|
|
3
|
+
|
|
4
|
+
exports.formatDateForMan = require_man.formatDateForMan;
|
|
5
|
+
exports.formatDocPageAsMan = require_man.formatDocPageAsMan;
|
|
6
|
+
exports.formatUsageTermAsRoff = require_man.formatUsageTermAsRoff;
|
package/dist/man.d.cts
ADDED
package/dist/man.d.ts
ADDED
package/dist/man.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Message } from "@optique/core/message";
|
|
2
|
+
|
|
3
|
+
//#region src/roff.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Escapes special roff characters in plain text.
|
|
7
|
+
*
|
|
8
|
+
* This function handles the following escapes:
|
|
9
|
+
* - Backslash (`\`) → `\\`
|
|
10
|
+
* - Period (`.`) at line start → `\&.`
|
|
11
|
+
* - Single quote (`'`) at line start → `\&'`
|
|
12
|
+
*
|
|
13
|
+
* @param text The plain text to escape.
|
|
14
|
+
* @returns The escaped text safe for use in roff documents.
|
|
15
|
+
* @since 0.10.0
|
|
16
|
+
*/
|
|
17
|
+
declare function escapeRoff(text: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Escapes hyphens in option names to prevent line breaks.
|
|
20
|
+
*
|
|
21
|
+
* In roff, a regular hyphen (`-`) can be used as a line break point.
|
|
22
|
+
* For option names like `--verbose`, we want to use `\-` which prevents
|
|
23
|
+
* line breaks and renders as a proper minus sign.
|
|
24
|
+
*
|
|
25
|
+
* @param text The text containing hyphens to escape.
|
|
26
|
+
* @returns The text with hyphens escaped as `\-`.
|
|
27
|
+
* @since 0.10.0
|
|
28
|
+
*/
|
|
29
|
+
declare function escapeHyphens(text: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* Formats a {@link Message} as roff markup for use in man pages.
|
|
32
|
+
*
|
|
33
|
+
* This function converts Optique's structured message format into roff
|
|
34
|
+
* markup suitable for man pages. Each message term type is converted
|
|
35
|
+
* to appropriate roff formatting:
|
|
36
|
+
*
|
|
37
|
+
* | Term Type | Roff Output |
|
|
38
|
+
* |-----------|-------------|
|
|
39
|
+
* | `text` | Plain text (escaped) |
|
|
40
|
+
* | `optionName` | `\fB--option\fR` (bold) |
|
|
41
|
+
* | `optionNames` | `\fB--opt1\fR, \fB-o\fR` (comma-separated bold) |
|
|
42
|
+
* | `metavar` | `\fIFILE\fR` (italic) |
|
|
43
|
+
* | `value` | `"value"` (quoted) |
|
|
44
|
+
* | `values` | `"a" "b" "c"` (space-separated quoted) |
|
|
45
|
+
* | `envVar` | `\fBVAR\fR` (bold) |
|
|
46
|
+
* | `commandLine` | `\fBcmd\fR` (bold) |
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* import { formatMessageAsRoff } from "@optique/man/roff";
|
|
51
|
+
* import { message, optionName, metavar } from "@optique/core/message";
|
|
52
|
+
*
|
|
53
|
+
* const msg = message`Use ${optionName("--config")} ${metavar("FILE")}`;
|
|
54
|
+
* const roff = formatMessageAsRoff(msg);
|
|
55
|
+
* // => "Use \\fB\\-\\-config\\fR \\fIFILE\\fR"
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @param msg The message to format.
|
|
59
|
+
* @returns The roff-formatted string.
|
|
60
|
+
* @since 0.10.0
|
|
61
|
+
*/
|
|
62
|
+
declare function formatMessageAsRoff(msg: Message): string;
|
|
63
|
+
//#endregion
|
|
64
|
+
export { escapeHyphens, escapeRoff, formatMessageAsRoff };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Message } from "@optique/core/message";
|
|
2
|
+
|
|
3
|
+
//#region src/roff.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Escapes special roff characters in plain text.
|
|
7
|
+
*
|
|
8
|
+
* This function handles the following escapes:
|
|
9
|
+
* - Backslash (`\`) → `\\`
|
|
10
|
+
* - Period (`.`) at line start → `\&.`
|
|
11
|
+
* - Single quote (`'`) at line start → `\&'`
|
|
12
|
+
*
|
|
13
|
+
* @param text The plain text to escape.
|
|
14
|
+
* @returns The escaped text safe for use in roff documents.
|
|
15
|
+
* @since 0.10.0
|
|
16
|
+
*/
|
|
17
|
+
declare function escapeRoff(text: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Escapes hyphens in option names to prevent line breaks.
|
|
20
|
+
*
|
|
21
|
+
* In roff, a regular hyphen (`-`) can be used as a line break point.
|
|
22
|
+
* For option names like `--verbose`, we want to use `\-` which prevents
|
|
23
|
+
* line breaks and renders as a proper minus sign.
|
|
24
|
+
*
|
|
25
|
+
* @param text The text containing hyphens to escape.
|
|
26
|
+
* @returns The text with hyphens escaped as `\-`.
|
|
27
|
+
* @since 0.10.0
|
|
28
|
+
*/
|
|
29
|
+
declare function escapeHyphens(text: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* Formats a {@link Message} as roff markup for use in man pages.
|
|
32
|
+
*
|
|
33
|
+
* This function converts Optique's structured message format into roff
|
|
34
|
+
* markup suitable for man pages. Each message term type is converted
|
|
35
|
+
* to appropriate roff formatting:
|
|
36
|
+
*
|
|
37
|
+
* | Term Type | Roff Output |
|
|
38
|
+
* |-----------|-------------|
|
|
39
|
+
* | `text` | Plain text (escaped) |
|
|
40
|
+
* | `optionName` | `\fB--option\fR` (bold) |
|
|
41
|
+
* | `optionNames` | `\fB--opt1\fR, \fB-o\fR` (comma-separated bold) |
|
|
42
|
+
* | `metavar` | `\fIFILE\fR` (italic) |
|
|
43
|
+
* | `value` | `"value"` (quoted) |
|
|
44
|
+
* | `values` | `"a" "b" "c"` (space-separated quoted) |
|
|
45
|
+
* | `envVar` | `\fBVAR\fR` (bold) |
|
|
46
|
+
* | `commandLine` | `\fBcmd\fR` (bold) |
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* import { formatMessageAsRoff } from "@optique/man/roff";
|
|
51
|
+
* import { message, optionName, metavar } from "@optique/core/message";
|
|
52
|
+
*
|
|
53
|
+
* const msg = message`Use ${optionName("--config")} ${metavar("FILE")}`;
|
|
54
|
+
* const roff = formatMessageAsRoff(msg);
|
|
55
|
+
* // => "Use \\fB\\-\\-config\\fR \\fIFILE\\fR"
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @param msg The message to format.
|
|
59
|
+
* @returns The roff-formatted string.
|
|
60
|
+
* @since 0.10.0
|
|
61
|
+
*/
|
|
62
|
+
declare function formatMessageAsRoff(msg: Message): string;
|
|
63
|
+
//#endregion
|
|
64
|
+
export { escapeHyphens, escapeRoff, formatMessageAsRoff };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/roff.ts
|
|
3
|
+
/**
|
|
4
|
+
* Escapes backslashes in text for roff.
|
|
5
|
+
* This is an internal helper that only handles backslash escaping.
|
|
6
|
+
*
|
|
7
|
+
* @param text The text to escape.
|
|
8
|
+
* @returns The text with backslashes escaped.
|
|
9
|
+
*/
|
|
10
|
+
function escapeBackslashes(text) {
|
|
11
|
+
return text.replace(/\\/g, "\\\\");
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Escapes period and single quote at line starts.
|
|
15
|
+
* In roff, these characters have special meaning when at the start of a line.
|
|
16
|
+
*
|
|
17
|
+
* @param text The text to process.
|
|
18
|
+
* @returns The text with line-start special characters escaped.
|
|
19
|
+
*/
|
|
20
|
+
function escapeLineStarts(text) {
|
|
21
|
+
if (text === "") return "";
|
|
22
|
+
let result = text;
|
|
23
|
+
if (result.startsWith(".") || result.startsWith("'")) result = "\\&" + result;
|
|
24
|
+
result = result.replace(/\n([.'])/g, "\n\\&$1");
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Escapes special roff characters in plain text.
|
|
29
|
+
*
|
|
30
|
+
* This function handles the following escapes:
|
|
31
|
+
* - Backslash (`\`) → `\\`
|
|
32
|
+
* - Period (`.`) at line start → `\&.`
|
|
33
|
+
* - Single quote (`'`) at line start → `\&'`
|
|
34
|
+
*
|
|
35
|
+
* @param text The plain text to escape.
|
|
36
|
+
* @returns The escaped text safe for use in roff documents.
|
|
37
|
+
* @since 0.10.0
|
|
38
|
+
*/
|
|
39
|
+
function escapeRoff(text) {
|
|
40
|
+
if (text === "") return "";
|
|
41
|
+
return escapeLineStarts(escapeBackslashes(text));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Escapes hyphens in option names to prevent line breaks.
|
|
45
|
+
*
|
|
46
|
+
* In roff, a regular hyphen (`-`) can be used as a line break point.
|
|
47
|
+
* For option names like `--verbose`, we want to use `\-` which prevents
|
|
48
|
+
* line breaks and renders as a proper minus sign.
|
|
49
|
+
*
|
|
50
|
+
* @param text The text containing hyphens to escape.
|
|
51
|
+
* @returns The text with hyphens escaped as `\-`.
|
|
52
|
+
* @since 0.10.0
|
|
53
|
+
*/
|
|
54
|
+
function escapeHyphens(text) {
|
|
55
|
+
return text.replace(/-/g, "\\-");
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Formats a single {@link MessageTerm} as roff markup.
|
|
59
|
+
* Note: This does NOT escape line-start characters (. and ') because
|
|
60
|
+
* these terms may be concatenated with other terms. Line-start escaping
|
|
61
|
+
* is done in formatMessageAsRoff after all terms are joined.
|
|
62
|
+
*
|
|
63
|
+
* @param term The message term to format.
|
|
64
|
+
* @returns The roff-formatted string.
|
|
65
|
+
*/
|
|
66
|
+
function formatTermAsRoff(term) {
|
|
67
|
+
switch (term.type) {
|
|
68
|
+
case "text": return escapeBackslashes(term.text);
|
|
69
|
+
case "optionName": return `\\fB${escapeHyphens(term.optionName)}\\fR`;
|
|
70
|
+
case "optionNames": return term.optionNames.map((name) => `\\fB${escapeHyphens(name)}\\fR`).join(", ");
|
|
71
|
+
case "metavar": return `\\fI${escapeBackslashes(term.metavar)}\\fR`;
|
|
72
|
+
case "value": return `"${escapeBackslashes(term.value)}"`;
|
|
73
|
+
case "values":
|
|
74
|
+
if (term.values.length === 0) return "";
|
|
75
|
+
return term.values.map((v) => `"${escapeBackslashes(v)}"`).join(" ");
|
|
76
|
+
case "envVar": return `\\fB${escapeBackslashes(term.envVar)}\\fR`;
|
|
77
|
+
case "commandLine": return `\\fB${escapeHyphens(escapeBackslashes(term.commandLine))}\\fR`;
|
|
78
|
+
default: {
|
|
79
|
+
const _exhaustive = term;
|
|
80
|
+
throw new TypeError(`Unknown message term type: ${_exhaustive.type}.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Formats a {@link Message} as roff markup for use in man pages.
|
|
86
|
+
*
|
|
87
|
+
* This function converts Optique's structured message format into roff
|
|
88
|
+
* markup suitable for man pages. Each message term type is converted
|
|
89
|
+
* to appropriate roff formatting:
|
|
90
|
+
*
|
|
91
|
+
* | Term Type | Roff Output |
|
|
92
|
+
* |-----------|-------------|
|
|
93
|
+
* | `text` | Plain text (escaped) |
|
|
94
|
+
* | `optionName` | `\fB--option\fR` (bold) |
|
|
95
|
+
* | `optionNames` | `\fB--opt1\fR, \fB-o\fR` (comma-separated bold) |
|
|
96
|
+
* | `metavar` | `\fIFILE\fR` (italic) |
|
|
97
|
+
* | `value` | `"value"` (quoted) |
|
|
98
|
+
* | `values` | `"a" "b" "c"` (space-separated quoted) |
|
|
99
|
+
* | `envVar` | `\fBVAR\fR` (bold) |
|
|
100
|
+
* | `commandLine` | `\fBcmd\fR` (bold) |
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* import { formatMessageAsRoff } from "@optique/man/roff";
|
|
105
|
+
* import { message, optionName, metavar } from "@optique/core/message";
|
|
106
|
+
*
|
|
107
|
+
* const msg = message`Use ${optionName("--config")} ${metavar("FILE")}`;
|
|
108
|
+
* const roff = formatMessageAsRoff(msg);
|
|
109
|
+
* // => "Use \\fB\\-\\-config\\fR \\fIFILE\\fR"
|
|
110
|
+
* ```
|
|
111
|
+
*
|
|
112
|
+
* @param msg The message to format.
|
|
113
|
+
* @returns The roff-formatted string.
|
|
114
|
+
* @since 0.10.0
|
|
115
|
+
*/
|
|
116
|
+
function formatMessageAsRoff(msg) {
|
|
117
|
+
const joined = msg.map(formatTermAsRoff).join("");
|
|
118
|
+
const escaped = escapeLineStarts(joined);
|
|
119
|
+
return escaped.replace(/\n\n+/g, "\n.PP\n");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
//#endregion
|
|
123
|
+
Object.defineProperty(exports, 'escapeHyphens', {
|
|
124
|
+
enumerable: true,
|
|
125
|
+
get: function () {
|
|
126
|
+
return escapeHyphens;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
Object.defineProperty(exports, 'escapeRoff', {
|
|
130
|
+
enumerable: true,
|
|
131
|
+
get: function () {
|
|
132
|
+
return escapeRoff;
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
Object.defineProperty(exports, 'formatMessageAsRoff', {
|
|
136
|
+
enumerable: true,
|
|
137
|
+
get: function () {
|
|
138
|
+
return formatMessageAsRoff;
|
|
139
|
+
}
|
|
140
|
+
});
|