Skip to content

Conversation

@tony
Copy link
Member

@tony tony commented Jan 18, 2026

Summary

Replace external sphinx-argparse dependency with custom sphinx_argparse_neo extensions, ported from vcspull project. Adds comprehensive documentation infrastructure for CLI commands.

Changes

New Extensions

  • docs/_ext/sphinx_argparse_neo/ - Complete argparse documentation engine
  • docs/_ext/argparse_exemplar.py - Transforms epilog examples into TOC sections
  • docs/_ext/argparse_lexer.py - Pygments lexers for CLI syntax highlighting
  • docs/_ext/argparse_roles.py - RST roles for inline CLI formatting
  • docs/_static/css/argparse-highlight.css - Styling for usage blocks and code

Bug Fixes

  • Use slice assignment for docutils node children manipulation (fixes parent tracking)
  • Add type annotation to fix mypy no-any-return error
  • Escape asterisks in glob patterns to prevent RST emphasis warnings
  • Add ID prefix to avoid duplicate section IDs across subcommand pages

Documentation

  • Restructure CLI command pages to match vcspull pattern
  • Add doctest exceptions to AGENTS.md for Sphinx visitor patterns
  • Add doctests to _create_example_section() and _extract_mutex_groups()

Removed

  • pretty_argparse.py (replaced by new extensions)
  • sphinx-argparse dependency

Test plan

  • uv run ruff check . --fix --show-fixes passes
  • uv run ruff format . passes
  • uv run mypy passes
  • uv run py.test passes (997 tests, 313 for docs/_ext)
  • cd docs && sphinx-build -b html . _build/html builds successfully

@codecov
Copy link

codecov bot commented Jan 18, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.11%. Comparing base (917dcd5) to head (2d80f70).
⚠️ Report is 15 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1009   +/-   ##
=======================================
  Coverage   80.11%   80.11%           
=======================================
  Files          28       28           
  Lines        2409     2409           
  Branches      457      457           
=======================================
  Hits         1930     1930           
  Misses        356      356           
  Partials      123      123           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tony tony marked this pull request as ready for review January 18, 2026 19:14
@tony
Copy link
Member Author

tony commented Jan 18, 2026

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

tony added 3 commits January 24, 2026 07:02
why: Custom extensions provide better control over CLI documentation
rendering and eliminate external dependency on sphinx-argparse.

what:
- Add docs/_ext/sphinx_argparse_neo/ module for argparse rendering
- Add docs/_ext/argparse_exemplar.py extension (replaces sphinxarg.ext)
- Add syntax highlighting lexers and roles for CLI documentation
- Replace pretty_argparse.py with argparse_exemplar.py
- Keep aafig.py extension (tmuxp-specific ASCII art rendering)
- Add comprehensive test suite for all Sphinx extensions
- Remove sphinx-argparse from dependencies
- Update docs/conf.py to use argparse_exemplar extension
- Update mypy overrides for new extension modules
Add argparse-highlight.css with "One Dark" color palette to style
the CLI usage lines with semantic colorization:
- Blue: "usage:" heading
- Purple: program name
- Green: subcommands
- Teal: options (-h, --tree)
- Yellow: metavars

Also strip ANSI codes and disable colors during doc builds to ensure
clean parsing by the argparse lexer.
Add intro paragraphs, wrap argparse directives in `## Command` sections,
and rename generic `## Usage` to descriptive section names for consistent
documentation structure across projects.
@tony tony force-pushed the cli-and-docs-our-argparse branch from ca2f0e8 to 5a79fc6 Compare January 24, 2026 13:02
The sphinx_argparse_neo module is in mypy's ignore_missing_imports,
causing strip_ansi's return type to be treated as Any. Adding explicit
str annotation ensures mypy knows the variable type throughout the
function.
@tony
Copy link
Member Author

tony commented Jan 24, 2026

Code review

Found 1 issue:

  1. Missing required doctests in sphinx_argparse_neo functions. CLAUDE.md states "All functions and methods MUST have working doctests. Doctests serve as both documentation and tests." Multiple functions in the new extension have complete docstrings with Parameters/Returns sections but lack the required Examples section with working doctests.

