Files
rich-print/src/rp/cli.py

142 lines
4.0 KiB
Python

"""CLI entry point for rp."""
from __future__ import annotations
import sys
from pathlib import Path
from typing import Annotated
import typer
from rich.console import Console
from rp import __author__, __license__, __version__
from rp.detect import detect_type
from rp.render import RenderOptions, render
app = typer.Typer(
name="rp",
help="Rich print files — markdown, JSON, code, and more — beautifully in your terminal.",
no_args_is_help=False,
rich_markup_mode="rich",
)
def version_callback(value: bool) -> None:
"""Print version information and exit.
Args:
value: Flag value from typer callback.
"""
if value:
console = Console()
console.print(f"Rich Print (rp) {__version__}")
console.print(f"Copyright (c) 2026 {__author__}")
console.print(f"License: {__license__}")
raise typer.Exit()
@app.command()
def main(
file: Annotated[
Path | None,
typer.Argument(help="File to rich-print. Use '-' or omit to read from stdin."),
] = None,
file_type: Annotated[
str | None,
typer.Option(
"--type", "-t", help="Force file type (e.g. json, python, markdown)."
),
] = None,
theme: Annotated[
str,
typer.Option("--theme", help="Pygments color theme."),
] = "monokai",
line_numbers: Annotated[
bool | None,
typer.Option(
"--line-numbers/--no-line-numbers",
help="Show line numbers. Default: auto.",
),
] = None,
pager: Annotated[
bool | None,
typer.Option(
"--pager/--no-pager",
help="Use pager for output. Default: auto-detect.",
),
] = None,
guess_content: Annotated[
bool,
typer.Option(
"--guess-content/--no-guess-content",
help="Use heuristic content-based type detection as a fallback.",
),
] = False,
version: Annotated[
bool | None,
typer.Option(
"--version",
"-v",
help="Show version.",
callback=version_callback,
is_eager=True,
),
] = None,
) -> None:
"""Render a file or stdin input in the terminal.
Args:
file: File to rich-print. Use '-' or omit to read from stdin.
file_type: Force file type (e.g. json, python, markdown).
theme: Pygments color theme.
line_numbers: Show line numbers. Default: auto.
pager: Use pager for output. Default: auto-detect.
guess_content: Use heuristic content-based type detection as a fallback.
version: Show version and exit.
"""
console = Console()
if file is None or str(file) == "-":
if sys.stdin.isatty():
console.print("[dim]Reading from stdin. Press Ctrl+D to end.[/dim]")
content = sys.stdin.read()
file_path = None
else:
if not file.exists():
console.print(f"[red]Error:[/red] File not found: {file}")
raise typer.Exit(code=1)
if file.is_dir():
console.print(f"[red]Error:[/red] Is a directory: {file}")
raise typer.Exit(code=1)
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)
file_path = str(file)
if not content:
console.print("[dim]Empty input, nothing to display.[/dim]")
raise typer.Exit()
file_type = detect_type(
path=file_path,
content=content,
explicit_type=file_type,
guess_content=guess_content,
)
options = RenderOptions(
theme=theme,
line_numbers=line_numbers,
pager=pager,
)
render(content, file_type, console, options)
if __name__ == "__main__":
app()