@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.
@@ -0,0 +1,162 @@
1
+ import { escapeHyphens, escapeRoff, formatMessageAsRoff } from "./roff-CK3-yKaY.js";
2
+ import { ManPageOptions, formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff } from "./man-tclTvdWs.js";
3
+ import { Program } from "@optique/core/program";
4
+ import { Mode, ModeValue, Parser } from "@optique/core/parser";
5
+
6
+ //#region src/generator.d.ts
7
+
8
+ /**
9
+ * Options for generating a man page from a parser.
10
+ * Extends {@link ManPageOptions} with the same configuration.
11
+ * @since 0.10.0
12
+ */
13
+ interface GenerateManPageOptions extends ManPageOptions {}
14
+ /**
15
+ * Options for generating a man page from a {@link Program}.
16
+ *
17
+ * This interface omits `name`, `version`, `author`, `bugs`, and `examples`
18
+ * from {@link ManPageOptions} since they are extracted from the program's
19
+ * metadata. You can still override them by providing values in this options
20
+ * object.
21
+ *
22
+ * @since 0.11.0
23
+ */
24
+ interface GenerateManPageProgramOptions extends Partial<Omit<ManPageOptions, "section">>, Pick<ManPageOptions, "section"> {}
25
+ /**
26
+ * Generates a man page from a parser synchronously.
27
+ *
28
+ * This function extracts documentation from the parser's structure
29
+ * and formats it as a complete man page in roff format.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * import { generateManPageSync } from "@optique/man";
34
+ * import { object, option, flag } from "@optique/core/primitives";
35
+ * import { string } from "@optique/core/valueparser";
36
+ *
37
+ * const parser = object({
38
+ * verbose: flag("-v", "--verbose"),
39
+ * config: option("-c", "--config", string()),
40
+ * });
41
+ *
42
+ * const manPage = generateManPageSync(parser, {
43
+ * name: "myapp",
44
+ * section: 1,
45
+ * version: "1.0.0",
46
+ * });
47
+ *
48
+ * console.log(manPage);
49
+ * ```
50
+ *
51
+ * @param parser The parser to generate documentation from.
52
+ * @param options The man page generation options.
53
+ * @returns The complete man page in roff format.
54
+ * @since 0.10.0
55
+ * @since 0.11.0 Added support for {@link Program} objects.
56
+ */
57
+ declare function generateManPageSync<T>(program: Program<"sync", T>, options: GenerateManPageProgramOptions): string;
58
+ declare function generateManPageSync(parser: Parser<"sync", unknown, unknown>, options: GenerateManPageOptions): string;
59
+ /**
60
+ * Generates a man page from a parser asynchronously.
61
+ *
62
+ * This function extracts documentation from the parser's structure
63
+ * and formats it as a complete man page in roff format. It supports
64
+ * both sync and async parsers.
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * import { generateManPageAsync } from "@optique/man";
69
+ * import { object, option } from "@optique/core/primitives";
70
+ * import { string } from "@optique/core/valueparser";
71
+ *
72
+ * const parser = object({
73
+ * config: option("-c", "--config", string()),
74
+ * });
75
+ *
76
+ * const manPage = await generateManPageAsync(parser, {
77
+ * name: "myapp",
78
+ * section: 1,
79
+ * });
80
+ * ```
81
+ *
82
+ * @param parser The parser to generate documentation from.
83
+ * @param options The man page generation options.
84
+ * @returns A promise that resolves to the complete man page in roff format.
85
+ * @since 0.10.0
86
+ * @since 0.11.0 Added support for {@link Program} objects.
87
+ */
88
+ declare function generateManPageAsync<M extends Mode, T>(program: Program<M, T>, options: GenerateManPageProgramOptions): Promise<string>;
89
+ declare function generateManPageAsync<M extends Mode>(parser: Parser<M, unknown, unknown>, options: GenerateManPageOptions): Promise<string>;
90
+ /**
91
+ * Generates a man page from a parser or program.
92
+ *
93
+ * This function extracts documentation from the parser's structure
94
+ * and formats it as a complete man page in roff format.
95
+ *
96
+ * For sync parsers, it returns the man page directly.
97
+ * For async parsers, it returns a Promise that resolves to the man page.
98
+ *
99
+ * @example Parser-based API
100
+ * ```typescript
101
+ * import { generateManPage } from "@optique/man";
102
+ * import { object, option, flag } from "@optique/core/primitives";
103
+ * import { string, integer } from "@optique/core/valueparser";
104
+ * import { message } from "@optique/core/message";
105
+ *
106
+ * const parser = object({
107
+ * verbose: flag("-v", "--verbose", {
108
+ * description: message`Enable verbose output.`,
109
+ * }),
110
+ * port: option("-p", "--port", integer(), {
111
+ * description: message`Port to listen on.`,
112
+ * }),
113
+ * });
114
+ *
115
+ * const manPage = generateManPage(parser, {
116
+ * name: "myapp",
117
+ * section: 1,
118
+ * version: "1.0.0",
119
+ * date: new Date(),
120
+ * author: message`Hong Minhee`,
121
+ * });
122
+ *
123
+ * // Write to file
124
+ * import { writeFileSync } from "node:fs";
125
+ * writeFileSync("myapp.1", manPage);
126
+ * ```
127
+ *
128
+ * @example Program-based API
129
+ * ```typescript
130
+ * import { generateManPage } from "@optique/man";
131
+ * import { defineProgram } from "@optique/core/program";
132
+ * import { object, flag } from "@optique/core/primitives";
133
+ * import { message } from "@optique/core/message";
134
+ *
135
+ * const prog = defineProgram({
136
+ * parser: object({
137
+ * verbose: flag("-v", "--verbose"),
138
+ * }),
139
+ * metadata: {
140
+ * name: "myapp",
141
+ * version: "1.0.0",
142
+ * author: message`Hong Minhee`,
143
+ * },
144
+ * });
145
+ *
146
+ * // Metadata is automatically extracted from the program
147
+ * const manPage = generateManPage(prog, { section: 1 });
148
+ * ```
149
+ *
150
+ * @param parserOrProgram The parser or program to generate documentation from.
151
+ * @param options The man page generation options.
152
+ * @returns The complete man page in roff format, or a Promise for async parsers.
153
+ * @since 0.10.0
154
+ * @since 0.11.0 Added support for {@link Program} objects.
155
+ */
156
+ declare function generateManPage<T>(program: Program<"sync", T>, options: GenerateManPageProgramOptions): string;
157
+ declare function generateManPage<T>(program: Program<"async", T>, options: GenerateManPageProgramOptions): Promise<string>;
158
+ declare function generateManPage(parser: Parser<"sync", unknown, unknown>, options: GenerateManPageOptions): string;
159
+ declare function generateManPage(parser: Parser<"async", unknown, unknown>, options: GenerateManPageOptions): Promise<string>;
160
+ declare function generateManPage<M extends Mode>(parser: Parser<M, unknown, unknown>, options: GenerateManPageOptions): ModeValue<M, string>;
161
+ //#endregion
162
+ export { type GenerateManPageOptions, type GenerateManPageProgramOptions, type ManPageOptions, escapeHyphens, escapeRoff, formatDateForMan, formatDocPageAsMan, formatMessageAsRoff, formatUsageTermAsRoff, generateManPage, generateManPageAsync, generateManPageSync };
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import { escapeHyphens, escapeRoff, formatMessageAsRoff } from "./roff-i3JSYqke.js";
2
+ import { formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff } from "./man-BmhJz56F.js";
3
+ import { generateManPage, generateManPageAsync, generateManPageSync } from "./generator-DQks_OLj.js";
4
+
5
+ export { escapeHyphens, escapeRoff, formatDateForMan, formatDocPageAsMan, formatMessageAsRoff, formatUsageTermAsRoff, generateManPage, generateManPageAsync, generateManPageSync };
@@ -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 };
@@ -0,0 +1,218 @@
1
+ import { escapeHyphens, formatMessageAsRoff } from "./roff-i3JSYqke.js";
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${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${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 = formatMessageAsRoff(entry.description);
109
+ if (entry.default) desc += ` [${formatMessageAsRoff(entry.default)}]`;
110
+ lines.push(desc);
111
+ } else if (entry.default) lines.push(`[${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} \\- ${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(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(formatMessageAsRoff(options.examples));
193
+ }
194
+ if (options.bugs) {
195
+ lines.push(".SH BUGS");
196
+ lines.push(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(formatMessageAsRoff(options.author));
209
+ }
210
+ if (page.footer) {
211
+ lines.push(".PP");
212
+ lines.push(formatMessageAsRoff(page.footer));
213
+ }
214
+ return lines.join("\n");
215
+ }
216
+
217
+ //#endregion
218
+ export { formatDateForMan, formatDocPageAsMan, formatUsageTermAsRoff };