Affected files include:

  • docs/_ext/sphinx_argparse_neo/nodes.py - 12 HTML visitor/depart functions (lines 310-553)
  • docs/_ext/sphinx_argparse_neo/renderer.py - create_renderer() function (lines 499-521)
  • docs/_ext/sphinx_argparse_neo/parser.py - _extract_mutex_groups() function

Example of a function missing doctests:

def visit_argparse_program_html(self: HTML5Translator, node: argparse_program) -> None:
"""Visit argparse_program node - start program container.
Parameters
----------
self : HTML5Translator
The Sphinx HTML translator.
node : argparse_program
The program node being visited.
"""
prog = node.get("prog", "")
self.body.append(f'<div class="argparse-program" data-prog="{prog}">\n')

def create_renderer(
config: RenderConfig | None = None,
state: RSTState | None = None,
renderer_class: type[ArgparseRenderer] | None = None,
) -> ArgparseRenderer:
"""Create a renderer instance.
Parameters
----------
config : RenderConfig | None
Rendering configuration.
state : RSTState | None
RST state for parsing.
renderer_class : type[ArgparseRenderer] | None
Custom renderer class to use.
Returns
-------
ArgparseRenderer
Configured renderer instance.
"""
cls = renderer_class or ArgparseRenderer
return cls(config=config, state=state)

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

tony added 7 commits January 24, 2026 10:00
Direct assignment to node.children bypasses docutils' setup_child()
mechanism which maintains parent-child relationships, document
references, and source tracking. Replace with clear() + extend() pattern
to properly invoke the docutils protocol.
Glob patterns like "django-*" in argparse help text trigger RST
"Inline emphasis start-string without end-string" warnings. Add
escape_rst_emphasis() utility that escapes problematic asterisks
(e.g., "django-*" → "django-\*") and apply it in the renderer's
_parse_text() method.
Document sphinx_argparse_neo migration and related fixes for v1.64.0.
Replace .clear() + .extend() with slice assignment (node[:] = children)
to fix mypy errors about Sequence[Node] having no .clear() method and
Node having no .extend() method. This is the idiomatic pattern used in
Sphinx's codebase for modifying node children.
Document when doctests are NOT required:
- Sphinx/docutils visit_*/depart_* methods (tested via integration)
- Sphinx setup() functions (entry points not testable in isolation)
- Complex recursive traversal functions (extract helpers instead)

This clarifies the doctest policy for docs/_ext/ Sphinx extensions
where visitor patterns don't have doctests across docutils, Sphinx,
or CPython's ast.NodeVisitor.
Add working doctests demonstrating:
- Basic section creation from definition nodes
- Page prefix for unique section IDs across docs
- Category-prefixed examples with descriptive IDs/titles

This helper function creates docutils section nodes and is
straightforward to test, unlike complex visitor traversals.
Add working doctests demonstrating:
- Extracting mutex groups from ArgumentParser
- Verifying actions map to same MutuallyExclusiveGroup instance
- Empty mapping for parsers without mutex groups

Uses identity checks (is) instead of set() since dataclasses
aren't hashable by default.
@tony
Copy link
Member Author

tony commented Jan 24, 2026

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

tony added 2 commits January 24, 2026 16:02
Add consistent styling to pre.argparse-usage to match code blocks:
- background: #272822 (Monokai, same as .highlight)
- padding, line-height, border-radius
- thin scrollbars using Furo CSS variable
Extract subcommand from parser_info.prog to generate unique section IDs:
- "tmuxp load" -> prefix "load" -> IDs like "load-usage", "load-options"
- "tmuxp" -> no prefix -> IDs like "usage", "options" (backwards compatible)

This prevents duplicate ID warnings when multiple argparse directives
exist on the same documentation page (e.g., documenting subcommands).

Changes:
- Add _extract_id_prefix() static method
- Add id_prefix parameter to render_usage_section()
- Add id_prefix parameter to render_group_section()
- Pass prefix from render() to section methods
@tony tony changed the title docs: Replace sphinx-argparse with sphinx_argparse_neo docs(cli): Custom argparse documentation engine with syntax highlighting Jan 24, 2026
@tony tony changed the title docs(cli): Custom argparse documentation engine with syntax highlighting docs(cli) Custom argparse documentation engine with syntax highlighting Jan 24, 2026
@tony tony merged commit ea07c7f into master Jan 24, 2026
14 checks passed
@tony tony deleted the cli-and-docs-our-argparse branch January 24, 2026 22:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants