simple-resume 0.1.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. simple_resume/__init__.py +132 -0
  2. simple_resume/core/__init__.py +47 -0
  3. simple_resume/core/colors.py +215 -0
  4. simple_resume/core/config.py +672 -0
  5. simple_resume/core/constants/__init__.py +207 -0
  6. simple_resume/core/constants/colors.py +98 -0
  7. simple_resume/core/constants/files.py +28 -0
  8. simple_resume/core/constants/layout.py +58 -0
  9. simple_resume/core/dependencies.py +258 -0
  10. simple_resume/core/effects.py +154 -0
  11. simple_resume/core/exceptions.py +261 -0
  12. simple_resume/core/file_operations.py +68 -0
  13. simple_resume/core/generate/__init__.py +21 -0
  14. simple_resume/core/generate/exceptions.py +69 -0
  15. simple_resume/core/generate/html.py +233 -0
  16. simple_resume/core/generate/pdf.py +659 -0
  17. simple_resume/core/generate/plan.py +131 -0
  18. simple_resume/core/hydration.py +55 -0
  19. simple_resume/core/importers/__init__.py +3 -0
  20. simple_resume/core/importers/json_resume.py +284 -0
  21. simple_resume/core/latex/__init__.py +60 -0
  22. simple_resume/core/latex/context.py +56 -0
  23. simple_resume/core/latex/conversion.py +227 -0
  24. simple_resume/core/latex/escaping.py +68 -0
  25. simple_resume/core/latex/fonts.py +93 -0
  26. simple_resume/core/latex/formatting.py +81 -0
  27. simple_resume/core/latex/sections.py +218 -0
  28. simple_resume/core/latex/types.py +84 -0
  29. simple_resume/core/markdown.py +127 -0
  30. simple_resume/core/models.py +102 -0
  31. simple_resume/core/palettes/__init__.py +38 -0
  32. simple_resume/core/palettes/common.py +73 -0
  33. simple_resume/core/palettes/data/default_palettes.json +58 -0
  34. simple_resume/core/palettes/exceptions.py +33 -0
  35. simple_resume/core/palettes/fetch_types.py +52 -0
  36. simple_resume/core/palettes/generators.py +137 -0
  37. simple_resume/core/palettes/registry.py +76 -0
  38. simple_resume/core/palettes/resolution.py +123 -0
  39. simple_resume/core/palettes/sources.py +162 -0
  40. simple_resume/core/paths.py +21 -0
  41. simple_resume/core/protocols.py +134 -0
  42. simple_resume/core/py.typed +0 -0
  43. simple_resume/core/render/__init__.py +37 -0
  44. simple_resume/core/render/manage.py +199 -0
  45. simple_resume/core/render/plan.py +405 -0
  46. simple_resume/core/result.py +226 -0
  47. simple_resume/core/resume.py +609 -0
  48. simple_resume/core/skills.py +60 -0
  49. simple_resume/core/validation.py +321 -0
  50. simple_resume/py.typed +0 -0
  51. simple_resume/shell/__init__.py +3 -0
  52. simple_resume/shell/assets/static/css/README.md +213 -0
  53. simple_resume/shell/assets/static/css/common.css +641 -0
  54. simple_resume/shell/assets/static/css/fonts.css +42 -0
  55. simple_resume/shell/assets/static/css/preview.css +82 -0
  56. simple_resume/shell/assets/static/css/print.css +99 -0
  57. simple_resume/shell/assets/static/fonts/AvenirLTStd-Book.otf +0 -0
  58. simple_resume/shell/assets/static/fonts/AvenirLTStd-Light.otf +0 -0
  59. simple_resume/shell/assets/static/fonts/AvenirLTStd-Medium.otf +0 -0
  60. simple_resume/shell/assets/static/fonts/AvenirLTStd-Oblique.otf +0 -0
  61. simple_resume/shell/assets/static/fonts/AvenirLTStd-Roman.otf +0 -0
  62. simple_resume/shell/assets/static/fonts/fontawesome/Font Awesome 6 Brands-Regular-400.otf +0 -0
  63. simple_resume/shell/assets/static/fonts/fontawesome/Font Awesome 6 Free-Solid-900.otf +0 -0
  64. simple_resume/shell/assets/static/images/default_profile_1.jpg +0 -0
  65. simple_resume/shell/assets/static/images/default_profile_2.png +0 -0
  66. simple_resume/shell/assets/static/schema.json +236 -0
  67. simple_resume/shell/assets/static/themes/README.md +208 -0
  68. simple_resume/shell/assets/static/themes/bold.yaml +64 -0
  69. simple_resume/shell/assets/static/themes/classic.yaml +64 -0
  70. simple_resume/shell/assets/static/themes/executive.yaml +64 -0
  71. simple_resume/shell/assets/static/themes/minimal.yaml +64 -0
  72. simple_resume/shell/assets/static/themes/modern.yaml +64 -0
  73. simple_resume/shell/assets/templates/html/cover.html +129 -0
  74. simple_resume/shell/assets/templates/html/demo.html +13 -0
  75. simple_resume/shell/assets/templates/html/resume_base.html +453 -0
  76. simple_resume/shell/assets/templates/html/resume_no_bars.html +316 -0
  77. simple_resume/shell/assets/templates/html/resume_with_bars.html +362 -0
  78. simple_resume/shell/cli/__init__.py +35 -0
  79. simple_resume/shell/cli/main.py +975 -0
  80. simple_resume/shell/cli/palette.py +75 -0
  81. simple_resume/shell/cli/random_palette_demo.py +407 -0
  82. simple_resume/shell/config.py +96 -0
  83. simple_resume/shell/effect_executor.py +211 -0
  84. simple_resume/shell/file_opener.py +308 -0
  85. simple_resume/shell/generate/__init__.py +37 -0
  86. simple_resume/shell/generate/core.py +650 -0
  87. simple_resume/shell/generate/lazy.py +284 -0
  88. simple_resume/shell/io_utils.py +199 -0
  89. simple_resume/shell/palettes/__init__.py +1 -0
  90. simple_resume/shell/palettes/fetch.py +63 -0
  91. simple_resume/shell/palettes/loader.py +321 -0
  92. simple_resume/shell/palettes/remote.py +179 -0
  93. simple_resume/shell/pdf_executor.py +52 -0
  94. simple_resume/shell/py.typed +0 -0
  95. simple_resume/shell/render/__init__.py +1 -0
  96. simple_resume/shell/render/latex.py +308 -0
  97. simple_resume/shell/render/operations.py +240 -0
  98. simple_resume/shell/resume_extensions.py +737 -0
  99. simple_resume/shell/runtime/__init__.py +7 -0
  100. simple_resume/shell/runtime/content.py +190 -0
  101. simple_resume/shell/runtime/generate.py +497 -0
  102. simple_resume/shell/runtime/lazy.py +138 -0
  103. simple_resume/shell/runtime/lazy_import.py +173 -0
  104. simple_resume/shell/service_locator.py +80 -0
  105. simple_resume/shell/services.py +256 -0
  106. simple_resume/shell/session/__init__.py +6 -0
  107. simple_resume/shell/session/config.py +35 -0
  108. simple_resume/shell/session/manage.py +386 -0
  109. simple_resume/shell/strategies.py +181 -0
  110. simple_resume/shell/themes/__init__.py +35 -0
  111. simple_resume/shell/themes/loader.py +230 -0
  112. simple_resume-0.1.9.dist-info/METADATA +201 -0
  113. simple_resume-0.1.9.dist-info/RECORD +116 -0
  114. simple_resume-0.1.9.dist-info/WHEEL +4 -0
  115. simple_resume-0.1.9.dist-info/entry_points.txt +5 -0
  116. simple_resume-0.1.9.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env python3
