Improve code quality: dedup, state safety, and modern patterns

- Reset markdown parser state before each convert() call
- Avoid loop variable reassignment in model_processor.py
- Extract build_name_variants() to deduplicate path_resolver.py
- Move WhatsApp phone TypeAdapter to module level (was per-call)
- Use tomllib for TOML parsing instead of string splitting
- Replace busy-wait loop with observer.join() in watcher.py
This commit is contained in:
Sina Atalay
2026-03-24 18:55:50 +03:00
parent 49384c8889
commit dd90554764
6 changed files with 46 additions and 47 deletions

View File

@@ -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()

View File

@@ -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

View File

@@ -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(

View File

@@ -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)

View File

@@ -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

View File

@@ -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: