diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..7723979 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,187 @@ +# 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 # 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 +``` + +### Testing +This project does not yet have automated tests. When adding tests: +```bash +# Run all tests +uv run pytest + +# Run a single test file +uv run pytest tests/test_detect.py + +# Run a single test function +uv run pytest tests/test_detect.py::test_detect_json_from_content + +# 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, Optional + +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, +) -> 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` + +### Rendering +- `markdown` and `md` → use `rich.markdown.Markdown` +- `json` → use `rich.json.JSON` with fallback to `Syntax` +- All other types → use `rich.syntax.Syntax` + +## Project Structure +``` +rich-viewer/ +├── 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 + └── py.typed # PEP 561 marker +``` + +## Dependencies +- `rich>=13.0` — Terminal rendering (Markdown, JSON, Syntax) +- `typer>=0.12` — CLI framework + +Both are runtime dependencies. No dev dependencies are currently specified. + +## Git Conventions +- Write clear, imperative commit messages +- Reference issues/PRs when applicable +- Keep commits focused (one logical change per commit) + +## 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