Verified Commit 3f2ccd06 authored by Alberto Miranda's avatar Alberto Miranda ♨️
Browse files

Colorize diagnostic messages

parent d386a72c
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -8,3 +8,6 @@ py==1.11.0
pyparsing==3.0.6
pytest==6.2.5
toml==0.10.2
rich~=10.15.1
setuptools~=58.3.0
+15 −4
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ from pathlib import Path
import lark.exceptions

from rpcc.exceptions import RpccError
from rpcc.console import console
from rpcc.version import __version__ as rpcc_version
from rpcc.parser import Parser
from rpcc.transformers.cxx import Transformer as CxxTransformer
@@ -23,7 +24,7 @@ class PrintVersion(argparse.Action):

    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, self.const)
        print(f"rpcc {rpcc_version}")
        console.print(f"rpcc {rpcc_version}")
        sys.exit(0)


@@ -43,6 +44,15 @@ def main(args=None):
        help="The file containing the specifications for the RPCs."
    )

    parser.add_argument(
        "--color-diagnostics",
        choices=["never", "always", "auto"],
        metavar="WHEN",
        default="auto",
        help="Use escape sequences in diagnostic messages to display them in color in the terminal. WHEN is never, "
             "always, or auto (the default)."
    )

    parser.add_argument(
        "--version",
        "-V",
@@ -51,18 +61,19 @@ def main(args=None):
    )
    args = parser.parse_args()

    console.configure(args.color_diagnostics)

    try:
        parser = Parser()
        ast = parser.parse(args.rpc_proto_file)
    except RpccError as e:
        print(e, file=sys.stderr)
        console.eprint(e)
        return 1

    try:
        ast = CxxTransformer(parser).transform(ast)
    except lark.exceptions.VisitError as e:
        exc = e.orig_exc
        print(exc, file=sys.stderr)
        console.eprint(e.orig_exc)
    return 0


+82 −0
Original line number Diff line number Diff line
from typing import Any

from rich.console import Console as RichConsole

__all__ = ["console"]

from rpcc.console.highlighter import DiagnosticHighlighter
from rpcc.console.theme import diagnostics_theme


class Console:
    """The main Console class."""

    def __init__(self) -> None:
        """Initialize a `Console` with default options."""
        self.console = RichConsole(highlight=False)
        self.error_console = RichConsole(stderr=True)

    def configure(self, colorize_output: str = "auto") -> None:
        """Reconfigure a `Console`.

        :param colorize_output: Control whether the `Console` should emit ANSI color codes when printing messages.
        The argument accepts either ``"always"``, ``"never"`` or ``"auto"`` (the default).
        """
        ft = {
            "always": True,
            "never": False,
            "auto": None
        }.get(colorize_output, None)

        self.console = RichConsole(force_terminal=ft, highlight=False)
        self.error_console = RichConsole(stderr=True,
                                         theme=diagnostics_theme,
                                         highlighter=DiagnosticHighlighter(),
                                         force_terminal=ft)

    def print(self, *args: Any):
        """Print a message to `stdout`.

        :param args: Objects to print to the terminal.
        """
        self.console.print(*args)

    def eprint(self, *args: Any):
        """Print a message to `stderr`.

        :param args: Objects to print to the terminal.
        """
        self.error_console.print(*args)


# the default console
console = Console()


# ---------------------------------------------------------------------------
# Utility functions at module level.
# Basically delegate everything to `console`
# ---------------------------------------------------------------------------
def configure(colorize: str = "auto") -> None:
    """Configure the default `console`.

    :param colorize: Control whether the default console should emit ANSI color codes when printing messages.
    The argument accepts either ``"always"``, ``"never"`` or ``"auto"`` (the default).
    """
    console.configure(colorize)


def print(*args: Any) -> None:
    """Print to `stdout` using the default console.

    :param args: The objects to print.
    """
    console.print(*args)


def eprint(*args: Any):
    """Print to `stderr` using the default console.

    :param args: The objects to print.
    """
    console.eprint(*args)
+12 −0
Original line number Diff line number Diff line
from rich.highlighter import RegexHighlighter


class DiagnosticHighlighter(RegexHighlighter):
    """Apply a style to diagnostic messages"""

    base_style = "diagnostic."
    highlights = [
        r"(?P<location>.*?:\d+:\d+:)\s(?P<error>error:)\s(?P<message>.*?)(?P<context>(?:\n.*){2})(?P<caret>\^)\n",
        r"(?P<location>.*?:\d+:\d+:)\s(?P<note>note:)\s(?P<message>.*?)(?P<context>(?:\n.*){2})(?P<caret>\^)\n",
        r"Expected:\n\s+’(?P<token>.*?)’"
    ]

rpcc/console/theme.py

0 → 100644
+12 −0
Original line number Diff line number Diff line
from rich.theme import Theme

# the colors that should be applied to each part of a diagnostic message
diagnostics_theme = Theme({
    "diagnostic.location": "bold bright_white",
    "diagnostic.error": "bold bright_red",
    "diagnostic.note": "bold cyan",
    "diagnostic.message": "bold bright_white",
    "diagnostic.context": "default",
    "diagnostic.caret": "bold bright_green",
    "diagnostic.token": "bold bright_cyan"
})
Loading