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

1import pathlib 

2 

3from rendercv.schema.models.rendercv_model import RenderCVModel 

4 

5from .templater.date import build_date_placeholders 

6from .templater.string_processor import substitute_placeholders 

7 

8 

9def build_name_variants(name: str | None) -> dict[str, str]: 

10 """Generate all case/separator variants of a name for placeholder substitution. 

11 

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. 

16 

17 Args: 

18 name: The CV owner's name, or None. 

19 

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 } 

34 

35 

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. 

40 

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. 

47 

48 Args: 

49 file_path: Absolute path potentially containing an OUTPUT_FOLDER component. 

50 output_folder: Absolute path to the actual output folder. 

51 

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 

58 

59 idx = parts.index("OUTPUT_FOLDER") 

60 suffix_parts = parts[idx + 1 :] 

61 

62 if suffix_parts: 

63 return output_folder / pathlib.Path(*suffix_parts) 

64 return output_folder 

65 

66 

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. 

71 

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. 

78 

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

88 

89 Args: 

90 rendercv_model: CV model containing name, date, and output folder for 

91 substitution. 

92 file_path: Template path with placeholders. 

93 

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) 

99 

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