1import dataclasses as dc 2from typing import Literal, NoReturn, overload 3 4 5@dc.dataclass 6class ClinicError(Exception): 7 message: str 8 _: dc.KW_ONLY 9 lineno: int | None = None 10 filename: str | None = None 11 12 def __post_init__(self) -> None: 13 super().__init__(self.message) 14 15 def report(self, *, warn_only: bool = False) -> str: 16 msg = "Warning" if warn_only else "Error" 17 if self.filename is not None: 18 msg += f" in file {self.filename!r}" 19 if self.lineno is not None: 20 msg += f" on line {self.lineno}" 21 msg += ":\n" 22 msg += f"{self.message}\n" 23 return msg 24 25 26class ParseError(ClinicError): 27 pass 28 29 30@overload 31def warn_or_fail( 32 *args: object, 33 fail: Literal[True], 34 filename: str | None = None, 35 line_number: int | None = None, 36) -> NoReturn: ... 37 38@overload 39def warn_or_fail( 40 *args: object, 41 fail: Literal[False] = False, 42 filename: str | None = None, 43 line_number: int | None = None, 44) -> None: ... 45 46def warn_or_fail( 47 *args: object, 48 fail: bool = False, 49 filename: str | None = None, 50 line_number: int | None = None, 51) -> None: 52 joined = " ".join([str(a) for a in args]) 53 error = ClinicError(joined, filename=filename, lineno=line_number) 54 if fail: 55 raise error 56 else: 57 print(error.report(warn_only=True)) 58 59 60def warn( 61 *args: object, 62 filename: str | None = None, 63 line_number: int | None = None, 64) -> None: 65 return warn_or_fail(*args, filename=filename, line_number=line_number, fail=False) 66 67def fail( 68 *args: object, 69 filename: str | None = None, 70 line_number: int | None = None, 71) -> NoReturn: 72 warn_or_fail(*args, filename=filename, line_number=line_number, fail=True) 73