diff --git a/src/rendercv/cli/render_command/watcher.py b/src/rendercv/cli/render_command/watcher.py index 6d006aad..bde457ff 100644 --- a/src/rendercv/cli/render_command/watcher.py +++ b/src/rendercv/cli/render_command/watcher.py @@ -1,6 +1,5 @@ import contextlib import pathlib -import time from collections.abc import Callable import typer @@ -63,8 +62,7 @@ def run_function_if_files_change( function() try: - while True: - time.sleep(1) + observer.join() except KeyboardInterrupt: observer.stop() - observer.join() + observer.join() diff --git a/src/rendercv/renderer/path_resolver.py b/src/rendercv/renderer/path_resolver.py index 814e8302..5068f77c 100644 --- a/src/rendercv/renderer/path_resolver.py +++ b/src/rendercv/renderer/path_resolver.py @@ -6,6 +6,33 @@ from .templater.date import build_date_placeholders from .templater.string_processor import substitute_placeholders +def build_name_variants(name: str | None) -> dict[str, str]: + """Generate all case/separator variants of a name for placeholder substitution. + + Why: + Output file paths use placeholders like NAME_IN_LOWER_SNAKE_CASE. + Centralizing variant generation avoids repeating the same + null-check-and-transform pattern for each variant. + + Args: + name: The CV owner's name, or None. + + Returns: + Dictionary mapping placeholder names to their values. + """ + if name is None: + return {} + return { + "NAME": name, + "NAME_IN_SNAKE_CASE": name.replace(" ", "_"), + "NAME_IN_LOWER_SNAKE_CASE": name.replace(" ", "_").lower(), + "NAME_IN_UPPER_SNAKE_CASE": name.replace(" ", "_").upper(), + "NAME_IN_KEBAB_CASE": name.replace(" ", "-"), + "NAME_IN_LOWER_KEBAB_CASE": name.replace(" ", "-").lower(), + "NAME_IN_UPPER_KEBAB_CASE": name.replace(" ", "-").upper(), + } + + def resolve_output_folder_placeholder( file_path: pathlib.Path, output_folder: pathlib.Path ) -> pathlib.Path: @@ -71,38 +98,9 @@ def resolve_rendercv_file_path( file_path = resolve_output_folder_placeholder(file_path, output_folder) current_date = rendercv_model.settings._resolved_current_date - file_path_placeholders = { + file_path_placeholders: dict[str, str] = { **build_date_placeholders(current_date, locale=rendercv_model.locale), - "NAME": rendercv_model.cv.name, - "NAME_IN_SNAKE_CASE": ( - rendercv_model.cv.name.replace(" ", "_") if rendercv_model.cv.name else None - ), - "NAME_IN_LOWER_SNAKE_CASE": ( - rendercv_model.cv.name.replace(" ", "_").lower() - if rendercv_model.cv.name - else None - ), - "NAME_IN_UPPER_SNAKE_CASE": ( - rendercv_model.cv.name.replace(" ", "_").upper() - if rendercv_model.cv.name - else None - ), - "NAME_IN_KEBAB_CASE": ( - rendercv_model.cv.name.replace(" ", "-") if rendercv_model.cv.name else None - ), - "NAME_IN_LOWER_KEBAB_CASE": ( - rendercv_model.cv.name.replace(" ", "-").lower() - if rendercv_model.cv.name - else None - ), - "NAME_IN_UPPER_KEBAB_CASE": ( - rendercv_model.cv.name.replace(" ", "-").upper() - if rendercv_model.cv.name - else None - ), - } - file_path_placeholders = { - k: v for k, v in file_path_placeholders.items() if v is not None + **build_name_variants(rendercv_model.cv.name), } file_name = substitute_placeholders(file_path.name, file_path_placeholders) resolved_file_path = file_path.parent / file_name diff --git a/src/rendercv/renderer/pdf_png.py b/src/rendercv/renderer/pdf_png.py index e8873a74..61b17d0b 100644 --- a/src/rendercv/renderer/pdf_png.py +++ b/src/rendercv/renderer/pdf_png.py @@ -3,6 +3,7 @@ import functools import pathlib import shutil import tempfile +import tomllib import rendercv_fonts import typst @@ -125,13 +126,13 @@ def read_version_from_typst_toml(typst_toml_path: pathlib.Path) -> str: Returns: The version string. """ - for line in typst_toml_path.read_text(encoding="utf-8").splitlines(): - stripped = line.strip() - if stripped.startswith("version"): - return stripped.split("=", 1)[1].strip().strip('"') - - message = f"Could not find version in {typst_toml_path}" - raise RenderCVInternalError(message) + with open(typst_toml_path, "rb") as f: + data = tomllib.load(f) + try: + return data["package"]["version"] + except KeyError: + message = f"Could not find version in {typst_toml_path}" + raise RenderCVInternalError(message) def install_bundled_typst_package( diff --git a/src/rendercv/renderer/templater/markdown_parser.py b/src/rendercv/renderer/templater/markdown_parser.py index 0e5eda1d..dc65e9d8 100644 --- a/src/rendercv/renderer/templater/markdown_parser.py +++ b/src/rendercv/renderer/templater/markdown_parser.py @@ -183,8 +183,10 @@ def markdown_to_typst(markdown_string: str) -> str: while i < len(lines) and lines[i].startswith(" "): block.append(lines[i]) i += 1 + md.reset() result_parts.append(md.convert("\n".join(block))) else: + md.reset() result_parts.append(md.convert(lines[i])) i += 1 return "\n".join(result_parts) diff --git a/src/rendercv/renderer/templater/model_processor.py b/src/rendercv/renderer/templater/model_processor.py index 747566b7..144b39a6 100644 --- a/src/rendercv/renderer/templater/model_processor.py +++ b/src/rendercv/renderer/templater/model_processor.py @@ -134,14 +134,14 @@ def process_model( in rendercv_model.design.sections.show_time_spans_in ) for i, entry in enumerate(section.entries): - entry = render_entry_templates( # NOQA: PLW2901 + processed_entry = render_entry_templates( entry, templates=rendercv_model.design.templates, locale=rendercv_model.locale, show_time_span=show_time_span, current_date=rendercv_model.settings._resolved_current_date, ) - section.entries[i] = process_fields(entry, string_processors) + section.entries[i] = process_fields(processed_entry, string_processors) return rendercv_model diff --git a/src/rendercv/schema/models/cv/social_network.py b/src/rendercv/schema/models/cv/social_network.py index 6985515f..55343379 100644 --- a/src/rendercv/schema/models/cv/social_network.py +++ b/src/rendercv/schema/models/cv/social_network.py @@ -10,6 +10,9 @@ from ...pydantic_error_handling import CustomPydanticErrorTypes from ..base import BaseModelWithoutExtraKeys url_validator = pydantic.TypeAdapter[pydantic.HttpUrl](pydantic.HttpUrl) +phone_validator = pydantic.TypeAdapter[pydantic_phone_numbers.PhoneNumber]( + pydantic_phone_numbers.PhoneNumber +) type SocialNetworkName = Literal[ "LinkedIn", "GitHub", @@ -127,9 +130,6 @@ class SocialNetwork(BaseModelWithoutExtraKeys): " 'username.bsky.social' or 'domain.com').", ) case "WhatsApp": - phone_validator = pydantic.TypeAdapter[ - pydantic_phone_numbers.PhoneNumber - ](pydantic_phone_numbers.PhoneNumber) try: phone_validator.validate_python(username) except pydantic.ValidationError as e: