@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 ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright 2025–2026 Hong Minhee
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,113 @@
1
+ @optique/man
2
+ ============
3
+
4
+ Man page generator for [Optique] CLI parsers. This package generates Unix man
5
+ pages from Optique's structured parser metadata, enabling automatic
6
+ documentation generation that stays synchronized with your CLI's actual
7
+ behavior.
8
+
9
+ [Optique]: https://optique.dev/
10
+
11
+
12
+ Installation
13
+ ------------
14
+
15
+ ::: code-group
16
+
17
+ ~~~~ bash [Deno]
18
+ deno add jsr:@optique/man
19
+ ~~~~
20
+
21
+ ~~~~ bash [npm]
22
+ npm add @optique/man
23
+ ~~~~
24
+
25
+ ~~~~ bash [pnpm]
26
+ pnpm add @optique/man
27
+ ~~~~
28
+
29
+ ~~~~ bash [Yarn]
30
+ yarn add @optique/man
31
+ ~~~~
32
+
33
+ ~~~~ bash [Bun]
34
+ bun add @optique/man
35
+ ~~~~
36
+
37
+ :::
38
+
39
+
40
+ Quick start
41
+ -----------
42
+
43
+ ~~~~ typescript
44
+ import { generateManPage } from "@optique/man";
45
+ import { object, option, argument } from "@optique/core/primitives";
46
+ import { string, integer } from "@optique/core/valueparser";
47
+
48
+ const parser = object({
49
+ port: option("-p", "--port", integer(), { description: "Port to listen on" }),
50
+ host: option("-h", "--host", string(), { description: "Host to bind to" }),
51
+ });
52
+
53
+ const manPage = generateManPage(parser, {
54
+ name: "myapp",
55
+ section: 1,
56
+ version: "1.0.0",
57
+ date: new Date(),
58
+ });
59
+
60
+ console.log(manPage);
61
+ ~~~~
62
+
63
+
64
+ API
65
+ ---
66
+
67
+ ### Low-level: Message to roff conversion
68
+
69
+ ~~~~ typescript
70
+ import { formatMessageAsRoff, escapeRoff } from "@optique/man/roff";
71
+ import { message, optionName } from "@optique/core/message";
72
+
73
+ // Escape special roff characters
74
+ escapeRoff("Use .TH for title"); // "Use \\.TH for title"
75
+
76
+ // Convert Message to roff
77
+ const msg = message`Use ${optionName("--help")} for more info.`;
78
+ formatMessageAsRoff(msg); // "Use \\fB\\-\\-help\\fR for more info."
79
+ ~~~~
80
+
81
+ ### Mid-level: DocPage to man page
82
+
83
+ ~~~~ typescript
84
+ import { formatDocPageAsMan } from "@optique/man/man";
85
+ import type { DocPage } from "@optique/core/doc";
86
+
87
+ const manPage = formatDocPageAsMan(docPage, {
88
+ name: "myapp",
89
+ section: 1,
90
+ version: "1.0.0",
91
+ author: message`Hong Minhee`,
92
+ });
93
+ ~~~~
94
+
95
+ ### High-level: Parser to man page
96
+
97
+ ~~~~ typescript
98
+ import { generateManPage } from "@optique/man";
99
+
100
+ const manPage = generateManPage(parser, {
101
+ name: "myapp",
102
+ section: 1,
103
+ version: "1.0.0",
104
+ });
105
+ ~~~~
106
+
107
+
108
+ Documentation
109
+ -------------
110
+
111
+ See the [Optique documentation] for more information.
112
+
113
+ [Optique documentation]: https://optique.dev/
package/dist/cli.cjs ADDED
@@ -0,0 +1,319 @@
1
+ #!/usr/bin/env node
2
+ const require_generator = require('./generator-DFTQgmHv.cjs');
3
+ require('./roff-DzxoXJIL.cjs');
4
+ require('./man-K9LPAprq.cjs');
5
+ const __optique_core_constructs = require_generator.__toESM(require("@optique/core/constructs"));
6
+ const __optique_core_primitives = require_generator.__toESM(require("@optique/core/primitives"));
7
+ const __optique_core_valueparser = require_generator.__toESM(require("@optique/core/valueparser"));
8
+ const __optique_core_modifiers = require_generator.__toESM(require("@optique/core/modifiers"));
9
+ const __optique_core_message = require_generator.__toESM(require("@optique/core/message"));
10
+ const __optique_core_program = require_generator.__toESM(require("@optique/core/program"));
11
+ const __optique_run = require_generator.__toESM(require("@optique/run"));
12
+ const node_fs = require_generator.__toESM(require("node:fs"));
13
+ const node_fs_promises = require_generator.__toESM(require("node:fs/promises"));
14
+ const node_path = require_generator.__toESM(require("node:path"));
15
+ const node_process = require_generator.__toESM(require("node:process"));
16
+ const node_url = require_generator.__toESM(require("node:url"));
17
+
18
+ //#region deno.json
19
+ var name = "@optique/man";
20
+ var version = "0.10.0";
21
+ var license = "MIT";
22
+ var exports$1 = {
23
+ ".": "./src/index.ts",
24
+ "./roff": "./src/roff.ts",
25
+ "./man": "./src/man.ts",
26
+ "./cli": "./src/cli.ts"
27
+ };
28
+ var imports = { "tsx/esm/api": "npm:tsx@^4.21.0/esm/api" };
29
+ var exclude = ["dist/", "tsdown.config.ts"];
30
+ var tasks = {
31
+ "build": "pnpm build",
32
+ "test": "deno test",
33
+ "test:node": {
34
+ "dependencies": ["build"],
35
+ "command": "node --experimental-transform-types --test"
36
+ },
37
+ "test:bun": {
38
+ "dependencies": ["build"],
39
+ "command": "bun test"
40
+ },
41
+ "test-all": { "dependencies": [
42
+ "test",
43
+ "test:node",
44
+ "test:bun"
45
+ ] }
46
+ };
47
+ var deno_default = {
48
+ name,
49
+ version,
50
+ license,
51
+ exports: exports$1,
52
+ imports,
53
+ exclude,
54
+ tasks
55
+ };
56
+
57
+ //#endregion
58
+ //#region src/cli.ts
59
+ const EXIT_FILE_NOT_FOUND = 1;
60
+ const EXIT_EXPORT_NOT_FOUND = 2;
61
+ const EXIT_NOT_PROGRAM_OR_PARSER = 3;
62
+ const EXIT_TSX_REQUIRED = 4;
63
+ const EXIT_GENERATION_FAILED = 5;
64
+ const EXIT_WRITE_FAILED = 6;
65
+ const sectionValues = [
66
+ 1,
67
+ 2,
68
+ 3,
69
+ 4,
70
+ 5,
71
+ 6,
72
+ 7,
73
+ 8
74
+ ];
75
+ /**
76
+ * CLI program definition for optique-man.
77
+ */
78
+ const cliProgram = (0, __optique_core_program.defineProgram)({
79
+ parser: (0, __optique_core_constructs.object)({
80
+ file: (0, __optique_core_primitives.argument)((0, __optique_core_valueparser.string)({ metavar: "FILE" }), { description: __optique_core_message.message`Path to a TypeScript or JavaScript file that exports
81
+ a ${(0, __optique_core_message.metavar)("PROGRAM")} or ${(0, __optique_core_message.metavar)("PARSER")} to generate a man page from.` }),
82
+ section: (0, __optique_core_primitives.option)("-s", "--section", (0, __optique_core_valueparser.choice)(sectionValues, { metavar: "SECTION" }), { description: __optique_core_message.message`Man page section number (${"1"}-${"8"}). Common sections:
83
+
84
+ ${"1"} User commands
85
+ ${"5"} File formats
86
+ ${"8"} System administration` }),
87
+ exportName: (0, __optique_core_modifiers.withDefault)((0, __optique_core_primitives.option)("-e", "--export", (0, __optique_core_valueparser.string)({ metavar: "NAME" }), { description: __optique_core_message.message`JavaScript export name to use. The export must be
88
+ a ${(0, __optique_core_message.metavar)("PROGRAM")} (from ${(0, __optique_core_message.commandLine)("defineProgram()")}) or
89
+ a ${(0, __optique_core_message.metavar)("PARSER")}. If not specified, the default export is used.` }), "default"),
90
+ output: (0, __optique_core_modifiers.optional)((0, __optique_core_primitives.option)("-o", "--output", (0, __optique_core_valueparser.string)({ metavar: "PATH" }), { description: __optique_core_message.message`Output file path. If not specified, the man page
91
+ is written to stdout.` })),
92
+ name: (0, __optique_core_modifiers.optional)((0, __optique_core_primitives.option)("--name", (0, __optique_core_valueparser.string)({ metavar: "NAME" }), { description: __optique_core_message.message`Program name to use in the man page header.
93
+ If not specified, inferred from the ${(0, __optique_core_message.metavar)("PROGRAM")} metadata
94
+ or the input file name.` })),
95
+ date: (0, __optique_core_modifiers.optional)((0, __optique_core_primitives.option)("--date", (0, __optique_core_valueparser.string)({ metavar: "DATE" }), { description: __optique_core_message.message`Date to display in the man page footer.
96
+ Defaults to the current date.` })),
97
+ versionString: (0, __optique_core_modifiers.optional)((0, __optique_core_primitives.option)("--version-string", (0, __optique_core_valueparser.string)({ metavar: "VERSION" }), { description: __optique_core_message.message`Version string for the man page footer
98
+ (e.g., ${"MyApp 1.0.0"}). Overrides the version from
99
+ ${(0, __optique_core_message.metavar)("PROGRAM")} metadata if provided.` })),
100
+ manual: (0, __optique_core_modifiers.optional)((0, __optique_core_primitives.option)("--manual", (0, __optique_core_valueparser.string)({ metavar: "TITLE" }), { description: __optique_core_message.message`Manual name for the man page header
101
+ (e.g., ${"User Commands"}).` }))
102
+ }),
103
+ metadata: {
104
+ name: "optique-man",
105
+ version: deno_default.version,
106
+ brief: __optique_core_message.message`Generate Unix man pages from Optique parsers`,
107
+ description: __optique_core_message.message`Generates a Unix man page from an Optique
108
+ ${(0, __optique_core_message.metavar)("PROGRAM")} or ${(0, __optique_core_message.metavar)("PARSER")} exported from a TypeScript
109
+ or JavaScript file.
110
+
111
+ The input file should export a ${(0, __optique_core_message.metavar)("PROGRAM")} (created with
112
+ ${(0, __optique_core_message.commandLine)("defineProgram()")}) or a ${(0, __optique_core_message.metavar)("PARSER")}. When using
113
+ a ${(0, __optique_core_message.metavar)("PROGRAM")}, metadata like name, version, author, and examples
114
+ are automatically extracted.`,
115
+ examples: __optique_core_message.message`Generate a man page for section ${"1"} (user commands):
116
+
117
+ ${(0, __optique_core_message.commandLine)("optique-man ./src/cli.ts -s 1")}
118
+
119
+ Use a named export instead of the default export:
120
+
121
+ ${(0, __optique_core_message.commandLine)("optique-man ./src/cli.ts -s 1 -e myProgram")}
122
+
123
+ Write output to a file:
124
+
125
+ ${(0, __optique_core_message.commandLine)("optique-man ./src/cli.ts -s 1 -o myapp.1")}
126
+
127
+ Override the program name:
128
+
129
+ ${(0, __optique_core_message.commandLine)("optique-man ./src/cli.ts -s 1 --name myapp")}`,
130
+ bugs: __optique_core_message.message`Report bugs to: <https://github.com/dahlia/optique/issues>.`
131
+ }
132
+ });
133
+ /**
134
+ * Gets the Node.js major and minor version numbers.
135
+ * @returns A tuple of [major, minor] or null if not running on Node.js.
136
+ */
137
+ function getNodeMajorMinor() {
138
+ if (typeof node_process.default === "undefined" || !node_process.default.versions?.node) return null;
139
+ const [major, minor] = node_process.default.versions.node.split(".").map(Number);
140
+ return [major, minor];
141
+ }
142
+ /**
143
+ * Checks if Node.js natively supports TypeScript via type stripping.
144
+ * Node.js 25.2.0+ has type stripping enabled by default.
145
+ * @returns true if native TypeScript is supported.
146
+ */
147
+ function nodeSupportsNativeTypeScript() {
148
+ const version$1 = getNodeMajorMinor();
149
+ if (version$1 == null) return false;
150
+ const [major, minor] = version$1;
151
+ return major > 25 || major === 25 && minor >= 2;
152
+ }
153
+ /**
154
+ * Error handler for file not found.
155
+ */
156
+ function fileNotFoundError(filePath) {
157
+ (0, __optique_run.printError)(__optique_core_message.message`File ${filePath} not found.
158
+
159
+ Make sure the file path is correct and the file exists.`, { exitCode: EXIT_FILE_NOT_FOUND });
160
+ }
161
+ /**
162
+ * Error handler for export not found.
163
+ */
164
+ function exportNotFoundError(filePath, exportName, availableExports) {
165
+ const exportDisplay = exportName === "default" ? "default export" : `export ${exportName}`;
166
+ let suggestion;
167
+ if (availableExports.length > 0) suggestion = __optique_core_message.message`
168
+
169
+ Available exports: ${availableExports.join(", ")}.
170
+ Use ${(0, __optique_core_message.optionName)("-e")} to specify one of these exports.`;
171
+ else suggestion = __optique_core_message.message`
172
+
173
+ The file has no exports. Make sure it exports a Program or Parser.`;
174
+ (0, __optique_run.printError)(__optique_core_message.message`No ${exportDisplay} found in ${filePath}.${suggestion}`, { exitCode: EXIT_EXPORT_NOT_FOUND });
175
+ }
176
+ /**
177
+ * Error handler for invalid export type.
178
+ */
179
+ function notProgramOrParserError(filePath, exportName, actualType) {
180
+ const exportDisplay = exportName === "default" ? "default export" : `export ${exportName}`;
181
+ (0, __optique_run.printError)(__optique_core_message.message`The ${exportDisplay} in ${filePath} is not a Program or Parser.
182
+
183
+ Got type: ${actualType}
184
+
185
+ The export should be created with ${(0, __optique_core_message.commandLine)("defineProgram()")} or be
186
+ an Optique parser (e.g., from ${(0, __optique_core_message.commandLine)("object()")},
187
+ ${(0, __optique_core_message.commandLine)("command()")}, etc.).`, { exitCode: EXIT_NOT_PROGRAM_OR_PARSER });
188
+ }
189
+ /**
190
+ * Error handler for missing tsx.
191
+ */
192
+ function tsxRequiredError(filePath) {
193
+ const version$1 = getNodeMajorMinor();
194
+ const versionStr = version$1 ? `${version$1[0]}.${version$1[1]}` : "unknown";
195
+ (0, __optique_run.printError)(__optique_core_message.message`TypeScript file ${filePath} cannot be loaded on Node.js ${versionStr}.
196
+
197
+ Install tsx as a dev dependency:
198
+
199
+ ${(0, __optique_core_message.commandLine)("npm install -D tsx")}
200
+
201
+ Or upgrade to Node.js 25.2.0 or later, which supports TypeScript natively.
202
+
203
+ Alternatively, use a pre-compiled JavaScript file instead.`, { exitCode: EXIT_TSX_REQUIRED });
204
+ }
205
+ /**
206
+ * Error handler for man page generation failure.
207
+ */
208
+ function generationError(error) {
209
+ (0, __optique_run.printError)(__optique_core_message.message`Failed to generate man page: ${error.message}`, { exitCode: EXIT_GENERATION_FAILED });
210
+ }
211
+ /**
212
+ * Error handler for file write failure.
213
+ */
214
+ function writeError(outputPath, error) {
215
+ (0, __optique_run.printError)(__optique_core_message.message`Failed to write to ${outputPath}: ${error.message}
216
+
217
+ Make sure you have write permission and the parent directory exists.`, { exitCode: EXIT_WRITE_FAILED });
218
+ }
219
+ /**
220
+ * Imports a module from the given file path.
221
+ * Handles TypeScript files on Node.js by using tsx if needed.
222
+ */
223
+ async function importModule(filePath) {
224
+ const absolutePath = (0, node_path.resolve)(filePath);
225
+ if (!(0, node_fs.existsSync)(absolutePath)) fileNotFoundError(filePath);
226
+ const isTypeScript = /\.[mc]?ts$/.test(filePath);
227
+ if (typeof globalThis.Deno !== "undefined") return await import(absolutePath);
228
+ if (typeof globalThis.Bun !== "undefined") return await import(absolutePath);
229
+ if (isTypeScript && !nodeSupportsNativeTypeScript()) try {
230
+ const tsx = await import("tsx/esm/api");
231
+ tsx.register();
232
+ } catch {
233
+ tsxRequiredError(filePath);
234
+ }
235
+ const fileUrl = (0, node_url.pathToFileURL)(absolutePath).href;
236
+ return await import(fileUrl);
237
+ }
238
+ /**
239
+ * Checks if a value is a Program object.
240
+ */
241
+ function isProgram(value) {
242
+ return value != null && typeof value === "object" && "parser" in value && "metadata" in value && typeof value.metadata === "object" && value.metadata != null;
243
+ }
244
+ /**
245
+ * Checks if a value is a Parser object.
246
+ */
247
+ function isParser(value) {
248
+ return value != null && typeof value === "object" && "parse" in value && typeof value.parse === "function" && "$mode" in value && "usage" in value;
249
+ }
250
+ /**
251
+ * Infers the program name from a file path.
252
+ */
253
+ function inferNameFromPath(filePath) {
254
+ const base = (0, node_path.basename)(filePath);
255
+ const ext = (0, node_path.extname)(base);
256
+ return base.slice(0, -ext.length);
257
+ }
258
+ /**
259
+ * Gets available exports from a module.
260
+ */
261
+ function getAvailableExports(mod) {
262
+ return Object.keys(mod).filter((key) => key !== "__esModule");
263
+ }
264
+ /**
265
+ * Describes the type of a value for error messages.
266
+ */
267
+ function describeType(value) {
268
+ if (value === null) return "null";
269
+ if (value === void 0) return "undefined";
270
+ if (Array.isArray(value)) return "array";
271
+ return typeof value;
272
+ }
273
+ /**
274
+ * Main CLI entry point.
275
+ */
276
+ async function main() {
277
+ const args = (0, __optique_run.runSync)(cliProgram, {
278
+ help: "option",
279
+ version: {
280
+ value: deno_default.version,
281
+ mode: "option"
282
+ }
283
+ });
284
+ const mod = await importModule(args.file);
285
+ const target = args.exportName === "default" ? mod.default : mod[args.exportName];
286
+ if (target === void 0) exportNotFoundError(args.file, args.exportName, getAvailableExports(mod));
287
+ if (!isProgram(target) && !isParser(target)) notProgramOrParserError(args.file, args.exportName, describeType(target));
288
+ const name$1 = args.name ?? (isProgram(target) ? target.metadata.name : null) ?? inferNameFromPath(args.file);
289
+ let manPage;
290
+ try {
291
+ if (isProgram(target)) manPage = await require_generator.generateManPageAsync(target, {
292
+ section: args.section,
293
+ name: args.name,
294
+ date: args.date,
295
+ version: args.versionString,
296
+ manual: args.manual
297
+ });
298
+ else manPage = await require_generator.generateManPageAsync(target, {
299
+ name: name$1,
300
+ section: args.section,
301
+ date: args.date,
302
+ version: args.versionString,
303
+ manual: args.manual
304
+ });
305
+ } catch (error) {
306
+ generationError(error instanceof Error ? error : new Error(String(error)));
307
+ }
308
+ if (args.output) try {
309
+ await (0, node_fs_promises.writeFile)(args.output, manPage, "utf-8");
310
+ } catch (error) {
311
+ writeError(args.output, error instanceof Error ? error : new Error(String(error)));
312
+ }
313
+ else console.log(manPage);
314
+ }
315
+ const isMain = "main" in import.meta ? void 0 : node_process.default.argv[1] === (0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href);
316
+ if (isMain) main();
317
+
318
+ //#endregion
319
+ exports.main = main;
package/dist/cli.d.cts ADDED
@@ -0,0 +1,7 @@
1
+ //#region src/cli.d.ts
2
+ /**
3
+ * Main CLI entry point.
4
+ */
5
+ declare function main(): Promise<void>;
6
+ //#endregion
7
+ export { main };
package/dist/cli.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ //#region src/cli.d.ts
2
+ /**
3
+ * Main CLI entry point.
4
+ */
5
+ declare function main(): Promise<void>;
6
+ //#endregion
7
+ export { main };