2
+ """Define the Simple Resume public API.
3
+
4
+ Symbols listed in `:data:simple_resume.__all__` are covered by the
5
+ stability contract, mirroring pandas' curated ``pandas.api`` surface.
6
+ Other components (utility helpers, palette plumbing, rendering shell, etc.)
7
+ reside under `:mod:simple_resume.core` and `:mod:simple_resume.shell`
8
+ and may change without notice. Import from those modules only if prepared
9
+ to track upstream changes.
10
+
11
+ High-level categories include:
12
+
13
+ * **Core models** – `:class:Resume`, `:class:ResumeConfig`, and
14
+ `:class:RenderPlan` represent resumes and render plans.
15
+ * **Sessions & results** – `:class:ResumeSession`, `:class:SessionConfig`,
16
+ `:class:GenerationResult`, and `:class:BatchGenerationResult`.
17
+ * **Generation helpers** – ``generate_pdf/html/all/resume`` plus new
18
+ convenience wrappers `:func:generate` and `:func:preview` for one-liner
19
+ workflows, similar to ``requests`` verb helpers.
20
+ * **FCIS Architecture** – Functional core in `:mod:simple_resume.core`
21
+ (e.g., `:mod:simple_resume.core.colors`) provides pure functions,
22
+ while shell layer handles I/O and side effects.
23
+
24
+ Refer to ``README.md`` and ``wiki/architecture/ADR003-api-surface-design.md``
25
+ for the API map and stability notes.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ # Exception hierarchy
31
+ from simple_resume.core.exceptions import (
32
+ ConfigurationError,
33
+ FileSystemError,
34
+ GenerationError,
35
+ PaletteError,
36
+ SessionError,
37
+ SimpleResumeError,
38
+ TemplateError,
39
+ ValidationError,
40
+ )
41
+
42
+ # Core classes (data models only - no I/O methods)
43
+ from simple_resume.core.models import (
44
+ GenerationConfig,
45
+ RenderPlan,
46
+ ResumeConfig,
47
+ ValidationResult,
48
+ )
49
+ from simple_resume.core.resume import Resume
50
+
51
+ # Public API namespaces - higher-level generation functions
52
+ from simple_resume.shell.generate import (
53
+ generate,
54
+ generate_all,
55
+ generate_html,
56
+ generate_pdf,
57
+ generate_resume,
58
+ preview,
59
+ )
60
+
61
+ # Shell layer I/O operations - these are the primary generation functions
62
+ from simple_resume.shell.resume_extensions import (
63
+ generate as resume_generate,
64
+ )
65
+ from simple_resume.shell.resume_extensions import (
66
+ to_html,
67
+ to_pdf,
68
+ )
69
+
70
+ # Rich result objects (lazy-loaded)
71
+ from simple_resume.shell.runtime.lazy_import import (
72
+ lazy_BatchGenerationResult as BatchGenerationResult,
73
+ )
74
+
75
+ # Session management (lazy-loaded)
76
+ from simple_resume.shell.runtime.lazy_import import (
77
+ lazy_create_session as create_session,
78
+ )
79
+ from simple_resume.shell.runtime.lazy_import import (
80
+ lazy_GenerationMetadata as GenerationMetadata,
81
+ )
82
+ from simple_resume.shell.runtime.lazy_import import (
83
+ lazy_GenerationResult as GenerationResult,
84
+ )
85
+ from simple_resume.shell.runtime.lazy_import import (
86
+ lazy_ResumeSession as ResumeSession,
87
+ )
88
+ from simple_resume.shell.runtime.lazy_import import (
89
+ lazy_SessionConfig as SessionConfig,
90
+ )
91
+
92
+ # Version
93
+ __version__ = "0.1.9"
94
+
95
+ # Public API exports - organized by functionality
96
+ __all__ = [
97
+ "__version__",
98
+ # Core models (data only)
99
+ "Resume",
100
+ "ResumeConfig",
101
+ "RenderPlan",
102
+ "ValidationResult",
103
+ # Exceptions
104
+ "SimpleResumeError",
105
+ "ValidationError",
106
+ "ConfigurationError",
107
+ "TemplateError",
108
+ "GenerationError",
109
+ "PaletteError",
110
+ "FileSystemError",
111
+ "SessionError",
112
+ # Results & sessions
113
+ "GenerationResult",
114
+ "GenerationMetadata",
115
+ "BatchGenerationResult",
116
+ "ResumeSession",
117
+ "SessionConfig",
118
+ "create_session",
119
+ # Generation primitives
120
+ "GenerationConfig",
121
+ "generate_pdf",
122
+ "generate_html",
123
+ "generate_all",
124
+ "generate_resume",
125
+ # Shell layer I/O functions
126
+ "to_pdf",
127
+ "to_html",
128
+ "resume_generate",
129
+ # Convenience helpers
130
+ "generate",
131
+ "preview",
132
+ ]
@@ -0,0 +1,47 @@
1
+ """Core resume data transformations - pure functions without side effects."""
2
+
3
+ from simple_resume.core.file_operations import (
4
+ find_yaml_files,
5
+ get_resume_name_from_path,
6
+ iterate_yaml_files,
7
+ )
8
+ from simple_resume.core.models import (
9
+ RenderMode,
10
+ RenderPlan,
11
+ ResumeConfig,
12
+ ValidationResult,
13
+ )
14
+ from simple_resume.core.render import (
15
+ prepare_html_generation_request,
16
+ prepare_pdf_generation_request,
17
+ validate_render_plan,
18
+ )
19
+ from simple_resume.core.render.plan import (
20
+ build_render_plan,
21
+ normalize_with_palette_fallback,
22
+ prepare_render_data,
23
+ transform_for_mode,
24
+ validate_resume_config,
25
+ validate_resume_config_or_raise,
26
+ )
27
+ from simple_resume.core.resume import Resume
28
+
29
+ __all__ = [
30
+ "Resume",
31
+ "ResumeConfig",
32
+ "RenderPlan",
33
+ "ValidationResult",
34
+ "RenderMode",
35
+ "find_yaml_files",
36
+ "get_resume_name_from_path",
37
+ "iterate_yaml_files",
38
+ "build_render_plan",
39
+ "normalize_with_palette_fallback",
40
+ "prepare_render_data",
41
+ "transform_for_mode",
42
+ "validate_resume_config",
43
+ "validate_resume_config_or_raise",
44
+ "prepare_html_generation_request",
45
+ "prepare_pdf_generation_request",
46
+ "validate_render_plan",
47
+ ]
@@ -0,0 +1,215 @@
1
+ """Color calculation helpers that belong to the core domain layer.
2
+
3
+ This module centralizes hex parsing, luminance math, and contrast helpers so
4
+ domain services (and utility shims) do not need to re-implement
5
+ workarounds to avoid circular imports.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ from typing import Any
12
+
13
+ from simple_resume.core.constants.colors import (
14
+ BOLD_DARKEN_FACTOR,
15
+ DEFAULT_COLOR_SCHEME,
16
+ HEX_COLOR_FULL_LENGTH,
17
+ HEX_COLOR_SHORT_LENGTH,
18
+ ICON_CONTRAST_THRESHOLD,
19
+ LUMINANCE_DARK,
20
+ LUMINANCE_VERY_DARK,
21
+ LUMINANCE_VERY_LIGHT,
22
+ WCAG_LINEARIZATION_DIVISOR,
23
+ WCAG_LINEARIZATION_EXPONENT,
24
+ WCAG_LINEARIZATION_OFFSET,
25
+ WCAG_LINEARIZATION_THRESHOLD,
26
+ )
27
+
28
+ __all__ = [
29
+ "calculate_contrast_ratio",
30
+ "calculate_icon_contrast_color",
31
+ "calculate_luminance",
32
+ "ColorCalculationService",
33
+ "darken_color",
34
+ "get_contrasting_text_color",
35
+ "hex_to_rgb",
36
+ "is_valid_color",
37
+ ]
38
+
39
+
40
+ def hex_to_rgb(hex_color: str) -> tuple[int, int, int]:
41
+ """Convert hex color to an RGB tuple."""
42
+ processed = hex_color.lstrip("#")
43
+ if len(processed) == HEX_COLOR_SHORT_LENGTH:
44
+ processed = "".join(char * 2 for char in processed)
45
+ if len(processed) != HEX_COLOR_FULL_LENGTH:
46
+ raise ValueError(f"Invalid hex color: {hex_color}")
47
+ try:
48
+ r = int(processed[0:2], 16)
49
+ g = int(processed[2:4], 16)
50
+ b = int(processed[4:6], 16)
51
+ return r, g, b
52
+ except ValueError as exc: # pragma: no cover - defensive
53
+ raise ValueError(f"Invalid hex color: {hex_color}") from exc
54
+
55
+
56
+ def darken_color(hex_color: str, factor: float) -> str:
57
+ """Return a darker variant of the provided hex color."""
58
+ try:
59
+ rgb = hex_to_rgb(hex_color)
60
+ except ValueError:
61
+ return "#585858"
62
+
63
+ darkened = tuple(max(0, min(255, round(component * factor))) for component in rgb)
64
+ return "#{:02X}{:02X}{:02X}".format(*darkened)
65
+
66
+
67
+ def _calculate_luminance_from_rgb(rgb: tuple[int, int, int]) -> float:
68
+ """Calculate relative luminance from an RGB tuple using WCAG 2.1 formula.
69
+
70
+ Args:
71
+ rgb: An RGB color tuple (e.g., (255, 255, 255)).
72
+
73
+ Returns:
74
+ The relative luminance (0.0 to 1.0).
75
+
76
+ """
77
+ r, g, b = rgb
78
+
79
+ def _linearize(component: int) -> float:
80
+ value = component / 255.0
81
+ return (
82
+ value / WCAG_LINEARIZATION_DIVISOR
83
+ if value <= WCAG_LINEARIZATION_THRESHOLD
84
+ else ((value + WCAG_LINEARIZATION_OFFSET) / (1 + WCAG_LINEARIZATION_OFFSET))
85
+ ** WCAG_LINEARIZATION_EXPONENT
86
+ )
87
+
88
+ r_linear = _linearize(r)
89
+ g_linear = _linearize(g)
90
+ b_linear = _linearize(b)
91
+
92
+ return 0.2126 * r_linear + 0.7152 * g_linear + 0.0722 * b_linear
93
+
94
+
95
+ def calculate_luminance(hex_color: str) -> float:
96
+ """Return the relative luminance for ``hex_color``."""
97
+ rgb = hex_to_rgb(hex_color)
98
+ return _calculate_luminance_from_rgb(rgb)
99
+
100
+
101
+ def calculate_contrast_ratio(color1: str, color2: str) -> float:
102
+ """Return the WCAG contrast ratio between two hex colors."""
103
+ rgb1 = hex_to_rgb(color1)
104
+ rgb2 = hex_to_rgb(color2)
105
+ lum1 = _calculate_luminance_from_rgb(rgb1)
106
+ lum2 = _calculate_luminance_from_rgb(rgb2)
107
+ lighter = max(lum1, lum2)
108
+ darker = min(lum1, lum2)
109
+ return (lighter + 0.05) / (darker + 0.05)
110
+
111
+
112
+ def get_contrasting_text_color(background_color: str) -> str:
113
+ """Return a readable text color for the provided background."""
114
+ try:
115
+ luminance = calculate_luminance(background_color)
116
+ if luminance <= LUMINANCE_VERY_DARK:
117
+ return "#F5F5F5"
118
+ if luminance <= LUMINANCE_DARK:
119
+ return "#FFFFFF"
120
+ if luminance >= LUMINANCE_VERY_LIGHT:
121
+ return "#333333"
122
+ return "#000000"
123
+ except (ValueError, TypeError): # pragma: no cover - defensive
124
+ return "#000000"
125
+
126
+
127
+ def calculate_icon_contrast_color(
128
+ user_color: str | None,
129
+ background_color: str,
130
+ *,
131
+ contrast_threshold: float = ICON_CONTRAST_THRESHOLD,
132
+ ) -> str:
133
+ """Return an icon color that respects the WCAG AA contrast threshold."""
134
+ if user_color and is_valid_color(user_color):
135
+ ratio = calculate_contrast_ratio(user_color, background_color)
136
+ if ratio >= contrast_threshold:
137
+ return user_color
138
+ return get_contrasting_text_color(background_color)
139
+
140
+
141
+ def is_valid_color(color: str) -> bool:
142
+ """Check if a color string is a valid hex color code."""
143
+ if not color:
144
+ return False
145
+ return bool(re.match(r"^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$", color))
146
+
147
+
148
+ class ColorCalculationService:
149
+ """Service for calculating various color values based on configuration."""
150
+
151
+ @staticmethod
152
+ def calculate_sidebar_text_color(config: dict[str, Any]) -> str:
153
+ """Calculate sidebar text color based on sidebar background."""
154
+ sidebar_color = config.get("sidebar_color")
155
+ if sidebar_color and is_valid_color(sidebar_color):
156
+ return get_contrasting_text_color(sidebar_color)
157
+ return DEFAULT_COLOR_SCHEME.get("sidebar_text_color", "#000000")
158
+
159
+ @staticmethod
160
+ def calculate_sidebar_bold_color(config: dict[str, Any]) -> str:
161
+ """Calculate sidebar bold color based on sidebar background."""
162
+ sidebar_color = config.get("sidebar_color")
163
+ if sidebar_color and is_valid_color(sidebar_color):
164
+ # Make the bold color slightly darker than the background
165
+ return darken_color(sidebar_color, BOLD_DARKEN_FACTOR)
166
+ return "#000000" # Default black
167
+
168
+ @staticmethod
169
+ def calculate_heading_icon_color(config: dict[str, Any]) -> str:
170
+ """Calculate heading icon color based on configuration."""
171
+ user_icon_color = config.get("heading_icon_color")
172
+ frame_color = config.get("frame_color", "#000000")
173
+
174
+ if isinstance(user_icon_color, str) and is_valid_color(user_icon_color):
175
+ return user_icon_color
176
+ theme_color = config.get("theme_color")
177
+ if isinstance(theme_color, str) and is_valid_color(theme_color):
178
+ return theme_color
179
+ return str(frame_color)
180
+
181
+ @staticmethod
182
+ def calculate_sidebar_icon_color(config: dict[str, Any]) -> str:
183
+ """Calculate sidebar icon color based on configuration."""
184
+ sidebar_color = config.get("sidebar_color")
185
+ if sidebar_color and is_valid_color(sidebar_color):
186
+ return get_contrasting_text_color(sidebar_color)
187
+ return "#FFFFFF" # Default white icon
188
+
189
+ @staticmethod
190
+ def ensure_color_contrast(
191
+ background: str,
192
+ text_color: str,
193
+ *,
194
+ contrast_threshold: float = 3.0,
195
+ ) -> str:
196
+ """Ensure text color has sufficient contrast against background.
197
+
198
+ Args:
199
+ background: Background color in hex format
200
+ text_color: Text color in hex format
201
+ contrast_threshold: Minimum contrast ratio required
202
+
203
+ Returns:
204
+ Original text color if contrast is sufficient, otherwise a fallback color
205
+
206
+ """
207
+ if not is_valid_color(background) or not is_valid_color(text_color):
208
+ return "#333333" # Default fallback color
209
+
210
+ contrast = calculate_contrast_ratio(text_color, background)
211
+ if contrast >= contrast_threshold:
212
+ return text_color
213
+
214
+ # Return fallback color with better contrast
215
+ return get_contrasting_text_color(background)