mirror of
https://github.com/rendercv/rendercv.git
synced 2026-04-19 06:22:56 -04:00
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:
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user