7.7 KiB
AGENTS.md
Guidelines for agentic coding agents working in this repository.
Project Overview
rp is a Python CLI tool for rich-printing files (markdown, JSON, code, etc.) in the terminal. It uses rich for rendering and typer for the CLI interface.
- Language: Python 3.12+
- Package Manager: uv
- Entry Point:
rp→rp.cli:app - Source Location:
src/rp/
Build/Lint/Test Commands
Setup
uv sync # Install dependencies
Running the CLI
uv run rp <file> # Rich-print a file
uv run rp --help # Show help
uv run rp --version # Show version
echo '{"a":1}' | uv run rp # Read from stdin
cat patch.txt | uv run rp --guess-content # Enable heuristic content guessing
Testing
Run tests with pytest:
# Run all tests
uv run pytest
# Run a single test file
uv run pytest tests/test_detect.py
# Run a single test class
uv run pytest tests/test_detect.py::TestExplicitType
# Run a single test function
uv run pytest tests/test_detect.py::TestExplicitType::test_explicit_overrides_everything
# Run with verbose output
uv run pytest -v
Linting & Type Checking (recommended)
uv run ruff check src/ # Lint
uv run ruff format src/ # Format
uv run mypy src/ # Type check
Code Style Guidelines
Imports
Order imports using standard library → third-party → local grouping:
from __future__ import annotations
import sys
from pathlib import Path
from typing import Annotated
import typer
from rich.console import Console
from rp import __version__
from rp.detect import detect_type
- Always use
from __future__ import annotationsat the top for modern type hint syntax - Use explicit imports (no
from module import *) - Import
Pathfrompathlib, notos.path
Formatting
- Line length: 88 characters (ruff default)
- Quotes: Prefer double quotes for strings, single quotes inside f-strings if needed
- Trailing commas: Use in multi-line collections and function arguments
Type Hints
- Use modern union syntax:
str | Noneinstead ofOptional[str] - Use
Annotated[Type, typer.Option(...)]for CLI parameters - Always annotate function parameters and return types
- Use
dataclassfor simple data containers
def detect_type(
path: str | None = None,
content: str | None = None,
explicit_type: str | None = None,
guess_content: bool = False,
) -> str:
...
Naming Conventions
- Modules: lowercase with underscores (
detect.py,render.py) - Classes: PascalCase (
RenderOptions) - Functions/Variables: snake_case (
detect_type,file_path) - Constants: UPPER_SNAKE_CASE (
EXTENSION_MAP,_FILENAME_MAP) - Private module-level: prefix with underscore (
_MARKDOWN_TYPES) - CLI parameter names: Avoid Python builtins (use
file_typenottype)
Error Handling
- Use
rich.Consolefor user-facing error messages with[red]Error:[/red]prefix - Exit with
typer.Exit(code=1)for errors,typer.Exit()for success - Catch specific exceptions, not bare
except:
try:
content = file.read_text(encoding="utf-8")
except UnicodeDecodeError:
console.print(f"[red]Error:[/red] Cannot read binary file: {file}")
raise typer.Exit(code=1)
except (PermissionError, OSError) as e:
console.print(f"[red]Error:[/red] {e}")
raise typer.Exit(code=1)
- For fallback behavior (e.g., unknown lexer), catch broadly but document why:
try:
renderable = Syntax(content, file_type, ...)
except Exception:
# Unknown lexer — fall back to plain text
renderable = Syntax(content, "text", ...)
CLI Patterns
- Use
typer.Option()with--flag/--no-flagfor boolean toggles - Use
Annotated[Type, typer.Option(...)]for all options - Set
no_args_is_help=Falsewhen stdin input is valid - Use callbacks for
--versionwithis_eager=True
Module Structure
Each module should have:
- Module docstring (one line)
from __future__ import annotations- Standard library imports
- Third-party imports
- Local imports
- Constants (module-level)
- Classes
- Functions
File Type Detection
When adding new file types:
- Add extension to
EXTENSION_MAPindetect.py - Use Pygments lexer name as the value
- For special filenames (e.g.,
Dockerfile), add to_FILENAME_MAP
Detection stays conservative by default: explicit type, filename map, extension map,
shebang, and JSON detection all run before falling back to text. Heuristic
Pygments content guessing is opt-in via --guess-content and should remain a
late fallback.
Rendering
markdownandmd→ userich.markdown.Markdownjson→ userich.json.JSONwith fallback toSyntax- All other types → use
rich.syntax.Syntax
Pager Behavior
The pager prefers less -R --mouse when available (version 543+) for mouse scrolling. Falls back to Rich's SystemPager otherwise.
Version Sync
Version metadata must stay in sync between pyproject.toml and src/rp/__init__.py.
Project Structure
rich-print/
├── pyproject.toml # Project config, dependencies, entry point
├── uv.lock # Lockfile (commit this)
├── .gitignore
├── README.md
├── AGENTS.md # This file
├── src/
│ └── rp/
│ ├── __init__.py # Version metadata
│ ├── cli.py # Typer CLI entry point
│ ├── detect.py # File type detection
│ ├── render.py # Rendering logic
│ ├── pager.py # Pager integration
│ └── py.typed # PEP 561 marker
└── tests/
├── conftest.py
├── test_cli.py
├── test_detect.py
├── test_render.py
└── test_pager.py
Dependencies
rich>=13.0— Terminal rendering (Markdown, JSON, Syntax)pygments>=2.13.0— Lexer detection and syntax lexerstyper>=0.12— CLI framework
Runtime dependencies. Dev dependencies: ruff>=0.9, mypy>=1.14, pytest>=8.0, pytest-cov>=5.0, types-pygments>=2.19.
Git Workflow
Branches
- Long-lived branches
main— stable release branch; only updated via merges fromdevdev— integration branch for day-to-day development
- Short-lived branches
feature/<topic>— new featuresfix/<topic>— bug fixesdocs/<topic>— documentation changeschore/<topic>— maintenance, tooling, CIrefactor/<topic>— refactoring without behavior changetest/<topic>— adding or improving tests
Branching Rules
- Start normal work from the latest
dev - Keep branches short-lived and focused on one logical change
- Do not commit directly to
mainordevunless explicitly requested - Delete the short-lived branch after it is merged
Pull Requests
- Open normal PRs into
dev, notmain - Use squash merge for PRs into
dev - Ensure CI passes before merging
- Keep PRs focused; split unrelated changes into separate PRs
Release Flow
- Merge
devintomainwhen changes are ready to release - Tag releases from
mainwith version numbers (e.g.,v1.2.3) - If a hotfix is made directly against
main, merge it back todev
Agent Expectations
- Unless the user says otherwise, assume new work should target
dev - If a branch must be created, follow the naming rules above
- Do not create or push branches automatically unless the user asks
- Do not commit changes unless the user explicitly asks
Notes for Agents
- This is a small, focused CLI tool — prefer simplicity over abstraction
- The
src/layout is intentional for proper packaging - The
py.typedmarker indicates type hints are available to consumers - When in doubt, follow patterns from existing code in the same file