Files
rich-print/AGENTS.md

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: rprp.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
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 annotations at the top for modern type hint syntax
  • Use explicit imports (no from module import *)
  • Import Path from pathlib, not os.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 | None instead of Optional[str]
  • Use Annotated[Type, typer.Option(...)] for CLI parameters
  • Always annotate function parameters and return types
  • Use dataclass for 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_type not type)

Error Handling

  • Use rich.Console for 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-flag for boolean toggles
  • Use Annotated[Type, typer.Option(...)] for all options
  • Set no_args_is_help=False when stdin input is valid
  • Use callbacks for --version with is_eager=True

Module Structure

Each module should have:

  1. Module docstring (one line)
  2. from __future__ import annotations
  3. Standard library imports
  4. Third-party imports
  5. Local imports
  6. Constants (module-level)
  7. Classes
  8. Functions

File Type Detection

When adding new file types:

  1. Add extension to EXTENSION_MAP in detect.py
  2. Use Pygments lexer name as the value
  3. 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

  • markdown and md → use rich.markdown.Markdown
  • json → use rich.json.JSON with fallback to Syntax
  • 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 lexers
  • typer>=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 from dev
    • dev — integration branch for day-to-day development
  • Short-lived branches
    • feature/<topic> — new features
    • fix/<topic> — bug fixes
    • docs/<topic> — documentation changes
    • chore/<topic> — maintenance, tooling, CI
    • refactor/<topic> — refactoring without behavior change
    • test/<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 main or dev unless explicitly requested
  • Delete the short-lived branch after it is merged

Pull Requests

  • Open normal PRs into dev, not main
  • Use squash merge for PRs into dev
  • Ensure CI passes before merging
  • Keep PRs focused; split unrelated changes into separate PRs

Release Flow

  • Merge dev into main when changes are ready to release
  • Tag releases from main with version numbers (e.g., v1.2.3)
  • If a hotfix is made directly against main, merge it back to dev

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.typed marker indicates type hints are available to consumers
  • When in doubt, follow patterns from existing code in the same file