Skip to content

Commit 08bef37

Browse files
bartlomiejuclaude
andauthored
improve: redesign CLI reference flag rendering (#2999)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b4eec47 commit 08bef37

1 file changed

Lines changed: 87 additions & 28 deletions

File tree

_includes/renderCommand.tsx

Lines changed: 87 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,34 @@ function flagsToInlineCode(text: string): string {
3737
return text.replaceAll(FLAGS_RE, "`$&`");
3838
}
3939

40+
/**
41+
* Parse the usage string to extract the value type.
42+
* Examples:
43+
* "--config <FILE>" → "FILE"
44+
* "--fail-fast[=<N>]" → "N"
45+
* "--coverage[=<DIR>]" → "DIR"
46+
* "--no-check[=<NO_CHECK_TYPE>]" → "NO_CHECK_TYPE"
47+
* "--no-run" → null (boolean flag)
48+
*/
49+
function parseValueType(usage: string): string | null {
50+
const match = usage.match(/<([^>]+)>/);
51+
if (!match) return null;
52+
53+
const raw = match[1];
54+
// Clean up common patterns
55+
return raw
56+
.replace(/\.\.\.$/, "")
57+
.trim();
58+
}
59+
60+
/**
61+
* Determine if a flag is boolean (no value) or takes a value.
62+
* Optional values like `--flag[=<VAL>]` are marked as optional.
63+
*/
64+
function isOptionalValue(usage: string): boolean {
65+
return usage.includes("[=<") || usage.includes("[<");
66+
}
67+
4068
export default function renderCommand(
4169
commandName: string,
4270
helpers: Lume.Helpers,
@@ -47,7 +75,7 @@ export default function renderCommand(
4775

4876
const toc: TableOfContentsItem_[] = [];
4977

50-
// Add null check for command.about
78+
// Process the about text (description from CLI help)
5179
let about = "";
5280
if (command.about) {
5381
about = command.about.replaceAll(
@@ -102,7 +130,7 @@ export default function renderCommand(
102130
);
103131
}
104132

105-
const args = [];
133+
// Separate args into positional args and grouped options
106134
const options: Record<string, ArgType[]> = {};
107135

108136
for (const arg of command.args) {
@@ -113,21 +141,35 @@ export default function renderCommand(
113141
if (arg.long) {
114142
const key = arg.help_heading ?? "Options";
115143
options[key] ??= [];
116-
options[key].push(arg);
117-
} else {
118-
args.push(arg);
144+
options[key].push(arg as ArgType);
119145
}
120146
}
121147

122148
const rendered = (
123149
<div>
150+
<div class="bg-transparent mt-4 mb-12 relative pl-2 border-l border-background-tertiary">
151+
<div class="text-xs font-bold mb-1">
152+
Command line usage:
153+
</div>
154+
<div>
155+
<pre class="!mb-0 !p-6">
156+
<code>
157+
{command.usage.replaceAll(ANSI_RE, "").slice("usage: ".length)}
158+
</code>
159+
</pre>
160+
</div>
161+
</div>
162+
163+
{about && (
164+
<div
165+
class="flex flex-col gap-4"
166+
dangerouslySetInnerHTML={{ __html: helpers.md(about) }}
167+
/>
168+
)}
169+
124170
{Object.entries(options).map(([heading, flags]) => {
125171
const id = heading.toLowerCase().replace(/\s/g, "-");
126172

127-
const renderedFlags = flags.toSorted((a: ArgType, b: ArgType) =>
128-
a.long.localeCompare(b.long)
129-
).map((flag: ArgType) => renderOption(id, flag, helpers));
130-
131173
toc.push({
132174
text: heading,
133175
slug: id,
@@ -139,7 +181,13 @@ export default function renderCommand(
139181
<h2 id={id}>
140182
{heading} <HeaderAnchor id={id} />
141183
</h2>
142-
{renderedFlags}
184+
<div class="flex flex-col gap-8 mt-4">
185+
{flags
186+
.toSorted((a: ArgType, b: ArgType) =>
187+
a.long.localeCompare(b.long)
188+
)
189+
.map((flag: ArgType) => renderOption(id, flag, helpers))}
190+
</div>
143191
</>
144192
);
145193
})}
@@ -154,9 +202,11 @@ export default function renderCommand(
154202

155203
function renderOption(group: string, arg: ArgType, helpers: Lume.Helpers) {
156204
const id = `${group}-${arg.long}`;
205+
const valueType = parseValueType(arg.usage);
206+
const optional = isOptionalValue(arg.usage);
157207

158-
let docsLink = null;
159-
// Add null check for arg.help
208+
// Extract docs link from help text if present
209+
let docsLink: string | null = null;
160210
let help = arg.help ? arg.help.replaceAll(ANSI_RE, "") : "";
161211
if (help) {
162212
const helpLines = help.split("\n");
@@ -171,24 +221,33 @@ function renderOption(group: string, arg: ArgType, helpers: Lume.Helpers) {
171221
}
172222
}
173223

224+
// Build the flag display: --long, -short
225+
const longFlag = "--" + arg.long;
226+
const flagDisplay = arg.short ? `${longFlag}, -${arg.short}` : longFlag;
227+
174228
return (
175-
<>
176-
<h3 id={id}>
177-
<code>
178-
{docsLink
179-
? <a href={docsLink}>{"--" + arg.long}</a>
180-
: ("--" + arg.long)}
181-
</code>{" "}
229+
<div id={id}>
230+
<div class="flex items-baseline justify-between gap-4 flex-wrap">
231+
<div class="flex items-baseline gap-2">
232+
<code class="text-sm font-semibold">
233+
{docsLink ? <a href={docsLink}>{flagDisplay}</a> : flagDisplay}
234+
</code>
235+
{valueType && (
236+
<span class="text-xs text-foreground-secondary">
237+
{"<"}
238+
{valueType}
239+
{">"}
240+
{optional && (
241+
<span class="text-foreground-tertiary ml-1">optional</span>
242+
)}
243+
</span>
244+
)}
245+
</div>
182246
<HeaderAnchor id={id} />
183-
</h3>
184-
{arg.short && (
185-
<p class="text-sm">
186-
Short flag: <code>-{arg.short}</code>
187-
</p>
188-
)}
189-
{arg.help && (
247+
</div>
248+
{help && (
190249
<div
191-
class="block !whitespace-pre-line"
250+
class="mt-2 text-sm !whitespace-pre-line"
192251
dangerouslySetInnerHTML={{
193252
__html: helpers.md(
194253
flagsToInlineCode(help) +
@@ -197,6 +256,6 @@ function renderOption(group: string, arg: ArgType, helpers: Lume.Helpers) {
197256
}}
198257
/>
199258
)}
200-
</>
259+
</div>
201260
);
202261
}

0 commit comments

Comments
 (0)