diff options
author | Erlend E. Aasland <erlend@python.org> | 2023-08-03 00:00:06 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-08-03 00:00:06 (GMT) |
commit | 1cd479c6d371605e9689c88ae1789dbcbceb2da0 (patch) | |
tree | 920479f62bc515a3289bc389c9b5ab77730c51c8 /Tools/clinic | |
parent | 017f047183fa33743f7e36c5c360f5c670032be3 (diff) | |
download | cpython-1cd479c6d371605e9689c88ae1789dbcbceb2da0.zip cpython-1cd479c6d371605e9689c88ae1789dbcbceb2da0.tar.gz cpython-1cd479c6d371605e9689c88ae1789dbcbceb2da0.tar.bz2 |
gh-104683: Rework Argument Clinic error handling (#107551)
Introduce ClinicError, and use it in fail(). The CLI runs main(),
catches ClinicError, formats the error message, prints to stderr
and exits with an error.
As a side effect, this refactor greatly improves the accuracy of
reported line numbers in case of error.
Also, adapt the test suite to work with ClinicError.
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Diffstat (limited to 'Tools/clinic')
-rwxr-xr-x | Tools/clinic/clinic.py | 69 |
1 files changed, 43 insertions, 26 deletions
diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index ce81847..1bcdb6b 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -28,7 +28,6 @@ import shlex import string import sys import textwrap -import traceback from collections.abc import ( Callable, @@ -137,6 +136,28 @@ def text_accumulator() -> TextAccumulator: text, append, output = _text_accumulator() return TextAccumulator(append, output) + +@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 + + @overload def warn_or_fail( *args: object, @@ -160,25 +181,16 @@ def warn_or_fail( line_number: int | None = None, ) -> None: joined = " ".join([str(a) for a in args]) - add, output = text_accumulator() - if fail: - add("Error") - else: - add("Warning") if clinic: if filename is None: filename = clinic.filename if getattr(clinic, 'block_parser', None) and (line_number is None): line_number = clinic.block_parser.line_number - if filename is not None: - add(' in file "' + filename + '"') - if line_number is not None: - add(" on line " + str(line_number)) - add(':\n') - add(joined) - print(output()) + error = ClinicError(joined, filename=filename, lineno=line_number) if fail: - sys.exit(-1) + raise error + else: + print(error.report(warn_only=True)) def warn( @@ -347,7 +359,7 @@ def version_splitter(s: str) -> tuple[int, ...]: accumulator: list[str] = [] def flush() -> None: if not accumulator: - raise ValueError('Unsupported version string: ' + repr(s)) + fail(f'Unsupported version string: {s!r}') version.append(int(''.join(accumulator))) accumulator.clear() @@ -360,7 +372,7 @@ def version_splitter(s: str) -> tuple[int, ...]: flush() version.append('abc'.index(c) - 3) else: - raise ValueError('Illegal character ' + repr(c) + ' in version string ' + repr(s)) + fail(f'Illegal character {c!r} in version string {s!r}') flush() return tuple(version) @@ -2233,11 +2245,7 @@ impl_definition block assert dsl_name in parsers, f"No parser to handle {dsl_name!r} block." self.parsers[dsl_name] = parsers[dsl_name](self) parser = self.parsers[dsl_name] - try: - parser.parse(block) - except Exception: - fail('Exception raised during parsing:\n' + - traceback.format_exc().rstrip()) + parser.parse(block) printer.print_block(block) # these are destinations not buffers @@ -4600,7 +4608,11 @@ class DSLParser: for line_number, line in enumerate(lines, self.clinic.block_parser.block_start_line_number): if '\t' in line: fail('Tab characters are illegal in the Clinic DSL.\n\t' + repr(line), line_number=block_start) - self.state(line) + try: + self.state(line) + except ClinicError as exc: + exc.lineno = line_number + raise self.do_post_block_processing_cleanup() block.output.extend(self.clinic.language.render(self.clinic, block.signatures)) @@ -4701,8 +4713,8 @@ class DSLParser: if existing_function.name == function_name: break else: - print(f"{cls=}, {module=}, {existing=}") - print(f"{(cls or module).functions=}") + print(f"{cls=}, {module=}, {existing=}", file=sys.stderr) + print(f"{(cls or module).functions=}", file=sys.stderr) fail(f"Couldn't find existing function {existing!r}!") fields = [x.strip() for x in full_name.split('.')] @@ -5719,8 +5731,13 @@ def run_clinic(parser: argparse.ArgumentParser, ns: argparse.Namespace) -> None: def main(argv: list[str] | None = None) -> NoReturn: parser = create_cli() args = parser.parse_args(argv) - run_clinic(parser, args) - sys.exit(0) + try: + run_clinic(parser, args) + except ClinicError as exc: + sys.stderr.write(exc.report()) + sys.exit(1) + else: + sys.exit(0) if __name__ == "__main__": |