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.
- simple_resume/__init__.py +132 -0
- simple_resume/core/__init__.py +47 -0
- simple_resume/core/colors.py +215 -0
- simple_resume/core/config.py +672 -0
- simple_resume/core/constants/__init__.py +207 -0
- simple_resume/core/constants/colors.py +98 -0
- simple_resume/core/constants/files.py +28 -0
- simple_resume/core/constants/layout.py +58 -0
- simple_resume/core/dependencies.py +258 -0
- simple_resume/core/effects.py +154 -0
- simple_resume/core/exceptions.py +261 -0
- simple_resume/core/file_operations.py +68 -0
- simple_resume/core/generate/__init__.py +21 -0
- simple_resume/core/generate/exceptions.py +69 -0
- simple_resume/core/generate/html.py +233 -0
- simple_resume/core/generate/pdf.py +659 -0
- simple_resume/core/generate/plan.py +131 -0
- simple_resume/core/hydration.py +55 -0
- simple_resume/core/importers/__init__.py +3 -0
- simple_resume/core/importers/json_resume.py +284 -0
- simple_resume/core/latex/__init__.py +60 -0
- simple_resume/core/latex/context.py +56 -0
- simple_resume/core/latex/conversion.py +227 -0
- simple_resume/core/latex/escaping.py +68 -0
- simple_resume/core/latex/fonts.py +93 -0
- simple_resume/core/latex/formatting.py +81 -0
- simple_resume/core/latex/sections.py +218 -0
- simple_resume/core/latex/types.py +84 -0
- simple_resume/core/markdown.py +127 -0
- simple_resume/core/models.py +102 -0
- simple_resume/core/palettes/__init__.py +38 -0
- simple_resume/core/palettes/common.py +73 -0
- simple_resume/core/palettes/data/default_palettes.json +58 -0
- simple_resume/core/palettes/exceptions.py +33 -0
- simple_resume/core/palettes/fetch_types.py +52 -0
- simple_resume/core/palettes/generators.py +137 -0
- simple_resume/core/palettes/registry.py +76 -0
- simple_resume/core/palettes/resolution.py +123 -0
- simple_resume/core/palettes/sources.py +162 -0
- simple_resume/core/paths.py +21 -0
- simple_resume/core/protocols.py +134 -0
- simple_resume/core/py.typed +0 -0
- simple_resume/core/render/__init__.py +37 -0
- simple_resume/core/render/manage.py +199 -0
- simple_resume/core/render/plan.py +405 -0
- simple_resume/core/result.py +226 -0
- simple_resume/core/resume.py +609 -0
- simple_resume/core/skills.py +60 -0
- simple_resume/core/validation.py +321 -0
- simple_resume/py.typed +0 -0
- simple_resume/shell/__init__.py +3 -0
- simple_resume/shell/assets/static/css/README.md +213 -0
- simple_resume/shell/assets/static/css/common.css +641 -0
- simple_resume/shell/assets/static/css/fonts.css +42 -0
- simple_resume/shell/assets/static/css/preview.css +82 -0
- simple_resume/shell/assets/static/css/print.css +99 -0
- simple_resume/shell/assets/static/fonts/AvenirLTStd-Book.otf +0 -0
- simple_resume/shell/assets/static/fonts/AvenirLTStd-Light.otf +0 -0
- simple_resume/shell/assets/static/fonts/AvenirLTStd-Medium.otf +0 -0
- simple_resume/shell/assets/static/fonts/AvenirLTStd-Oblique.otf +0 -0
- simple_resume/shell/assets/static/fonts/AvenirLTStd-Roman.otf +0 -0
- simple_resume/shell/assets/static/fonts/fontawesome/Font Awesome 6 Brands-Regular-400.otf +0 -0
- simple_resume/shell/assets/static/fonts/fontawesome/Font Awesome 6 Free-Solid-900.otf +0 -0
- simple_resume/shell/assets/static/images/default_profile_1.jpg +0 -0
- simple_resume/shell/assets/static/images/default_profile_2.png +0 -0
- simple_resume/shell/assets/static/schema.json +236 -0
- simple_resume/shell/assets/static/themes/README.md +208 -0
- simple_resume/shell/assets/static/themes/bold.yaml +64 -0
- simple_resume/shell/assets/static/themes/classic.yaml +64 -0
- simple_resume/shell/assets/static/themes/executive.yaml +64 -0
- simple_resume/shell/assets/static/themes/minimal.yaml +64 -0
- simple_resume/shell/assets/static/themes/modern.yaml +64 -0
- simple_resume/shell/assets/templates/html/cover.html +129 -0
- simple_resume/shell/assets/templates/html/demo.html +13 -0
- simple_resume/shell/assets/templates/html/resume_base.html +453 -0
- simple_resume/shell/assets/templates/html/resume_no_bars.html +316 -0
- simple_resume/shell/assets/templates/html/resume_with_bars.html +362 -0
- simple_resume/shell/cli/__init__.py +35 -0
- simple_resume/shell/cli/main.py +975 -0
- simple_resume/shell/cli/palette.py +75 -0
- simple_resume/shell/cli/random_palette_demo.py +407 -0
- simple_resume/shell/config.py +96 -0
- simple_resume/shell/effect_executor.py +211 -0
- simple_resume/shell/file_opener.py +308 -0
- simple_resume/shell/generate/__init__.py +37 -0
- simple_resume/shell/generate/core.py +650 -0
- simple_resume/shell/generate/lazy.py +284 -0
- simple_resume/shell/io_utils.py +199 -0
- simple_resume/shell/palettes/__init__.py +1 -0
- simple_resume/shell/palettes/fetch.py +63 -0
- simple_resume/shell/palettes/loader.py +321 -0
- simple_resume/shell/palettes/remote.py +179 -0
- simple_resume/shell/pdf_executor.py +52 -0
- simple_resume/shell/py.typed +0 -0
- simple_resume/shell/render/__init__.py +1 -0
- simple_resume/shell/render/latex.py +308 -0
- simple_resume/shell/render/operations.py +240 -0
- simple_resume/shell/resume_extensions.py +737 -0
- simple_resume/shell/runtime/__init__.py +7 -0
- simple_resume/shell/runtime/content.py +190 -0
- simple_resume/shell/runtime/generate.py +497 -0
- simple_resume/shell/runtime/lazy.py +138 -0
- simple_resume/shell/runtime/lazy_import.py +173 -0
- simple_resume/shell/service_locator.py +80 -0
- simple_resume/shell/services.py +256 -0
- simple_resume/shell/session/__init__.py +6 -0
- simple_resume/shell/session/config.py +35 -0
- simple_resume/shell/session/manage.py +386 -0
- simple_resume/shell/strategies.py +181 -0
- simple_resume/shell/themes/__init__.py +35 -0
- simple_resume/shell/themes/loader.py +230 -0
- simple_resume-0.1.9.dist-info/METADATA +201 -0
- simple_resume-0.1.9.dist-info/RECORD +116 -0
- simple_resume-0.1.9.dist-info/WHEEL +4 -0
- simple_resume-0.1.9.dist-info/entry_points.txt +5 -0
- simple_resume-0.1.9.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""Centralized constants for simple-resume.
|
|
2
|
+
|
|
3
|
+
This package contains core constants and enums for resume generation.
|
|
4
|
+
Domain-specific constants are organized in separate submodules.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Final
|
|
11
|
+
|
|
12
|
+
# =============================================================================
|
|
13
|
+
# CLI Exit Codes
|
|
14
|
+
# =============================================================================
|
|
15
|
+
|
|
16
|
+
EXIT_SUCCESS: Final[int] = 0
|
|
17
|
+
EXIT_SIGINT: Final[int] = 130 # Ctrl+C cancellation
|
|
18
|
+
EXIT_FILE_SYSTEM_ERROR: Final[int] = 2
|
|
19
|
+
EXIT_INTERNAL_ERROR: Final[int] = 3
|
|
20
|
+
EXIT_RESOURCE_ERROR: Final[int] = 4
|
|
21
|
+
EXIT_INPUT_ERROR: Final[int] = 5
|
|
22
|
+
EXIT_GENERIC_ERROR: Final[int] = 1
|
|
23
|
+
|
|
24
|
+
# =============================================================================
|
|
25
|
+
# Error Messages
|
|
26
|
+
# =============================================================================
|
|
27
|
+
|
|
28
|
+
ERROR_UNKNOWN_COMMAND: Final[str] = "Unknown command"
|
|
29
|
+
ERROR_FILE_NOT_FOUND: Final[str] = "Resume file not found"
|
|
30
|
+
ERROR_INVALID_FORMAT: Final[str] = "Invalid format"
|
|
31
|
+
ERROR_PERMISSION_DENIED: Final[str] = "Permission denied"
|
|
32
|
+
|
|
33
|
+
# =============================================================================
|
|
34
|
+
# Process and Resource Limits
|
|
35
|
+
# =============================================================================
|
|
36
|
+
|
|
37
|
+
DEFAULT_PROCESS_TIMEOUT_SECONDS: Final[int] = 30
|
|
38
|
+
MAX_RESUME_SIZE_MB: Final[int] = 10
|
|
39
|
+
MAX_PALETTE_SIZE_MB: Final[int] = 1
|
|
40
|
+
|
|
41
|
+
# =============================================================================
|
|
42
|
+
# File Extensions
|
|
43
|
+
# =============================================================================
|
|
44
|
+
|
|
45
|
+
PDF_EXTENSION: Final[str] = ".pdf"
|
|
46
|
+
HTML_EXTENSION: Final[str] = ".html"
|
|
47
|
+
TEX_EXTENSION: Final[str] = ".tex"
|
|
48
|
+
MARKDOWN_EXTENSION: Final[str] = ".md"
|
|
49
|
+
|
|
50
|
+
# =============================================================================
|
|
51
|
+
# Default Values
|
|
52
|
+
# =============================================================================
|
|
53
|
+
|
|
54
|
+
# Default values will be set after enum definitions
|
|
55
|
+
DEFAULT_FORMAT: str
|
|
56
|
+
DEFAULT_TEMPLATE: str
|
|
57
|
+
|
|
58
|
+
# =============================================================================
|
|
59
|
+
# Configuration
|
|
60
|
+
# =============================================================================
|
|
61
|
+
|
|
62
|
+
MIN_FILENAME_PARTS: Final[int] = 2
|
|
63
|
+
ALLOWED_PATH_OVERRIDES: Final[set[str]] = {"content_dir", "templates_dir", "static_dir"}
|
|
64
|
+
|
|
65
|
+
# =============================================================================
|
|
66
|
+
# Validation
|
|
67
|
+
# =============================================================================
|
|
68
|
+
|
|
69
|
+
MAX_FILE_SIZE_MB: Final[int] = 50
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class OutputFormat(str, Enum):
|
|
73
|
+
"""Define supported output formats for resume generation.
|
|
74
|
+
|
|
75
|
+
Final formats (require rendering):
|
|
76
|
+
PDF: Portable Document Format
|
|
77
|
+
HTML: HyperText Markup Language
|
|
78
|
+
|
|
79
|
+
Intermediate formats (editable before final render):
|
|
80
|
+
MARKDOWN: Markdown intermediate (for HTML path)
|
|
81
|
+
TEX: LaTeX intermediate (for PDF path)
|
|
82
|
+
LATEX: Alias for TEX (deprecated, use TEX)
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
PDF = "pdf"
|
|
86
|
+
HTML = "html"
|
|
87
|
+
MARKDOWN = "markdown"
|
|
88
|
+
TEX = "tex"
|
|
89
|
+
LATEX = "latex" # Alias for TEX, kept for backwards compatibility
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def values(cls) -> set[str]:
|
|
93
|
+
"""Return a set of all format values including aliases."""
|
|
94
|
+
return {
|
|
95
|
+
cls.PDF.value,
|
|
96
|
+
cls.HTML.value,
|
|
97
|
+
cls.MARKDOWN.value,
|
|
98
|
+
cls.TEX.value,
|
|
99
|
+
cls.LATEX.value,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@classmethod
|
|
103
|
+
def intermediate_formats(cls) -> set[OutputFormat]:
|
|
104
|
+
"""Return the set of intermediate (non-final) output formats."""
|
|
105
|
+
return {cls.MARKDOWN, cls.TEX}
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def is_intermediate(cls, fmt: OutputFormat) -> bool:
|
|
109
|
+
"""Check if a format is an intermediate format."""
|
|
110
|
+
return fmt in cls.intermediate_formats()
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def is_valid(cls, format_str: str) -> bool:
|
|
114
|
+
"""Check if a format string is valid."""
|
|
115
|
+
return format_str.lower() in cls.values()
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def normalize(
|
|
119
|
+
cls, value: str | OutputFormat, *, param_name: str | None = None
|
|
120
|
+
) -> OutputFormat:
|
|
121
|
+
"""Convert arbitrary input into an `OutputFormat` enum member."""
|
|
122
|
+
if isinstance(value, cls):
|
|
123
|
+
return value
|
|
124
|
+
if not isinstance(value, str):
|
|
125
|
+
raise TypeError(
|
|
126
|
+
"Output format must be provided as string or OutputFormat, "
|
|
127
|
+
f"got {type(value)}"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
normalized = value.strip().lower()
|
|
131
|
+
try:
|
|
132
|
+
return cls(normalized)
|
|
133
|
+
except ValueError as exc: # pragma: no cover - defensive path
|
|
134
|
+
label = f"{param_name} " if param_name else ""
|
|
135
|
+
raise ValueError(
|
|
136
|
+
f"Unsupported {label}format: {value}. "
|
|
137
|
+
f"Supported formats: {', '.join(sorted(cls.values()))}"
|
|
138
|
+
) from exc
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class TemplateType(str, Enum):
|
|
142
|
+
"""Define available resume templates."""
|
|
143
|
+
|
|
144
|
+
NO_BARS = "resume_no_bars"
|
|
145
|
+
WITH_BARS = "resume_with_bars"
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def values(cls) -> set[str]:
|
|
149
|
+
"""Return a set of all template values."""
|
|
150
|
+
return {cls.NO_BARS.value, cls.WITH_BARS.value}
|
|
151
|
+
|
|
152
|
+
@classmethod
|
|
153
|
+
def is_valid(cls, template_str: str) -> bool:
|
|
154
|
+
"""Check if a template string is valid."""
|
|
155
|
+
return template_str in cls.values()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class RenderMode(str, Enum):
|
|
159
|
+
"""Define rendering modes for resume generation."""
|
|
160
|
+
|
|
161
|
+
HTML = "html"
|
|
162
|
+
LATEX = "latex"
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# Set defaults using enum values
|
|
166
|
+
DEFAULT_FORMAT = OutputFormat.PDF.value
|
|
167
|
+
DEFAULT_TEMPLATE = TemplateType.NO_BARS.value
|
|
168
|
+
SUPPORTED_FORMATS: Final[set[str]] = OutputFormat.values()
|
|
169
|
+
SUPPORTED_TEMPLATES: Final[set[str]] = TemplateType.values()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
__all__ = [
|
|
173
|
+
# Exit codes
|
|
174
|
+
"EXIT_SUCCESS",
|
|
175
|
+
"EXIT_SIGINT",
|
|
176
|
+
"EXIT_FILE_SYSTEM_ERROR",
|
|
177
|
+
"EXIT_INTERNAL_ERROR",
|
|
178
|
+
"EXIT_RESOURCE_ERROR",
|
|
179
|
+
"EXIT_INPUT_ERROR",
|
|
180
|
+
"EXIT_GENERIC_ERROR",
|
|
181
|
+
# Error messages
|
|
182
|
+
"ERROR_UNKNOWN_COMMAND",
|
|
183
|
+
"ERROR_FILE_NOT_FOUND",
|
|
184
|
+
"ERROR_INVALID_FORMAT",
|
|
185
|
+
"ERROR_PERMISSION_DENIED",
|
|
186
|
+
# Process and resource limits
|
|
187
|
+
"DEFAULT_PROCESS_TIMEOUT_SECONDS",
|
|
188
|
+
"MAX_RESUME_SIZE_MB",
|
|
189
|
+
"MAX_PALETTE_SIZE_MB",
|
|
190
|
+
"MAX_FILE_SIZE_MB",
|
|
191
|
+
# File extensions
|
|
192
|
+
"PDF_EXTENSION",
|
|
193
|
+
"HTML_EXTENSION",
|
|
194
|
+
"TEX_EXTENSION",
|
|
195
|
+
"MARKDOWN_EXTENSION",
|
|
196
|
+
# Defaults and configuration
|
|
197
|
+
"DEFAULT_FORMAT",
|
|
198
|
+
"DEFAULT_TEMPLATE",
|
|
199
|
+
"MIN_FILENAME_PARTS",
|
|
200
|
+
"ALLOWED_PATH_OVERRIDES",
|
|
201
|
+
"SUPPORTED_FORMATS",
|
|
202
|
+
"SUPPORTED_TEMPLATES",
|
|
203
|
+
# Enums
|
|
204
|
+
"OutputFormat",
|
|
205
|
+
"TemplateType",
|
|
206
|
+
"RenderMode",
|
|
207
|
+
]
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Color-related constants for simple-resume."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Final
|
|
6
|
+
|
|
7
|
+
DEFAULT_COLOR_SCHEME: Final[dict[str, str]] = {
|
|
8
|
+
"theme_color": "#0395DE",
|
|
9
|
+
"sidebar_color": "#F6F6F6",
|
|
10
|
+
"sidebar_text_color": "#000000",
|
|
11
|
+
"bar_background_color": "#DFDFDF",
|
|
12
|
+
"date2_color": "#616161",
|
|
13
|
+
"frame_color": "#757575",
|
|
14
|
+
"heading_icon_color": "#0395DE",
|
|
15
|
+
"bold_color": "#585858",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
# WCAG 2.1 relative luminance formula constants
|
|
19
|
+
# Reference: https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
|
|
20
|
+
# These constants implement the standard relative luminance calculation
|
|
21
|
+
# for determining color contrast and accessibility compliance.
|
|
22
|
+
WCAG_LINEARIZATION_THRESHOLD: Final[float] = 0.03928
|
|
23
|
+
WCAG_LINEARIZATION_DIVISOR: Final[float] = 12.92
|
|
24
|
+
WCAG_LINEARIZATION_EXPONENT: Final[float] = 2.4
|
|
25
|
+
WCAG_LINEARIZATION_OFFSET: Final[float] = 0.055
|
|
26
|
+
|
|
27
|
+
# Color manipulation constants
|
|
28
|
+
BOLD_DARKEN_FACTOR: Final[float] = 0.75
|
|
29
|
+
SIDEBAR_BOLD_DARKEN_FACTOR: Final[float] = 0.8
|
|
30
|
+
|
|
31
|
+
# Luminance thresholds for color contrast calculations
|
|
32
|
+
LUMINANCE_VERY_DARK: Final[float] = 0.15
|
|
33
|
+
LUMINANCE_DARK: Final[float] = 0.5
|
|
34
|
+
LUMINANCE_VERY_LIGHT: Final[float] = 0.8
|
|
35
|
+
ICON_CONTRAST_THRESHOLD: Final[float] = 3.0
|
|
36
|
+
|
|
37
|
+
# Color Format Constants
|
|
38
|
+
HEX_COLOR_SHORT_LENGTH: Final[int] = 3
|
|
39
|
+
HEX_COLOR_FULL_LENGTH: Final[int] = 6
|
|
40
|
+
|
|
41
|
+
# UI Element Constants
|
|
42
|
+
DEFAULT_BOLD_COLOR: Final[str] = "#585858"
|
|
43
|
+
|
|
44
|
+
# Define color field ordering for palette application
|
|
45
|
+
COLOR_FIELD_ORDER: Final[tuple[str, ...]] = (
|
|
46
|
+
"accent_color",
|
|
47
|
+
"sidebar_color",
|
|
48
|
+
"text_color",
|
|
49
|
+
"emphasis_color",
|
|
50
|
+
"link_color",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Direct color keys that can be specified in configuration
|
|
54
|
+
DIRECT_COLOR_KEYS: Final[set[str]] = {
|
|
55
|
+
"accent_color",
|
|
56
|
+
"sidebar_color",
|
|
57
|
+
"text_color",
|
|
58
|
+
"emphasis_color",
|
|
59
|
+
"link_color",
|
|
60
|
+
"sidebar_text_color",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Resume configuration color ordering (used by palette normalization)
|
|
64
|
+
CONFIG_COLOR_FIELDS: Final[tuple[str, ...]] = (
|
|
65
|
+
"theme_color",
|
|
66
|
+
"sidebar_color",
|
|
67
|
+
"sidebar_text_color",
|
|
68
|
+
"bar_background_color",
|
|
69
|
+
"date2_color",
|
|
70
|
+
"frame_color",
|
|
71
|
+
"heading_icon_color",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
CONFIG_DIRECT_COLOR_KEYS: Final[tuple[str, ...]] = CONFIG_COLOR_FIELDS + (
|
|
75
|
+
"bold_color",
|
|
76
|
+
"sidebar_bold_color",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
__all__ = [
|
|
80
|
+
"DEFAULT_COLOR_SCHEME",
|
|
81
|
+
"WCAG_LINEARIZATION_THRESHOLD",
|
|
82
|
+
"WCAG_LINEARIZATION_DIVISOR",
|
|
83
|
+
"WCAG_LINEARIZATION_EXPONENT",
|
|
84
|
+
"WCAG_LINEARIZATION_OFFSET",
|
|
85
|
+
"BOLD_DARKEN_FACTOR",
|
|
86
|
+
"SIDEBAR_BOLD_DARKEN_FACTOR",
|
|
87
|
+
"LUMINANCE_VERY_DARK",
|
|
88
|
+
"LUMINANCE_DARK",
|
|
89
|
+
"LUMINANCE_VERY_LIGHT",
|
|
90
|
+
"ICON_CONTRAST_THRESHOLD",
|
|
91
|
+
"HEX_COLOR_SHORT_LENGTH",
|
|
92
|
+
"HEX_COLOR_FULL_LENGTH",
|
|
93
|
+
"DEFAULT_BOLD_COLOR",
|
|
94
|
+
"COLOR_FIELD_ORDER",
|
|
95
|
+
"DIRECT_COLOR_KEYS",
|
|
96
|
+
"CONFIG_COLOR_FIELDS",
|
|
97
|
+
"CONFIG_DIRECT_COLOR_KEYS",
|
|
98
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Filesystem and file-format constants for simple-resume."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Final
|
|
6
|
+
|
|
7
|
+
# Supported file extensions
|
|
8
|
+
SUPPORTED_YAML_EXTENSIONS: Final[set[str]] = {".yaml", ".yml"}
|
|
9
|
+
SUPPORTED_YAML_EXTENSIONS_STR: Final[str] = "yaml" # For CLI usage
|
|
10
|
+
|
|
11
|
+
# Default template paths
|
|
12
|
+
DEFAULT_LATEX_TEMPLATE: Final[str] = "latex/basic.tex"
|
|
13
|
+
|
|
14
|
+
# Font scaling constants
|
|
15
|
+
FONTAWESOME_DEFAULT_SCALE: Final[float] = 0.72
|
|
16
|
+
|
|
17
|
+
# Byte conversion constants
|
|
18
|
+
BYTES_PER_KB: Final[int] = 1024
|
|
19
|
+
BYTES_PER_MB: Final[int] = 1024 * 1024
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"SUPPORTED_YAML_EXTENSIONS",
|
|
23
|
+
"SUPPORTED_YAML_EXTENSIONS_STR",
|
|
24
|
+
"DEFAULT_LATEX_TEMPLATE",
|
|
25
|
+
"FONTAWESOME_DEFAULT_SCALE",
|
|
26
|
+
"BYTES_PER_KB",
|
|
27
|
+
"BYTES_PER_MB",
|
|
28
|
+
]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Layout and measurement constants for simple-resume."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Final
|
|
6
|
+
|
|
7
|
+
# Default page dimensions in millimeters (A4 paper: 210x297mm)
|
|
8
|
+
DEFAULT_PAGE_WIDTH_MM: Final[int] = 210
|
|
9
|
+
DEFAULT_PAGE_HEIGHT_MM: Final[int] = 297
|
|
10
|
+
|
|
11
|
+
# Default sidebar width in millimeters (A4 standard layout)
|
|
12
|
+
DEFAULT_SIDEBAR_WIDTH_MM: Final[int] = 65
|
|
13
|
+
|
|
14
|
+
# Default padding values in points/millimeters
|
|
15
|
+
DEFAULT_PADDING: Final[int] = 12
|
|
16
|
+
DEFAULT_SIDEBAR_PADDING_ADJUSTMENT: Final[int] = -2
|
|
17
|
+
DEFAULT_SIDEBAR_PADDING: Final[int] = 12
|
|
18
|
+
|
|
19
|
+
# Frame padding values
|
|
20
|
+
DEFAULT_FRAME_PADDING: Final[int] = 10
|
|
21
|
+
|
|
22
|
+
# Cover letter specific padding
|
|
23
|
+
DEFAULT_COVER_PADDING_TOP: Final[int] = 10
|
|
24
|
+
DEFAULT_COVER_PADDING_BOTTOM: Final[int] = 20
|
|
25
|
+
DEFAULT_COVER_PADDING_HORIZONTAL: Final[int] = 25
|
|
26
|
+
|
|
27
|
+
# Validation constraints
|
|
28
|
+
MIN_PAGE_WIDTH_MM: Final[int] = 100
|
|
29
|
+
MAX_PAGE_WIDTH_MM: Final[int] = 300
|
|
30
|
+
MIN_PAGE_HEIGHT_MM: Final[int] = 150
|
|
31
|
+
MAX_PAGE_HEIGHT_MM: Final[int] = 400
|
|
32
|
+
|
|
33
|
+
MIN_SIDEBAR_WIDTH_MM: Final[int] = 30
|
|
34
|
+
MAX_SIDEBAR_WIDTH_MM: Final[int] = 100
|
|
35
|
+
|
|
36
|
+
MIN_PADDING: Final[int] = 0
|
|
37
|
+
MAX_PADDING: Final[int] = 50
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"DEFAULT_PAGE_WIDTH_MM",
|
|
41
|
+
"DEFAULT_PAGE_HEIGHT_MM",
|
|
42
|
+
"DEFAULT_SIDEBAR_WIDTH_MM",
|
|
43
|
+
"DEFAULT_PADDING",
|
|
44
|
+
"DEFAULT_SIDEBAR_PADDING_ADJUSTMENT",
|
|
45
|
+
"DEFAULT_SIDEBAR_PADDING",
|
|
46
|
+
"DEFAULT_FRAME_PADDING",
|
|
47
|
+
"DEFAULT_COVER_PADDING_TOP",
|
|
48
|
+
"DEFAULT_COVER_PADDING_BOTTOM",
|
|
49
|
+
"DEFAULT_COVER_PADDING_HORIZONTAL",
|
|
50
|
+
"MIN_PAGE_WIDTH_MM",
|
|
51
|
+
"MAX_PAGE_WIDTH_MM",
|
|
52
|
+
"MIN_PAGE_HEIGHT_MM",
|
|
53
|
+
"MAX_PAGE_HEIGHT_MM",
|
|
54
|
+
"MIN_SIDEBAR_WIDTH_MM",
|
|
55
|
+
"MAX_SIDEBAR_WIDTH_MM",
|
|
56
|
+
"MIN_PADDING",
|
|
57
|
+
"MAX_PADDING",
|
|
58
|
+
]
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""Dependency injection container and interfaces for improved testability.
|
|
2
|
+
|
|
3
|
+
This module introduces dependency injection patterns to reduce coupling between
|
|
4
|
+
Session and Resume classes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any, Protocol
|
|
11
|
+
|
|
12
|
+
from simple_resume.core.paths import Paths
|
|
13
|
+
from simple_resume.core.resume import Resume
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SessionConfigProtocol(Protocol):
|
|
17
|
+
"""Minimal session config contract needed by the core."""
|
|
18
|
+
|
|
19
|
+
default_template: str | None
|
|
20
|
+
default_palette: str | None
|
|
21
|
+
preview_mode: bool
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class NullSessionConfig:
|
|
25
|
+
"""Fallback config used when no SessionConfig is supplied."""
|
|
26
|
+
|
|
27
|
+
default_template: str | None = None
|
|
28
|
+
default_palette: str | None = None
|
|
29
|
+
preview_mode: bool = False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ResumeLoader(Protocol):
|
|
33
|
+
"""Protocol for loading resume instances."""
|
|
34
|
+
|
|
35
|
+
def load_resume(
|
|
36
|
+
self,
|
|
37
|
+
name: str,
|
|
38
|
+
*,
|
|
39
|
+
paths: Paths | None = None,
|
|
40
|
+
transform_markdown: bool = True,
|
|
41
|
+
) -> Resume:
|
|
42
|
+
"""Load a resume by name with given configuration."""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ResumeCache(Protocol):
|
|
47
|
+
"""Protocol for caching resume instances."""
|
|
48
|
+
|
|
49
|
+
def get_resume(self, key: str) -> Resume | None:
|
|
50
|
+
"""Get a resume from cache."""
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
def put_resume(self, key: str, resume: Resume) -> None:
|
|
54
|
+
"""Put a resume into cache."""
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
def invalidate_resume(self, key: str | None = None) -> None:
|
|
58
|
+
"""Invalidate cached resume(s)."""
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
def clear_cache(self) -> None:
|
|
62
|
+
"""Clear all cached resumes."""
|
|
63
|
+
...
|
|
64
|
+
|
|
65
|
+
def get_cache_keys(self) -> list[str]:
|
|
66
|
+
"""Get list of cached resume keys."""
|
|
67
|
+
...
|
|
68
|
+
|
|
69
|
+
def get_cache_size(self) -> int:
|
|
70
|
+
"""Get number of cached resumes."""
|
|
71
|
+
...
|
|
72
|
+
|
|
73
|
+
def get_memory_usage(self) -> int:
|
|
74
|
+
"""Get estimated memory usage of cached resumes in bytes."""
|
|
75
|
+
...
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ResumeConfigurator(Protocol):
|
|
79
|
+
"""Protocol for configuring resume instances."""
|
|
80
|
+
|
|
81
|
+
def configure_resume(self, resume: Resume, config: SessionConfigProtocol) -> Resume:
|
|
82
|
+
"""Apply session configuration to a resume."""
|
|
83
|
+
...
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class DefaultResumeLoader:
|
|
87
|
+
"""Default implementation of ResumeLoader using Resume.read_yaml."""
|
|
88
|
+
|
|
89
|
+
def load_resume(
|
|
90
|
+
self,
|
|
91
|
+
name: str,
|
|
92
|
+
*,
|
|
93
|
+
paths: Paths | None = None,
|
|
94
|
+
transform_markdown: bool = True,
|
|
95
|
+
) -> Resume:
|
|
96
|
+
"""Load resume using Resume.read_yaml method."""
|
|
97
|
+
return Resume.read_yaml(
|
|
98
|
+
name=name,
|
|
99
|
+
paths=paths,
|
|
100
|
+
transform_markdown=transform_markdown,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class MemoryResumeCache:
|
|
105
|
+
"""In-memory implementation of ResumeCache."""
|
|
106
|
+
|
|
107
|
+
def __init__(self) -> None:
|
|
108
|
+
"""Initialize an empty in-memory cache."""
|
|
109
|
+
self._cache: dict[str, Resume] = {}
|
|
110
|
+
|
|
111
|
+
def get_resume(self, key: str) -> Resume | None:
|
|
112
|
+
"""Get a resume from cache."""
|
|
113
|
+
return self._cache.get(key)
|
|
114
|
+
|
|
115
|
+
def put_resume(self, key: str, resume: Resume) -> None:
|
|
116
|
+
"""Put a resume into cache."""
|
|
117
|
+
self._cache[key] = resume
|
|
118
|
+
|
|
119
|
+
def invalidate_resume(self, key: str | None = None) -> None:
|
|
120
|
+
"""Invalidate cached resume(s)."""
|
|
121
|
+
if key is None:
|
|
122
|
+
self._cache.clear()
|
|
123
|
+
else:
|
|
124
|
+
self._cache.pop(key, None)
|
|
125
|
+
|
|
126
|
+
def clear_cache(self) -> None:
|
|
127
|
+
"""Clear all cached resumes."""
|
|
128
|
+
self._cache.clear()
|
|
129
|
+
|
|
130
|
+
def get_cache_keys(self) -> list[str]:
|
|
131
|
+
"""Get list of cached resume keys."""
|
|
132
|
+
return list(self._cache.keys())
|
|
133
|
+
|
|
134
|
+
def get_cache_size(self) -> int:
|
|
135
|
+
"""Get number of cached resumes."""
|
|
136
|
+
return len(self._cache)
|
|
137
|
+
|
|
138
|
+
def get_memory_usage(self) -> int:
|
|
139
|
+
"""Get estimated memory usage of cached resumes in bytes."""
|
|
140
|
+
return sum(len(str(resume._data)) for resume in self._cache.values())
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class DefaultResumeConfigurator:
|
|
144
|
+
"""Default implementation of ResumeConfigurator."""
|
|
145
|
+
|
|
146
|
+
def configure_resume(self, resume: Resume, config: SessionConfigProtocol) -> Resume:
|
|
147
|
+
"""Apply session configuration to a resume."""
|
|
148
|
+
result = resume
|
|
149
|
+
|
|
150
|
+
# Apply default template if specified
|
|
151
|
+
if config.default_template:
|
|
152
|
+
result = result.with_template(config.default_template)
|
|
153
|
+
|
|
154
|
+
# Apply default palette if specified
|
|
155
|
+
if config.default_palette:
|
|
156
|
+
result = result.with_palette(config.default_palette)
|
|
157
|
+
|
|
158
|
+
# Apply preview mode if enabled
|
|
159
|
+
if config.preview_mode:
|
|
160
|
+
result = result.preview()
|
|
161
|
+
|
|
162
|
+
return result
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class ResumeRepository:
|
|
166
|
+
"""Repository for managing resume loading and caching."""
|
|
167
|
+
|
|
168
|
+
def __init__(
|
|
169
|
+
self,
|
|
170
|
+
loader: ResumeLoader,
|
|
171
|
+
cache: ResumeCache,
|
|
172
|
+
configurator: ResumeConfigurator,
|
|
173
|
+
) -> None:
|
|
174
|
+
"""Initialize repository with dependencies."""
|
|
175
|
+
self._loader = loader
|
|
176
|
+
self._cache = cache
|
|
177
|
+
self._configurator = configurator
|
|
178
|
+
|
|
179
|
+
def get_resume(
|
|
180
|
+
self,
|
|
181
|
+
name: str,
|
|
182
|
+
paths: Paths | None = None,
|
|
183
|
+
use_cache: bool = True,
|
|
184
|
+
config: SessionConfigProtocol | None = None,
|
|
185
|
+
) -> Resume:
|
|
186
|
+
"""Get a resume, loading from cache or file as needed."""
|
|
187
|
+
cache_key = name
|
|
188
|
+
|
|
189
|
+
# Try cache first
|
|
190
|
+
if use_cache and (cached_resume := self._cache.get_resume(cache_key)):
|
|
191
|
+
# Apply configuration to cached resume
|
|
192
|
+
merged_config = config or NullSessionConfig()
|
|
193
|
+
return self._configurator.configure_resume(cached_resume, merged_config)
|
|
194
|
+
|
|
195
|
+
# Load from file
|
|
196
|
+
resume = self._loader.load_resume(name, paths=paths)
|
|
197
|
+
|
|
198
|
+
# Apply configuration
|
|
199
|
+
if config:
|
|
200
|
+
resume = self._configurator.configure_resume(resume, config)
|
|
201
|
+
|
|
202
|
+
# Cache the result
|
|
203
|
+
if use_cache:
|
|
204
|
+
self._cache.put_resume(cache_key, resume)
|
|
205
|
+
|
|
206
|
+
return resume
|
|
207
|
+
|
|
208
|
+
def invalidate_cache(self, name: str | None = None) -> None:
|
|
209
|
+
"""Invalidate cached resume(s)."""
|
|
210
|
+
self._cache.invalidate_resume(name)
|
|
211
|
+
|
|
212
|
+
def clear_cache(self) -> None:
|
|
213
|
+
"""Clear all cached resumes."""
|
|
214
|
+
self._cache.clear_cache()
|
|
215
|
+
|
|
216
|
+
def get_cache_info(self) -> dict[str, Any]:
|
|
217
|
+
"""Return information about cached resume data.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Dictionary with cache statistics.
|
|
221
|
+
|
|
222
|
+
"""
|
|
223
|
+
return {
|
|
224
|
+
"cached_resumes": self._cache.get_cache_keys(),
|
|
225
|
+
"cache_size": self._cache.get_cache_size(),
|
|
226
|
+
"memory_usage_estimate": self._cache.get_memory_usage(),
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
def get_cache_keys(self) -> list[str]:
|
|
230
|
+
"""Get list of cached resume keys."""
|
|
231
|
+
return self._cache.get_cache_keys()
|
|
232
|
+
|
|
233
|
+
def get_cache_size(self) -> int:
|
|
234
|
+
"""Get number of cached resumes."""
|
|
235
|
+
return self._cache.get_cache_size()
|
|
236
|
+
|
|
237
|
+
def get_memory_usage(self) -> int:
|
|
238
|
+
"""Get estimated memory usage of cached resumes in bytes."""
|
|
239
|
+
return self._cache.get_memory_usage()
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@dataclass
|
|
243
|
+
class DIContainer:
|
|
244
|
+
"""Dependency injection container for creating configured objects."""
|
|
245
|
+
|
|
246
|
+
resume_loader: ResumeLoader = field(default_factory=DefaultResumeLoader)
|
|
247
|
+
resume_cache: ResumeCache = field(default_factory=MemoryResumeCache)
|
|
248
|
+
resume_configurator: ResumeConfigurator = field(
|
|
249
|
+
default_factory=DefaultResumeConfigurator
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def create_resume_repository(self) -> ResumeRepository:
|
|
253
|
+
"""Create a ResumeRepository with configured dependencies."""
|
|
254
|
+
return ResumeRepository(
|
|
255
|
+
loader=self.resume_loader,
|
|
256
|
+
cache=self.resume_cache,
|
|
257
|
+
configurator=self.resume_configurator,
|
|
258
|
+
)
|