import argparse import logging import sys from typing import Tuple from rich.console import Console from rich.panel import Panel from rich.terminal_theme import ( DIMMED_MONOKAI, DEFAULT_TERMINAL_THEME, MONOKAI, NIGHT_OWLISH, TerminalTheme, ) from rich.text import Text from rich.traceback import install from rich_rst import RestructuredText, __version__ log = logging.getLogger(__name__) def rgb(r: int, g: int, b: int) -> Tuple[int, int, int]: """Represent color in RGB format.""" return (r, g, b) _DRACULA_TERMINAL_THEME = TerminalTheme( rgb(40, 42, 54), rgb(248, 248, 242), [ rgb(40, 42, 54), rgb(255, 85, 85), rgb(80, 250, 123), rgb(241, 250, 140), rgb(189, 147, 249), rgb(255, 121, 198), rgb(139, 233, 253), rgb(255, 255, 255), ], [ rgb(40, 42, 54), rgb(255, 85, 85), rgb(80, 250, 123), rgb(241, 250, 140), rgb(189, 147, 249), rgb(255, 121, 198), rgb(139, 233, 253), rgb(255, 255, 255), ], ) _HTML_THEMES = { "dracula": _DRACULA_TERMINAL_THEME, "monokai": MONOKAI, "night-owl": NIGHT_OWLISH, "dimmed-monokai": DIMMED_MONOKAI, "default": DEFAULT_TERMINAL_THEME, } def parse_arguments() -> argparse.Namespace: """Parse command line arguments.""" parser = argparse.ArgumentParser( description="Render reStructuredText to the console with rich-rst" ) parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}") parser.add_argument( "path", metavar="PATH", nargs="?", default=None, help="path to file, or - for stdin", ) parser.add_argument( "-c", "--force-color", dest="force_color", action="store_true", help="force color for non-terminals", ) parser.add_argument( "-e", "--encoding", dest="encoding", type=str, default="utf-8", help="encoding for file (default: utf-8)", ) parser.add_argument( "-w", "--width", type=int, dest="width", default=None, help="width of output (default will auto-detect)", ) parser.add_argument( "-hw", "--html-width", type=str, dest="html_width", default="1675px", help="width of html output (default: 1675px)", ) parser.add_argument( "-t", "--code-theme", dest="code_theme", type=str, default="monokai", help="pygments code theme (default: monokai)", ) parser.add_argument( "--show-line-numbers", action="store_true", dest="show_line_numbers", help="show line numbers in code blocks", ) parser.add_argument( "-S", "--save-html", type=str, dest="html_filename", default=None, help="export rendered output to HTML file", ) parser.add_argument( "-r", "--wrap", dest="word_wrap", action="store_true", help="enable word wrapping for long lines", ) parser.add_argument( "-s", "--soft-wrap", action="store_true", dest="soft_wrap", help="enable soft wrapping mode", ) parser.add_argument( "-gl", "--guess-lexer", action="store_true", dest="guess_lexer", help="guess lexer for code blocks without language", ) parser.add_argument( "-dl", "--default-lexer", type=str, dest="default_lexer", default="python", help="default lexer if no lexer found (default: python)", ) parser.add_argument( "-se", "--show-errors", action="store_true", dest="show_errors", help="display reStructuredText parsing errors", ) parser.add_argument( "--admonition-style", dest="admonition_style", type=str, default="panel", choices=["panel", "compact"], help="rendering style for admonitions (default: panel)", ) parser.add_argument( "--html-theme", dest="html_theme", type=str, default="dracula", choices=list(_HTML_THEMES), help="color theme for HTML export (default: dracula)", ) parser.add_argument( "--list-html-themes", action="store_true", dest="list_html_themes", help="list available HTML export themes and exit", ) parser.add_argument( "-o", "--output", dest="output", type=str, default=None, help="write plain-text output to FILE instead of stdout", ) parser.add_argument( "-d", "--debug", action="store_true", dest="debug", help="enable debug logging", ) args = parser.parse_args() if not args.list_html_themes and args.path is None: parser.error("the following arguments are required: PATH") return args def main() -> int: """Main entry point.""" args = parse_arguments() if args.debug: logging.basicConfig(level=logging.DEBUG) log.debug("Debug mode enabled") if args.list_html_themes: for name in sorted(_HTML_THEMES): print(name) return 0 html_format = ( """\
{code}
"""
).replace("HTML_WIDTH_PLACEHOLDER", args.html_width)
record = bool(args.html_filename) or bool(args.output)
console = Console(force_terminal=args.force_color, width=args.width, record=record)
try:
if args.path == "-":
log.debug("Reading from stdin")
code = sys.stdin.read()
else:
log.debug(f"Reading file: {args.path}")
with open(args.path, "rt", encoding=args.encoding) as file_handle:
code = file_handle.read()
except OSError as e:
console.print(
Panel(
Text(
f"[bold red]Error reading file[/bold red]\n\n"
f"Path: {args.path!r}\n"
f"Reason: {e.strerror}\n\n"
"Check that the file exists and you have read permission."
),
title="[bold]File Error[/bold]",
style="red",
)
)
if args.debug:
log.exception("File read error")
return 1
try:
log.debug("Parsing reStructuredText")
rst = RestructuredText(
code,
code_theme=args.code_theme,
show_line_numbers=args.show_line_numbers,
guess_lexer=args.guess_lexer,
default_lexer=args.default_lexer,
show_errors=args.show_errors,
filename=args.path if args.path != "-" else "