feat: termynal output mode for the directive - #1
Conversation
Render a Typer/Click app's --help as an animated, colored termynal block instead of Markdown tables, via the :termynal: (and :width:) directive options or the global plugin config. The app is introspected in-process: each command's --help is captured with rich's terminal output forced, hidden commands skipped, ANSI converted to inline HTML with ansi2html, and wrapped in termynal's data-ty markup. No subprocess, deterministic, and it runs under both mkdocs and zensical since it is a markdown extension. termynal and ansi2html are an optional extra (mkdocs-typer2[termynal]); using :termynal: without it raises a clear install hint. The markup is emitted directly rather than importing termynal's renderer, and a contract test guards it against drift. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Expose the ANSI color palette and dark/light variant for termynal mode, as per-block directive options (:scheme:, :dark_bg:) and global plugin config, mirroring :width:. scheme accepts ansi2html's eight palettes (ansi2html, dracula, mint-terminal, osx, osx-basic, osx-solid-colors, solarized, xterm) and falls back to xterm on an invalid value. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The drift-guard test called escape() as a method on the Termynal instance, which raises AttributeError on termynal>=0.12 where escape is a module-level function. All 7 parametrized cases failed, so the guard never actually ran. Import and use the module-level escape instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
termynal styles [data-termynal] with padding but no margin, and exposes no wrapper class or inter-block spacing hook. Normal termynal usage doesn't notice because each terminal is its own Markdown block; we emit several <div data-termynal> glued together in one HTML-stash blob, so the root command and per-subcommand blocks stacked flush. Add margin-top only to blocks after the first, so stacked blocks are separated without imposing a margin on the boundary between the first/last block and surrounding page content (that stays the theme's concern). A lone block gets no inline style at all. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_resolve_app in termynal_render duplicated the import + getattr-fallback + _resolve_click_command dance already in build_tree_from_click_app. Extract it as pretty.resolve_click_command and call it from both, so the two output modes can't drift in how they locate the app. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The top-level width/scheme/dark_bg plugin options were generic and only meaningful in termynal mode. Rename to termynal_width/termynal_scheme/ termynal_dark_bg so the global config reads unambiguously and the names won't collide with future options. Block-level :width:/:scheme:/:dark_bg: stay as-is since they're already scoped under the directive. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a tracked CLI (Termynal) page alongside the other mode pages, wire it into the nav, and enable the termynal MkDocs plugin so its CSS/JS animate the generated blocks. Builds clean under mkdocs --strict. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collapse the width/scheme/dark_bg kwargs (previously re-listed across TyperExtension and TyperProcessor and threaded individually into the renderer) into a single TermynalOptions dataclass, defined in termynal_render.py alongside the option domains. Option values are now Literal-typed (AnsiScheme, ButtonStyle) with their tuples derived via get_args, and the data-ty-* attribute names live in module-level globals as the single source of truth. The directive parser is DRY'd behind _directive_value / _as_bool / _as_int helpers. No behavior change for the existing width/scheme/dark_bg options. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Expose buttons (macos|windows), prompt, and the type/line/start delay timings as both directive (:buttons:, :prompt:, :type_delay:, ...) and plugin (termynal_buttons, ...) options, mapped to the matching data-ty-* attributes. buttons falls back to macos on invalid input; timings emit attributes only when set, otherwise termynal's own defaults apply. Drift guards now assert our button attributes appear in termynal's CSS and our timing attributes in termynal.js, so a future termynal rename fails loudly against the BUTTONS / TIMING_ATTRS source of truth. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Warning Review limit reached
More reviews will be available in 55 minutes and 21 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. 📝 WalkthroughWalkthroughAdds a ChangesTermynal Output Mode
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/mkdocs_typer2/pretty.py`:
- Around line 46-49: The code in the section starting with the first getattr
call for `name` currently falls back to module-level `app` even when an explicit
`name` was provided by the user. This masks configuration errors. Modify the
logic to only use the module-level `app` fallback when `name` is empty or None;
if `name` was explicitly provided but the attribute cannot be resolved, raise an
error instead of silently falling back.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 53d9c9f7-b688-4faa-8420-7be3a2f2a790
📒 Files selected for processing (10)
README.mddocs/cli-termynal.mdmkdocs.yamlpyproject.tomlsrc/mkdocs_typer2/markdown.pysrc/mkdocs_typer2/plugin.pysrc/mkdocs_typer2/pretty.pysrc/mkdocs_typer2/termynal_render.pytests/test_termynal_contract.pytests/test_termynal_render.py
| app = getattr(module_ref, name, None) if name else None | ||
| if app is None: | ||
| app = getattr(module_ref, "app", None) | ||
| if app is None: |
There was a problem hiding this comment.
Fail fast when explicit name cannot be resolved.
Line 46–49 currently falls back to module-level app even when name was explicitly provided. That masks typos/misconfiguration and can render docs for the wrong command. Only use fallback when name is empty; otherwise raise.
Suggested fix
def resolve_click_command(module: str, name: str) -> click.core.Command:
@@
- app = getattr(module_ref, name, None) if name else None
- if app is None:
- app = getattr(module_ref, "app", None)
+ if name:
+ app = getattr(module_ref, name, None)
+ if app is None:
+ raise ValueError(
+ f"Unable to resolve Typer app '{name}' from module '{module}'."
+ )
+ else:
+ app = getattr(module_ref, "app", None)
if app is None:
raise ValueError(f"Unable to resolve Typer app from module '{module}'.")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| app = getattr(module_ref, name, None) if name else None | |
| if app is None: | |
| app = getattr(module_ref, "app", None) | |
| if app is None: | |
| if name: | |
| app = getattr(module_ref, name, None) | |
| if app is None: | |
| raise ValueError( | |
| f"Unable to resolve Typer app '{name}' from module '{module}'." | |
| ) | |
| else: | |
| app = getattr(module_ref, "app", None) | |
| if app is None: | |
| raise ValueError(f"Unable to resolve Typer app from module '{module}'.") |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/mkdocs_typer2/pretty.py` around lines 46 - 49, The code in the section
starting with the first getattr call for `name` currently falls back to
module-level `app` even when an explicit `name` was provided by the user. This
masks configuration errors. Modify the logic to only use the module-level `app`
fallback when `name` is empty or None; if `name` was explicitly provided but the
attribute cannot be resolved, raise an error instead of silently falling back.
|
Superseded by the upstream PR syn54x#35 (same branch). Closing this fork-internal one. |
Summary
Adds a
termynaloutput mode that renders a CLI's--helpas an animated, colored termynal terminal instead of Markdown tables. The app is introspected in-process (no subprocess): each command's--helpis rendered through rich so Typer apps come out colored, plain Click apps render monochrome, hidden commands are skipped, and the ANSI is converted to inline HTML viaansi2htmland wrapped in termynal'sdata-tymarkup. The root command renders first, followed by one block per non-hidden direct subcommand.Enabled globally (
termynal: true) or per block (:termynal: true). Lives behind an optional[termynal]extra (ansi2html,termynal); using it without the extra raises a clear install hint, and the rest of the plugin keeps no termynal dependency.Options
Each is available per-block (
:option:) and globally (termynal_-prefixed); block-level wins.widthschemextermdark_bgbuttonsmacos|windows; invalid →macosprompt$)type_delay/line_delay/start_delayOptions are bundled in a
TermynalOptionsdataclass withLiteral-typed domains (AnsiScheme,ButtonStyle); thedata-ty-*attribute names and option domains live as module-level constants intermynal_render.pyas the single source of truth.Design notes
[data-termynal]blocks (they only drive its fenced-code preprocessor, which we bypass). Anything tunable is emitted as an explicitdata-ty-*attribute by us.Termynal.convert, becauseconvertescapes its output — which would destroy the color spans — and does prompt-parsing over the whole blob. Per-line control is required.[data-termynal]with padding but no margin); amargin-topis applied only between stacked blocks, so a lone block imposes no margin on surrounding page content.Tests
htmlStashinjection through the Markdown pipeline.Docs: a tracked
CLI (Termynal)page is added to the site nav, and the README documents the mode and all options.Breaking change
None for end users yet (feature is new). Note for the changelog: the termynal plugin options are namespaced
termynal_width/termynal_scheme/termynal_dark_bg/ etc.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests
Chores