diff --git a/rendercv/__main__.py b/rendercv/__main__.py index a910b66..b42d3a2 100644 --- a/rendercv/__main__.py +++ b/rendercv/__main__.py @@ -38,16 +38,23 @@ def user_friendly_errors(func: Callable) -> Callable: except ValidationError as e: # It is a Pydantic error error_messages = [] - error_messages.append("There are validation errors!") + error_messages.append("There are some problems with your input 🧐") # Translate Pydantic's error messages to make them more user-friendly custom_error_messages_by_type = { "url_scheme": "This is not a valid URL 😿", + "string_type": "This is not a valid string 🤭", + "missing": "This field is required, but it is missing 😆", + "literal_error": "Only the following values are allowed: {expected} 😒", } custom_error_messages_by_msg = { "value is not a valid phone number": ( "This is not a valid phone number 👺" - ) + ), + "String should match pattern '\\d+\\.?\\d* *(cm|in|pt|mm|ex|em)'": ( + "This is not a valid length! Use a number followed by a unit " + "of length (cm, in, pt, mm, ex, em) 👺" + ), } new_errors: list[ErrorDetails] = [] for error in e.errors(): @@ -68,21 +75,34 @@ def user_friendly_errors(func: Callable) -> Callable: if custom_message: ctx = error.get("ctx") - error["msg"] = ( - custom_message.format(**ctx) if ctx else custom_message - ) + if ctx: + if ctx.get("error"): + error["msg"] = ctx["error"].args[0] + else: + error["msg"] = custom_message.format(**ctx) + else: + error["msg"] = custom_message - # If the input value is a dictionary or if the input value is in the - # error message, remove it - if isinstance(error["input"], dict) or error["input"] in error["msg"]: - error["input"] = None + if error["input"] is not None: + # If the input value is a dictionary, remove it + if isinstance(error["input"], dict): + error["input"] = None + elif isinstance(error["input"], (float, int, bool, str)): + # Or if the input value is in the error message, remove it + input_value = str(error["input"]) + if input_value in error["msg"]: + error["input"] = None new_errors.append(error) # Create a custom error message for RenderCV users for error in new_errors: - location = ".".join(error["loc"]) - error_messages.append(f"{location}:\n {error['msg']}") + if len(error["loc"]) > 0: + location = ".".join(error["loc"]) + error_messages.append(f"{location}:\n {error['msg']}") + else: + error_messages.append(f"{error['msg']}") + if error["input"]: error_messages[-1] += f"\n Your input was \"{error['input']}\"" error_message = "\n\n ".join(error_messages) diff --git a/rendercv/data_model.py b/rendercv/data_model.py index 4f6d60f..6445363 100644 --- a/rendercv/data_model.py +++ b/rendercv/data_model.py @@ -65,6 +65,7 @@ dictionary = [ "javascript", ] spell = SpellChecker() +all_misspelled_words = set() def check_spelling(sentence: str) -> str: @@ -110,10 +111,7 @@ def check_spelling(sentence: str) -> str: if word in dictionary: continue - logger.warning( - f'The word "{word}" might be misspelled according to the' - " pyspellchecker." - ) + all_misspelled_words.add(word) return sentence @@ -1232,6 +1230,35 @@ class CurriculumVitae(BaseModel): return model + @model_validator(mode="after") + @classmethod + def print_all_the_misspeled_words(cls, model): + """Print all the words that are misspelled according to pyspellchecker.""" + if len(all_misspelled_words) > 0: + messages = [] + messages.append( + "The following words might be misspelled (according to pyspellchecker):" + ) + + misspelled_words = list(all_misspelled_words) + + # Make misspeled_words a list of lists where each list contains 5: + misspelled_words = [ + misspelled_words[i : i + 5] for i in range(0, len(misspelled_words), 5) + ] + + # Join the words in each list with a comma, and join the lists with a new + # line: + misspelled_words = "\n ".join( + [", ".join(words) for words in misspelled_words] + ) + messages.append(f" {misspelled_words}") + + # Print the messages: + logger.warning("\n".join(messages)) + + return model + @computed_field @cached_property def connections(self) -> list[Connection]: @@ -1328,6 +1355,10 @@ class CurriculumVitae(BaseModel): link_text = custom_section.link_text entries = custom_section.entries break + else: + entry_type = None + link_text = None + entries = None if entry_type is None or entries is None: raise ValueError( @@ -1396,10 +1427,15 @@ class RenderCVDataModel(BaseModel): section_titles = [section.title for section in cv.sections] for title in design.options.show_timespan_in: # type: ignore if title not in section_titles: + not_used_section_titles = list( + set(section_titles) - set(design.options.show_timespan_in) + ) + not_used_section_titles = ", ".join(not_used_section_titles) raise ValueError( f'The section "{title}" that is specified in the' - ' "show_timespan_in" option is not found in the CV 😱! The' - f" available section titles are: {section_titles}" + ' "show_timespan_in" option is not found in the CV 😱 You' + " might have wanted to use one of these:" + f" {not_used_section_titles}" ) return model