Coverage for src / rendercv / renderer / path_resolver.py: 100%
26 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-25 13:49 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-25 13:49 +0000
1import pathlib
3from rendercv.schema.models.rendercv_model import RenderCVModel
5from .templater.date import build_date_placeholders
6from .templater.string_processor import substitute_placeholders
9def build_name_variants(name: str | None) -> dict[str, str]:
10 """Generate all case/separator variants of a name for placeholder substitution.
12 Why:
13 Output file paths use placeholders like NAME_IN_LOWER_SNAKE_CASE.
14 Centralizing variant generation avoids repeating the same
15 null-check-and-transform pattern for each variant.
17 Args:
18 name: The CV owner's name, or None.
20 Returns:
21 Dictionary mapping placeholder names to their values.
22 """
23 if name is None:
24 return {}
25 return {
26 "NAME": name,
27 "NAME_IN_SNAKE_CASE": name.replace(" ", "_"),
28 "NAME_IN_LOWER_SNAKE_CASE": name.replace(" ", "_").lower(),
29 "NAME_IN_UPPER_SNAKE_CASE": name.replace(" ", "_").upper(),
30 "NAME_IN_KEBAB_CASE": name.replace(" ", "-"),
31 "NAME_IN_LOWER_KEBAB_CASE": name.replace(" ", "-").lower(),
32 "NAME_IN_UPPER_KEBAB_CASE": name.replace(" ", "-").upper(),
33 }
36def resolve_output_folder_placeholder(
37 file_path: pathlib.Path, output_folder: pathlib.Path
38) -> pathlib.Path:
39 """Replace the OUTPUT_FOLDER directory component with the actual output folder.
41 Why:
42 Output file paths use OUTPUT_FOLDER as a placeholder directory so users
43 can redirect all output via the single ``output_folder`` setting. Both
44 paths are already absolute (resolved by PlannedPathRelativeToInput from
45 the same base), so the replacement swaps the placeholder component and
46 appends remaining path segments.
48 Args:
49 file_path: Absolute path potentially containing an OUTPUT_FOLDER component.
50 output_folder: Absolute path to the actual output folder.
52 Returns:
53 Path with OUTPUT_FOLDER replaced, or original path if not present.
54 """
55 parts = file_path.parts
56 if "OUTPUT_FOLDER" not in parts:
57 return file_path
59 idx = parts.index("OUTPUT_FOLDER")
60 suffix_parts = parts[idx + 1 :]
62 if suffix_parts:
63 return output_folder / pathlib.Path(*suffix_parts)
64 return output_folder
67def resolve_rendercv_file_path(
68 rendercv_model: RenderCVModel, file_path: pathlib.Path
69) -> pathlib.Path:
70 """Resolve output file path with placeholder substitution and directory creation.
72 Why:
73 Users specify output paths like ``OUTPUT_FOLDER/NAME_CV_YEAR.pdf`` with
74 placeholders for dynamic naming. This function first resolves the
75 OUTPUT_FOLDER directory component to the configured output folder, then
76 substitutes name and date placeholders in the filename, and finally
77 creates parent directories as needed.
79 Example:
80 ```py
81 # Given model with name="John Doe", year=2025, output_folder=rendercv_output
82 path = resolve_rendercv_file_path(
83 model,
84 pathlib.Path("/cv/OUTPUT_FOLDER/NAME_IN_LOWER_SNAKE_CASE_CV_YEAR.pdf"),
85 )
86 # Returns: pathlib.Path("/cv/rendercv_output/john_doe_CV_2025.pdf")
87 ```
89 Args:
90 rendercv_model: CV model containing name, date, and output folder for
91 substitution.
92 file_path: Template path with placeholders.
94 Returns:
95 Resolved absolute path with substituted placeholders.
96 """
97 output_folder = rendercv_model.settings.render_command.output_folder
98 file_path = resolve_output_folder_placeholder(file_path, output_folder)
100 current_date = rendercv_model.settings._resolved_current_date
101 file_path_placeholders: dict[str, str] = {
102 **build_date_placeholders(current_date, locale=rendercv_model.locale),
103 **build_name_variants(rendercv_model.cv.name),
104 }
105 file_name = substitute_placeholders(file_path.name, file_path_placeholders)
106 resolved_file_path = file_path.parent / file_name
107 resolved_file_path.parent.mkdir(parents=True, exist_ok=True)
108 return resolved_file_path