252 lines
7.7 KiB
Markdown
252 lines
7.7 KiB
Markdown
# 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
|
|
```bash
|
|
uv sync # Install dependencies
|
|
```
|
|
|
|
### Running the CLI
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
# 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)
|
|
```bash
|
|
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:
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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:`
|
|
|
|
```python
|
|
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:
|
|
```python
|
|
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
|