import dataclasses as dc from typing import Literal, NoReturn, overload @dc.dataclass class ClinicError(Exception): message: str _: dc.KW_ONLY lineno: int | None = None filename: str | None = None def __post_init__(self) -> None: super().__init__(self.message) def report(self, *, warn_only: bool = False) -> str: msg = "Warning" if warn_only else "Error" if self.filename is not None: msg += f" in file {self.filename!r}" if self.lineno is not None: msg += f" on line {self.lineno}" msg += ":\n" msg += f"{self.message}\n" return msg class ParseError(ClinicError): pass @overload def warn_or_fail( *args: object, fail: Literal[True], filename: str | None = None, line_number: int | None = None, ) -> NoReturn: ... @overload def warn_or_fail( *args: object, fail: Literal[False] = False, filename: str | None = None, line_number: int | None = None, ) -> None: ... def warn_or_fail( *args: object, fail: bool = False, filename: str | None = None, line_number: int | None = None, ) -> None: joined = " ".join([str(a) for a in args]) error = ClinicError(joined, filename=filename, lineno=line_number) if fail: raise error else: print(error.report(warn_only=True)) def warn( *args: object, filename: str | None = None, line_number: int | None = None, ) -> None: return warn_or_fail(*args, filename=filename, line_number=line_number, fail=False) def fail( *args: object, filename: str | None = None, line_number: int | None = None, ) -> NoReturn: warn_or_fail(*args, filename=filename, line_number=line_number, fail=True)