From 9d29df44375b59ef0e1662303f66845517236aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Cardao?= Date: Thu, 1 Aug 2024 15:26:15 +0200 Subject: [PATCH] Improve qualifiers error Add fuzz matching mechanism. Indirectly linked to it/e3-adacore#64 --- src/e3/anod/qualifiers_manager.py | 27 ++++++++++++++++++- tests/tests_e3/anod/test_qualifier_manager.py | 26 +++++++++++++++--- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/e3/anod/qualifiers_manager.py b/src/e3/anod/qualifiers_manager.py index 8c265171..ea6358c0 100644 --- a/src/e3/anod/qualifiers_manager.py +++ b/src/e3/anod/qualifiers_manager.py @@ -1,5 +1,6 @@ from __future__ import annotations +from difflib import get_close_matches from typing import TYPE_CHECKING from hashlib import sha1 from e3.anod.error import AnodError @@ -708,7 +709,31 @@ def compute_qualifier_values( ) if invalid_keys: invalid_keys_str = ", ".join(invalid_keys) - raise AnodError(f"{self.origin}: Invalid qualifier(s): {invalid_keys_str}") + error_msg = f"{self.origin}: Invalid qualifier(s): {invalid_keys_str}\n" + + if self.qualifier_decls: + probable_qualifiers = [ + repr( + get_close_matches( + key, self.qualifier_decls.keys(), n=1, cutoff=0 + )[0] + ) + for key in invalid_keys + ] + if len(probable_qualifiers) == 1: + error_msg += f"Did you mean {probable_qualifiers[0]}?\n" + else: + error_msg += ( + f"Did you mean {', '.join(probable_qualifiers[:-1])} or " + f"{probable_qualifiers[-1]}?\n" + ) + + error_msg += ( + f"Use `anod help {self.anod_instance.name}` to get a list of valid " + "qualifiers" + ) + + raise AnodError(error_msg) # Update default dict with user values result.update( diff --git a/tests/tests_e3/anod/test_qualifier_manager.py b/tests/tests_e3/anod/test_qualifier_manager.py index d56bb82c..50dfb763 100644 --- a/tests/tests_e3/anod/test_qualifier_manager.py +++ b/tests/tests_e3/anod/test_qualifier_manager.py @@ -68,6 +68,16 @@ class AnodDummy(Anod): name = "dummy_spec" enable_name_generator = True + class AnodDummyWithQual(Anod): + name = "dummy_spec" + enable_name_generator = True + + def declare_qualifiers_and_components(self, qm: QualifiersManager) -> None: + qm.declare_tag_qualifier( + name="test", + description="test", + ) + anod_dummy = AnodDummy("", kind="build") # Add a qualifier after parse @@ -165,7 +175,7 @@ class AnodDummy(Anod): with pytest.raises(AnodError) as err: qualifiers_manager.parse({}) assert str(err.value) == ( - "build(name=dummy_spec, qual={}): " "Missing qualifier(s): mandatory_qual" + "build(name=dummy_spec, qual={}): Missing qualifier(s): mandatory_qual" ) # Use of undeclared qualifier @@ -173,7 +183,16 @@ class AnodDummy(Anod): AnodDummy("invalid_qual", kind="build") assert str(err.value) == ( "build(name=dummy_spec, qual={'invalid_qual': ''}): " - "Invalid qualifier(s): invalid_qual" + "Invalid qualifier(s): invalid_qual\n" + "Use `anod help dummy_spec` to get a list of valid qualifiers" + ) + with pytest.raises(AnodError) as err: + AnodDummyWithQual("tst", kind="build") + assert str(err.value) == ( + "build(name=dummy_spec, qual={'tst': ''}): " + "Invalid qualifier(s): tst\n" + "Did you mean 'test'?\n" + "Use `anod help dummy_spec` to get a list of valid qualifiers" ) # Pass a key_value qualifier with a value not in choices @@ -203,7 +222,8 @@ class AnodDummy(Anod): qualifiers_manager.parse({"test_qual": "val1"}) assert str(err.value) == ( "build(name=dummy_spec, qual={'test_qual': 'val1'}): " - "Invalid qualifier(s): test_qual" + "Invalid qualifier(s): test_qual\n" + "Use `anod help dummy_spec` to get a list of valid qualifiers" ) # use a not declared qualifier in component