Skip to content

Commit 687871e

Browse files
authored
fix: render Click \b verbatim blocks in CLI reference docs (#866) (#867)
* fix: render Click \b verbatim blocks in CLI reference docs (#866) The docs generator was silently dropping \b-delimited sections (Modes, Best practices, Detection notes, Rewrites) from m fix async and m fix genslots. These blocks were buried inside the Raises section by the docstring parser and never rendered, causing a fidelity gap with --help output. Adds _extract_verbatim_blocks() to split on \x08 directly from the raw help text, independent of the section parser. Three new tests verify the extraction logic and that the affected content now appears in the generated reference page. * fix: render bullet \b blocks as markdown lists not code fences Best practices, Detection notes and Rewrites sections in \b blocks are bullet lists and should render as markdown, not monospace code. Modes retains a code fence as it uses columnar alignment. Continuation lines wrapped across multiple source lines are joined back into their parent bullet. * fix: render two-column \b blocks as markdown tables Click-style two-column aligned blocks (e.g. Modes in m fix async) were rendered as code fences. The generator now detects the name + 2-space + description pattern and emits a markdown table, preserving the original terminal-aligned docstring format while rendering cleanly in HTML docs. * fix: render two-column blocks as bold-name bullets not tables Table cells render in a smaller font than body text in Mintlify, creating inconsistency with bullet-list blocks. Render two-column \b blocks as `- **\`name\`** — description` bullets instead: consistent font, no invented column headers.
1 parent 77ff434 commit 687871e

2 files changed

Lines changed: 180 additions & 0 deletions

File tree

tooling/docs-autogen/generate_cli_reference.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,79 @@ def _get_click_app():
149149
return typer.main.get_command(cli)
150150

151151

152+
def _parse_two_column_block(content: str) -> list[tuple[str, str]] | None:
153+
"""Parse a Click-style two-column aligned block into (name, description) pairs.
154+
155+
Click authors sometimes format ``\\b`` blocks as a fixed-width two-column
156+
table (name left-aligned, description right-aligned with consistent padding)
157+
for legible ``--help`` output. This function detects that format and returns
158+
``(name, description)`` pairs so the generator can emit a proper markdown
159+
table instead of a raw code fence.
160+
161+
Returns ``None`` if the content does not match the two-column pattern.
162+
163+
Example input::
164+
165+
add-await-result Adds await_result=True to each call.
166+
Continuation of the description.
167+
add-stream-loop Inserts a while loop after each call.
168+
"""
169+
pairs: list[tuple[str, str]] = []
170+
current_name: str | None = None
171+
current_desc_parts: list[str] = []
172+
desc_col: int | None = None
173+
174+
for raw in content.splitlines():
175+
if not raw.strip():
176+
continue
177+
# Detect a new entry: leading whitespace, a name (no spaces), then 2+
178+
# spaces, then description text — all starting before the desc column.
179+
m = re.match(r"^(\s+)(\S+)(\s{2,})(\S.*)", raw)
180+
if m and (desc_col is None or m.start(4) == desc_col):
181+
if current_name is not None:
182+
pairs.append((current_name, " ".join(current_desc_parts)))
183+
desc_col = m.start(4)
184+
current_name = m.group(2)
185+
current_desc_parts = [m.group(4)]
186+
continue
187+
# Continuation line: non-space content starts at or near desc_col
188+
if desc_col is not None and current_name is not None:
189+
leading = len(raw) - len(raw.lstrip())
190+
if leading >= desc_col - 1:
191+
current_desc_parts.append(raw.strip())
192+
continue
193+
# Does not fit either pattern — not a two-column block
194+
return None
195+
196+
if current_name is not None:
197+
pairs.append((current_name, " ".join(current_desc_parts)))
198+
199+
return pairs if pairs else None
200+
201+
202+
def _extract_verbatim_blocks(help_text: str) -> list[str]:
203+
"""Extract Click ``\\b`` verbatim blocks from help text.
204+
205+
Click uses the backspace character (``\\x08``, written as ``\\b`` in Python
206+
source string literals) as a marker to prevent paragraph rewrapping in
207+
``--help`` output. The generator must extract these blocks independently
208+
because the section parser buries them inside whatever named section
209+
(e.g. ``Raises``) happens to precede them, and that section is never
210+
rendered.
211+
212+
Returns a list of stripped block strings (first line is typically the
213+
block title, e.g. ``"Modes:"``, followed by indented content lines).
214+
"""
215+
# In memory the docstring contains actual \x08 chars; split on them.
216+
parts = re.split(r"\x08\s*\n", help_text)
217+
blocks: list[str] = []
218+
for part in parts[1:]: # parts[0] is content before the first \b
219+
block = part.rstrip()
220+
if block.strip():
221+
blocks.append(block)
222+
return blocks
223+
224+
152225
def _format_default(value: Any) -> str:
153226
"""Format a parameter default for display."""
154227
if value is None:
@@ -301,6 +374,57 @@ def _render_command(
301374
lines.append(f"| {flags} | {ptype} | {default} | {help_text} |")
302375
lines.append("")
303376

377+
# \b verbatim blocks — Click-style preformatted sections (e.g. "Modes:",
378+
# "Best practices:") that appear in --help but are buried inside the
379+
# Raises/Args sections of the parsed docstring and would otherwise be lost.
380+
if cmd.help:
381+
vb_blocks = _extract_verbatim_blocks(cmd.help)
382+
for block in vb_blocks:
383+
first_line, _, rest = block.partition("\n")
384+
title = first_line.strip()
385+
if title:
386+
lines.append(f"**{title}**")
387+
lines.append("")
388+
if rest.strip():
389+
# Bullet lists render as markdown; columnar/aligned content
390+
# (e.g. mode tables) keeps a code fence for monospace alignment.
391+
first_content = next(
392+
(line.strip() for line in rest.splitlines() if line.strip()), ""
393+
)
394+
if first_content.startswith("- "):
395+
# Bullet list — join wrapped continuation lines into bullets
396+
bullets: list[str] = []
397+
current: str | None = None
398+
for raw in rest.splitlines():
399+
s = raw.strip()
400+
if not s:
401+
if current is not None:
402+
bullets.append(current)
403+
current = None
404+
elif s.startswith("- "):
405+
if current is not None:
406+
bullets.append(current)
407+
current = s
408+
else:
409+
current = (current + " " + s) if current is not None else s
410+
if current is not None:
411+
bullets.append(current)
412+
for b in bullets:
413+
lines.append(b)
414+
else:
415+
# Try two-column aligned format → markdown table
416+
pairs = _parse_two_column_block(rest)
417+
if pairs:
418+
# Render as bold-name bullets — consistent font with
419+
# other bullet blocks, no invented column headers.
420+
for name, desc in pairs:
421+
lines.append(f"- **`{name}`** — {desc}")
422+
else:
423+
lines.append("```")
424+
lines.append(rest.rstrip())
425+
lines.append("```")
426+
lines.append("")
427+
304428
# Output
305429
output = _rst_to_md(sections.get("Output", ""))
306430
if output:

tooling/docs-autogen/test_cli_reference.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,59 @@ def test_no_mdx_or_framework_specific_syntax(generated_md):
191191
assert "<Callout" not in generated_md
192192
assert "<Tab" not in generated_md
193193
assert "import " not in generated_md.split("---")[-1] # After frontmatter
194+
195+
196+
def test_verbatim_blocks_rendered(generated_md):
197+
"""Click \\b verbatim blocks must appear in the generated docs.
198+
199+
``m fix async`` and ``m fix genslots`` contain \\b-delimited sections
200+
(Modes, Best practices, Detection notes, Rewrites) that are visible in
201+
``--help`` output. These were previously silently dropped because they
202+
appear after ``Raises:`` in the docstring, which the generator never
203+
renders. They should now appear correctly formatted.
204+
"""
205+
assert "**Modes:**" in generated_md, "fix async Modes block missing"
206+
assert "add-await-result" in generated_md, "fix async mode value missing"
207+
assert "**Best practices:**" in generated_md, "Best practices block missing"
208+
assert "**Detection notes:**" in generated_md, "Detection notes block missing"
209+
assert "**Rewrites:**" in generated_md, "fix genslots Rewrites block missing"
210+
assert "GenerativeStub" in generated_md, "fix genslots rewrite target missing"
211+
212+
213+
def test_bullet_blocks_not_in_code_fence(generated_md):
214+
"""Bullet-list \\b blocks must render as markdown lists, not code fences.
215+
216+
Best practices / Detection notes / Rewrites start with ``- `` items and
217+
should be plain markdown bullets so they render properly in the browser,
218+
not as monospace preformatted blocks.
219+
"""
220+
# Find the Best practices section and verify the bullet follows as plain text
221+
idx = generated_md.index("**Best practices:**")
222+
# Next non-empty line after the heading should be a markdown bullet, not ```
223+
after = generated_md[idx:].split("\n")
224+
content_lines = [ln for ln in after[1:] if ln.strip()]
225+
assert content_lines[0].startswith("- "), (
226+
f"Expected markdown bullet after Best practices, got: {content_lines[0]!r}"
227+
)
228+
assert content_lines[0] != "```", "Best practices content is inside a code fence"
229+
230+
231+
def test_extract_verbatim_blocks_basic():
232+
"""_extract_verbatim_blocks should split on \\x08 and return each block."""
233+
from generate_cli_reference import _extract_verbatim_blocks
234+
235+
text = "Summary.\n\nRaises:\n SomeError: ...\n\x08\nModes:\n foo bar\n\x08\nBest practices:\n - do X\n"
236+
blocks = _extract_verbatim_blocks(text)
237+
assert len(blocks) == 2
238+
assert blocks[0].startswith("Modes:")
239+
assert "foo bar" in blocks[0]
240+
assert blocks[1].startswith("Best practices:")
241+
assert "do X" in blocks[1]
242+
243+
244+
def test_extract_verbatim_blocks_empty():
245+
"""No \\b chars means no verbatim blocks."""
246+
from generate_cli_reference import _extract_verbatim_blocks
247+
248+
assert _extract_verbatim_blocks("plain text with no backspace") == []
249+
assert _extract_verbatim_blocks("") == []

0 commit comments

Comments
 (0)